修复一次支付发放两张会员卡的问题
This commit was merged in pull request #45.
This commit is contained in:
+12
@@ -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);
|
||||
|
||||
/**
|
||||
* 查询所有有效记录
|
||||
*/
|
||||
|
||||
+38
-2
@@ -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)
|
||||
|
||||
+7
-10
@@ -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
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user