Forráskód Böngészése

fix: 支付回调幂等门闩移到余额更新之前

- 原实现 PayLog 插入位于余额更新之后:并发重复回调时第二个线程
  会先重复增加余额,再在插入 PayLog 时被唯一索引拦截
- 门闩作为事务内第一个写操作,重复回调在余额更新前即被拦截,
  后续业务失败事务回滚时 payLog 一并回滚可安全重试

Co-Authored-By: Claude <noreply@anthropic.com>
skyline 3 hete
szülő
commit
6dd45526cb

+ 19 - 18
car-wash-service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -391,24 +391,8 @@ public class WxPayServiceImpl implements WxPayService {
                     grantsAmount = rechargeConfig.getGrantsAmount();
                 }
 
-                // 更新余额(赠款计入不可退优惠金额)
-                var account = accountService.getAccountByUserId(walletDetail.getUserId());
-                accountService.lambdaUpdate().setSql("balance = balance + {0}, recharge_balance = recharge_balance + {0}, grants_balance = grants_balance + {1}, discount_amount = discount_amount + {1}", transaction.getAmount().getTotal(), grantsAmount)
-                        .eq(Account::getUserId, walletDetail.getUserId()).update();
-
-                walletDetail.setStatus(WalletDetail.STATUS_已确认);  //已确认
-                walletDetail.setSource("WX_PAY");
-                walletDetail.setCurrency(transaction.getAmount().getCurrency());
-                walletDetail.setAmount(transaction.getAmount().getTotal());
-                walletDetail.setGrantsAmount(grantsAmount);
-                walletDetail.setBeforeBalance(account.getBalance());
-                walletDetail.setAfterBalance(account.getBalance() + walletDetail.getAmount());
-                walletDetail.setBeforeGrantsBalance(account.getGrantsBalance());
-                walletDetail.setAfterGrantsBalance(account.getGrantsBalance() + grantsAmount);
-                walletDetail.setTransactionTime(successTime);
-                walletDetailService.updateById(walletDetail);
-
-                // 支付记录(幂等门闩:先落库 payLog,唯一索引 uk_out_trade_no 兜底并发重复回调)
+                // 支付记录(幂等门闩:必须先落库 payLog,唯一索引 uk_out_trade_no 兜底并发重复回调;
+                // 若放在余额更新之后,重复回调会先重复加余额再被拦截)
                 var payLog = new PayLog();
                 payLog.setUserId(walletDetail.getUserId());
                 payLog.setOpenid(transaction.getPayer().getOpenid());
@@ -433,6 +417,23 @@ public class WxPayServiceImpl implements WxPayService {
                     return ResponseEntity.status(HttpStatus.OK).build();
                 }
 
+                // 更新余额(赠款计入不可退优惠金额)
+                var account = accountService.getAccountByUserId(walletDetail.getUserId());
+                accountService.lambdaUpdate().setSql("balance = balance + {0}, recharge_balance = recharge_balance + {0}, grants_balance = grants_balance + {1}, discount_amount = discount_amount + {1}", transaction.getAmount().getTotal(), grantsAmount)
+                        .eq(Account::getUserId, walletDetail.getUserId()).update();
+
+                walletDetail.setStatus(WalletDetail.STATUS_已确认);  //已确认
+                walletDetail.setSource("WX_PAY");
+                walletDetail.setCurrency(transaction.getAmount().getCurrency());
+                walletDetail.setAmount(transaction.getAmount().getTotal());
+                walletDetail.setGrantsAmount(grantsAmount);
+                walletDetail.setBeforeBalance(account.getBalance());
+                walletDetail.setAfterBalance(account.getBalance() + walletDetail.getAmount());
+                walletDetail.setBeforeGrantsBalance(account.getGrantsBalance());
+                walletDetail.setAfterGrantsBalance(account.getGrantsBalance() + grantsAmount);
+                walletDetail.setTransactionTime(successTime);
+                walletDetailService.updateById(walletDetail);
+
                 // V2 结算方案:充值资金记录分账流水,结算日统一处理,不再即时转入站点账户
                 // 无归属站点的用户充值暂不生成分账记录,首次消费时追溯补建
                 if (CommUtil.isNotEmptyAndNull(stationId) && !"000".equals(stationId)) {