6.1 KiB
Context
当前商户池在支付单创建时冻结商户、商户池和统计世代;金额/笔数路由通过 tb_payment_merchant_routing_success 只读取首次支付成功事实。微信直连、微信 v2、富友和支付宝的预下单均在本地支付单创建后于事务外执行,明确失败时会关闭或标记支付单失败,但不会改变商户状态或当前路由统计。
本变更横跨商户池路由、支付创建入口、渠道结果分类、支付审计和数据库统计事实。已有支付单必须保持原商户归属,历史成功统计不得被重复计入。
Goals / Non-Goals
Goals:
- 为每次新支付建立不可变的商户池路由尝试事实,按创建时冻结的商户、商户池、统计世代和金额记录一次。
- 金额/笔数路由基于路由尝试事实推进,不依赖支付最终成功;新尝试成功、明确失败、取消或过期均不回冲、不重复计入。
- 明确预下单失败时自动禁用当前商户,使后续新支付跳过该商户。
- 保留结果未知的支付恢复/查单边界;未知结果不能直接视为明确失败并禁用商户。
- 对并发创建、重复回调、迟到成功和商户停用保持幂等与历史支付可服务性。
Non-Goals:
- 不在一次失败后自动将同一个支付单改挂到其他商户,也不自动向其他商户重试同一笔外部支付。
- 不改变历史支付回调、查单和退款按冻结商户/旧支付配置读取的兼容规则。
- 不增加人工启停开关、失败重试次数配置、商户健康评分或熔断恢复调度。
- 不把结果未知、请求未发出、应用初始化失败等所有错误统一判定为商户失效;这些分类必须沿用可证明的渠道结果语义。
- 不追溯改写上线前支付单或把历史成功事实转换为新的尝试事实。
Decisions
1. 使用独立的路由尝试事实,不扩展成功事实的语义
新增与支付 ID 一一对应的不可变路由尝试事实,至少保存支付 ID、商户 ID、商户池 ID、统计世代、金额、创建时间和尝试结果归类。支付创建事务在冻结路由后写入该事实;唯一约束保证同一支付单只计一次。
保留现有成功事实用于历史兼容或成功审计,但金额/笔数选路只读取新的路由尝试事实。这样“成功统计”与“交易尝试统计”语义不会继续混淆,也不会因回调重放增加笔数。
2. 统计边界定在本地支付单创建提交
只要新支付已经在本地事务中创建,并冻结了商户池路由,就计入一次路由尝试金额/笔数;后续渠道成功、明确失败、取消、过期或结果未知都不再改变这次计数。未成功提交本地支付事实的请求不计入。
该边界使并发选路可以由数据库事务锁和当前统计世代保护,避免“预下单失败后没有累计、下一次继续卡在同一商户”。支付单创建后再发生的路由切换不会改写其冻结事实。
3. 明确失败与结果未知分离
渠道适配层或支付发起编排必须输出稳定的结果分类:明确失败、成功、结果未知和未发送/本地失败。只有已经实际发起且渠道明确拒绝、明确返回失败的结果才触发商户自动禁用;超时、连接中断、响应解析无法确认、请求上下文取消等结果未知不得自动禁用。
本地配置校验或适配器初始化失败是否禁用商户,需要按是否能证明该商户凭证/商户身份不可用分类;不能证明时只关闭本次支付事实并保留商户,避免暂时性基础设施故障误杀整个账户。
4. 禁用与失败留痕采用条件更新
明确失败处理使用“商户仍为启用状态”的条件更新,多个并发失败只产生一次有效状态变化;失败原因、支付 ID、支付单号和发生时间写入受控的商户失败留痕或审计事实,不写入凭证。已经被其他管理员停用的商户不被重新启用,历史支付仍可通过商户 ID 加载当前凭证处理回调、查单和退款。
5. 轮换与自动禁用的先后顺序
支付创建事务先锁定启用商户池并冻结路由尝试,再提交支付单;事务外渠道明确失败后条件禁用该商户。下一次支付重新读取启用成员和当前统计世代,因此既受尝试金额/笔数阈值影响,也会跳过刚被禁用的商户。当前失败支付不被自动迁移,避免同一业务单产生多个渠道交易号。
6. 统计世代和自然周期沿用现有规则
每轮累计仍在全部成员达到阈值时开启新世代;自然日/月仍在当期没有可用成员时拒绝新支付。新路由尝试事实必须带创建时统计世代,自然日/月还按尝试创建时间过滤。配置变更按现有规则推进世代;不重新解释旧事实。
7. 覆盖三类商户池新支付入口
设计适用于 C 端套餐购买、C 端资产钱包充值和代理在线预存款充值。后台钱包/线下支付不进入商户池。历史旧支付入口只在已经按商户池创建并冻结路由的范围内接入新尝试事实;旧 payment_config_id 历史支付不补造路由尝试。
Risks / Trade-offs
- 按本地支付单创建计数会把结果未知和用户未完成支付也算作一次尝试,可能比“仅统计已发起请求”更积极;这是满足“成功失败都轮换”且避免坏账户卡死的明确取舍。
- 自动禁用对明确失败错误分类高度敏感。分类过宽会误禁用健康商户,分类过窄仍可能让坏商户短暂保留;必须优先采用渠道能证明的失败码和本地适配器错误语义。
- 商户禁用是全局状态,同一商户可能服务多个支付方式入口但商户本身只有一种支付方式;并发已冻结支付不受影响,新支付即时跳过。
- 需要新表或新列及索引,迁移必须只新增,不修改既有迁移;历史数据不回填,发布顺序需先完成 Schema 再启用新路由读取。
- 运营方要求“失败自动禁用”与结果未知保护存在业务张力;设计明确选择宁可进入查单恢复,也不因未知结果直接禁用或重试。