修复一次支付发放两张会员卡的问题 #45

Merged
liwentao merged 1 commits from feature/fix-pay into dev 2026-06-28 17:57:31 +08:00
5 changed files with 2983 additions and 78 deletions
Showing only changes of commit 80759f6793 - Show all commits
@@ -110,6 +110,18 @@ public interface MemberCardRecordRepository extends R2dbcRepository<MemberCardRe
@Query("SELECT * FROM member_card_record WHERE status = 'ACTIVE' AND expire_time < NOW() AND deleted_at IS NULL LIMIT 500")
Flux<MemberCardRecord> findExpiredCards();
/**
* 根据支付订单ID查询会员卡记录(用于幂等性检查)
*/
@Query("SELECT * FROM member_card_record WHERE source_order_id = :sourceOrderId AND deleted_at IS NULL")
Mono<MemberCardRecord> findBySourceOrderId(Long sourceOrderId);
/**
* 查询会员在指定时间后购买的同类活跃卡(防前端重复调用)
*/
@Query("SELECT * FROM member_card_record WHERE member_id = :memberId AND member_card_id = :memberCardId AND status = 'ACTIVE' AND deleted_at IS NULL AND purchase_time > :since ORDER BY purchase_time DESC LIMIT 1")
Mono<MemberCardRecord> findRecentActivePurchase(Long memberId, Long memberCardId, LocalDateTime since);
/**
* 查询所有有效记录
*/
@@ -44,6 +44,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
private static final String MEMBER_CARD_CACHE_PREFIX = "member:card:";
private static final long CACHE_EXPIRE_SECONDS = 300;
private static final long DUPLICATE_WINDOW_SECONDS = 10;
public MemberCardServiceImpl(MemberCardRepository memberCardRepository,
MemberCardRecordRepository recordRepository,
@@ -130,6 +131,30 @@ public class MemberCardServiceImpl implements IMemberCardService {
if (memberCardId == null) {
return Mono.error(new RuntimeException("会员卡类型ID不能为空"));
}
// 快速路径:幂等性检查,同一笔支付订单已创建过记录则直接返回
if (sourceOrderId != null) {
return recordRepository.findBySourceOrderId(sourceOrderId)
.flatMap(existingRecord -> {
log.info("支付订单已生成会员卡记录,幂等返回: sourceOrderId={}, memberCardRecordId={}",
sourceOrderId, existingRecord.getMemberCardRecordId());
return Mono.just(existingRecord);
})
.switchIfEmpty(Mono.defer(() -> doPurchaseCard(memberId, memberCardId, sourceOrderId)));
}
// sourceOrderId为null(前端直接调用):检查10秒内是否已有同类卡,防前端重复调用
LocalDateTime since = LocalDateTime.now().minusSeconds(DUPLICATE_WINDOW_SECONDS);
return recordRepository.findRecentActivePurchase(memberId, memberCardId, since)
.flatMap(existingRecord -> {
log.info("前端重复购买检测命中: memberId={}, memberCardId={}, memberCardRecordId={}, purchaseTime={}",
memberId, memberCardId, existingRecord.getMemberCardRecordId(), existingRecord.getPurchaseTime());
return Mono.just(existingRecord);
})
.switchIfEmpty(Mono.defer(() -> doPurchaseCard(memberId, memberCardId, sourceOrderId)));
}
private Mono<MemberCardRecord> doPurchaseCard(Long memberId, Long memberCardId, Long sourceOrderId) {
// 前端传的是数据库主键id,不是业务字段member_card_id
return memberCardRepository.findByIdAndDeletedAtIsNull(memberCardId)
.switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
@@ -143,8 +168,19 @@ public class MemberCardServiceImpl implements IMemberCardService {
return distributedLockService.executeWithLock(
memberId.toString(),
cardType.name(),
Mono.defer(() -> createCardRecord(memberId, memberCardId, sourceOrderId, card))
// 修改:将 card 对象传递给 createTransaction 以获取购买金额
Mono.defer(() -> {
// 持锁后再次检查幂等,防止并发请求同时通过外层检查
if (sourceOrderId != null) {
return recordRepository.findBySourceOrderId(sourceOrderId)
.flatMap(existingRecord -> {
log.info("持锁幂等检查命中: sourceOrderId={}, memberCardRecordId={}",
sourceOrderId, existingRecord.getMemberCardRecordId());
return Mono.just(existingRecord);
})
.switchIfEmpty(Mono.defer(() -> createCardRecord(memberId, memberCardId, sourceOrderId, card)));
}
return createCardRecord(memberId, memberCardId, sourceOrderId, card);
})
.flatMap(record -> createTransactionForPurchase(record, card, TransactionType.PURCHASE, "购买会员卡")
.thenReturn(record))
.flatMap(record -> expirationReminderService.scheduleExpirationReminder(record)
@@ -222,28 +222,25 @@ public class PaymentServiceImpl implements PaymentService {
if ("00000000".equals(respCode)) {
if ("S".equals(transStat)) {
if (!"SUCCESS".equals(order.getPayStatus())) {
log.info("[Huifu] 查询到支付成功,更新订单状态并触发业务处理, orderNo={}, orderType={}",
order.getOrderNo(), order.getOrderType());
log.info("[Huifu] 查询到支付成功,更新订单状态业务处理由汇付异步回调触发), orderNo={}", order.getOrderNo());
order.setPayStatus("SUCCESS");
order.setHfSeqId(String.valueOf(response.getOrDefault("hf_seq_id", order.getHfSeqId())));
order.setPayTime(LocalDateTime.now());
return paymentOrderRepository.save(order)
.flatMap(savedOrder -> {
log.info("[Huifu] 订单状态已更新,开始触发业务处理, orderNo={}", savedOrder.getOrderNo());
return paymentNotifyService.notifyPaymentStatus(savedOrder.getOrderNo(), "SUCCESS", memberId)
.thenReturn(convertToResponse(savedOrder));
.map(savedOrder -> {
log.info("[Huifu] 订单状态已更新为SUCCESS, orderNo={}", savedOrder.getOrderNo());
return convertToResponse(savedOrder);
});
} else {
log.info("[Huifu] 订单已处理过,跳过业务处理, orderNo={}", order.getOrderNo());
log.info("[Huifu] 订单已处理过,跳过, orderNo={}", order.getOrderNo());
}
} else if ("F".equals(transStat)) {
if (!"FAIL".equals(order.getPayStatus())) {
log.info("[Huifu] 查询到支付失败,更新订单状态, orderNo={}", order.getOrderNo());
log.info("[Huifu] 查询到支付失败,更新订单状态, orderNo={}", order.getOrderNo());
order.setPayStatus("FAIL");
order.setErrorMsg(String.valueOf(response.getOrDefault("resp_desc", "支付失败")));
return paymentOrderRepository.save(order)
.flatMap(savedOrder -> paymentNotifyService.notifyPaymentStatus(savedOrder.getOrderNo(), "FAIL", memberId)
.thenReturn(convertToResponse(savedOrder)));
.map(savedOrder -> convertToResponse(savedOrder));
}
} else if ("P".equals(transStat)) {
order.setPayStatus("PENDING");
+2919 -59
View File
File diff suppressed because it is too large Load Diff
@@ -22,7 +22,6 @@ public class PaymentNotifyServiceImpl implements PaymentNotifyService {
private final MemberStoredCardRepository memberStoredCardRepository;
private final MemberStoredCardRechargeRepository memberStoredCardRechargeRepository;
private final IMemberCardService memberCardService;
private Long memberIdGlob;
public PaymentNotifyServiceImpl(PaymentOrderRepository paymentOrderRepository,
MemberStoredCardRepository memberStoredCardRepository,
@@ -35,8 +34,7 @@ public class PaymentNotifyServiceImpl implements PaymentNotifyService {
}
@Override
public Mono<Void> notifyPaymentStatus(String orderNo, String status,Long memberId) {
memberIdGlob = memberId;
public Mono<Void> notifyPaymentStatus(String orderNo, String status, Long memberId) {
return paymentOrderRepository.findByOrderNo(orderNo)
.flatMap(order -> {
log.info("[PaymentNotify] 支付通知: orderNo={}, status={}, orderType={}", orderNo, status, order.getOrderType());
@@ -114,13 +112,15 @@ public class PaymentNotifyServiceImpl implements PaymentNotifyService {
final Long finalMemberCardId = memberCardId;
final int finalCount = count;
final Long sourceOrderId = order.getId();
final Long orderMemberId = order.getMemberId();
log.info("[PaymentNotify] 处理会员卡购买: orderNo={}, memberId={}, memberCardId={}, count={}",
order.getOrderNo(), memberIdGlob, finalMemberCardId, finalCount);
order.getOrderNo(), orderMemberId, finalMemberCardId, finalCount);
return memberCardService.purchaseCard(memberIdGlob, finalMemberCardId, sourceOrderId)
// 幂等性检查:同一笔支付订单只创建一次会员卡记录
return memberCardService.purchaseCard(orderMemberId, finalMemberCardId, sourceOrderId)
.doOnSuccess(record -> log.info("[PaymentNotify] 会员卡购买完成: orderNo={}, memberId={}, memberCardRecordId={}",
order.getOrderNo(), memberIdGlob, record.getMemberCardRecordId()))
order.getOrderNo(), orderMemberId, record.getMemberCardRecordId()))
.doOnError(e -> log.error("[PaymentNotify] 会员卡购买异常, orderNo={}", order.getOrderNo(), e))
.onErrorResume(e -> Mono.empty())
.then();
@@ -166,7 +166,7 @@ public class PaymentNotifyServiceImpl implements PaymentNotifyService {
final BigDecimal finalRechargeAmount = rechargeAmount;
final BigDecimal finalBonus = bonus != null ? bonus : BigDecimal.ZERO;
final BigDecimal totalAddAmount = finalRechargeAmount.add(finalBonus);
Long memberId = memberIdGlob;
Long memberId = order.getMemberId();
final String orderNo = order.getOrderNo();
final BigDecimal payAmount = order.getTransAmt();
final LocalDateTime now = LocalDateTime.now();