소스 검색

fix(omg-pay): 移除 queryTrade 首查延迟 40min 闸(误读 OMG 节流,致已付客户等 40min)

闸1"下单 40min 内不查 OMG"是基于对 OMG 节流规则的误读:OMG 的 HTTP 403 是"短时间内反复查同一笔"
(频率)触发,不是"下单后 N 分钟内不能查"。已付款交易可立即查状态(所有支付网关都支持付完即查)。

危害:notify 丢失时,/query 和定时任务都被闸1 挡 40min → 已付客户要等 40min 才看到订单状态变更,
恰恰把它最该生效的场景堵死了,不可接受。

修复:删除闸1(OmgQueryThrottle.isWithinFirstQueryDelay 方法 + firstQueryMinutes 配置 + reconcileByQuery
里的调用块)。防 OMG 403 靠剩下的频率闸即可:
- per-MerchantID 令牌桶(1token/3s,burst1)
- per-ddId 扫描间隔(/query 60s、定时 180s)
"刚创建未付"由定时任务扫描窗口的 2min 宽限(create_time <= now-2min 才扫)保护,无需 40min 黑名单。

改后已付客户三条路径都无 40min 等待:notify 秒级 / /query 立刻 / 定时任务 2-3min 内补单。
mvn compile BUILD SUCCESS。

Co-Authored-By: Claude <noreply@anthropic.com>
qmj 2 주 전
부모
커밋
fc46fed861

+ 1 - 6
ruoyi-admin/src/main/java/com/ruoyi/app/pay/OmgPayController.java

@@ -847,12 +847,7 @@ public class OmgPayController extends BaseController {
                 log.error("[OMG-{}] 门店凭证不可用: ddId={}, mtcn={}", source, ddId, p.getMerchantTradeNo());
                 continue;
             }
-            // 闸1:首查延迟——下单 40min 内不查 OMG(否则按 MerchantID 403 罚 30min)(T055)
-            if (omgQueryThrottle.isWithinFirstQueryDelay(p.getCreateTime())) {
-                log.debug("[OMG-{}] 首查延迟(<40min),跳过本行: ddId={}, mtcn={}", source, ddId, p.getMerchantTradeNo());
-                continue;
-            }
-            // 闸2:per-MerchantID 令牌桶(1token/3s,burst1)——拿不到令牌跳过本行本轮,不阻塞(T055)
+            // per-MerchantID 令牌桶节流(1token/3s,burst1):拿不到令牌跳过本行本轮,不阻塞(防 OMG 403)
             if (!omgQueryThrottle.tryAcquireMerchantToken(p.getMerchantId())) {
                 log.debug("[OMG-{}] MerchantID 令牌桶限流,跳过本行本轮: ddId={}, mtcn={}", source, ddId, p.getMerchantTradeNo());
                 continue;

+ 10 - 23
ruoyi-admin/src/main/java/com/ruoyi/app/utils/omg/OmgQueryThrottle.java

@@ -4,28 +4,24 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Component;
 
-import java.util.Date;
 import java.util.concurrent.TimeUnit;
 
 /**
- * OMG queryTrade 三道节流闸(T055,防 OMG 按 MerchantID HTTP 403 自残)
+ * OMG queryTrade 节流(防 OMG 按 MerchantID HTTP 403 自残)
  *
- * <p>设计见 {@code specs/016-omg-payment/payment-attempt-lifecycle.md} §6.4【C】。三闸:
+ * <p>OMG 的 403 是"短时间内反复查同一笔"触发(频率),不是"下单后 N 分钟内不能查"。故只做 <b>频率</b> 节流、
+ * 不做时间黑名单——已付款订单可立即查状态(notify 丢失时 /query 立刻补单)。原"40min 首查延迟"是对 OMG 节流的
+ * 误读(会让已付客户等 40min 才看到状态变更,不可接受),已移除。两道闸,均基于 Redis {@code SET NX EX}(原子,无锁竞争):
  * <ol>
- *   <li>闸1 首查延迟:下单后 N 分钟(默认 40)内不查 OMG(OMG/ECPay 节流,过早查触发 403 罚 30 分钟)。
- *       跳“新”行(创建距今 &lt; 40min),与 OMG 官方“下单后 40 分钟内别查”一致。</li>
- *   <li>闸2 per-MerchantID 令牌桶:同一商店代号每 interval(默认 3s)只放行一次 queryTrade(burst 1),
+ *   <li>per-MerchantID 令牌桶:同一商店代号每 interval(默认 3s)只放行一次 queryTrade(burst 1),
  *       非阻塞——拿不到令牌跳过本行本轮。</li>
- *   <li>闸3 per-ddId 扫描间隔:{@code /query} 默认 60s、定时默认 180s 内不重复查同一订单(挡前端 2s 轮询放大)。</li>
+ *   <li>per-ddId 扫描间隔:{@code /query} 默认 60s、定时默认 180s 内不重复查同一订单(挡前端 2s 轮询放大)。</li>
  * </ol>
- * 全部基于 Redis {@code SET NX EX}(原子),无锁竞争。参数 §9 待 OMG 官方确认后据实调整。
+ * 参数 §9 待 OMG 官方确认后据实调整。
  */
 @Component
 public class OmgQueryThrottle {
 
-    @Value("${omg.query-throttle.first-query-minutes:40}")
-    private int firstQueryMinutes;
-
     @Value("${omg.query-throttle.merchant-token-seconds:3}")
     private int merchantTokenSeconds;
 
@@ -41,16 +37,7 @@ public class OmgQueryThrottle {
         this.redis = redis;
     }
 
-    /** 闸1:该流水是否仍在首查延迟窗口内(创建距今 &lt; firstQueryMinutes 分钟 → 跳过不查)。未知创建时间不拦。 */
-    public boolean isWithinFirstQueryDelay(Date createTime) {
-        if (createTime == null) {
-            return false;
-        }
-        long ageMs = System.currentTimeMillis() - createTime.getTime();
-        return ageMs < firstQueryMinutes * 60_000L;
-    }
-
-    /** 闸2:per-MerchantID 令牌桶(1 token / merchantTokenSeconds,burst 1)。true=已占位可查;false=限流跳过本行本轮。 */
+    /** per-MerchantID 令牌桶(1 token / merchantTokenSeconds,burst 1)。true=已占位可查;false=限流跳过本行本轮。 */
     public boolean tryAcquireMerchantToken(String merchantId) {
         if (merchantId == null || merchantId.isEmpty()) {
             return true;
@@ -60,8 +47,8 @@ public class OmgQueryThrottle {
         return Boolean.TRUE.equals(ok);
     }
 
-    /** 闸3:per-ddId 扫描间隔。source="query"→/query(queryDdSeconds);其他→定时(reconcileDdSeconds)
-     *  true=可处理;false=窗口内重复,跳过本轮。 */
+    /** per-ddId 扫描间隔。source="query"→/query(queryDdSeconds);其他→定时(reconcileDdSeconds)
+     *  true=可处理;false=窗口内重复,跳过本轮。 */
     public boolean acquireDdIdSlot(String ddId, String source) {
         if (ddId == null || ddId.isEmpty()) {
             return true;