| New file |
| | |
| | | # 汇付/斗拱支付链路 —— 踩坑清单确认表 |
| | | |
| | | **用途**:这是交接文档 `06-踩坑清单.md` 的原始素材,需要你确认后才能成文。 |
| | | **怎么用**:只看最后两列。**证据列我已经核过**(commit / 文件:行 / 注释原文),你不需要回头看代码。 |
| | | 你只需要在 **❓待确认** 列回答,或者直接在 **✓** 列打 `[x]` 表示「这条对,照写」。 |
| | | |
| | | **措辞约定**(成文时严格执行,不把推断写成结论): |
| | | |
| | | | 标记 | 含义 | |
| | | |---|---| |
| | | | **已验证** | 有真实通道回执、真实提交,或开发库实测数据 | |
| | | | **推断** | 有代码证据但没有实测,逻辑上成立 | |
| | | | **未确认** | 只在我们这边成立,汇付侧没有确认过 | |
| | | | `[待确认]` | 我答不上来,需要你补一句 | |
| | | |
| | | **统计**:96 条,按链路分 8 组,组内按「会动钱 > 卡死流程 > 体验/呈现」排序。 |
| | | |
| | | --- |
| | | |
| | | ## A. 进件(9 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 1 | [ ] | 通道侧配置失败的商户,进件页永远停在「审核中」,表单进不去、同一张卡也用不了,商户彻底卡死 | 进件页见 `pending/approved` 就跳待审页;`submitOnboarding` 见 active 进件直接返回旧记录;银行卡校验又拦「该银行卡已有审核中的进件」 | 已验证 · `e3efdaa`;`MerchantSettlementService.java:880-893` | 生产/联调环境里 `status=pending` 且两步都 `failed` 的进件现在有几条?要不要脚本收口成 rejected? | |
| | | | 2 | [ ] | 进件放行了,但商户用同一张卡重提仍被拒「该银行卡已有审核中的进件或已通过的申请」 | 收口只把进件标 rejected,漏了它绑定的 `merchant_bank_card`(卡停在 pending 本来就是这条失败进件留下的) | 已验证 · `0d9afc5`;`MerchantSettlementService.java:897-918`(注释:「否则商户连同一张卡都不能用——实际就踩到过这个坑」) | 控台里那批 rejected 进件对应的银行卡现在什么状态?还有 pending 卡挂在 rejected 进件上吗? | |
| | | | 3 | [ ] | 第一次提交进件就 500,报 `Unknown column 'license_validity_type'` / `subject_cert_end_date` | 代码 INSERT 写了这些列,V80/V99/V127/V128 都没建;此前测试用 JDBC 直接造进件记录,**绕过了真实入口** | 已验证 · V137 头注释(「工单 06 的端到端验收才把它暴露出来」)、V138 头注释 | 除了这两列,进件链表还有别的列只被手建、没进迁移吗?要不要跑一次「代码列名 vs 迁移列名」全量比对? | |
| | | | 4 | [ ] | 个人(小微)走完进件也拿不到微信收款能力,微信开通与实名都缺材料 | 门店照片(F22 门头照 / F24 内景照)**一个字段都没收**;官方在业务开通 `micro_biz_type` 与实名 `store_header_copy`/`store_indoor_copy` 两处都点名要 | 已验证 · `9553a90`;V146 头注释;`.scratch/merchant-individual-onboarding/audit.md` 三、1 | 三种小微经营类型(线上商品服务/流动经营/门店场所)各该传什么图,业务侧书面定稿了吗? | |
| | | | 5 | [ ] | 已拿到真实汇付号的存量个人进件一次业务开通都没做过,也没有补图入口 | 开发库实测:`subject_type=individual` 10 条、3 条有 huifu 号,**全部 10 条 `business_open_status=not_started`**;分阶段模型下重复提交不新建进件,商户没有别的补图入口 | 已验证 · `audit.md` 四之二(只读统计原文);`supplyMissingStorePhotos` | 这 10 条里除 3 条有商户号的,其余 7 条要不要清理?已开出的商户号控台还都「正常」吗? | |
| | | | 6 | [ ] | 商户端换账号登录,进件页弹出「已恢复上次未提交的草稿」,把**上一个账号**的身份证号、银行卡号预填进来 | 草稿存在固定全局 key,uni storage 不分账号,`restoreDraft()` 不校验归属 | 已验证 · `c7c19c5`(原文:「这不是体验问题,是把一个账号的敏感资料泄露给了下一个账号」) | 线上有商户用过「换账号登录」吗?旧全局 key 下的草稿要不要在发布时强制清一次? | |
| | | | 7 | [ ] | 进件能过、商户建得出来,但平台对它调 config/open/query 一律 `20002 数据权限认证失败` | 曾以为是归属错,改用 `head_office_flag=0`+`head_huifu_id` 建成平台下级,**实测无效**:20002 与层级无关,取决于签名身份本身的数据权限;head 路径还额外索要 F725/F726/F727 | 已验证 · `7dfc5b7`(第一版)→`e89a164`(回退);`HuifuPaymentProperties.java:21-35` | 最终定的是「渠道商 → 普通商户」两级,对吧?这个结论是**找汇付客服确认过**,还是只靠自测推断的? | |
| | | | 8 | [ ] | 企业进件通过后,微信配置与业务开通两步**一次都不会被触发**,商户永远停在 `not_started` | `applyOnboardingResult` 把「拿到银行卡序列号 `token_no`」当推进后续配置的前置;而企业进件的同步响应常常只回 huifu_id 和 apply_no | 推断 · `39a5163`(注释原文:「企业进件的同步响应常常只回…于是后续两步一次都不会被触发」) | 真实企业进件的同步响应里 `token_no` 到底会不会回?还是只有绑卡回调才给? | |
| | | | 9 | [ ] | 商户在进件表单里选的行业没人读,上报 MCC 一律落到「其它(7399)」,微信行业类目与费率跟着错 | 表单把行业塞进 `companyType`,而个人进件 MCC 取的是商家档案 `merchant_type_code`;后续又发现取值优先级反了(表单盖过档案) | 已验证 · `9553a90`、`667abfb`;`MerchantOnboardingIndustryMccTests`(5 项) | 「行业改不了、只有系统动态匹配」是最终口径吗?商户侧要不要加一句「行业由平台维护」? | |
| | | |
| | | ## B. 业务开通与实名(16 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 10 | [ ] | **任何新商户**(企业、个人都一样)都开不出微信收款能力:微信支付、人才端绑定、实名全部连坐 | 「微信小程序配置」排在「商户业务开通」**之前**,而微信子商户号是业务开通创建的,配置必被驳「根据渠道号或微信子商户号未查到入驻记录」;配置一失败就 `return`,业务开通永远轮不到 | 已验证 · `9553a90`;`audit.md` 七之二(含申请 452 的库内证据);`MerchantSettlementService.java:571-572` | 之前用存量/参考商户验证时全被掩盖(它们的微信业务早开通了)——现在有没有真的用一个**全新**商户从零走通过? | |
| | | | 11 | [ ] | 业务开通报 `wx_conf_list格式错误`(第一层) | 小微的 `mcc` 写在 `fee_rule_id` 的 `else` 分支里,平台配了默认规则号 756 之后小微的 mcc **一次都没发过**;代码注释还写着「个人主体属于小微商户,所以必须带上」 | 已验证 · `9553a90`;`HuifuMerchantBusinessOpenGateway.java:92-93` | 个体工商户走企业接口但对私结算,它的 `mcc` 是否也必填?(测试里是必填) | |
| | | | 12 | [ ] | 换号后**仍是**同一句 `wx_conf_list格式错误`(第二层) | `wx_real_name_info` **双重编码**:官方字段目录类型列是 Object,代码又 `JSON.toJSONString` 编了一层,通道按对象取字段取不到;网关自己的 javadoc 写着「父字段列 + 同名子表的写法约定即为嵌套对象」,**实现与注释不一致** | 已验证 · `audit.md` 七之二「第三次失败」;`HuifuMerchantBusinessOpenGateway.java:112` | `material_card_info` 是官方勘误的例外(类型列写 Object 但要按字符串发)——这条勘误名单还有别的字段吗? | |
| | | | 13 | [ ] | 业务开通报「微信费率规则号填写不正确,请检查!」 | CID 按「支付场景 × 业务开通类型 × **主体类型**」三维定位:企业 756 / 个体户 761 / 小微 775,不能全平台共用一个号 | 已验证 · `HuifuPaymentProperties.java:52-62`;`application.yml:121-128`;`HuifuBusinessOpenRequiredFieldsTests` | 事业单位 773 / 社会组织 774 会不会有商户出现?要不要一起配上可覆盖的键? | |
| | | | 14 | [ ] | 六项阶段里 5 项绿,「微信支付」永远未确认;`sync_error` 是空的(查询其实成功) | 判定要求 `fee_min_amt` 必须出现且为 0,而该字段可选、文档写明「大于 0」——不设最低收费时我们不下发、通道也不返回,缺失被判成未生效 | 推断 · `MerchantOnboardingProgressService.java:85-89`(注释:「缺失必须按 0 处理,否则这条判定永远不成立」) | 这个「缺失 == 0」的语义要不要向汇付**书面确认**一次? | |
| | | | 15 | [ ] | 商户端没有授权二维码,而「本人扫码授权」是完成实名的唯一途径 | `qrCode()` 写死 PNG 的 base64 magic(`iVBORw0KGgo`),通道回的是 **JPEG**(`/9j/`),被判非法返回空串;体积与字符集都过关,**只有格式假设错了** | 已验证 · `9553a90`;`MerchantOnboardingProgressService.java:425-441`;`MerchantRealnameServiceTests` | 通道回码的格式是否稳定为 JPEG?会不会有 PNG/其他格式混用? | |
| | | | 16 | [ ] | 已授权成功的商户(`authorize_stat=1`)被页面显示成「待办理」,二维码还在让人去扫 | 授权判定要求 `APPLYMENT_STATE_PASSED`;而真实样本是 `APPLYMENT_STATE_FREEZED`(「该主体已完成过入驻」)+ `authorize_stat=1`。授权完成**只能看 `authorize_stat`** | 已验证 · `7baa02d`;`HuifuMerchantRealnameGateway.java:26`;`evidence-2026-09-15.md`(参考商户真实样本) | 授权成功后通道仍回传二维码、前端按状态收起——这个「有码但已授权」的组合还会出现在别的场景吗? | |
| | | | 17 | [ ] | 实名发起被驳回,且**驳回后要换业务申请编号重来** | 实名接口的 name/mobile/id_card_number 三项都必须是**法人本人**的,而进件只收了联系人手机号——实测那张商户的联系人是经办人、法人是另一个人 | 已验证 · V145 头注释;`MerchantOnboardingRequest.java:32-33`;`MerchantRealnameService.java:131` | 存量进件里有多少条 `legal_mobile` 是空的、或误填成联系人号码? | |
| | | | 18 | [ ] | 实名路径选不了经办人 | 经办人(`contact_type=SUPER`)额外要求经办人身份证正反面 + **加盖公章的《微信业务办理授权函》**,盖章件无法自助提交 | 已验证 · ADR-0001 2026-09-16 修订;`HuifuMerchantRealnameGateway` | 将来有商户坚持走经办人,平台是否接受人工代传盖章件? | |
| | | | 19 | [ ] | 无回调的部署里整单**永远完不成**,能力快照也永远不刷新,商户明明开通了却推不到收款就绪 | `needsProvisioningStep` 只认 `blank/not_started/failed`,**不含 `processing`**,而 `approved` 正是能力刷新与整单完成的判据 | 推断 · `9553a90`;`audit.md`「顺带修掉的:processing 永远不会被推进到 approved」 | 除了 `processing`,还有别的中间态会被同样的条件吃掉吗? | |
| | | | 20 | [ ] | 业务开通申请被受理了,紧接着发微信配置仍被拒「未查到入驻记录」 | 配置的闸门原取**本地状态**(`processing`),而**微信入驻是异步的**:`90000000` 只表示申请被受理,那一刻通道侧还查不到子商户号。闸门必须取通道证据 | 已验证 · `audit.md` 第五步;`MerchantSettlementService.java:791` | 微信入驻从受理到子商户号可查,一般要多久?商户侧要不要给「预计 X 分钟后刷新」的提示? | |
| | | | 21 | [ ] | 业务开通/微信配置被驳回后重试,通道当成重复请求 | 重试复用同一流水号;通道按流水号去重。修「换号」时踩了第二个坑:随机后缀必须保住,先按剩余宽度裁前缀再接后缀,否则超长截断会把随机位一起切掉,两次重试又变成同一个号 | 推断 · `MerchantSettlementService.java:847-853` | 通道对「同号重提」是拒还是幂等返回原申请?有真实回执吗? | |
| | | | 22 | [ ] | 业务开通被拒「失败:复用交易信息,上级商户号为空」 | `use_chains_flag=3` 是「复用上级微信商户号」,要求上级**本身有商户级微信配置**;我们的上级是渠道商(查它自己的微信配置直接返回「huifuId不存在」) | 已验证 · `c18b9fa`;`application.yml:115-119`;`HuifuPaymentProperties.java:37-45` | 这条结论有没有找汇付确认过「渠道商不能做 AT 复用」?还是纯自测推断? | |
| | | | 23 | [ ] | 业务开通被拒「【media_merCommon_flag-媒介主体与商户主体是否一致】不能为空,或格式非法」 | `media_mer_common_flag` 是 `online_media_info_list` 的**元素字段**,不是顶层参数;之前那次「补上顶层字段」的修复**从来没被验证过** | 已验证 · `c18b9fa`;`evidence-2026-09-15.md`「本轮修掉的三个真实缺陷」1 | 平台一个运营媒介服务多个商户,声明 N 会连带索要授权/开发证明材料——将来开其他业务时这材料怎么拿? | |
| | | | 24 | [ ] | 已开好的商户在两端页面被显示成「失败」 | `90000000`(审核中)被业务开通网关当成失败,会把一次成功受理记成 `business_open_status=failed`;两个进件网关都有这个判定 | 已验证 · `c18b9fa`;`evidence-2026-09-15.md` 缺陷 3 | 除了 `90000000`,还有哪些「受理类」返回码在别的网关上漏了?要不要做一次全网关返回码盘点? | |
| | | | 25 | [ ] | 收款就绪门槛一旦单独落地,**所有没有进度记录的存量商户都收不了款** | 门槛要求「微信开通 + 实名/授权」双 confirmed,而 `confirmed` 依赖分阶段进度记录;存量商户必须先刷新过进度才可能通过 | 推断 · `4ef9418`;`7baa02d` 票面「收款就绪门槛暂不落」;03 票 Comments(3) | 存量商户要不要给一次批量进度刷新,还是明确告知必须逐个刷? | |
| | | |
| | | ## C. 收款能力与闸门(10 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 26 | [ ] | 延迟入账上线后,所有新支付都被闸门拦住,报「自动结算尚未关闭」 | 「自动结算必须关闭才能收款」是延迟分账之前立的规则;延迟户已兜住未核销的钱(官方定义:不可结算、不可取现),这条规则不但多余,还把支付路径直接堵死 | 已验证 · `723b756`(提交正文);`MerchantPaymentReadinessService.java:252` | 闸门里还有别的「遗留规则」在偷偷拦支付吗?要不要做一次闸门前提全盘 review? | |
| | | | 27 | [ ] | 进件页标题说「请按待办理项补齐缺失能力」,但六项阶段全绿,一个待办理项都没有 | `complete()` 还要求 `bank`(结算卡核实)与 `merchantNormal`(通道商户状态),这两项既不在 `LABELS` 里、`view()` 也不返回 | 已验证 · `.scratch/merchant-closure-impact/issues/huifu-merchant-closure-impact.md` 遗留一;`MerchantOnboardingProgressService.java:298-343` | 这两个完成条件的文案(「请联系汇付或后台处理」)需要产品再定一次措辞吗? | |
| | | | 28 | [ ] | *(改法风险)*把两项核实加进阶段列表,进件会**永远无法 approved** | `LABELS` 同时是展示列表与完成判定的键集,且 `bank`/`merchantNormal` 是布尔值、其余六项必须是字符串 `"confirmed"`——加进去会两次破坏完成判定 | 已验证 · ADR-0002「考虑过但没选的」;`MerchantOnboardingProgressService.java:306-308` | 将来有人想给进件页加第 7 项,这条约束要不要写进 `AGENTS.md` / `CONTEXT.md`? | |
| | | | 29 | [ ] | 汇付控台关闭商户后,进件页依旧显示「进件已完成 + 六项全已确认」,商户其实已收不了款;感知完全依赖「下一次下单被拒」 | `approved` 之后商户端「刷新进度」按钮被隐藏,而全仓四个 `@Scheduled` 没有一个刷进件进度快照 | 已验证 · `huifu-merchant-closure-impact.md` 证据节 + 遗留二 | 「下单被拒」的 toast 文案要不要也改成能自解释的?(closure-impact 文档里这条被列为下游) | |
| | | | 30 | [ ] | 被通道关停/从没核实过通道状态的商户,能力快照最长有约 **6 小时空窗**,期间我们仍认为可收款 | 能力定时核实 30 分钟一轮、只扫超过 `stale-hours`(默认 6)未核实的申请;空窗内的失败推迟到真正发起支付时才由通道拒绝 | 已验证 · `huifu-merchant-closure-impact.md`;`application.yml:143-146` | 6 小时这个值是谁定的?生产上要不要缩短(代价是通道查询量)? | |
| | | | 31 | [ ] | 「立即核实」按钮可以被连点;冷却若改用 `capability_verified_at` 会导致失败时无节流 | 查询失败时 `capability_verified_at` 不推进,等于没节流;`updated_at` 是 MySQL `on update current_timestamp`,任何无关 UPDATE 都会顺带重置它 | 已验证 · ADR-0002「考虑过但没选的」;V151 头注释(原文:「不放在 capability_verified_at:查询失败时那一列不推进,会被连点」) | 冷却默认 5 分钟(与进件刷新 5 分钟租约同量级),够用吗? | |
| | | | 32 | [ ] | 一进「收款能力」页就 500,报 `Unknown column 'capability_sync_until'` | 开发库 flyway 关闭、新列靠初始化器补,而那条 alter 没成功(等 DDL 元数据锁上限 5 秒);且 `capabilityCooldownSeconds` 是本文件里唯一点名列的查询,缺列会让整条 SQL 报错 | 已验证 · `20e19d8`;`docs/agents/testing.md:87-89` | 别处还有多少「点名列的查询」会在缺列时把整页打成 500?要不要统一改成从已读到的行里算? | |
| | | | 33 | [ ] | 冷却倒计时只在重新进页面时才恢复;没查通道也说「刚核实过」 | 第一版只在重新进页面时判冷却到期,且 `refreshed=false` 时也宣称刚核实过;进件未通过时把按钮写成「稍后可再次核实」(那根本没有冷却) | 已验证 · `1435780` | 「暂不可核实」和「冷却中」这两种不能点,文案区分够清楚吗? | |
| | | | 34 | [ ] | 结算记录页每次打开都白多几条失败提示(被关停的商户逐条报「客户与产品关系已关闭」) | 页面此前把「进件通过且有子商户号」的商户**全都**问一遍,包括被关停的和从没核实过通道状态的 | 已验证 · `8e503e2`(原文:「实测通道回『客户与产品关系已关闭』」) | 结算记录监控的商户范围,业务上就应该是「通道侧收款能力正常」这一档吗? | |
| | | | 35 | [ ] | 商户端页面说能收款、下单却被拒(同一个事实两套口径) | 页面判定与下单闸门读了不同的数据;ADR-0002 定为「同源同文案」 | 已验证 · ADR-0002;`MerchantSettlementService.java:100-101`;测试 `merchantViewReusesTheGateMessage` | 这套「同源同文案」要不要在 `CONTEXT.md` 里写成硬规定? | |
| | | |
| | | ## D. 支付(18 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 36 | [ ] | 关掉微信收银台后再点「确认支付」和「取消订单」都只报「支付结果确认中」,订单从放弃那一刻起就是死的,直到通道改口 | 三处叠加:① 重付先用一次通道查单当闸门,通道对**未付款**的预支付单答「处理中(P)」,代码把 P 判成「不确定」→ 抛错(而本地其实存着那张单的支付参数);② 取消同样以查单为前置门,查单答 P 时**连关单接口都不调用**;③ 到期清理刻意排除带未定局流水的订单,所以永远不会自动到期 | 已验证 · `.scratch/pending-order-payment-flow/issues/01-pending-order-deadlock.md`(含用户截图与 `file:line`);ADR-0003;`8fe2249`、`e3f412c` | 「P 判成不确定」这个规则当时是谁定的?有没有想过未付款的预支付单在通道侧就是 P? | |
| | | | 37 | [ ] | 用户没付过钱但订单被取消,钱却在通道被收下,人才端显示「已取消」、**连退款入口都没有**(退款要求订单已支付) | 本地取消/超时只把流水置 `LOCAL_CLOSED`、订单置取消,**从不调通道关单**;用户手上那张预支付单照样能付进来 | 已验证 · `8fe2249`(真实数据 2026-09-16 14:22 一笔 ¥0.02 订单就是这样被取消的);审计 §8 | 那两笔(订单 520/521)以外,还可能有别的同类悬账吗?要不要按同一口径再全库扫一次? | |
| | | | 38 | [ ] | 反复「继续支付 → 关掉收银台」后,微信弹「订单状态异常,支付请求无效」,只能等旧单真被取消才能在同一商品上再下一单 | 关单回执 `23000000`(原文「原订单已为终态,无法发起关单操作」)被并入「其余拒绝」,于是那张**已经不可能被支付**的预支付单被记成 `CANCEL_UNCONFIRMED`(= 关不掉 = 可能还能付)并继续被 `reusablePrepay` 交给客户端 | 已验证 · `3fd8f1e`;`issues/09-terminal-prepay-zombie-order.md`(含订单 534 库内证据:`close_attempts=3`、`provider_response_code=23000000`) | `10000016`(「原订单已为终态,请发起查询交易获取」)也是同一支吗?两个码的差异要不要再向汇付确认? | |
| | | | 39 | [ ] | 一笔订单变成「既付不了、同一商品也再下不了单」的僵尸,永远不会被自动收掉 | `giveUpClosing` 只在「当前状态是 `CLOSING`」时才结束订单;第一次关单就被拒的流水直接落 `CANCEL_UNCONFIRMED`,到上限既不结束、也不被到期清理收掉 | 已验证 · `3fd8f1e`;`issues/09` 根因 3 | 除了 `CANCEL_UNCONFIRMED`,还有别的状态会掉进「既不在排除表、也不在结束路径」的缝里吗? | |
| | | | 40 | [ ] | 点「继续支付」**没有任何反应**(按钮被禁),而那张凭证其实还活着 | 复用集合砍过头:`CLOSING` 是「关单可能已生效」,而 `CANCEL_UNCONFIRMED` 是「通道明确说这次关单没生效」(拒绝关单、或关单请求本身失败 `90000000 交易失败,请重试`)——后者凭证还活着,正是最该复用的一张 | 已验证 · `06db42a`;`HuifuPaymentService.java:106-125` | 「可复用集合」口径已经改了四轮(`PREPAY_CREATED` → +`CLOSING` → 只留 `PREPAY_CREATED` → +`CANCEL_UNCONFIRMED`)——要不要在 `CONTEXT.md` 里定死成不变量? | |
| | | | 41 | [ ] | 关单明明几秒就完成,用户却在前台等了 **72 秒** | 汇付有专门的「关单查询」接口(文档写明「关单处理中应继续调用关单查询确认结果」),我们只发关单、不问结论;副作用是快车道每次重发关单都让 `close_attempts` 增长,一条正常记录会被判到上限,而「到上限」会顺手取消订单 | 已验证 · `c4baf2f`;ADR-0004「待补的三处查询对等物」;`HuifuPaymentCloseQueryCommand` | 关单查询的结论只有「关单成功」算数;「关单失败」与「查询没答上来」一律按没有结论——这个保守口径认可吗? | |
| | | | 42 | [ ] | 「关单重试每分钟一次、最多三次」实际是「每五分钟一次、最多十五分钟」,用户等的那一分钟里没有任何重试发生 | `CLOSE_RETRY_MINUTES=1` 只是「距上次处理满一分钟」的**资格窗口**,扫描本身由 `fixed-delay-ms` 决定,默认 300000 | 已验证 · `c46ef64`;`application.yml:95-107`;`PaymentSchedulingConfigurationTests` | 还有别的「资格窗口 vs 扫描周期」被同样混淆的地方吗?(比如慢车道 `pending-after-minutes` 与 `last_reconciled_at`) | |
| | | | 43 | [ ] | 用户点了取消、页面关了,取消也丢了;订单仍是「待支付」,只能再点一次或等 15 分钟 | `cancel` 在关单没结论时只回一个 `cancellationPending`,意图没有落库;而关单**后来真关掉**时没有任何人去收那笔订单(`giveUpClosing` 只在重试到上限时跑) | 已验证 · `c46ef64`、V151/V154 头注释(「取消接口直接以 Unknown column 500」) | `benefit_order.cancel_requested_at` 现在降级为审计事实(`0c8ea84` 把消费它的扫描删了)——这一列还留着做什么用?将来要接审计报表吗? | |
| | | | 44 | [ ] | 点了取消一直卡着;实测**三次取消全部**拿到「关单处理中」 | 通道对**刚创建的微信交易**最常见的答复就是「关单处理中」,叠上对账扫描周期,而用户根本没付过钱 | 已验证 · ADR-0003 2026-09-17 修订;`0c8ea84` | 「三次全中」这个样本量够不够下结论?要不要用更大样本再测一轮(例如故意做 20 次)? | |
| | | | 45 | [ ] | 偶发「用户付了钱又被自动退回」,且**手续费大概率不退** | 取消改成「查单确认没收款就立刻生效」,要求用户正好在「查单之后、凭证被关掉之前」那几秒完成支付 | 已验证 · ADR-0003 2026-09-17 修订(代价段);`0c8ea84` | 这个代价有没有和财务/法务对齐过?手续费损失由谁承担、要不要给个上限告警? | |
| | | | 46 | [ ] | 待支付页**每秒闪一次**(内容被反复换成「正在加载订单」);「刷新支付状态」看着没反应 | 客户端 `loadOrder ↔ checkPaymentStatus` 互相触发:`checkPaymentStatus` 读到 `CANCELLED` 就重读订单,而读订单又会查一次支付状态——无限循环 | 已验证 · `3fd8f1e`;`issues/09` 根因 4 | 客户端没有可编译的反馈回路(本机没有 HBuilderX / uni CLI)——这类环路要怎么在仓库里防住? | |
| | | | 47 | [ ] | 点「确认支付」不拉起微信支付,而是跳到另一个页面再点一次同名按钮 | 设计如此,但按钮名与行为不符;且支付结果轮询只有 5×1.5s=7.5s,且只在 `requestPayment` 回调 success 后才开始,回调稍慢就把用户推去「使用记录」自己找 | 已验证 · `issues/06`、`issues/README.md` B3;`e3f412c` | 现在退避轮询到约 45 秒 + 手动「刷新支付状态」,这个上限够吗? | |
| | | | 48 | [ ] | 「关不掉的预支付单」所在订单被自动取消,而那张单子**仍然可付** → 钱落在已取消订单上 | `expireDueOrders`/`expireOrder` 的排除表只列了 `CREATED/PREPAY_CREATED/CREATE_UNKNOWN`,**漏了 `CANCEL_UNCONFIRMED`**;钱进来后记 `PAYMENT_ORDER_CANCELLED`/`CLOSED_LATE_SUCCESS`,而正常退款接口要求 `order_status=1`,这笔钱没有程序化出口 | 已验证 · `issues/README.md` C2/C3;`04-payment-converges-per-attempt.md` 要点「到期与自动取消」 | 老数据里还有多少条 `PAYMENT_ORDER_CANCELLED` 是这种形态?现在都清了吗(05 票说开发库已 0 条)? | |
| | | | 49 | [ ] | 退款/排查失去唯一的通道凭证(开发库 refund 43/44 的 `provider_refund_no` 都是空) | 支付查询回执里通道流水号的字段是 `org_hf_seq_id`;取 `hf_seq_id` 会把它**静默写成 NULL**。`hf_seq_id` 只出现在异步通知里 | 已验证 · `85831da`;`PaymentReconciliationService.java:610-611` | 还有别的「字段名在查询与通知里不一样」的通道字段吗?要不要做一份字段对照表? | |
| | | | 50 | [ ] | 0.01 元的成功回调可以把 100 元订单改成已支付(验签是模拟通过的,不是绕过) | 回调只查流水 ID、订单 ID、状态,**不取金额比对**;提现回调也未核对金额、汇付账户、银行卡关联 | 已验证 · 审计 §10;`d879291`(回调与主动查询共用 `PaymentSettlementService`,校验金额/商户号/订单归属) | 现在金额与收款子商户都校验了——币种、通道流水、原交易关系要不要也一起校验? | |
| | | | 51 | [ ] | 漏回调后由主动补查成功的到店订单长期不能计入可提现 | `PaymentReconciliationService` 更新支付与订单,但没像回调那样置 `STORE_CONSUME.verify_status=1`;随后回调因流水已 `SUCCEEDED` 走幂等分支,也不会补核销 | 已验证 · 审计 §11;`PaymentSettlementService.java:127` | 回调/查询/人工核查现在共用一个幂等成功入口了吗?还是仍有两份实现? | |
| | | | 52 | [ ] | 一笔订单存在两张都能被支付的预支付单;两笔都成功时**没有多收款退回流程** | 防重只按 `idempotency_key` 查已有流水,不按订单阻止另一个键创建有效流水(一次订单、两个键 → 两次预授权,审计已复现) | 已验证 · 审计 §9;ADR-0003「考虑过但没选的」;`HuifuPaymentService.java:143-160` | 前端固定键是「订单号+价格版本」——价格版本变化时会不会仍能造出第二张?有回归覆盖吗? | |
| | | | 53 | [ ] | 对账台报错:同一笔业务被反复「结掉又开出」时撞唯一键 `(issue_type, provider, business_no, status)` | 结掉那一步会撞上已存在的 resolved 行 | 已验证 · `e3f412c`(「两处超出票面但为落地所必需」);`04` issue Comments | 还有多少张表用了「状态进唯一键」的写法?值得全局搜一次吗? | |
| | | |
| | | ## E. 退款与资金(13 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 54 | [ ] | 通道确认收了钱、但订单已取消(悬账),原先只在账目上留一条问题记录等人工,**没有任何自动出口** | 退款链路的硬门是 `OrderFundBoundaryService.requireRefundable()` 的 `orderStatus != 1`;四个 `@Scheduled` 没有一个消费这类问题记录;后台「支付对账」页是唯一能看到的地方 | 已验证 · `issues/README.md` C3;审计 §8 后半段;`e719199` | 悬账自动退款现在默认 5 分钟一轮(`StrandedPaymentRefundRetry`)——生产上要不要把告警同步推给运营? | |
| | | | 55 | [ ] | 「本地已关闭但通道其实收了钱」那类悬账**连支付键都看不到**,退款无从发起 | 支付键可见性只认「已成功流水」;旧实现下 `CLOSED` + 一条「取消后才收到钱」的问题记录定位不到原支付流水 | 已验证 · `e719199`;`HuifuRefundService.java:219-221` | 放宽到「已成功,或已关闭且有一条 `PAYMENT_ORDER_CANCELLED` 问题记录」——这个判据够严吗? | |
| | | | 56 | [ ] | 实付 100 退了 40 之后,剩下的 60 **永远退不了** | 旧规则「存在退款中或已退款就阻止新退款」;正确规则是按「已成功金额 + 处理中金额 < 实付」控制 | 已验证 · 审计 §14;`e719199`;`HuifuRefundService.java:256` | 现在退款能否发起全靠订单行锁 + 金额额度判定,删掉了「已有生效中退款」的抛错——这个放宽要在评审时重点确认吗? | |
| | | | 57 | [ ] | 退款查单确认时把唯一的通道凭证擦成空串,退款排查与对账失去定位依据 | 通道退款流水号**只在发起退款的那次回执里有**,退款查询回执不带它;旧代码无条件覆盖 | 已验证 · `3da8557`;`HuifuRefundService.java:288-289` | 还有哪些「只在某一次回执里出现的字段」被无条件覆盖过?要不要全局搜一遍 `coalesce(nullif(` 与直接 update 的对照? | |
| | | | 58 | [ ] | 14:37:31 提交退款、14:43:41 才对账收敛,中间六分钟里钱已退回人才账户,人才端却仍显示「待核销」,商户端还当它是可核销订单,去核销被资金边界拒 | 退款提交时通道只回「处理中」,退款通知和支付通知一样到不了本地;读路径不收敛 | 已验证 · `3da8557`(含开发库实测时间);`HuifuRefundService.java:160-166`(读订单/列表前收敛必须是调用方事务的**第一条**读语句,因为 MySQL 一致性快照就在那条语句上定下) | 「提交后 20 秒内不查、一轮最多两笔」这两个限流值是怎么定的?列表页本身慢的时候会不会还是拖长? | |
| | | | 59 | [ ] | 商户端首页「交易总额」把未核销、退款中的订单也算进去,退款后金额不回落(看起来像缓存) | 首页卡片 SQL 只过滤 `order_status = 1`,**漏了核销条件**;退款受理只把订单置「退款中」(`order_status` 仍是 1),而首页统计的读路径没有退款收敛 | 已验证 · `.scratch/order-payment-display/issues/bug-1-merchant-dashboard-total.md`(含修前红的断言 `expected: 1000L but was: 7000L`);`2aabed5` | 同文件遗留:**补贴总额是同一类缺陷**(`subsidyAmountCents` 也只过滤 `order_status=1`)——那是有意还是漏的? | |
| | | | 60 | [ ] | 撤销核销只翻库、不碰钱:订单回到「待核销」,钱却留在商户手里,等 D1 结算到银行卡后就**再也追不回来** | 核销时钱已从延迟户划给商户,撤销核销必须调通道的【交易确认退款】把钱退回延迟户 | 已验证 · `5c33cf1`;V147 头注释;`CONTEXT.md`「延迟入账与交易确认」 | 撤销窗口「止于下一次自动结算」——这个窗口长度(D1,实际可能只有几小时)业务上接受吗? | |
| | | | 61 | [ ] | 回退被拒时只能给商户一句「撤销失败」,商户不知道是该重试还是该找平台 | 回退结果有四种归途:成功→`pending`;明确拒绝(已结算到银行卡/余额不足)→退回可重试的 `confirmed`;语义未定(`23000003`/`23000004`)→停在 `reverting` 交人工;通道还在处理→在途 | 已验证 · `5c33cf1`;`HuifuPayload.java:63-77`(「判成功等于放行可能没动的钱,判失败等于让本地以为钱没动、于是再动一次」) | `23000003` 在回退路径上的真实语义,有拿到过真实回执吗?有向汇付要过一条明确说明吗? | |
| | | | 62 | [ ] | 撤销核销被永久卡住(补偿任务只扫 `pending`/`confirming`,`reverting` 永远没人再看) | 补偿任务状态集合漏了 `reverting`;`delaytrans.confirmquery`(确认查询)有实现却是**死代码**(全仓零调用);且同一天认领的 `confirming` 要等到次日才重认领 | 已验证 · `issues/README.md` F4;`08-query-parity-and-config-cleanup.md`;`ab9b1f3` | 还有别的状态值是「写进去就没人读」的吗?要不要做一次状态机可达性梳理? | |
| | | | 63 | [ ] | 确认被认领后进程中断(部署/崩溃)留下 `confirming` 孤儿,那笔钱**永远冻在延迟户** | 补偿任务只挑得到它、从不重发 | 已验证 · `5c33cf1`(「隔天收掉这种孤儿重新确认一次(重发是幂等的)」) | 「隔天」这个兜底窗口在生产的结算节奏下够快吗? | |
| | | | 64 | [ ] | 扫码核销与后台核销都只把订单置为已核销,资金一直冻在通道延迟户,商户**永远收不到钱** | 核销有三条入口,此前只有「订单管理里点确认」那条调用了交易确认 | 已验证 · `84913d9` | 现在三条入口都收敛到同一个方法了吗?还是三处各写一遍? | |
| | | | 65 | [ ] | 补偿任务每隔几分钟无意义地打一次通道,永远不自愈 | 通道对「已经没有待确认金额」的延迟交易返回 `23000003`,且**同时给 `trans_stat=F`**;旧实现先看交易状态、再看返回码,把幂等成功判成失败 | 已验证 · `063d60e`;`HuifuDelayAccountGateway.java:223-239`(「这个判断放在交易状态之前」) | 还有别的「成功码配 trans_stat=F」的组合吗?要不要把「先判业务码再判交易状态」写成全局约定? | |
| | | | 66 | [ ] | 未核销的钱直接落进商户账户,退款时收不回来 | `delay_acct_flag` 是否延迟由订单类型决定;写反或漏配(例如新增订单类型默认不延迟)就会把钱直接分给商户 | 已验证 · `HuifuMiniProgramPaymentGateway.java:56-57`;`5364ba7`;`CONTEXT.md` | 将来新增订单类型时,这条「默认延迟」的规则有测试钉住吗? | |
| | | |
| | | ## F. 结算与对账(10 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 67 | [ ] | 后台「结算记录」页一条流水都不显示,而通道里其实有记录 | 斗拱结算记录查询 `pageSize` 上限 50,超了直接返回 `00000003`「基础参数校验失败:【pageSize-分页条数】…最大值为50」,**一条都不返回**;后台默认传 100。实测:20/50 正常返回 2 条,51/100 一条不返回 | 已验证 · `ad49519`;`HuifuSettlementRecordGateway.java:29-37` | 除 pageSize,这个接口还有别的硬上限(日期跨度、总条数)吗?要不要先把官方限制字段全量核一遍? | |
| | | | 68 | [ ] | 结算记录页的商家/联系电话/商家类型/状态四个查询条件**完全无效**;日期栏标签写「申请时间」,实际筛的是结算日期 | 前端切到结算记录时只发日期与条数,四个条件根本没发出去;状态下拉在结算记录下是空列表 | 已验证 · `cfd30ec`;`.scratch/settlement-query-failure/verify-filters.sh` | 状态筛的三档(已结算/结算失败/处理中)是与通道 `S/F/P` 一一对应吗?有真实失败流水样本吗? | |
| | | | 69 | [ ] | 快速切换标签时,结算流水的响应**覆盖**了已经切过去的进件/银行卡表格:表头是进件的列、内容是结算记录,分页总数也变成结算的条数 | `load()` 写回表格前没有身份校验,两次请求同时在飞时后到者覆盖先到者;而结算记录要逐商户问斗拱,是最慢的一次 | 已验证 · `cfd30ec` | 别的后台页面有同样的「无请求序号」写法吗?要不要一次性统一加请求序号? | |
| | | | 70 | [ ] | 一笔已经受理的取现被本地误判成失败,解锁订单并把**同一笔钱再取一次** | 取现结果查询用 `huifu_id + org_req_date + org_req_seq_id` 定位;如果商户在提交与查询之间换了子商户(重新进件),用「当前最新子商户号」去查会得到 `23000001 交易不存在` | 已验证 · V136 头注释 | 这条链路已随 V140 退役——`merchant_withdrawal` 里还有需要人工收尾的旧记录吗? | |
| | | | 71 | [ ] | 提现失败当天被当成「已汇总」,钱后来可取了也没有任何出账路径 | 失败记录没有退回 pending 复用同一条 | 已验证 · `85831da`;V135 头注释 | *(同样已退役)*后台补跑接口 `POST /merchant-settlement/auto-withdrawal/run` 还有调用方吗? | |
| | | | 72 | [ ] | 定时对账(支付与退款唯一的后台兜底)可能**静默消失** | 间隔靠注解默认值生效、没写进配置;而注解上 `enabled` 默认是 **false**,靠 yml 那一行打开——删掉那行兜底就没了 | 已验证 · `issues/README.md` F6;`ab9b1f3`;`PaymentSchedulingConfigurationTests` | 「注解默认值与 yml 必须一致」现在有测试钉住了——别的定时任务(能力刷新、补偿任务)要不要一起钉? | |
| | | | 73 | [ ] | 交易确认补偿任务的开关**从未被读取**,任务永远在跑(javadoc 却宣称有这个开关) | 配置键 `app.payment.fund-confirmation-retry.enabled` 在代码里从未读取、yml 里也不存在 | 已验证 · `issues/README.md` F7;`ab9b1f3` | 现在真的读了吗?默认值取「开」还是「关」? | |
| | | | 74 | [ ] | *(生产 bug)*绑卡(业务开通)回调地址**从未在通道侧注册**,即使有公网也收不到 | `app.payment.huifu.merchant-business-notify-url` 在 yml 里根本没这个键,它靠 `notify-url` 推导出一个值——「通道侧到底注册了哪个地址」取决于另一个配置 | 已验证 · `issues/README.md` F3;`ab9b1f3`;`application.yml:92-94`;`HuifuCallbackUrlAudit` | 五个回调地址现在在汇付控台/接口里各注册的是什么?要不要逐条比对一次并留档? | |
| | | | 75 | [ ] | 回调丢了以后**没有任何人推进状态**(订单 520/521 的悬账根因就是回调没到达) | 只靠回调推进的状态在本地环境根本收不到(回调地址指向公网 IP,开发机不可达) | 已验证 · ADR-0004;`fd5fc5e`(订单 520/521 的 2 分钱 10:14 才补退成功) | 五个回调端点里还有没有「没有查询对等物」的?ADR-0004 的清单要不要每次新增通道能力都补一遍? | |
| | | | 76 | [ ] | *(高风险)*后台仍有「跳过真实支付、直接置为已支付」的入口,且页面权限拦截器没有匹配 `/api/orders/**` | `OrderService.pay` 只更新 `order_status=1`,不要求支付流水或汇付结果;该订单再被核销便进入结算统计 | 推断 · 审计 §7;ADR-0004「不允许为了开发便利开跳过真实通道的置付捷径」 | 这个入口现在还在吗?如果留着,是否已有独立资金来源/凭证/审核流程? | |
| | | |
| | | ## G. 工程与环境(13 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 77 | [ ] | 2026-09-15 后台**全站不可用**(所有页面加载到超时,健康检查都超时) | 进件回调先 `select ... for update` 锁住进件记录,然后在事务里同步调汇付 HTTP:拿着行锁 + 一条数据库连接等一个不受控的网络响应,连接池(10 条)很快借光 | 已验证 · `609bcde`(含当天会话状态原文) | `runAfterCommit` 之后,还有哪些回调用「事务内同步调外部」的写法?要不要全局搜一遍 `for update` + 网关调用? | |
| | | | 78 | [ ] | 2026-09-15 上午后台**再次**全站不可用 | 启动时补列的 `ALTER` 永久等锁(一个空闲未提交事务持着 SHARED_READ 元数据锁 280 秒),该表所有查询排队,连接池借光;`application.yml` 同时打开了 Hikari `leak-detection-threshold=10s` | 已验证 · `4fff3f4`(含 `conn 201/202` 的原文诊断) | 现在初始化器等锁上限 5 秒、失败只记 error——「宁可少一列也不让启动被挂住」,这个取舍认可吗? | |
| | | | 79 | [ ] | 重启过也不代表有新列:`legal_mobile` 缺列时,**任何新进件提交**都报 `Unknown column 'legal_mobile'`,实名被拦成「发起实名还缺:法人手机号」(看起来像商户没填) | `addColumn` 把 `lock_wait_timeout` 压到 5 秒,拿不到锁就放弃并记日志;同一轮循环里它后面的列都加成功、只有它没有 | 已验证 · `.scratch/merchant-individual-onboarding/audit.md`「顺带查到的环境问题」(含逐项排障 4 步);`MerchantOnboardingSchemaInitializer.java:174` | 初始化器要建的 49 列与库里实际列做过一次完整比对——这个「列清单比对」能不能做成常驻的启动自检/测试? | |
| | | | 80 | [ ] | `GET /api/merchant/onboarding/status` 第一个请求就 500(`Table 'talent_card.merchant_onboarding_progress' doesn't exist`) | V144 新建的两张表在开发库从未建立;开发库 `spring.flyway.enabled=false`,而初始化器**只有加列路径、没有建表路径**;测试库由 Flyway 建表,所以测试全绿看不出 | 已验证 · `621b519`;`evidence-2026-09-15.md` | 现在有 `MerchantOnboardingSchemaInitializerTests` 断言「迁移新建的进件表必须在初始化器登记 + 两侧列名一致」——这个模式要不要推广到**支付流水表**? | |
| | | | 81 | [ ] | 合并后 Flyway 报「同一版本两个迁移」,本地四个迁移全部要改名 | 双方从同一祖先(V147)各自独立占了 V148–V151(git 不报冲突,Flyway 会直接报重) | 已验证 · `1805a7b`;`.scratch/pending-order-payment-flow/README.md` | 「先确认当前最大版本号」这条要不要写进 `AGENTS.md` 并加一个 CI 检查? | |
| | | | 82 | [ ] | 迁移在 MySQL 8.0.36 上直接 `ERROR 1064` | 不能写 `add column if not exists`——那是 MariaDB 的扩展(本机实测确认);迁移只执行一次,本来也不需要它 | 已验证 · V143/V145/V146 头注释(同一条坑被记了三次) | 5.7(开发库)与 8.0(测试库)双兼容只在测试库跑不出来——每次新增迁移都实际在 5.7 上跑过一遍吗? | |
| | | | 83 | [ ] | 已执行过的迁移改一个字符,测试库直接起不来 | Flyway checksum 不匹配 | 已验证 · `docs/agents/testing.md:91-95` | 历史上有多少次靠改迁移而不是新增迁移来「修」DDL?(`scripts/repair-local-mysql-v*.sql` 是为此付出的代价) | |
| | | | 84 | [ ] | 看起来不可能的报错,例如 `No qualifying bean of type 'HuifuPaymentProperties'` | 同一个 checkout 上并发跑两个 Maven 构建会写坏 `target/`;另外 `mvnw.cmd` 是 batch/PowerShell 混合 wrapper,**总是**用 PowerShell 5.1 引导,而 5.1 会把 Maven 打到 stderr 的警告当成终止错误 → 编译中途莫名中断 | 已验证 · `docs/agents/testing.md:37-47`;`50c71b3`、`3ec11f3`、`f7a1861` | 团队里有人用 IDE 自带构建 + 命令行脚本并行吗?要不要在脚本里加 lockfile 防并发? | |
| | | | 85 | [ ] | 首页统计的回归测试在 H2 缝上**根本跑不起来**(HEAD 上就红) | `selectDashboardCards`/`selectPeriodSummaries` 内联了 `GovernmentSubsidySql.AMOUNT_CENTS`,用到 MySQL 的 `json_extract` 与 `if()`;H2 2.4.240(MODE=MySQL)两者都没有,`if()` 更是语法错误、无法用 `create alias` 绕开 | 已验证 · `bug-1` 文档「另一件值得记的事」;`.scratch/order-payment-display/H2Probe.java` | 现在有多少条 SQL 只能在真 MySQL 上测?要不要把「H2 只作兜底」写成测试约定? | |
| | | | 86 | [ ] | 测试全部报 `Connection refused`,或更绕的 `Unable to determine Dialect without JDBC metadata` | 测试库那个 8.0.36 跑在 **WSL 的 docker** 里;WSL 虚拟机会在空闲时自己关掉,容器跟着停。**光起容器不够**——命令一返回 VM 又空闲关掉,现象是容器每次 `docker ps` 都显示 `Up 2 seconds`,要用一个长命进程挂住它 | 已验证 · `docs/agents/testing.md:120-150`(2026-09-17 实测);`06db42a` | 有更稳的做法吗(把 8134 换成本机常驻实例、或给 VM 设自启)?长期依赖「挂一个 sleep」很容易忘。 | |
| | | | 87 | [ ] | 某个用例**从今往后每跑必红**(`PaymentFundConfirmationTests` 补偿用例),不是偶发 | 补偿任务扫全库 + `order by o.id asc limit 50`;用例自己新建的那单 id 最大,排在窗口之外。测试库共享且从不清理,符合条件的历史行累计到 50 条以上就饱和 | 已验证 · `.scratch/payment-fund-confirmation-retry/issues/01-...md`(实测 `eligible=60 oldestId=906 newestId=6549 firstSeen=2026-09-14`);`ab9b1f3` 已修 | 这条已修,但同一机制(全库扫描 + limit + 共享测试库)还在别处吗?要不要一次性搜所有 `limit ?` 的后台任务? | |
| | | | 88 | [ ] | 拿旧副本库判断开发库状态,结论全错 | 本机 `8134` 上有三个库是合并前的本机副本,**不再是开发库**;而 `.scratch/` 下很早的探针把 URL 写死成 `127.0.0.1:8134/talent_card` | 已验证 · `docs/agents/testing.md:18-21`;`.scratch/db-merge/README.md` | 那些写死 8134 的一次性探针要不要统一加一行「默认指向旧副本、复用请显式传 URL」的注释? | |
| | | | 89 | [ ] | *(环境事故)*开发库里迁移从未创建的历史表被删掉,一批数据丢失 | 「把测试库结构克隆到开发库」「`reset-test-db.ps1 -Database talent_card`」这类操作会删掉 `dependent_card_apply`、`merchant_benefit_rule` 等手建表;2026-09-13 就这样丢过一批 | 已验证 · `docs/agents/testing.md:64-70` | 开发库里还有多少张「迁移从未创建」的表?要不要先盘一份清单入库? | |
| | | |
| | | ## H. 凭据与配置(7 条) |
| | | |
| | | | # | ✓ | 现象 | 我推断的根因 | 证据 | ❓ 待确认 | |
| | | |---|---|---|---|---|---| |
| | | | 90 | [ ] | 查目标商户一律 `20002 数据权限认证失败`;以目标商户自身作 `sys_id` 查自身,SDK 报无响应、HTTP 922 空响应 | 平台身份对不归自己的商户没有数据权限。凭据轮换成目标商户的**直属上级渠道**后 20002 才解除 | 已验证 · `evidence-2026-09-15.md`(第一轮「查询权限对照」表) | 当时汇付客服给的结论是什么?还是只靠轮换凭据试出来的?「渠道商作 `sys_id` 能管下级」有官方依据吗? | |
| | | | 91 | [ ] | 在控台的商户关系上做文章(head 层级、总部客群类型)对 20002 完全无效,还平白增加进件失败面 | 20002 与层级无关,取决于签名身份本身有没有那些商户的数据权限 | 已验证 · `HuifuPaymentProperties.java:26-31`;`application.yml:112-115`;`7dfc5b7`→`e89a164` | 「渠道商 → 普通商户」两级是最终形态吗?将来拆多平台/多运营主体时,这套凭据模型还成立吗? | |
| | | | 92 | [ ] | 测试输出里出现运行配置的**明文私钥** | `HuifuSignatureServiceTests` 会打印运行配置;已改为使用现成的 `keySummary` 摘要函数 | 已验证 · `evidence-2026-09-15.md`「全量后端测试 179 项」段末 | 除了这个测试,还有别的地方(日志、探针、报错信息)会带出私钥/密钥吗?要不要加一条统一的日志脱敏? | |
| | | | 93 | [ ] | 微信费率默认写成 0.60,费率规则号默认为空,导致真实补开时费率与 CID 都是错的 | 默认值是在「企业 756 + 0.70」定稿之前写的;`HuifuPaymentProperties` 的 Java 默认值(`0.60`、`merchantWechatFeeRuleId=""`)与 yml 不一致 | 已验证 · `c18b9fa`;`HuifuPaymentProperties.java:46,51`;`application.yml:120-128` | 0.70 / 756 / 761 / 775 这四个值有汇付书面的费率确认函吗?还是只按官方 Excel 推的? | |
| | | | 94 | [ ] | 配置里写着开发也能用的回调地址,实际开发机不可达 | 五个回调地址全指向一个公网 IP;且平台没有公网服务器 | 已验证 · `issues/README.md` F1;`application.yml:91-110` | 生产/联调环境的公网回调地址现在配的是哪个?在通道侧注册了吗?(ADR-0004 把这条单独列为「生产 bug」) | |
| | | | 95 | [ ] | 回调地址为空时会静默指向一个不可达地址,没人知道回调其实没启用 | 原实现靠 `notify-url` 推导其余四个地址,缺一个也看不出来 | 已验证 · `ab9b1f3`;`HuifuCallbackUrlAudit` | 五个回调地址里,现在哪几个是空/推导值?要不要在后台做一个只读的「回调注册状态」页? | |
| | | | 96 | [ ] | 单测各自正常,合起来异常(「一次提现之后支付用错产品号」) | SDK 的 `initWithMerConfig` 写静态 `configMap["default"]`,未指定配置名的请求读该默认配置;支付/提现/结算三类网关各用自己的锁,彼此不能隔离(对 `dg-java-sdk 3.0.40` 字节码检查确认过) | 已验证 · 审计 §15;`HuifuSettlementRecordGateway.java:25,95-111` | 提现网关已随 V140 退役,但**多个网关仍各自初始化 SDK 默认配置**——要不要统一改成命名配置 + 每次请求显式传配置名? | |
| | | |
| | | --- |
| | | |
| | | ## 附:汇付侧行为反直觉的 28 条(`05` 的核心内容) |
| | | |
| | | > **2026-09-18 推导结论(原「确认过吗」列作废,答案已自行查明)** |
| | | > |
| | | > 全仓检索确认:**仓库里没有任何「汇付官方回复」的留档**——无客服对话、无邮件、无工单编号、无官方口头结论。检索词覆盖 客服/客户经理/技术支持/工单/官方回复/汇付答复/对话记录/聊天/截图/邮件/对接群/汇付侧说,命中为零(`V137` 里的「工单 06」是我方开发任务编号,不是汇付工单)。 |
| | | > |
| | | > 唯一一处以「汇付」为主语的转述——`.scratch/merchant-closure-impact/issues/huifu-merchant-closure-impact.md:9`「汇付的说法:控台关闭商户后…」——**无出处、无时间、无渠道、无留档**,按标准只能算【推断】,也就是第 25 条的通道侧行为。 |
| | | > |
| | | > 因此下表 28 条的置信档**没有一条是「已与汇付确认」**。逐条推导结果(`05` 里按这个标注): |
| | | |
| | | | 档 | 条数 | 条目 | |
| | | |---|---|---| |
| | | | **【官方原件】**(本地有原件可引) | 4 | 13(`huifu-api_shjj_wxsmrzztcx.md`)、18/19(`wx-fee-rule-id.xlsx` 官方 Excel 全表)、27(`huifu-wx-config-doc.md` 原文) | |
| | | | **【官方·外部】**(官方写了,仓库内无原件) | 4 | 2、3(关单异步与一分钟限制)、4(`23000000` 有官方码表行;**`10000016` 只有官方返回示例文案、无码表行**)、15、23 | |
| | | | **【实测】** | 17 | 1、5、6、8、9、10、11、12、14、16、17、20、21、22、25(本地侧)、28 | |
| | | | **【推断】** | 3 | 7 的后半(退款/回退路径语义未定)、24(回退遇 D1 已结算会失败)、26(流水号去重) | |
| | | |
| | | **三条「我方实测与官方字段表冲突」——必须在 `05` 里显式标出,接手的人照官方文档写会和我们现在的实现打架:** |
| | | |
| | | | 条 | 我方实测 | 官方字段表 | 处理 | |
| | | |---|---|---|---| |
| | | | 21 | 支付查询回执里通道流水号是 `org_hf_seq_id` | 官方支付查询响应表写的是 `hf_seq_id`「汇付全局流水号」,`org_hf_seq_id` 在该页出现 0 次 | 标**需复核**:可能官方表漏列、也可能别的产品线字段名不同。我们按实测走,但写明「官方表与此不一致,新增用途前先取证」 | |
| | | | 22 | 退款查询回执**不带**退款流水号 | 官方退款查询响应表列有 `org_hf_seq_id`「退款全局流水号」(必填 N) | 标**需复核**:我们按实测走(无条件覆盖会把唯一凭证擦空),但不要断言「官方说错了」 | |
| | | | 14 | 授权二维码是**裸 base64 的 JPEG**(`/9j/`) | 官方示例给的是 `data:image/png;base64,…` | 按实测走,且**不要写死格式假设**——已实测到通道会回 JPEG | |
| | | |
| | | **其余结论的置信档**:A–H 表 96 条里,凡我在「证据」列标了 **已验证** 的即【实测】,标 **推断** 的即【推断】——两者的区别是「有没有真实回执/库内数据」,与我是否找过汇付无关(答案是都没找过)。 |
| | | |
| | | | # | 汇付的行为 | 常识/文档会让人以为 | 我们的绕法 | 确认过吗? | |
| | | |---|---|---|---|---| |
| | | | 1 | 未付款的预支付单在通道侧是「处理中(`P`)」 | 「未付款」应该是不确定/未支付 | `notCollected()` 只承认三种「绝不可能收过钱」的答复:`F`、`I`、`23000001` | ? | |
| | | | 2 | 关单是**异步**的,且关单查询是**另一个接口** | 关单接口返回就代表关掉了 | 只发关单不问结论 → 用户实测等 72 秒 | ? | |
| | | | 3 | 通道**不允许关闭一分钟以内**的订单 | 想关就能关 | 最短重试间隔被通道钉在 1 分钟 | ? | |
| | | | 4 | `23000000` / `10000016`「原订单已为终态」意思是「**它已经结束了**」 | 「现在不让关」= 还没结束 | 不能并入「其余拒绝」,否则死凭证被当成「还能付」继续交回客户端 | ? | |
| | | | 5 | 关单请求**本身也会失败**:`90000000 交易失败,请重试` 表示这次关单**没生效**,凭证还活着 | 请求失败 = 状态未知 | 与 `CLOSING`(关单可能已生效)语义完全相反,必须分开建模 | ? | |
| | | | 6 | `23000003` 会**同时带 `trans_stat=F`**,但在交易确认路径上是幂等成功(「已无未确认金额」) | 状态 `F` = 失败 | 先判业务码、再判交易状态 | ? | |
| | | | 7 | **同一个业务码在不同路径上语义不同**:`23000003`/`23000004` 在交易确认路径已验证是幂等成功,在退款/交易确认退款路径上**含义未定** | 错误码全局唯一语义 | 两个方向的错都很贵(判成功=放行可能没动的钱;判失败=重复出款),未定时停在中间态交人工 | ? | |
| | | | 8 | 通道对**刚创建的微信交易**,最常见的关单答复就是「关单处理中」(实测三次取消全中) | 关单应该很快 | 取消不再等关单结论,按用户意图立刻生效 | ? | |
| | | | 9 | 业务开通是**申请型**接口,返回 `90000000` 是「受理/审核中」,**不是失败** | 非成功码 = 失败 | 旧网关漏了它,会把一次成功受理记成 `failed` | ? | |
| | | | 10 | **微信入驻是异步的**:业务开通被受理(`90000000`)那一刻,通道侧还查不到微信子商户号 | 受理了就该能配置 | 配置闸门必须取通道证据(`qry_wx_conf_list` 里真出现线上场景),不能取本地状态 | ? | |
| | | | 11 | 不设最低收费时,通道**不返回** `fee_min_amt`(不是返回 `0.00`) | 缺失 = 未生效 | 缺失必须按 0 处理,否则阶段判定永远不成立 | ? | |
| | | | 12 | `apply_status=Y`(申请单通过)**不代表**微信业务也开通了:原申请可能本来就没带微信业务,`wx_conf_result_list` 会整个缺失 | 申请单通过 = 业务开通 | 两项必须分别判定 | ? | |
| | | | 13 | 授权是否完成**只能看 `authorize_stat`**:`APPLYMENT_STATE_PASSED` 单独不算;反过来 `APPLYMENT_STATE_FREEZED`(「该主体已完成过入驻」)也可能已授权成功(真实样本就是 FREEZED + `authorize_stat=1`) | 状态字段字面义 | 只看 `authorize_stat` | ? | |
| | | | 14 | 授权二维码是**裸 base64 的 JPEG**(`/9j/`),既不是 PNG、也不带 `data:image/...;base64,` 前缀;且授权成功后通道仍会回传该码 | base64 图片常见是 PNG | 判格式不能只看前缀假设;前端按状态收起而不是按「有没有码」 | ? | |
| | | | 15 | `media_mer_common_flag` 是 `online_media_info_list` 的**元素字段**,不是顶层参数 | 名字像顶层参数 | 放在列表元素里;且声明「不一致(N)」会连带索要授权或开发证明材料 | ? | |
| | | | 16 | `use_chains_flag=3` 要求**上级本身**有商户级微信配置——渠道商没有,所以渠道商做不了 AT 复用 | 上级渠道商天然能复用 | 不下发该标志,让子商户在渠道下拿自己的微信子商户号 | ? | |
| | | | 17 | **微信小程序配置必须排在业务开通之后**:微信子商户号是业务开通创建的 | 配置可以从容提前做 | 顺序反了必被驳「根据渠道号或微信子商户号未查到入驻记录」,而且配置一失败业务开通永远轮不到 | ? | |
| | | | 18 | CID 是「支付场景 × 业务开通类型 × **主体类型**」**三维**定位:企业 756 / 个体户 761 / 事业单位 773 / 社会组织 774 / 小微 775 | 全平台共用一个费率规则号 | 按主体类型分行配置;拿企业的 756 给小微会被拒 | ? | |
| | | | 19 | **场景 12(线上反扫)在官方《微信费率规则ID》表里没有独立行**,与线上公众号/小程序(4/5)共用同一条线上规则 | 每个场景都该有独立行 | 共用线上的那一条,并在配置注释里写清 | ? | |
| | | | 20 | 结算记录查询 `pageSize` 上限 **50**,超过直接 `00000003` 且**一条都不返回**(不是截断到 50) | 超限应该截断或报错 | 网关内钉住 `PROVIDER_MAX_PAGE_SIZE = 50` | ? | |
| | | | 21 | 支付查询回执的通道流水号字段是 **`org_hf_seq_id`**;`hf_seq_id` **只出现在异步通知里** | 两者是同一个字段 | 取错会**静默写成 NULL** | ? | |
| | | | 22 | **退款查询回执不带退款流水号**——它只在**发起退款的那次回执**里有 | 查单能拿到全部字段 | 不能无条件覆盖,否则把唯一凭证擦成空串 | ? | |
| | | | 23 | 延迟入账下「**订单已核销**」≠「**商户已拿到钱**」:钱停在通道「待确认」,必须显式调交易确认才按分账串分配;未核销的钱官方定义是「不可结算、不可取现」 | 核销即到账 | 核销时调交易确认,并把结果落 `funds_confirmation_status` | ? | |
| | | | 24 | 交易确认退款(回退)在钱被 D1 结算划到商户银行卡之后就会**失败**(基本户余额不足) | 随时能回退 | 撤销核销的窗口实际上止于下一次自动结算 | ? | |
| | | | 25 | 控台关闭商户后该商户**所有**支付相关功能受限,但我们本地的进件状态仍是 `approved`、能力快照最长 6 小时后才被发现 | 关停会推通知给我们 | 定时核实通道能力;感知仍依赖「下一次下单被拒」 | ? | |
| | | | 26 | 通道按**请求流水号去重**:业务开通/实名/配置被驳回后重试**必须换新号**,否则被当成重复请求 | 重试无副作用 | 换号时必须保住随机后缀(先按剩余宽度裁前缀再接后缀,否则超长截断把随机位一起切掉) | ? | |
| | | | 27 | 微信商户配置是**添加**接口:成功后**不支持修改和删除**,每种参数最多 5 组 | 配置可以反复改 | 一次配对,不指望改 | ? | |
| | | | 28 | `20002 数据权限`与商户层级**无关**:改了层级(head 字段)仍然 20002,取决于签名身份本身的数据权限;同一身份操作自己的数据全部 `00000000` | 归属错了就换层级 | 凭据轮换成目标商户的直属上级渠道 | ? | |
| | | |
| | | --- |
| | | |
| | | ## 附:疑似但未找到证据(10 条,成文时按 `[待确认]` 落笔) |
| | | |
| | | 这些我在本次调查里**没找到证据**,不写进正文的结论,只在文档里标注「未验证」。你如果知道答案,直接在这里补。 |
| | | |
| | | 1. 审计 §7 那个「后台跳过真实支付直接置为已支付」入口现在是否真的不存在(`OrderService.pay` 与 `AdminAuthInterceptor.page()` 的当前状态) |
| | | 2. 核销后交易确认在「商户被通道关停」下能否成功、钱是否还留在延迟户(closure-impact 写明「代码里看不出来,**需要实测**」) |
| | | 3. 真机上核销码图片取不回来的根因(候选:「http 图片被微信真机拦掉」vs「图片请求 404」),决定是「等后台切 HTTPS 合法域名后自然消失」 |
| | | 4. `provider_refund_no` 之外是否还有别的「只在某一次回执里出现的字段」被无条件覆盖(只找到退款流水号这一处修复,没找到全局排查记录) |
| | | 5. 进件的 `store_address_code` 与门店照片在真实通道上的接受度(audit.md 写「未验证……不能用『模拟测试通过』代替」) |
| | | 6. 业务开通的 `micro_biz_type` 是否与独立实名接口重复(audit.md 七记为「仍未官方确认」的唯一一条) |
| | | 7. 补贴总额(`subsidyAmountCents`)口径是否有意不带核销条件(类注释与交易总额不一致,文档标「需要产品和结算口径确认」) |
| | | 8. `DashboardSummaryService.summary()` 死副本(文档说「谁把它接上去,这个 bug 就会回来」,没找到删除记录) |
| | | 9. `PaymentFundConfirmationRetry.enabled` 的默认取值(README F7 建议实现它,实现的默认值未见明确记录) |
| | | 10. 进件进度 `sync_owner/sync_until` 的 5 分钟租约与 `capability_sync_until` 的 5 分钟冷却是否必须同值(ADR-0002 只说「同量级」) |
| | | |
| | | --- |
| | | |
| | | **我需要的**:~~A–H 表里 ❓待确认 列的回答~~ —— **2026-09-18 已自行推导,不再需要人工确认。** |
| | | |
| | | 处理方式:`❓待确认` 列的问题我已逐条回查代码与留档,能定的定了,定不了的按下面的规则落笔: |
| | | |
| | | - **能定** → 直接写入 `05`/`06`,并按四档标置信度(【官方原件】/【官方·外部】/【实测】/【推断】)。 |
| | | - **定不了** → 写 `[待确认:<具体问题>]` 内联标注,并在 `06` 末尾汇总成「待确认清单」。**不删**——因为「我们也不知道」本身是接手人必须知道的信息。 |
| | | - **凡属「汇付侧这么规定的」而无本地原件** → 一律标【官方·外部】并给官方 URL,**绝不写成「已与汇付确认」**。 |