4.5 KiB
商户池详情与商户凭证契约设计
Context
支付商户与商户池管理页已经上线,运营在使用时遇到三个问题:商户池详情入口和支付商户不一致且信息不完整、新增商户池直接报错、商户凭证缺少字段枚举与类型校验。本次优化只新增详情页、收敛成员顺序数据源,并在既有凭证写入表单上叠加契约校验,不改变接口路径与请求结构。
Goals
- 让商户池详情的入口和展示完整度与支付商户详情对齐。
- 让新增/编辑商户池恢复可用,并且拖拽排序结果仍按顺序提交。
- 让前端提交的商户凭证满足后端的字段枚举、必填键、值类型与商户标识约束。
Non-Goals
- 不改变“更换凭证”表单的交互方式与凭证值不回显约束。
- 不新增后端接口。
Decisions
商户池详情复用支付商户详情的入口模式
支付商户详情已采用“列表点击名称 → 独立详情页”的模式,因此商户池改为同样方式:列表名称列渲染为可点击文本,点击后跳转 /settings/payment-merchant-pools/pool-detail/:id,并移除行操作里的“详情”抽屉。
详情页复用 src/components/common/DetailPage.vue,按“基本信息”“轮询配置”“运行状态”三组展示字段。创建时间、更新时间只在接口返回时渲染,避免出现空白字段行。
替代方案是保留抽屉并补齐字段;但两个详情入口不一致会让运营难以形成稳定预期,因此不采用。
成员商户名称由前端解析
详情接口只返回 member_ids。详情页在加载详情后按该商户池的 payment_method 拉取支付商户列表,把成员 ID 映射为商户名称展示,同时保留成员数量。解析不到名称时展示“未知商户”,不展示 member_ids 原始 ID,避免把内部标识暴露给运营。
成员顺序使用单一数据源
原实现同时监听 form.member_ids 和 orderedMemberIds,并在两个回调中互相赋新数组,形成“赋值 → 触发 → 再赋值”的无限循环,从而在新增商户池时触发 Maximum recursive updates exceeded。
修复方式是把成员顺序收敛为单一数据源:orderedMemberIds 改为基于 form.member_ids 的 computed(get 返回成员数组,set 写回成员数组)。VueDraggable 通过 v-model 触发 setter 写回 form.member_ids,不再存在互相触发的 watch。
凭证字段枚举固化在类型模块并在提交前校验
凭证必填键由后端契约按 payment_method 与 provider_type 组合给出,前端与该契约保持一致,因此把枚举定义在 src/types/api/paymentMerchantPools.ts:PAYMENT_CREDENTIAL_FIELD_SPECS 描述每个 provider_type 的必填键与可选键,PAYMENT_CREDENTIAL_BOOLEAN_KEYS、PAYMENT_CREDENTIAL_INTEGER_KEYS 描述 ali_production、ali_pay_expire_minutes 的值类型,PAYMENT_MERCHANT_IDENTITY_KEYS 描述商户标识必须一致的凭证字段。枚举之外的字段名一律拦截,避免提交必然被后端拒绝的请求。
buildPaymentCredentials 负责提交前校验并转换:拒绝不属于当前服务商的字段名、补齐必填键检查、把布尔字段与整数字段从输入框字符串转换为布尔值/数字、校验 merchant_identity 与对应凭证字段一致。表单继续使用“字段名 + 字段值”的通用编辑方式,只在分隔线下方展示当前服务商的必填/可选字段提示。
替代方案是把凭证表单改成按服务商渲染固定中文标签字段;该方案会改变既有交互与凭证只写约定,本次不采用。
凭证仅平台账号可读写
凭证读取与写入入口复用既有平台账号限制:/settings/payment-merchant-pools 及其详情路由都带 allowedUserTypes: [1, 2],页面内 canManage 再按 isPlatformAccount 过滤操作。凭证值只在当前编辑会话内存中存在,提交、取消或关闭后清空,不进入 Pinia 持久化、浏览器存储、日志或错误上报。
Risks and Trade-offs
- 详情页与支付商户详情一样受
allowedUserTypes: [1, 2]限制,代理与企业账号无法访问。 - 商户池成员名称依赖支付商户列表接口,成员数量超过单页上限时可能解析不到名称,此时展示“未知商户”而不是原始 ID。
- 凭证字段枚举与后端校验规则必须保持一致;后端新增字段时前端需要同步更新枚举,否则提交会被前端拦截。