日期:2026-09-22
依据:docs/superpowers/specs/2026-09-21-leshouying-payment-channel-open-questions.md §2.1–2.3 裁定
设计出处:design §6.6 PAY_IN_PROCESS → REFUNDING,由 Job 2 查 v3/queryrefund 推进
设计 §6.6 写了这条腿,实现时掉了 —— LsyReconcileScheduler.reconcile() 四条腿全是订单,
退款侧零对账。后果是一条链:
REFUNDING 的退款行永久卡住(§2.3),在途闸门 + 部分唯一索引双双锁死该单;TRADE_REFUND_FAIL 必须写 FAIL 放闸 —— 那是唯一能让失败退款
离开在途状态的通道(§2.2 的承重权衡)。补上对账腿后 §2.3 直接消除,且 §2.2 的守闸才有可能变安全(本增量不改编回调行为)。
D:\project2\leshouying-api-docs.md)| 事实 | 出处 |
|---|---|
退款查询 result_code ∈ {PAY_SUCCESS, PAY_IN_PROCESS, PAY_FAIL} |
## 15. 退款查询 :603 |
退款回调是另一套词表(TRADE_REFUND*) |
## 8. :441 |
「PAY_IN_PROCESS 需调用退款查询接口确认退款结果」= 查询是权威确认通道 |
:485 |
退款查询 mch_no 必填 |
:596 |
LsyRefundService.queryRefund 补 mch_no文档标必填;结构孪生 LsyQueryService.queryOrder 传了它、且联调实测查单走通;同类的
createRefund 也传。注入 LsyOrderMapper,按 refund.getOrderId() 取 order 的 mchNo。
取不到则不传(LeshouyingClient.post 过滤 null)⇒ 退化为当前行为,不会更糟。
⚠ 这会改两条钉住 keySet 的测试(LsyRefundServiceTest:343 / :381)。那是刻意的契约变更,
不是把测试改绿 —— 新集合要多一个 mch_no。
LsyRefundService.settleFromQuery(refund, resp)(新)镜像 LsyOrderService.settleFromQuery(:412)的形状与纪律:
isPaySuccess → applyNotifyResult(..., "SUCCESS") ⇒ 命中则订单侧
settleOrderAfterRefund + callbackService.notifyOrder ⇒ 返回 "SUCCESS"isPayFail → applyNotifyResult(..., "FAIL") ⇒ 返回 "FAIL" 或 null
(依据 :485,查询是权威确认通道;这与 createRefund 同步分支守闸不矛盾 ——
同步分支手上没有权威答案,对账腿有)settleFromQuery 支付侧同纪律)PAY_IN_PROCESS → 不动,等下一轮订单侧收敛放在这里而不是调度器:调度器 javadoc 明令「本调度器自己不解析任何上游报文」。
LsyReconcileScheduler.reconcilePendingRefunds()(新)形状对齐 reconcilePendingOrders():扫 status IN LsyRefundStatus.PENDING
且 createdTime <= now - reconcileDelaySeconds,LIMIT 100;逐条 try/catch 隔离,单条失败不中断。
新增依赖 LsyRefundMapper + LsyRefundService,接进 reconcile() 第 5 条腿。
「退款侧没有兜底对账」在 6 个文件里被当成事实断言,加腿后即为假:
enums/LsyRefundStatus / scheduler/LsyOrderPollScheduler / scheduler/LsyReconcileScheduler
(含那段靠 grep 计数的纪律)/ service/LsyNotifyService(×2) / service/LsyRefundServiceTest(×3)
/ db/migration/V1.8(×2)。
★ V1.8 那两处要先确认是 -- 注释还是 COMMENT ON:若是后者,改文件会让它与已执行的库
分叉(库里的列注释是逐字核对过的物证)。
LsyNotifyService.handleRefundNotify 对 TRADE_REFUND_FAIL 落 FAIL 的行为。
台帐 §2.2 裁定:它是承重权衡,本腿只是让「将来改成守闸」变安全。⚠ 2026-09-22 补充:handleRefundNotify 的定位段恒落空(退款回调报文不带任何
退款标识,查单 refund != null 永不成立)⇒ 上面这条「承重权衡」以及整个退款回调的
三分支词表/幂等闸门,在真实流量下都是死区。本腿(对账)因此不只是「兜底」,
而是退款侧唯一真正在跑的收敛通道。见台账 §一「★★ 2026-09-22 裁定」。
本增量不改定位逻辑(挂起中),只订正了注释。
LATE_REFUND_AFTER_TERMINAL 目前只有事后探测器)。退款对账腿没有上界,可被老行饿死。 订单侧有 absoluteExpireHours=24 强制收敛
(expireStaleOrders),退款侧不可能有对应物 —— 退款行没有任何安全的本地终态:
写 FAIL 会释放 uk_lsy_refund_one_pending + 在途闸门(可重复出金),写 SUCCESS 是谎报出金。
所以一个永远拿不到权威答案的退款行会每 5 分钟被问一次上游,直到永远,
且它一直占着 LIMIT 100 的配额 ⇒ 老行堆积后新退款行轮不到(静默饿死)。
没做的理由:上界该取多少、超界后该怎么办(停查 + 告警?转人工池?),我没有任何上游依据
可推 —— 上游文档没说退款查询结果的有效期。本项目铁律是不得猜测,故不发明这个数字。
建议方向(需裁定,不是结论):只查 createdTime 在 [now-delay, now-上界] 的行,
超界的停查 + log.error(不写任何终态),把无界重试换成有界重试 + 响亮告警。
LsyRefundService(+LsyOrderMapper 依赖、queryRefund 补 mch_no、
新增 settleFromQuery)、LsyReconcileScheduler(+LsyRefundMapper/LsyRefundService 依赖、
新增 reconcilePendingRefunds 并接为 reconcile() 第 ⑤ 条腿)。LsySchedulerTest 18、LsyRefundServiceTest 29,全量 401 绿 / 0 失败(原 390)。queryRefund 的 mch_no ⇒ 两条钉 keySet 的用例红,负对照
queryRefund_订单查不到_不送mchNo 仍绿(它本就只该在实现送 null 键时红);LsyRefundStatus.PENDING 收窄成 Set.of("REFUNDING") ⇒
扫描条件用例红,报出 expected: <[CREATED, REFUNDING]> but was: <[REFUNDING]>;reconcile() 摘掉 ⇒ 「已接入」用例红。LsyRefundServiceTest 的「没有任何定时任务调它」),已一并订正。
顺带订正 LsyReconcileScheduler javadoc 里另一条已过期的断言:
「退款回调自锁、retryFailed() 一行都捞不到」—— 该自锁已由
LsyRefundService.pushRefundCallbackIfTerminal 解除,现不再成立。