From 7bbd9688f58fd0ce612e9c2d3b3d0ccb64dffa2e Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 12 九月 2026 13:42:08 +0800
Subject: [PATCH] refactor(merchant): 斗拱能力查询移出数据库事务

---
 talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java |   46 ++++++++++++++++++++++++++--------------------
 1 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java
index 8bb5530..1ebd55c 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantPaymentReadinessService.java
@@ -37,6 +37,7 @@
     private final HuifuMerchantCapabilityGateway capabilityGateway;
     private final HuifuPaymentProperties huifuPaymentProperties;
     private final TransactionTemplate refreshTransaction;
+    private final TransactionTemplate providerTransaction;
 
     public MerchantPaymentReadinessService(
             JdbcTemplate jdbc,
@@ -51,6 +52,10 @@
         // The snapshot must survive a failed provider query in the caller's transaction, so it runs
         // in its own transaction instead of joining the caller's.
         this.refreshTransaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+        // The provider is an HTTP call: never make payment or onboarding hold a database transaction
+        // (and its row locks) while waiting for it.
+        this.providerTransaction = new TransactionTemplate(refreshTransaction.getTransactionManager());
+        this.providerTransaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
     }
 
     /** Readiness of the merchant's newest approved onboarding, refreshing the provider view when stale. */
@@ -75,8 +80,8 @@
 
     /** Re-reads the provider configuration and stores the resulting snapshot. */
     public Map<String, Object> refresh(Long applicationId) {
-        Application application = requireApprovedApplication(applicationId);
-        queryProvider(application);
+        // The snapshot is only updated inside providerTransaction, which needs no row lock.
+        queryProviderOutsideTransaction(requireApprovedApplication(applicationId));
         return capabilityView(requireApprovedApplication(applicationId));
     }
 
@@ -84,11 +89,10 @@
      * Capability snapshot for the admin settlement page. It queries the provider when the snapshot
      * has never been confirmed, because the explicit admin request is the moment to find out.
      */
-    @Transactional
     public Map<String, Object> capability(Long applicationId) {
         Application application = requireApprovedApplication(applicationId);
         if (application.capabilityVerifiedAt().isBlank()) {
-            queryProvider(application);
+            queryProviderOutsideTransaction(application);
             application = requireApprovedApplication(applicationId);
         }
         return capabilityView(application);
@@ -96,9 +100,8 @@
 
     /**
      * Attempts to refresh approved applications whose capability snapshot is missing or older than
-     * the configured interval. Each application is refreshed in its own transaction, so a provider
-     * outage leaves every previous snapshot untouched; the run stops early because one failure
-     * normally means the next call fails too.
+     * the configured interval. Each application is refreshed in its own transaction, so one failing
+     * merchant never affects the others or the snapshots already stored.
      */
     public int refreshStaleCapabilities(int staleHours, int limit) {
         if (!providerVerificationRequired() || !huifuPaymentProperties.isEnabled()) return 0;
@@ -111,8 +114,7 @@
                 """, Math.max(1, staleHours), Math.max(1, Math.min(limit, 100)));
         int refreshed = 0;
         for (Map<String, Object> row : rows) {
-            if (!refreshQuietly(((Number) row.get("id")).longValue())) break;
-            refreshed++;
+            if (refreshQuietly(((Number) row.get("id")).longValue())) refreshed++;
         }
         return refreshed;
     }
@@ -124,8 +126,10 @@
     public boolean refreshQuietly(Long applicationId) {
         if (applicationId == null || !providerVerificationRequired() || !huifuPaymentProperties.isEnabled()) return false;
         try {
-            refreshTransaction.executeWithoutResult(status -> refresh(applicationId));
-            return true;
+            return Boolean.TRUE.equals(refreshTransaction.execute(status -> {
+                refresh(applicationId);
+                return Boolean.TRUE;
+            }));
         } catch (RuntimeException exception) {
             log.warn("Merchant capability refresh failed: applicationId={}, type={}", applicationId, exception.getClass().getSimpleName());
             return false;
@@ -138,15 +142,9 @@
                 || application.merchantStatus().isBlank()
                 || application.capabilityVerifiedAt().isBlank();
         if (unknown && !providerVerificationRequired()) return localOnly(application);
-        if (unknown) {
-            if (!queryProvider(application)) return Readiness.blocked("商户收款能力待确认,暂时无法收款");
-            Application refreshed = approvedApplication(application.merchantId(), application.storeId());
-            if (refreshed == null || refreshed.id() != application.id()) return Readiness.blocked("商户收款能力待确认,暂时无法收款");
-            if (refreshed.autoSettleStatus().isBlank() || refreshed.cashSwitchStatus().isBlank()) {
-                return Readiness.blocked("商户收款能力待确认,暂时无法收款");
-            }
-            application = refreshed;
-        }
+        // The payment path never calls the provider itself: an unfilled snapshot is filled by the
+        // onboarding completion, the scheduler, or an explicit admin refresh.
+        if (unknown) return Readiness.blocked("商户收款能力待确认,暂时无法收款");
         if (!isAutoSettleOff(application.autoSettleStatus())) return Readiness.blocked("商户自动结算尚未关闭,暂时无法收款");
         if (!isCashAvailable(application.cashSwitchStatus())) return Readiness.blocked("商户取现能力不可用,暂时无法收款");
         if (!isMerchantNormal(application.merchantStatus())) {
@@ -166,6 +164,14 @@
         return Readiness.allowed();
     }
 
+    /**
+     * Runs the provider query outside any surrounding transaction so a slow Huifu call never holds
+     * the caller's row locks. Returns whether the snapshot was refreshed.
+     */
+    private boolean queryProviderOutsideTransaction(Application application) {
+        return Boolean.TRUE.equals(providerTransaction.execute(status -> queryProvider(application)));
+    }
+
     /** Returns false when the provider explicitly rejected the query; the snapshot then stays unknown. */
     private boolean queryProvider(Application application) {
         try {

--
Gitblit v1.7.1