2026-09-24-har-refund-交付说明.md 5.7 KB

汇安融退款 交付说明(2026-09-24)

计划:.claude/plan/2026-09-24-har-refund.md(同批入库)

一、提交清单(6 个,均未 push)

提交 内容
4b896a0 上游客户端加退款 —— kf.alipay.trade.refund(HarClient#refund + 出站 URL 用例)
b36f397 退款服务 —— 先抢占「退款中」,再调上游(HarOrderService#openRefund + 12 条用例)
2f54088 退款端点 —— POST /payment/openapi/har/refund(控制器 + 5 条用例 + 认证清单一行)
ff7f517 租户文档补 10.4 退款,回调/状态顺延为 10.5/10.6
a631b9f 顺带:LeshouyingPropertiesTest 的 yml 取值限定段内(并入 HAR 时 har.base-url 排在 leshouying.base-url 之前导致取错键;与退款无关,先前遗留未提交)
9c48487 退款补 reason(联调实测出的必填格,见 §四 2)

本节末次修订于 2026-09-24 联调第一轮之后。§二 的读数写的是当时那一刻(871 条), 9c48487 之后的读数是 873 条。

设计裁定(见计划):只有开放 API 入口、不建 pay_har_refund 表、先抢占后退款、 上游明确拒绝才回退、结果未知不回退、不解析上游 detail、不加 @Transactional。

二、验证读数

  • mvn test 全量 871 条,0 失败 0 错误(logs/_har-refund-full.log)
  • HarOrderServiceTest 46 条(原 34 + 新 12)、HarOpenControllerTest 11 条(原 6 + 新 5)
  • 两向变异(脚本 logs/_har-refund-svc-mutate.py、logs/_har-ctl-mutate.py;每次还原后复核 sha256 与注入前逐字节一致):
变异体 结果
去掉抢占守卫 WHERE trade_status='1' 红 1(openRefund_preemptCarriesPaidGuardAndSchedulesPoll)
上游调用挪到抢占之前 红 3(含 openRefund_preemptsToRefundingBeforeCallingUpstream)
超时分支也回退 红 1(openRefund_upstreamTimeout_keepsRefundingState)
认证清单漏列 /refund 红 1(everyEndpoint_isListedInTheAuthFilterInventory)
控制器不设闸 红 3
闸挪到 openRefund 之后 红 2(404 照样抛得出,是 verifyNoInteractions 抓住的顺序)

三、部署要求

新端点,必须重新部署才生效。 无 SQL、无配置项、无菜单权限串改动。

9c48487(reason)也在同一批里 —— 它同样要重新部署才生效,所以联调第二轮之前 必须先发版,否则重试的还是旧包、还会回同一句「退款原因(reason)不能为空」。

四、未验证 / 未做(逐条,均为交付时的实测状态)

  1. 退款仍未跑成过 —— 第一轮真机请求被上游以参数校验拒了(见本条下一格), 补完 reason 后还没重试。所以「一笔钱真的退回去了」至今没有读数。
  2. method 取值未被上游确认 —— 2026-09-24 联调实测解除。不带 reason 的那次 请求,上游回的是「详细参数校验异常,退款原因(reason)不能为空!」:报错发生在参数校验, 说明 kf.alipay.trade.refund 这个 method 被上游认下来了(若 method 不存在,回来的会是 「接口不存在」一类,走不到参数校验)。表 8 备注格那个 kf.jh.stage.trade.refund 至此可以 当错字看待,HarClientOutboundUrlTest 里那行注释的依据不变。
  3. reason 是规范漏写的必填格(本轮新增,已修 9c48487):5.4(表 8)的 biz 只有 order_no / third_order_no,kf-request-demo 没有退款示例,两版 docx 转写件全文搜不到 reason —— 三处都没有,而线上必填。该表的「不为空」列因此被证明不可当全集读, 与 subject/title 那次是同一类缺陷。
    • 我们发 reason,调用方留空时补默认值「商户申请退款」,填了以调用方为准(9c48487)。
    • 长度上限与格式仍未公布,所以没有加 @Size、没有截断 —— 编一个上限出来会把本来 能成的调用方挡在门外。超长只能由上游拒,报错原样透出。
    • 是否还有别的隐藏必填格,同样未知:这轮只撞出 reason 一个。下一次联调若再回 「xx 不能为空」,按同一条路补。
  4. 上游表 9 的 detail 结构从未公开,我们不解析它。成功判据只用了 biz_content / response_code / msg。若上游实际语义与此不符,只会在联调中暴露。
  5. 退款收敛(4 → 2)没有任何用例钉住:findPendingForPoll 用 HarOrderStatus.PENDING = {0,4},而全测试树里 HarOrderStatus.PENDING / isPending / FINAL 的引用数为 0。谁把这个集合里的 4 去掉,退款会永远停在「退款中」而没有任何测试变红。
  6. 管理端无退款入口(裁定:只做开放 API)。pay_har_refund 表不存在(裁定:全额退款 + 状态是订单状态的一个维度 + detail 不公开 ⇒ 没有列可存)。
  7. 不支持部分退款、请求里没有金额 —— 上游 5.4 的 biz 里没有金额字段(该表已被证明漏写过 reason, 但「没有金额」这一条另有旁证:金额缺失时退款指令仍然被受理,只报了 reason 一处 —— 若金额也必填, 一次校验本该同时报出来)。

五、与本批无关的悬置项

  • third_shop_code 取值契约仍与上游互相矛盾(明文 19 位 ⇒ 被私钥解密报错;342 字符密文 ⇒ 「最长 32 位」)。已按单变量方式重发请求待上游答复。
  • 被扫(auth_code)仍未联调 —— 它由 auth_code 非空触发(HarOrderService.java:339),我们没有模式开关。