修复一次支付发放两张会员卡的问题 #45
+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")
|
@Query("SELECT * FROM member_card_record WHERE status = 'ACTIVE' AND expire_time < NOW() AND deleted_at IS NULL LIMIT 500")
|
||||||
Flux<MemberCardRecord> findExpiredCards();
|
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 String MEMBER_CARD_CACHE_PREFIX = "member:card:";
|
||||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||||
|
private static final long DUPLICATE_WINDOW_SECONDS = 10;
|
||||||
|
|
||||||
public MemberCardServiceImpl(MemberCardRepository memberCardRepository,
|
public MemberCardServiceImpl(MemberCardRepository memberCardRepository,
|
||||||
MemberCardRecordRepository recordRepository,
|
MemberCardRecordRepository recordRepository,
|
||||||
@@ -130,6 +131,30 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
|||||||
if (memberCardId == null) {
|
if (memberCardId == null) {
|
||||||
return Mono.error(new RuntimeException("会员卡类型ID不能为空"));
|
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
|
// 前端传的是数据库主键id,不是业务字段member_card_id
|
||||||
return memberCardRepository.findByIdAndDeletedAtIsNull(memberCardId)
|
return memberCardRepository.findByIdAndDeletedAtIsNull(memberCardId)
|
||||||
.switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
|
.switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
|
||||||
@@ -143,8 +168,19 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
|||||||
return distributedLockService.executeWithLock(
|
return distributedLockService.executeWithLock(
|
||||||
memberId.toString(),
|
memberId.toString(),
|
||||||
cardType.name(),
|
cardType.name(),
|
||||||
Mono.defer(() -> createCardRecord(memberId, memberCardId, sourceOrderId, card))
|
Mono.defer(() -> {
|
||||||
// 修改:将 card 对象传递给 createTransaction 以获取购买金额
|
// 持锁后再次检查幂等,防止并发请求同时通过外层检查
|
||||||
|
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, "购买会员卡")
|
.flatMap(record -> createTransactionForPurchase(record, card, TransactionType.PURCHASE, "购买会员卡")
|
||||||
.thenReturn(record))
|
.thenReturn(record))
|
||||||
.flatMap(record -> expirationReminderService.scheduleExpirationReminder(record)
|
.flatMap(record -> expirationReminderService.scheduleExpirationReminder(record)
|
||||||
|
|||||||
+7
-10
@@ -222,28 +222,25 @@ public class PaymentServiceImpl implements PaymentService {
|
|||||||
if ("00000000".equals(respCode)) {
|
if ("00000000".equals(respCode)) {
|
||||||
if ("S".equals(transStat)) {
|
if ("S".equals(transStat)) {
|
||||||
if (!"SUCCESS".equals(order.getPayStatus())) {
|
if (!"SUCCESS".equals(order.getPayStatus())) {
|
||||||
log.info("[Huifu] 查询到支付成功,更新订单状态并触发业务处理, orderNo={}, orderType={}",
|
log.info("[Huifu] 查询到支付成功,仅更新订单状态(业务处理由汇付异步回调触发), orderNo={}", order.getOrderNo());
|
||||||
order.getOrderNo(), order.getOrderType());
|
|
||||||
order.setPayStatus("SUCCESS");
|
order.setPayStatus("SUCCESS");
|
||||||
order.setHfSeqId(String.valueOf(response.getOrDefault("hf_seq_id", order.getHfSeqId())));
|
order.setHfSeqId(String.valueOf(response.getOrDefault("hf_seq_id", order.getHfSeqId())));
|
||||||
order.setPayTime(LocalDateTime.now());
|
order.setPayTime(LocalDateTime.now());
|
||||||
return paymentOrderRepository.save(order)
|
return paymentOrderRepository.save(order)
|
||||||
.flatMap(savedOrder -> {
|
.map(savedOrder -> {
|
||||||
log.info("[Huifu] 订单状态已更新,开始触发业务处理, orderNo={}", savedOrder.getOrderNo());
|
log.info("[Huifu] 订单状态已更新为SUCCESS, orderNo={}", savedOrder.getOrderNo());
|
||||||
return paymentNotifyService.notifyPaymentStatus(savedOrder.getOrderNo(), "SUCCESS", memberId)
|
return convertToResponse(savedOrder);
|
||||||
.thenReturn(convertToResponse(savedOrder));
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info("[Huifu] 订单已处理过,跳过业务处理, orderNo={}", order.getOrderNo());
|
log.info("[Huifu] 订单已处理过,跳过, orderNo={}", order.getOrderNo());
|
||||||
}
|
}
|
||||||
} else if ("F".equals(transStat)) {
|
} else if ("F".equals(transStat)) {
|
||||||
if (!"FAIL".equals(order.getPayStatus())) {
|
if (!"FAIL".equals(order.getPayStatus())) {
|
||||||
log.info("[Huifu] 查询到支付失败,更新订单状态, orderNo={}", order.getOrderNo());
|
log.info("[Huifu] 查询到支付失败,仅更新订单状态, orderNo={}", order.getOrderNo());
|
||||||
order.setPayStatus("FAIL");
|
order.setPayStatus("FAIL");
|
||||||
order.setErrorMsg(String.valueOf(response.getOrDefault("resp_desc", "支付失败")));
|
order.setErrorMsg(String.valueOf(response.getOrDefault("resp_desc", "支付失败")));
|
||||||
return paymentOrderRepository.save(order)
|
return paymentOrderRepository.save(order)
|
||||||
.flatMap(savedOrder -> paymentNotifyService.notifyPaymentStatus(savedOrder.getOrderNo(), "FAIL", memberId)
|
.map(savedOrder -> convertToResponse(savedOrder));
|
||||||
.thenReturn(convertToResponse(savedOrder)));
|
|
||||||
}
|
}
|
||||||
} else if ("P".equals(transStat)) {
|
} else if ("P".equals(transStat)) {
|
||||||
order.setPayStatus("PENDING");
|
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 MemberStoredCardRepository memberStoredCardRepository;
|
||||||
private final MemberStoredCardRechargeRepository memberStoredCardRechargeRepository;
|
private final MemberStoredCardRechargeRepository memberStoredCardRechargeRepository;
|
||||||
private final IMemberCardService memberCardService;
|
private final IMemberCardService memberCardService;
|
||||||
private Long memberIdGlob;
|
|
||||||
|
|
||||||
public PaymentNotifyServiceImpl(PaymentOrderRepository paymentOrderRepository,
|
public PaymentNotifyServiceImpl(PaymentOrderRepository paymentOrderRepository,
|
||||||
MemberStoredCardRepository memberStoredCardRepository,
|
MemberStoredCardRepository memberStoredCardRepository,
|
||||||
@@ -35,8 +34,7 @@ public class PaymentNotifyServiceImpl implements PaymentNotifyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> notifyPaymentStatus(String orderNo, String status,Long memberId) {
|
public Mono<Void> notifyPaymentStatus(String orderNo, String status, Long memberId) {
|
||||||
memberIdGlob = memberId;
|
|
||||||
return paymentOrderRepository.findByOrderNo(orderNo)
|
return paymentOrderRepository.findByOrderNo(orderNo)
|
||||||
.flatMap(order -> {
|
.flatMap(order -> {
|
||||||
log.info("[PaymentNotify] 支付通知: orderNo={}, status={}, orderType={}", orderNo, status, order.getOrderType());
|
log.info("[PaymentNotify] 支付通知: orderNo={}, status={}, orderType={}", orderNo, status, order.getOrderType());
|
||||||
@@ -114,13 +112,15 @@ public class PaymentNotifyServiceImpl implements PaymentNotifyService {
|
|||||||
final Long finalMemberCardId = memberCardId;
|
final Long finalMemberCardId = memberCardId;
|
||||||
final int finalCount = count;
|
final int finalCount = count;
|
||||||
final Long sourceOrderId = order.getId();
|
final Long sourceOrderId = order.getId();
|
||||||
|
final Long orderMemberId = order.getMemberId();
|
||||||
|
|
||||||
log.info("[PaymentNotify] 处理会员卡购买: orderNo={}, memberId={}, memberCardId={}, count={}",
|
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={}",
|
.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))
|
.doOnError(e -> log.error("[PaymentNotify] 会员卡购买异常, orderNo={}", order.getOrderNo(), e))
|
||||||
.onErrorResume(e -> Mono.empty())
|
.onErrorResume(e -> Mono.empty())
|
||||||
.then();
|
.then();
|
||||||
@@ -166,7 +166,7 @@ public class PaymentNotifyServiceImpl implements PaymentNotifyService {
|
|||||||
final BigDecimal finalRechargeAmount = rechargeAmount;
|
final BigDecimal finalRechargeAmount = rechargeAmount;
|
||||||
final BigDecimal finalBonus = bonus != null ? bonus : BigDecimal.ZERO;
|
final BigDecimal finalBonus = bonus != null ? bonus : BigDecimal.ZERO;
|
||||||
final BigDecimal totalAddAmount = finalRechargeAmount.add(finalBonus);
|
final BigDecimal totalAddAmount = finalRechargeAmount.add(finalBonus);
|
||||||
Long memberId = memberIdGlob;
|
Long memberId = order.getMemberId();
|
||||||
final String orderNo = order.getOrderNo();
|
final String orderNo = order.getOrderNo();
|
||||||
final BigDecimal payAmount = order.getTransAmt();
|
final BigDecimal payAmount = order.getTransAmt();
|
||||||
final LocalDateTime now = LocalDateTime.now();
|
final LocalDateTime now = LocalDateTime.now();
|
||||||
|
|||||||
Reference in New Issue
Block a user