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