Pārlūkot izejas kodu

feat(汇安融订单): 16 定性为未收款终态,不再排期轮询

上游对长时间未支付的单自行关单后回 trade_status=16。我方手上两份规范
(260924 / 261008)均未定义这个码,实测出现在长时间未支付的订单上
(下单约 9 分钟后被上游关闭,早于 pay_timeout=15)。2026-10-10 用户裁定
接纳这个值:本地按**未收款终态**分类,值逐字入库、逐字出网,不本地转义。

- HarOrderStatus 新增 TIMEOUT_CLOSED="16":
  · 进 FINAL ⇒ reschedulePoll 走「清空 next_query_time」那条路,不再排
    一个永远查不出结果的轮询;
  · 不进 PENDING(={0,4}):钱没到,「还在等支付」不成立;
  · 不进 PAY_RECORDED(={1}):首页收款统计不多算;
  · 退款腿只认 PAID="1",它不在可退集合里,行为不变。
- HarOrderService.reschedulePoll 注释补 16 的归属说明,并记下「表外值排期
  捞不回」(findPendingForPoll 只捞 PENDING)属历史遗留,本次不动。
- 测试:HarOrderServiceTest 增 reschedulePoll_timeoutClosed16_clearsNextQueryTime
  (字面量 "16",断言 nextQueryTime 被清空);新增 HarOrderStatusTest,三条
  把 16 钉在字面量上(isFinal=true / isNotPending / isNotPayRecorded)。

验证:两向变异(logs/_mut-status16/)J1 把 16 塞进 PENDING ⇒ 恰好
status16_isNotPending 红、其余全绿(j1-pending-red.txt);J2 塞进 PAY_RECORDED
⇒ 恰好 status16_isNotPayRecorded 红(j2-payrecorded-red.txt);两次还原后
4 文件 sha256 与变异前一致(java-baseline-sha.txt),还原后聚焦复跑 80/80 绿
(j-final-green.txt);全量套件 1282/0(logs/_build-status16-full-suite.txt,
跑的是同一批字节)。

残留风险(已裁定接受):上游若把 16 悄悄翻成别的状态,本地不会跟;出口是
管理端「同步上游状态」手工兜。
alphaH 4 dienas atpakaļ
vecāks
revīzija
f4fdb8a391

+ 15 - 2
java/src/main/java/com/payment/platform/module/payment/har/enums/HarOrderStatus.java

@@ -9,6 +9,7 @@ import java.util.Set;
  * 在排查问题时多一层翻译。
  *
  * 0 等待支付 / 1 支付成功 / 2 退款成功 / 4 退款中 / 8 交易关闭
+ * / 16 超时关闭(上游关单;两份规范均未定义,见 {@link #TIMEOUT_CLOSED})
  */
 public final class HarOrderStatus {
 
@@ -18,11 +19,23 @@ public final class HarOrderStatus {
     public static final String REFUNDING = "4";
     public static final String CLOSED = "8";
 
+    /**
+     * 上游对长时间未支付的单自行关单后回的状态码(2026-10-10 用户裁定接纳)。
+     *
+     * <p>我方手上的两份规范(260924 / 261008)都**没有定义 16** —— 语义以上游为准,
+     * 实测出现在长时间未支付的订单上(下单约 9 分钟后即被上游关闭,早于
+     * {@code pay_timeout},关单是上游自己的动作)。本地按**未收款终态**接纳:
+     * 不进 {@link #PENDING}(不再轮询)、不计 {@link #PAY_RECORDED}(钱没到)、
+     * 不可退款(退款腿只认 {@link #PAID})。值本身逐字入库、逐字出网,
+     * 中文文案由前端与下游文档各自转译,本地不转义。
+     */
+    public static final String TIMEOUT_CLOSED = "16";
+
     /** 非终态 —— 需要通过轮询收敛 */
     public static final Set<String> PENDING = Set.of(WAIT_PAY, REFUNDING);
 
-    /** 终态 —— 不再轮询 */
-    public static final Set<String> FINAL = Set.of(PAID, REFUNDED, CLOSED);
+    /** 终态 —— 不再轮询(16 见 {@link #TIMEOUT_CLOSED} 的定性) */
+    public static final Set<String> FINAL = Set.of(PAID, REFUNDED, CLOSED, TIMEOUT_CLOSED);
 
     /**
      * 计入收款净额的状态 —— 只有「1 支付成功」。

+ 5 - 0
java/src/main/java/com/payment/platform/module/payment/har/service/HarOrderService.java

@@ -679,6 +679,11 @@ public class HarOrderService {
      * 这条订单就再也查不到了,退款结果彻底失联。
      *
      * 遇到不认识的状态同样继续排期:上游将来加了新状态,继续查比静默停掉安全。
+     *
+     * <p>「16 超时关闭」自 2026-10-10 起**不是**不认识的状态 —— 它有名字、是终态
+     * ({@link HarOrderStatus#TIMEOUT_CLOSED}),走上面「终态清空」那条腿,不适用本段。
+     * 另注:表外值排了期也捞不回来({@link #findPendingForPoll} 只捞 PENDING),
+     * 历史遗留,本次未动。
      */
     public void reschedulePoll(HarOrderEntity order) {
         if (HarOrderStatus.isFinal(order.getTradeStatus())) {

+ 38 - 0
java/src/test/java/com/payment/platform/module/payment/har/enums/HarOrderStatusTest.java

@@ -0,0 +1,38 @@
+package com.payment.platform.module.payment.har.enums;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * 「16 超时关闭」的定性钉子(2026-10-10 用户裁定)。
+ *
+ * <p>16 是上游返回、我方两份规范(260924 / 261008)都未定义的值,实测出现在长时间
+ * 未支付的订单上(上游自行关单)。定性为**未收款终态**,这份裁定的机器可读含义就是
+ * 三个集合的成员关系 —— 谁把 16 挪进另一个集合,就是在改口径(轮询 / 收款统计),
+ * 必须在这里红。
+ *
+ * <p>断言用字面量 {@code "16"} 而不是常量名:钉的是**值**本身。上游发来的是字符串
+ * "16",常量改名不该红,常量改值必须红。
+ */
+class HarOrderStatusTest {
+
+    @Test
+    void status16_isFinal() {
+        assertTrue(HarOrderStatus.FINAL.contains("16"),
+                "16 是未收款终态:reschedulePoll 对终态清空 next_query_time,不再排期");
+    }
+
+    @Test
+    void status16_isNotPending() {
+        assertFalse(HarOrderStatus.PENDING.contains("16"),
+                "16 不进轮询捞出集合:上游关单后不会再有新状态,继续查是白跑");
+    }
+
+    @Test
+    void status16_isNotPayRecorded() {
+        assertFalse(HarOrderStatus.PAY_RECORDED.contains("16"),
+                "16 不计收款:钱没到(首页「支付宝收款统计」口径)");
+    }
+}

+ 17 - 0
java/src/test/java/com/payment/platform/module/payment/har/service/HarOrderServiceTest.java

@@ -830,6 +830,23 @@ class HarOrderServiceTest {
         assertNotNull(o.getNextQueryTime());
     }
 
+    /**
+     * 「16 超时关闭」不再是表外值:2026-10-10 用户裁定按未收款终态接纳,
+     * 排期腿就地清空 next_query_time(此前的实测行会挂着一个永远不执行的
+     * 「幽灵排期」,因为它不在 PENDING 里、findPendingForPoll 捞不回)。
+     *
+     * <p>用字面量 "16" 钉值本身;同款定性成员关系见 HarOrderStatusTest。
+     */
+    @Test
+    void reschedulePoll_timeoutClosed16_clearsNextQueryTime() {
+        HarOrderEntity o = order("HAR1", "16");
+        o.setNextQueryTime(java.time.OffsetDateTime.now().plusMinutes(5));
+
+        service.reschedulePoll(o);
+
+        assertNull(o.getNextQueryTime(), "16 按未收款终态接纳,不应再排期");
+    }
+
     /** 查询失败也要推进下次查询时间,否则这条记录每轮都被捞起,打爆上游 */
     @Test
     void sync_queryFailure_stillAdvancesNextQueryTime() {