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