记得上下班打卡 | git大法好,push需谨慎

Commit 1736355b authored by 姜秀龙's avatar 姜秀龙

收钱吧-换购价校验与自定义商品结算修复

已购票用户才使用演出换购价;商品结算采用 type=1 且不传顶层 amount,补齐非空图片;下单与预支付金额统一由 unitPriceYuan 转分;退款明细 type 与下单对齐;更新 dev/test 收钱吧应用配置。
Co-authored-by: 's avatarCursor <cursoragent@cursor.com>
parent 037dd94f
......@@ -90,12 +90,24 @@ public class SqbBiz {
* @return
*/
public SettlementCreateData createSettlement(String mallSn, String signature, String userId, List<SettlementCreateRequest.CheckoutItem> checkoutItems) {
return createSettlement(mallSn, signature, userId, checkoutItems, null);
}
/**
* @param totalAmountFen 纯金额结算时传总金额(分);商品结算(type=1 的 checkoutItems) 须传 null,勿与 checkoutItems 同时带 amount
*/
public SettlementCreateData createSettlement(String mallSn, String signature, String userId,
List<SettlementCreateRequest.CheckoutItem> checkoutItems,
Long totalAmountFen) {
SettlementCreateRequest request = new SettlementCreateRequest();
request.setAppid(sqbConfig.getAppId());
request.setMallID(buildMall(mallSn, signature));
request.setSeller(cachedSeller);
request.setBuyer(buildBuyer(userId));
request.setCheckoutItems(checkoutItems);
if (totalAmountFen != null) {
request.setAmount(totalAmountFen);
}
return createSettlement(request);
}
......@@ -179,7 +191,7 @@ public class SqbBiz {
* @param acquiringSn 收单号
* @param signature 收单签名
* @param userId 正在用户ID
* @param amount 支付金额
* @param amount 支付金额(分,字符串,如 "3" 表示 0.03 元)
* @param requestSn 支付请求号
* @param payTool 支付工具代码
* @param channelExt 通道扩展参数 {"sub_appid":"wx36e68952a6"}
......
......@@ -42,7 +42,7 @@ public class SettlementCreateRequest implements Serializable {
private List<CheckoutItem> checkoutItems;
/**
* 结算总金额(单位:分)
* 结算总金额(单位:分);纯金额结算时必填。商品结算走 checkoutItems 且 type=1 时不要传
*/
private Long amount;
......
......@@ -254,9 +254,9 @@ liquidnet:
#application-dev-end
sqb:
base-api: 'https://open-api.shouqianba.com'
app-id: '2026040900011060'
app-key: '5cd1e48eba44a5264914115cd31df494'
app-code: 'MDTEST'
app-id: '2026033100010967'
app-key: '5423d413f8349b186893892dcef6f76b'
app-code: 'MDWH'
public-key: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf1oOZm3u5NraTs4F8AABXbtU2jSiWYp+IWmQ36vokuq6s2s3eKQR6l4RkrPPxjC86bIvjT4pApJZJrFMA4YcjY4G49wFZySfom4IPaZlKsOrNGJH0Kag0BSO9U5el1z7dMz7oP9cChbdl4mjKuqYtgnNtaPT+SqhXRQdFcc9kiVybAGs8WEGqsdwxsmD9aZTd4rQMvLEGWIj/MLdo7w1avc0WVSPQSM5jRHjjQmUzEuusv+QGcDt3ttNaip2uo1xoQdcwILYmS6fnWL8xKw4V8lX0CWypUKIZcIc1Y/1N8VeUN+8MirdrS5JSghq62Yifu9A3W/mANB+S6yYwD+WQIDAQAB'
merchant-id: 'b2d63146-934f-401f-a864-14926d952c16'
merchant-user-id: '6d0d632e-50e9-464e-bbb3-be76047ec835'
......
......@@ -253,9 +253,9 @@ liquidnet:
#application-test-end
sqb:
base-api: 'https://open-api.shouqianba.com'
app-id: '2026040900011060'
app-key: '5cd1e48eba44a5264914115cd31df494'
app-code: 'MDTEST'
app-id: '2026033100010967'
app-key: '5423d413f8349b186893892dcef6f76b'
app-code: 'MDWH'
public-key: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf1oOZm3u5NraTs4F8AABXbtU2jSiWYp+IWmQ36vokuq6s2s3eKQR6l4RkrPPxjC86bIvjT4pApJZJrFMA4YcjY4G49wFZySfom4IPaZlKsOrNGJH0Kag0BSO9U5el1z7dMz7oP9cChbdl4mjKuqYtgnNtaPT+SqhXRQdFcc9kiVybAGs8WEGqsdwxsmD9aZTd4rQMvLEGWIj/MLdo7w1avc0WVSPQSM5jRHjjQmUzEuusv+QGcDt3ttNaip2uo1xoQdcwILYmS6fnWL8xKw4V8lX0CWypUKIZcIc1Y/1N8VeUN+8MirdrS5JSghq62Yifu9A3W/mANB+S6yYwD+WQIDAQAB'
merchant-id: 'b2d63146-934f-401f-a864-14926d952c16'
merchant-user-id: '6d0d632e-50e9-464e-bbb3-be76047ec835'
......
......@@ -51,6 +51,9 @@ public class GoblinSqbServiceImpl implements IGoblinSqbService {
private SqbBiz sqbBiz;
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** 与下单结算 CheckoutItem.type 一致:1=自定义商品(按实付单价下单时) */
private static final byte SQB_LINE_ITEM_TYPE_CUSTOM = 1;
@Autowired
private GoblinSqbOrderMapper goblinSqbOrderMapper;
......@@ -224,7 +227,7 @@ public class GoblinSqbServiceImpl implements IGoblinSqbService {
item.setTitle(orderSkuVo.getSkuName());
item.setImg(orderSkuVo.getSkuImage());
item.setQuantity(String.valueOf(orderSkuVo.getNum()));
item.setType((byte) 0);
item.setType(SQB_LINE_ITEM_TYPE_CUSTOM);
refundItems.add(item);
}
return refundItems;
......
......@@ -53,8 +53,14 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
@Autowired
private SqbBiz sqbBiz;
@Autowired
private DataUtils dataUtils;
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** 收钱吧结算明细:1=自定义商品,按传入单价(分)结算,避免 type=0 走商城原价 */
private static final byte SQB_CHECKOUT_ITEM_TYPE_CUSTOM = 1;
// ================================ 创建订单 ================================
......@@ -124,10 +130,13 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
}
log.info("[收钱吧下单] 扣减库存成功,skuId={}, 剩余库存={}", skuId, remaining);
BigDecimal unitPriceYuan = resolveSqbUnitPriceYuan(skuVo, matchedPerfRel);
log.info("[收钱吧下单] 单价(元) performancesId={}, skuId={}, settlementPrice={}, 实际计价={}",
performancesId, skuId,
matchedPerfRel != null ? matchedPerfRel.getSettlementPrice() : null, unitPriceYuan);
// 唯一计价入口(元);后续入库、收钱吧参数均只使用该单价/总价,传给 SQB 时再 yuanToFen
BigDecimal unitPriceYuan = resolveSqbUnitPriceYuan(userId, performancesId, skuVo, matchedPerfRel);
BigDecimal priceTotalYuan = unitPriceYuan.multiply(new BigDecimal(quantity));
Long payAmountFen = GoblinSqbConvertUtils.yuanToFen(priceTotalYuan);
Long unitPriceFen = GoblinSqbConvertUtils.yuanToFen(unitPriceYuan);
log.info("[收钱吧下单] 计价(元) unitPriceYuan={}, priceTotalYuan={}, 传SQB(分) unitPriceFen={}, payAmountFen={}, performancesId={}, skuId={}",
unitPriceYuan, priceTotalYuan, unitPriceFen, payAmountFen, performancesId, skuId);
// 获取该商品对应的商城的编号和密码
GoblinSqbGoodsExtVo sqbGoodsExt = goblinSqbRedisUtils.getSqbGoodsExt(spuId, skuId);
......@@ -136,12 +145,19 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
return ResponseDto.failure("下单失败");
}
// 商品结算(type=1):只传 checkoutItems,不传 request.amount;图片须非空
String checkoutImage = resolveSqbCheckoutImage(spuId, skuVo);
if (StringUtil.isBlank(checkoutImage)) {
goblinRedisUtils.incrSkuStock(null, skuId, quantity);
log.warn("[收钱吧下单] 自定义商品结算缺少图片 spuId={}, skuId={}", spuId, skuId);
return ResponseDto.failure("商品图片缺失,无法下单");
}
SettlementCreateData settlementData = sqbBiz.createSettlement(
sqbGoodsExt.getMallSn(),
sqbGoodsExt.getSignature(),
userId,
Collections.singletonList(buildSqbCheckOutItem(skuVo,
quantity, sqbGoodsExt.getSqbSkuId(), sqbGoodsExt.getSqbSpuId(), unitPriceYuan)));
Collections.singletonList(buildSqbCheckOutItem(skuVo, quantity,
sqbGoodsExt.getSqbSkuId(), sqbGoodsExt.getSqbSpuId(), unitPriceFen, checkoutImage)));
if (settlementData == null) {
goblinRedisUtils.incrSkuStock(null, skuId, quantity);
return ResponseDto.failure("创建结算明细失败");
......@@ -197,12 +213,12 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
Map<String, Object> channelExt = new HashMap<>();
channelExt.put("sub_appid", "wx4732efeaa2b08086");
// 创建微信预支付订单
// 预支付金额与结算一致,均用 payAmountFen(分),不使用收银台返回金额
CreateWechatPrepayOrderData prepayData = sqbBiz.createWechatPrepayOrder(sqbAcquiringSn,
sqbAcquiringSign,
userId,
openId,
String.valueOf(cashierData.getAmount()),
String.valueOf(payAmountFen),
buildRequestId(IDGenerator.nextSnowId()),
payTool,
channelExt,
......@@ -239,10 +255,8 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
storeOrderVo.setUserName("");
storeOrderVo.setUserMobile("");
BigDecimal skuPrice = unitPriceYuan;
BigDecimal priceTotal = skuPrice.multiply(new BigDecimal(quantity));
storeOrderVo.setPriceTotal(priceTotal);
storeOrderVo.setPriceActual(priceTotal); // 演出关联换购价或 SKU 售价,无额外运费/券
storeOrderVo.setPriceTotal(priceTotalYuan);
storeOrderVo.setPriceActual(priceTotalYuan);
storeOrderVo.setPriceRefund(BigDecimal.ZERO);
storeOrderVo.setPriceExpress(BigDecimal.ZERO);
storeOrderVo.setPriceCoupon(BigDecimal.ZERO);
......@@ -266,8 +280,8 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
orderSkuVo.setSpuName(goodsInfo != null ? goodsInfo.getName() : "收钱吧未知商品");
orderSkuVo.setSkuId(skuId);
orderSkuVo.setNum(quantity);
orderSkuVo.setSkuPrice(skuPrice);
orderSkuVo.setSkuPriceActual(priceTotal); // 按件数总价
orderSkuVo.setSkuPrice(unitPriceYuan);
orderSkuVo.setSkuPriceActual(priceTotalYuan);
orderSkuVo.setSkuName(skuInfo != null ? skuInfo.getName() : "收钱吧未匹配SKU");
String skuImg = "";
if (skuInfo != null && StringUtil.isNotBlank(skuInfo.getSkuPic())) {
......@@ -299,7 +313,7 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
orderVo.setSpuId(spuId);
orderVo.setSkuId(skuId);
orderVo.setQuantity(quantity);
orderVo.setAmount(GoblinSqbConvertUtils.yuanToFen(priceTotal));
orderVo.setAmount(payAmountFen);
orderVo.setSqbOrderSn(sqbOrderSn);
orderVo.setSqbOrderSignature(sqbOrderSignature);
orderVo.setSqbAcquiringSn(sqbAcquiringSn);
......@@ -350,7 +364,7 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
LinkedList<Object[]> sqlDataSqbOrder = new LinkedList<>();
sqlDataSqbOrder.add(new Object[]{
orderId, userId, performancesId, spuId, skuId, quantity,
GoblinSqbConvertUtils.yuanToFen(priceTotal),
payAmountFen,
sqbOrderSn, sqbOrderSignature, sqbAcquiringSn, sqbAcquiringSign, checkoutItemsId,
0, now, now
});
......@@ -389,18 +403,10 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
}
/**
* 构建收钱吧-结算明细条目
*
* @param skuVo
* @param quantity
* @param sqbSkuId
* @param sqbSpuId
* @return
*/
/**
* 演出入口下单:后台为该 SKU 配置了换购价且大于 0 时用换购价(元),否则用 SKU 销售价,再否则现价;换购价高于售价时按售价避免多收。
* 演出入口下单:后台配置了换购价且用户已购买本场演出门票时用换购价(元),否则用 SKU 销售价;换购价高于售价时按售价避免多收。
*/
private BigDecimal resolveSqbUnitPriceYuan(GoblinGoodsSkuInfoVo skuVo, GoblinSqbPerformanceGoods perfRel) {
private BigDecimal resolveSqbUnitPriceYuan(String userId, String performancesId,
GoblinGoodsSkuInfoVo skuVo, GoblinSqbPerformanceGoods perfRel) {
if (skuVo == null) {
return BigDecimal.ZERO;
}
......@@ -418,23 +424,41 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
if (sp.compareTo(base) > 0) {
return base;
}
int performanceBuyCount = dataUtils.getUserPBuyCount(userId, performancesId);
if (performanceBuyCount <= 0) {
log.info("[收钱吧下单] 用户未购买本场演出门票,不适用换购价 userId={}, performancesId={}, settlementPrice={}",
userId, performancesId, sp);
return base;
}
return sp;
}
/** 自定义商品结算要求 image 非空:优先 SKU 图,否则 SPU 封面 */
private String resolveSqbCheckoutImage(String spuId, GoblinGoodsSkuInfoVo skuVo) {
if (skuVo != null && StringUtil.isNotBlank(skuVo.getSkuPic())) {
return skuVo.getSkuPic().trim();
}
GoblinGoodsInfoVo goodsInfo = goblinMongoUtils.getGoodsInfoVo(spuId);
if (goodsInfo != null && StringUtil.isNotBlank(goodsInfo.getCoverPic())) {
return goodsInfo.getCoverPic().trim();
}
return null;
}
private SettlementCreateRequest.CheckoutItem buildSqbCheckOutItem(GoblinGoodsSkuInfoVo skuVo,
Integer quantity,
String sqbSkuId,
String sqbSpuId,
BigDecimal unitPriceYuan) {
// 获取商品与收钱吧商品对应的关联的skuId、spuId
Long unitPriceFen,
String imageUrl) {
SettlementCreateRequest.CheckoutItem checkoutItem = new SettlementCreateRequest.CheckoutItem();
checkoutItem.setSpuId(sqbSpuId);
checkoutItem.setSkuId(sqbSkuId);
checkoutItem.setPrice(GoblinSqbConvertUtils.yuanToFen(unitPriceYuan));
checkoutItem.setPrice(unitPriceFen);
checkoutItem.setQuantity(String.valueOf(quantity));
checkoutItem.setType((byte) 0);
checkoutItem.setType(SQB_CHECKOUT_ITEM_TYPE_CUSTOM);
checkoutItem.setTitle(skuVo.getName());
checkoutItem.setImage(skuVo.getSkuPic());
checkoutItem.setImage(imageUrl);
return checkoutItem;
}
......@@ -638,13 +662,17 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
Map<String, Object> channelExt = new HashMap<>();
channelExt.put("sub_appid", "wx4732efeaa2b08086");
// 重新创建微信预支付
Long repayFen = orderVo.getAmount();
if (repayFen == null) {
return ResponseDto.failure("订单金额异常");
}
// 重新创建微信预支付(金额与下单时 orderVo.amount 一致,单位:分)
CreateWechatPrepayOrderData prepayData = sqbBiz.createWechatPrepayOrder(
orderVo.getSqbAcquiringSn(),
orderVo.getSqbAcquiringSign(),
userId,
orderVo.getOpenId(),
String.valueOf(cashierData.getAmount()),
String.valueOf(repayFen),
buildRequestId(IDGenerator.nextSnowId()),
payTool,
channelExt,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment