Files
one-pipe-system/openspec/changes/add-payment-merchant-pool-management/design.md
2026-09-10 16:50:06 +08:00

4.4 KiB
Raw Blame History

支付商户与商户池管理设计

Context

接口文档把能力拆成支付商户、商户池和微信授权配置三部分。前端必须在同一个平台专属入口内完成管理,同时避免把商户凭证和内部路由细节带入页面状态。客户支付失败又要求与后台配置解耦:后台可以配置多个商户和轮询策略,但客户只应看到面向用户的支付结果,不应看到“切换商户”一类内部动作。

Goals

  • 通过一个后台入口管理支付商户、商户池和微信授权配置。
  • 仅允许超级管理员和平台用户访问。
  • 支持商户、商户池、授权配置的启停状态管理。
  • 支持商户池成员拖拽排序、策略选择和阈值配置。
  • 保证支付凭证只写、不回显、不持久化。
  • 统一“暂无可用商户”和普通支付失败的用户提示。

Non-Goals

  • 前端不实现商户路由算法。
  • 前端不保存、展示或恢复支付凭证明文。
  • 不给客户支付端展示商户池内部配置。
  • 不把“切换商户重试”作为用户可操作流程。

Decisions

单一管理入口与三个子页签

新增 /settings/payment-merchant-pools,页面内使用“支付商户”“商户池”“微信授权配置”三个页签。这样与接口文档的结构一致,也便于统一权限检查和凭证清理策略。

替代方案是为三部分分别增加菜单项;该方案会让权限、路由和状态管理重复,暂不采用。

user_type 控制访问

现有路由守卫主要依赖角色,但需求约束是超级管理员和平台用户,因此新增显式的用户类型限制:12 可访问,34 不可访问。菜单隐藏与直接 URL 访问必须使用同一判断,避免仅做 UI 隐藏。

凭证只写且只存在于内存

商户 credentials 和微信授权敏感字段只允许在创建或显式更换时输入。读取响应不得回填这些字段;页面模型只在当前弹层或表单生命周期内保存输入值,提交、取消、关闭或卸载时清理。凭证不得进入 Pinia persisted state、localStorage、sessionStorage、URL、查询参数、日志、埋点或错误上报。

详情页不回显凭证内容,只显示“已配置/未配置”和 credential_version。如果后端读取接口意外返回敏感字段,前端适配层必须丢弃,而不是仅依赖模板隐藏。

成员数组顺序就是轮询顺序

商户池编辑使用拖拽排序。提交时直接把当前排序后的商户 ID 数组写入 member_ids,不新增独立的 sort 字段,也不在前端重新排序。成员选择默认限制为与商户池 payment_method 相同的商户,并禁止重复 ID。

策略和阈值映射

  • strategy=amount:展示 threshold_amount,前端以元输入并按 value * 100 转为分后提交。
  • strategy=count:展示 threshold_count,只接受正整数。
  • strategy=time:展示 time_period_unittime_period_value,并按需提交 time_period_started_at
  • statistic_cycle:支持 rounddaymonth;只在接口允许的金额或笔数策略下提交有效值。
  • routing_epoch:仅展示后端返回的当前路由统计世代,前端不编辑。

无可用商户使用稳定错误码

接口简版没有给出支付失败错误码,实施前必须与后端确认稳定的机器可读值。前端只按错误码映射,不按 msg 文本判断。无论后端使用何种最终编码,命中该语义时客户界面都只显示“暂无可用商户”。

普通支付失败统一使用“支付失败,请重新发起支付”。前端不自动切换商户,也不重放同一支付请求;用户再次操作时按支付接口的新请求语义重新发起。

Risks and Trade-offs

  • 接口文档未定义 credentials 的字段结构。实现时需要后端补充各 provider_type 的写入 schema或继续保持单个只写对象但不得把结构暴露为可回显配置。
  • 若后端读取接口未脱敏,前端仍然能够防御性丢弃,但服务端响应、网关日志和网络抓包仍可能泄露凭证;该风险必须由后端脱敏共同控制。
  • 客户支付端若不在当前仓库,文案和错误码任务需要跨仓库联调;本提案负责固化契约,不能仅通过后台页面上线完成验收。
  • 金额阈值若直接按分展示会降低可读性,因此采用元输入、分传输;需要测试防止小数点精度和空值转换错误。