|
|
@@ -225,7 +225,7 @@
|
|
|
- 📌 行号提示:本次改动在 `createRefund` 里插入了约 60 行注释与代码,**本台账上文引用的 `:149` / `:181-182` 等行号已整体下移**;
|
|
|
按内容定位(`client.post("/v3/refundorder")` / `refundMapper.insert`),不要按行号。
|
|
|
|
|
|
-### 2.2 ★★ 退款失败的**同步/异步语义不对称** —— 唯一一条可能**重复出金**(承重前提已解除,行为未改)
|
|
|
+### 2.2 ★★ 退款失败的**同步/异步语义不对称** —— 唯一一条可能**重复出金**(**已修,2026-09-22**)
|
|
|
|
|
|
| 路径 | 写什么状态 | 在途闸门 |
|
|
|
|---|---|---|
|
|
|
@@ -238,6 +238,11 @@
|
|
|
|
|
|
#### ✅ 裁定(2026-09-22,逐条重读源码,行号已核对)
|
|
|
|
|
|
+> ⚠ **本节行号是裁定时刻的快照,别当现状用。** 本条的落地(下面「✅ 已实施」)
|
|
|
+> 在 `LsyNotifyService.handleRefundNotify` 里加长了约 35 行 ⇒ 该文件**本节之后**的行号
|
|
|
+> 已整体下移;`LsyRefundService` 同理(在其第 508 行附近加了注释)。
|
|
|
+> 与 V1.8 里的同款告诫一样:**按内容定位,别按行号**。
|
|
|
+
|
|
|
**成立。表里两行都复核无误:**
|
|
|
|
|
|
- **同步失败**:`LsyRefundService:205-261` 两个失败分支(`PAY_FAIL` `:205-230`、未知码 `:231-261`)都**只 `log.error`、不写任何状态** ⇒ 行留在 `REFUNDING` ⇒ 在途闸门**持着**。代码自己写明理由(`:216-218`):「落 FAIL 会**释放互斥窗口**:若上游这个 PAY_FAIL 是偶发误报(钱其实出去了),第二笔退款就真的放行了,方向正好是资金最坏的那一侧。」
|
|
|
@@ -267,7 +272,40 @@
|
|
|
- 但它同时把「回调丢了」的失败退款推迟到一个 `reconcileDelaySeconds` 之后才有出口,
|
|
|
且出口依赖上游 `/v3/queryrefund` 真的答得出来(未知码情形对账腿同样不落状态);
|
|
|
- ⇒ 这是一次**可做但未做的行为变更**,需要单独裁定,本增量只解除它的前提。
|
|
|
-- **仍未做**:`handleRefundNotify` 的 `TRADE_REFUND_FAIL` 依旧写 FAIL 释闸。
|
|
|
+
|
|
|
+#### ✅ 已实施(2026-09-22,裁定 R-1 落地)
|
|
|
+
|
|
|
+`LsyNotifyService.handleRefundNotify` 的 `TRADE_REFUND_FAIL` 分支不再 `outcome = "FAIL"`,
|
|
|
+改为 `log.error` + 审计行(新词 `HELD_FOR_RECONCILE`)+ **直接返回**,不调 `applyNotifyResult`
|
|
|
+⇒ 退款行留在 `REFUNDING`,两把锁继续生效。
|
|
|
+
|
|
|
+| 读数 | 结果 |
|
|
|
+|---|---|
|
|
|
+| 全量测试 | **412 绿 / 0 失败**(改前 411,差 = 新增 1 条) |
|
|
|
+| 变异 M-A(把守闸精确变回释闸:只改「写状态」这一件事,日志保留) | **恰好 1 条红** —— 正是新用例 |
|
|
|
+| 变异 M-B(无害:换日志措辞 + 交换 log 与审计行的先后 + 审计词走局部常量) | **22 全绿**(刻意钝感) |
|
|
|
+| 改前 RED | `applyNotifyResult(..., TRADE_REFUND_FAIL, null, FAIL)` —— 红在正确的位置 |
|
|
|
+
|
|
|
+**★ 一处订正:推翻我此前写下的「有测试要改」。** §八 R-1 的「落地要点」当时写着
|
|
|
+「现有一条钉住 `TRADE_REFUND_FAIL → FAIL` 的用例会红,那正是预期的红」——
|
|
|
+**这句是假的**。实测:`TRADE_REFUND_FAIL` 这个字面量在全测试树里**只出现在两处注释**中
|
|
|
+(`LsyNotifyServiceTest:164`、`LsyRefundServiceTest:562`),没有任何一条用例把
|
|
|
+`TRADE_REFUND_FAIL` 喂进 `handleRefundNotify`;调用点全是 `refundBody("TRADE_REFUND")`。
|
|
|
+⇒ 该分支改前是**零覆盖**,本次新写的用例是它的**第一条**覆盖,不存在「要改的旧用例」。
|
|
|
+(教训与 §约束文档自己会失真同类:我对自己刚写完的台账做计数断言,没有实测。)
|
|
|
+
|
|
|
+**连带改动(都是为了让文档不再失真,不是顺手重构):**
|
|
|
+- `LsyRefundService.applyNotifyResult` 的「三个调用点」注释补一句:**FAIL 现在只剩
|
|
|
+ `settleFromQuery` 一条路能传**。判据是 5 个调用点逐个量过(非终态 1、SUCCESS 3、FAIL 1)。
|
|
|
+- `V1.8` 两处「超过 16 字符的**恰好三条**/去重后共 **9** 个」→ **四条/10 个**
|
|
|
+ (`HELD_FOR_RECONCILE` = 18,是全部 10 个里第三长)。两处**都**改了 ——
|
|
|
+ 该注释自己要求「两处必须列同一组取值」。
|
|
|
+- `V1.8` 的 `COMMENT ON COLUMN ... process_status` 词表补上第 10 个词。
|
|
|
+ 实测库里 `process_status` **已是 `VARCHAR(32)`**(就地加宽那次确实生效了),
|
|
|
+ 10 个词逐一对过列宽,**超宽 0 个**。但库里那句**注释**还是旧的 9 词版
|
|
|
+ ⇒ 已交付 `db/lsy-notify-process-status-comment.sql`(纯元数据、幂等,**待用户执行**)。
|
|
|
+ ⚠ 「库里实际写过哪些 process_status 取值」这一读数**无意义**:`pay_lsy_notify_log` 0 行
|
|
|
+ (空洞为真),别把它当成「这 10 个词都用过」。
|
|
|
|
|
|
### 2.3 ★ 退款单可能永久卡在 `REFUNDING`(**已修,2026-09-22**)
|
|
|
|
|
|
@@ -544,6 +582,14 @@ component / is_frame / is_cache / menu_type / visible / perms / ...`),**本
|
|
|
> 而业务错误的约定**就是** HTTP 200 + `code`(文档 §3.6 那句「别信 HTTP 状态码」)。
|
|
|
> **⇒ 断言必须照规格写,不能照印象写**(先打印报文再断言)。修正后 8/8。
|
|
|
|
|
|
+### 3.4 `db/lsy-notify-process-status-comment.sql`(**非阻塞**,纯注释同步)
|
|
|
+
|
|
|
+裁定 R-1 新增了第 10 个审计词 `HELD_FOR_RECONCILE`(2026-09-22),
|
|
|
+`pay_lsy_notify_log.process_status` 的**列注释词表**要跟着补一个。
|
|
|
+实测:库里该列**已是 `VARCHAR(32)`**(10 个词逐一对过列宽,超宽 **0** 个)⇒ 宽度**不用改**;
|
|
|
+要改的只有那句注释本身。纯元数据、幂等、不碰数据也不碰索引 ⇒ **不阻塞任何事** ——
|
|
|
+不发也不影响功能,只是查库的人少看到一个词。同款先例:`db/lsy-refund-no-comment-fix.sql`。
|
|
|
+
|
|
|
---
|
|
|
|
|
|
## 四、数据可见性与排查提示(不是 bug,但排查时会踩)
|
|
|
@@ -665,9 +711,13 @@ component / is_frame / is_cache / menu_type / visible / perms / ...`),**本
|
|
|
|
|
|
### R-1 ★★ §2.2 的 `TRADE_REFUND_FAIL` → **改成守闸**
|
|
|
|
|
|
-**结论:改。** 同一个上游语义(「这笔退款没成功」)经两条路到达,现在判断相反:
|
|
|
+**结论:改。** 同一个上游语义(「这笔退款没成功」)经两条路到达,裁定当时判断相反:
|
|
|
+
|
|
|
+> ⚠ 下表第三列是**裁定时刻(2026-09-22)的现状快照**,不是现状 ——
|
|
|
+> 本条**已落地**,异步那一格现在也是守闸(实施记录见 §2.2 的「✅ 已实施」)。
|
|
|
+> 保留原表是因为它记录的正是「当时错在哪」,改掉就看不出这条裁定在修什么了。
|
|
|
|
|
|
-| 路径 | 位置 | 现在怎么做 |
|
|
|
+| 路径 | 位置 | 裁定当时怎么做 |
|
|
|
|---|---|---|
|
|
|
| 同步应答 `PAY_FAIL` | `LsyRefundService.createRefund:289-314` | **守闸**:只 `log.error`,**不写任何状态**,留在 `REFUNDING` |
|
|
|
| 异步回调 `TRADE_REFUND_FAIL` | `LsyNotifyService.handleRefundNotify:255-256` | **释闸**:`outcome = "FAIL"` → `applyNotifyResult(..., "FAIL")` |
|
|
|
@@ -702,16 +752,16 @@ component / is_frame / is_cache / menu_type / visible / perms / ...`),**本
|
|
|
本条不建立在「上游会误报」这个未经证实的假设上,而建立在**依据二**(确定的事实)与
|
|
|
**依据一**(代价不对称)上。
|
|
|
|
|
|
-**落地要点**(未实施):
|
|
|
-- `TRADE_REFUND_FAIL` 分支不再落 FAIL,改为 `log.error`(让人立刻看得见)+ 审计行。
|
|
|
-- 需要**一个新的审计状态词**:`UNKNOWN_RESULT_CODE` 是「不认识这个码」,语义不同,
|
|
|
- 不能复用;`APPLIED` 更不能(本次没有回写)。候选 `HELD_FOR_RECONCILE`
|
|
|
- (=「扣住,留待对账腿拿权威答案」)。
|
|
|
+**落地要点** → **✅ 已实施(2026-09-22)。实施读数与连带改动见 §2.2 的「✅ 已实施」。**
|
|
|
+- `TRADE_REFUND_FAIL` 分支不再落 FAIL,改为 `log.error` + 审计行 + 直接返回。
|
|
|
+- 审计词取 `HELD_FOR_RECONCILE`(18 字符,`process_status` 是 `VARCHAR(32)`,放得下)。
|
|
|
+ 不复用 `UNKNOWN_RESULT_CODE`:那是「不认识这个码」,语义不同(这里恰恰是认识)。
|
|
|
⚠ 「不新造上游词汇」那条约束针对的是**上游字段值**(如 `result_code` 的字面量,
|
|
|
必须文档实证);审计状态词本来就是**我们自己的**词表,新增是允许的。
|
|
|
-- 副作用(是好的):退款行的 `FAIL` 从此**只由一条路径写** —— 对账腿问上游拿到的
|
|
|
- 权威答案(`settleFromQuery:437-439`)。规则收敛成「终态只由权威答案写」。
|
|
|
-- 有测试要改:现有一条钉住 `TRADE_REFUND_FAIL → FAIL` 的用例会红,**那正是预期的红**。
|
|
|
+- 副作用(是好的,已落地):退款行的 `FAIL` 从此**只由一条路径写** —— 对账腿问上游拿到的
|
|
|
+ 权威答案(`settleFromQuery`)。规则收敛成「终态只由权威答案写」。
|
|
|
+- ⚠ **本节原先写的「有测试要改:现有一条钉住 `TRADE_REFUND_FAIL → FAIL` 的用例会红」
|
|
|
+ 是假的** —— 该分支改前**零覆盖**(字面量只出现在两处注释里)。详见 §2.2 的订正。
|
|
|
|
|
|
### R-2 `status='SUCCESS'` 而 `trade_no` 为空 → **登记 + 最小可见化**
|
|
|
|