From 3da8557482779faed72a07c256b141a77abbae86 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 16 九月 2026 16:35:49 +0800
Subject: [PATCH] fix(payment): 退款结果在读订单与列表时收敛,并修掉通道流水号被覆盖
---
talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuRefundService.java | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 55 insertions(+), 1 deletions(-)
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuRefundService.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuRefundService.java
index 5a8bdf8..48787ca 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuRefundService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuRefundService.java
@@ -13,6 +13,8 @@
import com.talentcard.server.common.ErrorCode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import java.math.BigDecimal;
@@ -98,6 +100,55 @@
return detail(refundNo);
}
+ /** 刚提交的退款通道必然还在处理,留一点时间再去问,避免用户一打开页面就打通道。 */
+ private static final int SUBMISSION_GRACE_SECONDS = 20;
+ /** 一轮最多收敛几笔在途退款:正常用户同一时刻只有一笔,列表读路径不该被它拖长。 */
+ public static final int PENDING_CONFIRM_LIMIT = 2;
+
+ /**
+ * 把这位人才名下还在途的退款各自向通道确认一次。
+ *
+ * <p>退款提交时通道多半只回「处理中」,最终结果只有通道知道;退款通知又会遇到和支付通知一样的
+ * 问题——本地或内网环境收不到。于是在读订单/读订单列表之前问一次,把「退款中」尽快收敛成通道的
+ * 最终答案,而不是等对账任务的下一个窗口(最长十分钟)。已经落定的退款连查都不查。
+ *
+ * <p>在自己的事务里执行,且必须是调用方事务里的**第一条**语句:调用方的事务要能读到收敛后的状态,
+ * 而 MySQL 的一致性快照是在该事务的第一条读语句上定下的。单笔查询失败只跳过这一笔——读路径上的
+ * 尽力收敛,查不到结果不该让订单列表打不开,剩下的交给对账任务和下一次读。
+ */
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public int confirmPendingRefunds(Long talentId, int limit) {
+ return confirmPendingRefunds("o.talent_id", talentId, limit);
+ }
+
+ /** 同上,按商户归属收敛:商户端订单列表读之前调用,免得商户看到的退款状态比人才端晚几分钟。 */
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public int confirmPendingRefundsOfMerchant(Long merchantId, int limit) {
+ return confirmPendingRefunds("o.merchant_id", merchantId, limit);
+ }
+
+ private int confirmPendingRefunds(String ownerColumn, Long ownerId, int limit) {
+ if (ownerId == null) return 0;
+ List<Map<String, Object>> pending = rows("""
+ select r.refund_no
+ from order_refund r
+ join benefit_order o on o.id = r.order_id
+ where r.deleted = 0 and r.provider = ? and r.refund_status = 1
+ and o.deleted = 0 and %s = ?
+ and r.created_at <= date_sub(now(), interval ? second)
+ order by r.id desc
+ limit ?
+ """.formatted(ownerColumn), PROVIDER, ownerId, SUBMISSION_GRACE_SECONDS, Math.max(1, limit));
+ for (Map<String, Object> row : pending) {
+ try {
+ query(text(row.get("refundNo")));
+ } catch (RuntimeException ignored) {
+ // 这一笔这次问不到,保持在途;对账任务与下一次读会再来。
+ }
+ }
+ return pending.size();
+ }
+
public Map<String, Object> detail(String refundNo) {
List<Map<String, Object>> rows = rows("""
select r.id, r.order_id, r.refund_no, r.order_no, r.order_type, r.paid_amount, r.refund_amount,
@@ -165,9 +216,12 @@
private void markSucceeded(PreparedRefund prepared, ProviderResult result) {
transactions.executeWithoutResult(status -> {
+ // 通道退款流水号只在发起退款的那次回执里有;退款查询的回执不带它。这里必须保留已有的号,
+ // 否则查单确认会把唯一的通道凭证覆盖成空,退款排查和对账就失去定位依据。
jdbc.update("""
update order_refund
- set refund_status = 2, provider_refund_no = ?, provider_response_code = ?, provider_response_message = ?, refund_time = now()
+ set refund_status = 2, provider_refund_no = coalesce(nullif(?, ''), provider_refund_no),
+ provider_response_code = ?, provider_response_message = ?, refund_time = now()
where refund_no = ? and refund_status = 1
""", result.providerNo(), result.code(), result.message(), prepared.refundNo());
jdbc.update("""
--
Gitblit v1.7.1