# 支付商户与商户池管理设计 ## Context 接口文档把能力拆成支付商户、商户池和微信授权配置三部分。前端必须在同一个平台专属入口内完成管理,同时避免把商户凭证和内部路由细节带入页面状态。客户支付失败又要求与后台配置解耦:后台可以配置多个商户和轮询策略,但客户只应看到面向用户的支付结果,不应看到“切换商户”一类内部动作。 ## Goals - 通过一个后台入口管理支付商户、商户池和微信授权配置。 - 仅允许超级管理员和平台用户访问。 - 支持商户、商户池、授权配置的启停状态管理。 - 支持商户池成员拖拽排序、策略选择和阈值配置。 - 保证支付凭证只写、不回显、不持久化。 - 统一“暂无可用商户”和普通支付失败的用户提示。 ## Non-Goals - 前端不实现商户路由算法。 - 前端不保存、展示或恢复支付凭证明文。 - 不给客户支付端展示商户池内部配置。 - 不把“切换商户重试”作为用户可操作流程。 ## Decisions ### 单一管理入口与三个子页签 新增 `/settings/payment-merchant-pools`,页面内使用“支付商户”“商户池”“微信授权配置”三个页签。这样与接口文档的结构一致,也便于统一权限检查和凭证清理策略。 替代方案是为三部分分别增加菜单项;该方案会让权限、路由和状态管理重复,暂不采用。 ### 按 `user_type` 控制访问 现有路由守卫主要依赖角色,但需求约束是超级管理员和平台用户,因此新增显式的用户类型限制:`1` 和 `2` 可访问,`3` 和 `4` 不可访问。菜单隐藏与直接 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_unit`、`time_period_value`,并按需提交 `time_period_started_at`。 - `statistic_cycle`:支持 `round`、`day`、`month`;只在接口允许的金额或笔数策略下提交有效值。 - `routing_epoch`:仅展示后端返回的当前路由统计世代,前端不编辑。 ### 无可用商户使用稳定错误码 接口简版没有给出支付失败错误码,实施前必须与后端确认稳定的机器可读值。前端只按错误码映射,不按 `msg` 文本判断。无论后端使用何种最终编码,命中该语义时客户界面都只显示“暂无可用商户”。 普通支付失败统一使用“支付失败,请重新发起支付”。前端不自动切换商户,也不重放同一支付请求;用户再次操作时按支付接口的新请求语义重新发起。 ## Risks and Trade-offs - 接口文档未定义 `credentials` 的字段结构。实现时需要后端补充各 `provider_type` 的写入 schema,或继续保持单个只写对象,但不得把结构暴露为可回显配置。 - 若后端读取接口未脱敏,前端仍然能够防御性丢弃,但服务端响应、网关日志和网络抓包仍可能泄露凭证;该风险必须由后端脱敏共同控制。 - 客户支付端若不在当前仓库,文案和错误码任务需要跨仓库联调;本提案负责固化契约,不能仅通过后台页面上线完成验收。 - 金额阈值若直接按分展示会降低可读性,因此采用元输入、分传输;需要测试防止小数点精度和空值转换错误。