From 61e873aada7f8c55ccebb4c5fdf93ec21eb5fffe Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 14 九月 2026 14:41:13 +0800
Subject: [PATCH] refactor(merchant): 核销收敛为一份实现,按是否涉及资金拆两个接口
---
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java | 63 +++++++++++++++++++------------
1 files changed, 38 insertions(+), 25 deletions(-)
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java
index d2383f6..0e307a3 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java
@@ -35,14 +35,15 @@
private final JdbcTemplate jdbcTemplate;
private final ManualConsumptionService manualConsumptionService;
private final OrderFundBoundaryService orderFundBoundary;
- private final PaymentFundConfirmationService fundConfirmation;
+ private final com.talentcard.server.business.order.OrderVerificationService orderVerificationService;
public MerchantWorkService(JdbcTemplate jdbcTemplate, ManualConsumptionService manualConsumptionService,
- OrderFundBoundaryService orderFundBoundary, PaymentFundConfirmationService fundConfirmation) {
+ OrderFundBoundaryService orderFundBoundary,
+ com.talentcard.server.business.order.OrderVerificationService orderVerificationService) {
this.jdbcTemplate = jdbcTemplate;
this.manualConsumptionService = manualConsumptionService;
this.orderFundBoundary = orderFundBoundary;
- this.fundConfirmation = fundConfirmation;
+ this.orderVerificationService = orderVerificationService;
}
@Transactional(readOnly = true)
@@ -184,31 +185,38 @@
}
locked.requireVerifiable();
- String verifyNo = BusinessIdGenerator.next("V");
- jdbcTemplate.update("""
- insert into verify_record(verify_no, order_id, talent_id, card_id, benefit_id, merchant_id,
- staff_id, verify_type, verify_amount, subsidy_amount, remark, idempotency_key)
- values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- verifyNo,
- order.get("id"),
- order.get("talentId"),
- order.get("cardId"),
- order.get("benefitId"),
- order.get("merchantId"),
- principal.operatorId(),
- request.verifyType(),
- order.get("payAmount"),
- order.get("subsidyAmount"),
- request.remark(), idempotencyKey.trim());
- int updated = jdbcTemplate.update("update benefit_order set verify_status = 1 where id = ? and verify_status = 0", locked.orderId());
- if (updated != 1) {
+ // 扫码后核销会带上扫码尝试编号:核销成功时要把这次尝试标记为已完成,并校验扫码码未过期。
+ Attempt attempt = attempt(request.verificationId(), principal);
+ // 核销只有一份实现:置订单已核销、写核销记录、确认延迟入账的资金。确认失败不会回滚核销
+ // (用户的服务已经核销掉了),资金保持待确认并由补偿任务重试。
+ String verifyNo = orderVerificationService.verifyByMerchant(principal.merchantId(), principal.storeId(),
+ principal.operatorId(), locked.orderId(),
+ verifyType(request.verifyType()), value(order.get("payAmount")), value(order.get("subsidyAmount")),
+ request.remark(), attempt.id(), idempotencyKey.trim(), attempt.expiresAt());
+ if (verifyNo.isBlank()) {
throw new BusinessException(ErrorCode.CONFLICT, "订单已核销");
}
- // 核销通过后把钱放给商户:延迟入账的支付此前停在通道「待确认」,确认成功商户才真正拿到钱。
- // 确认失败不会回滚核销(用户的服务已经核销掉了),资金保持待确认并留下记录供重试。
- fundConfirmation.confirmForOrder(locked.orderId());
return order(orderNo, principal.merchantId(), principal.storeId());
+ }
+
+ /** 核销方式不传时按商户现场核销处理。 */
+ private String verifyType(String requested) {
+ return requested == null || requested.isBlank() ? "merchant" : requested.trim();
+ }
+
+ /** 扫码尝试:核销成功后要标记完成,并且只有扫码码未过期才能核销。 */
+ private record Attempt(Long id, java.time.LocalDateTime expiresAt) { }
+
+ /** 把扫码尝试编号解析成主键与有效期;不属于当前商户/门店/操作员的尝试视为不存在。 */
+ private Attempt attempt(String verificationId, MerchantPrincipal principal) {
+ if (verificationId == null || verificationId.isBlank()) return new Attempt(null, null);
+ List<Map<String, Object>> attempts = query("""
+ select id, expires_at from merchant_verification_attempt
+ where verification_id = ? and merchant_id = ? and store_id = ? and operator_id = ?
+ limit 1
+ """, verificationId.trim(), principal.merchantId(), principal.storeId(), principal.operatorId());
+ if (attempts.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "核销凭证不存在或无权操作");
+ return new Attempt(number(attempts.get(0).get("id")), time(attempts.get(0).get("expiresAt")));
}
@Transactional
@@ -533,6 +541,11 @@
return value instanceof Number number ? number.longValue() : null;
}
+ /** 与 {@link #number} 同义,但缺值时按 0 处理:核销金额为空就是 0 分。 */
+ private long value(Object value) {
+ return value instanceof Number number ? number.longValue() : 0L;
+ }
+
private LocalDateTime time(Object value) {
if (value instanceof LocalDateTime dateTime) return dateTime;
if (value instanceof Timestamp timestamp) return timestamp.toLocalDateTime();
--
Gitblit v1.7.1