feat: 支付商户
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Payment Checkout Feedback Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 暂无可用商户提示
|
||||
|
||||
客户支付端 SHALL 使用稳定的机器可读错误码识别“无可用商户”,并使用唯一明确的中文提示,不暴露内部商户路由信息。
|
||||
|
||||
#### Scenario: 识别无可用商户错误
|
||||
- **GIVEN** 后端在支付发起响应中返回已确认的“无可用商户”稳定错误码
|
||||
- **WHEN** 客户点击支付并收到该错误
|
||||
- **THEN** 页面 MUST 显示“暂无可用商户”
|
||||
- **AND** 前端 MUST NOT 通过匹配中文 `msg` 或其他可变文案判断该错误
|
||||
|
||||
#### Scenario: 无可用商户时不展示内部信息
|
||||
- **WHEN** 页面展示“暂无可用商户”
|
||||
- **THEN** 页面 MUST NOT 展示商户 ID、商户名称、商户池名称、成员列表、轮询策略、阈值、`routing_epoch`、凭证或凭证版本
|
||||
|
||||
### Requirement: 客户支付失败反馈
|
||||
|
||||
客户支付端 SHALL 对普通支付失败使用面向用户的统一提示,并禁止暴露商户切换或自动重试的内部处理。
|
||||
|
||||
#### Scenario: 普通支付失败提示重新发起
|
||||
- **GIVEN** 客户支付请求因非“无可用商户”原因失败
|
||||
- **WHEN** 页面展示失败结果
|
||||
- **THEN** 页面 MUST 显示“支付失败,请重新发起支付”
|
||||
- **AND** 前端 MUST NOT 自动重放同一支付请求
|
||||
|
||||
#### Scenario: 不提示切换商户重试
|
||||
- **WHEN** 任意客户支付失败
|
||||
- **THEN** 页面 MUST NOT 显示“切换商户重试”或任何等价文案
|
||||
- **AND** 页面 MUST NOT 提供切换商户的按钮、入口或操作提示
|
||||
- **AND** 页面 MUST NOT 暴露后端是否尝试过多个商户
|
||||
|
||||
#### Scenario: 用户主动重新发起支付
|
||||
- **GIVEN** 客户已收到支付失败提示
|
||||
- **WHEN** 客户主动再次发起支付
|
||||
- **THEN** 前端 MUST 按支付接口约定创建一笔新的支付请求
|
||||
- **AND** 前端 MUST NOT 复用失败支付请求的商户选择或前端临时支付状态
|
||||
@@ -0,0 +1,170 @@
|
||||
# Payment Merchant Pool Management Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 平台专属的商户池管理入口
|
||||
|
||||
系统 SHALL 仅向超级管理员和平台用户开放支付商户、商户池及微信授权配置的管理入口和操作。
|
||||
|
||||
#### Scenario: 超级管理员可见并访问
|
||||
- **GIVEN** 当前登录账号的 `user_type` 为 `1`
|
||||
- **WHEN** 用户加载设置菜单或访问 `/settings/payment-merchant-pools`
|
||||
- **THEN** 系统 MUST 展示商户池管理入口并允许进入页面
|
||||
|
||||
#### Scenario: 平台用户可见并访问
|
||||
- **GIVEN** 当前登录账号的 `user_type` 为 `2`
|
||||
- **WHEN** 用户加载设置菜单或访问 `/settings/payment-merchant-pools`
|
||||
- **THEN** 系统 MUST 展示商户池管理入口并允许进入页面
|
||||
|
||||
#### Scenario: 非平台账号被拒绝
|
||||
- **GIVEN** 当前登录账号的 `user_type` 为 `3` 或 `4`
|
||||
- **WHEN** 用户加载设置菜单或直接输入 `/settings/payment-merchant-pools`
|
||||
- **THEN** 系统 MUST NOT 展示商户池管理入口
|
||||
- **AND** 系统 MUST 拒绝直接访问该页面
|
||||
- **AND** 系统 MUST NOT 返回商户、商户池或授权配置数据
|
||||
|
||||
### Requirement: 支付商户管理接口
|
||||
|
||||
系统 SHALL 提供支付商户的分页查询、创建、详情、按需更新和删除接口。
|
||||
|
||||
#### Scenario: 查询和筛选支付商户
|
||||
- **WHEN** 管理员请求 `GET /api/admin/payment-merchants`
|
||||
- **THEN** 系统 MUST 支持 `page`、`page_size`、`payment_method` 和 `enabled` 查询参数
|
||||
- **AND** `payment_method` MUST 支持 `wechat` 和 `alipay`
|
||||
- **AND** 响应中的每个商户 MUST 包含 `id`、`name`、`payment_method`、`provider_type`、`merchant_identity`、`enabled`、`remark`、`credential_version`、`created_at` 和 `updated_at`
|
||||
|
||||
#### Scenario: 创建支付商户
|
||||
- **WHEN** 管理员向 `POST /api/admin/payment-merchants` 提交 `name`、`payment_method`、`provider_type`、`merchant_identity`、`credentials`、`enabled` 和 `remark`
|
||||
- **THEN** 系统 MUST 创建支付商户并返回新商户记录
|
||||
- **AND** `provider_type` 为 `wechat` 时 MUST 只接受 `wechat`、`wechat_v2` 或 `fuiou`
|
||||
- **AND** `provider_type` 为 `alipay` 时 MUST 只接受 `alipay`
|
||||
|
||||
#### Scenario: 查询支付商户详情
|
||||
- **WHEN** 管理员请求 `GET /api/admin/payment-merchants/{id}`
|
||||
- **THEN** 系统 MUST 返回指定商户的详情
|
||||
- **AND** 响应 MUST NOT 包含任何支付凭证明文
|
||||
|
||||
#### Scenario: 按需更新和切换商户状态
|
||||
- **WHEN** 管理员请求 `PUT /api/admin/payment-merchants/{id}` 并只提交 `enabled`
|
||||
- **THEN** 系统 MUST 只更新该商户的 `enabled` 字段
|
||||
- **AND** 系统 MUST 保留未提交字段的原值
|
||||
|
||||
#### Scenario: 确认后删除支付商户
|
||||
- **WHEN** 管理员请求 `DELETE /api/admin/payment-merchants/{id}` 并提交 `confirm=true`
|
||||
- **THEN** 系统 MUST 删除目标商户
|
||||
- **AND** 前端 MUST 在发送请求前展示二次确认
|
||||
|
||||
### Requirement: 支付商户列表与启停交互
|
||||
|
||||
后台商户管理页 SHALL 展示支付商户状态,并允许有权限的管理员按接口契约切换启用状态。
|
||||
|
||||
#### Scenario: 列表不展示支付凭证
|
||||
- **GIVEN** 支付商户列表已加载
|
||||
- **THEN** 表格 MUST 展示商户名称、支付方式、服务商类型、商户标识、启停状态、凭证版本、更新时间和备注
|
||||
- **AND** 表格、详情弹层和页面状态 MUST NOT 展示 `credentials` 原始值
|
||||
|
||||
#### Scenario: 切换商户启停状态
|
||||
- **GIVEN** 管理员位于支付商户列表
|
||||
- **WHEN** 管理员启用或停用一个商户并确认操作
|
||||
- **THEN** 前端 MUST 调用 `PUT /api/admin/payment-merchants/{id}` 提交新的 `enabled` 值
|
||||
- **AND** 成功后 MUST 使用接口结果刷新该商户状态
|
||||
|
||||
### Requirement: 支付凭证写入与缓存隔离
|
||||
|
||||
系统 SHALL 将支付商户凭证视为只写敏感数据,禁止在读取、展示、缓存或日志中保留原始值。
|
||||
|
||||
#### Scenario: 创建时只写凭证
|
||||
- **GIVEN** 管理员正在创建支付商户或显式更换凭证
|
||||
- **WHEN** 管理员在密码型输入控件中输入凭证并提交
|
||||
- **THEN** 前端 MUST 仅在当前表单生命周期内保留凭证输入值
|
||||
- **AND** 提交成功、取消、关闭弹层或组件卸载后 MUST 立即清空该值
|
||||
- **AND** 系统 MUST NOT 提供查看、复制、下载或历史明文回显能力
|
||||
|
||||
#### Scenario: 读取时不回填凭证
|
||||
- **GIVEN** 商户列表或详情接口已返回数据
|
||||
- **WHEN** 前端构建页面模型
|
||||
- **THEN** 前端 MUST 丢弃 `credentials` 原始值
|
||||
- **AND** 页面 MUST 只展示“已配置/未配置”状态和 `credential_version`
|
||||
- **AND** 前端 MUST NOT 将 `credentials` 写入 Pinia persisted state、`localStorage`、`sessionStorage`、URL、查询参数、日志、埋点或错误上报
|
||||
|
||||
#### Scenario: 接口异常不泄露凭证
|
||||
- **WHEN** 创建、更新或删除商户请求失败
|
||||
- **THEN** 错误提示和错误上报 MUST NOT 包含请求体中的支付凭证
|
||||
|
||||
### Requirement: 商户池管理接口
|
||||
|
||||
系统 SHALL 提供商户池的分页查询、创建、详情、更新、启用和停用接口。
|
||||
|
||||
#### Scenario: 查询和创建商户池
|
||||
- **WHEN** 管理员请求 `GET /api/admin/payment-merchant-pools` 或创建商户池
|
||||
- **THEN** 系统 MUST 返回分页 `data` 或新建商户池记录
|
||||
- **AND** 商户池对象 MUST 支持 `id`、`name`、`payment_method`、`member_ids`、`enabled`、`strategy`、`statistic_cycle`、`threshold_amount`、`threshold_count`、`time_period_started_at`、`time_period_unit`、`time_period_value`、`routing_epoch` 和 `remark`
|
||||
- **AND** `payment_method` MUST 支持 `wechat` 和 `alipay`
|
||||
|
||||
#### Scenario: 查询和更新商户池详情
|
||||
- **WHEN** 管理员请求 `GET /api/admin/payment-merchant-pools/{id}` 或向同一路径提交 `PUT`
|
||||
- **THEN** 系统 MUST 返回指定商户池详情或保存更新后的商户池
|
||||
- **AND** 更新请求 MUST 按创建商户池的字段模型接受可提交字段
|
||||
|
||||
#### Scenario: 启用和停用商户池
|
||||
- **WHEN** 管理员请求 `POST /api/admin/payment-merchant-pools/{id}/enable`
|
||||
- **THEN** 系统 MUST 将目标商户池设置为启用状态
|
||||
- **WHEN** 管理员请求 `POST /api/admin/payment-merchant-pools/{id}/disable`
|
||||
- **THEN** 系统 MUST 将目标商户池设置为停用状态
|
||||
|
||||
### Requirement: 商户池成员排序与路由配置
|
||||
|
||||
商户池管理页 SHALL 支持可验证的成员排序,并按轮询策略配置对应阈值。
|
||||
|
||||
#### Scenario: 成员顺序按数组顺序保存
|
||||
- **GIVEN** 商户池表单中存在多个同支付方式的候选商户
|
||||
- **WHEN** 管理员拖拽调整成员顺序并提交
|
||||
- **THEN** `member_ids` MUST 按拖拽后的顺序提交
|
||||
- **AND** 系统 MUST 拒绝重复的商户 ID
|
||||
- **AND** 更新详情或重新编辑时 MUST 按接口返回的 `member_ids` 顺序展示
|
||||
|
||||
#### Scenario: 按金额轮换
|
||||
- **GIVEN** 管理员选择 `strategy=amount`
|
||||
- **WHEN** 管理员填写金额阈值并提交
|
||||
- **THEN** 页面 MUST 使用元作为输入单位并转换为整数分写入 `threshold_amount`
|
||||
- **AND** `statistic_cycle` MUST 为 `round`、`day` 或 `month`
|
||||
- **AND** `threshold_amount` MUST 为大于零的整数
|
||||
|
||||
#### Scenario: 按笔数轮换
|
||||
- **GIVEN** 管理员选择 `strategy=count`
|
||||
- **WHEN** 管理员填写笔数阈值并提交
|
||||
- **THEN** 页面 MUST 写入正整数 `threshold_count`
|
||||
- **AND** `statistic_cycle` MUST 为 `round`、`day` 或 `month`
|
||||
|
||||
#### Scenario: 按时间轮换
|
||||
- **GIVEN** 管理员选择 `strategy=time`
|
||||
- **WHEN** 管理员填写时间周期并提交
|
||||
- **THEN** `time_period_unit` MUST 为 `minute`、`hour` 或 `day`
|
||||
- **AND** `time_period_value` MUST 为大于零的整数
|
||||
- **AND** 系统 MUST 支持提交 `time_period_started_at`
|
||||
|
||||
#### Scenario: 路由世代只读
|
||||
- **GIVEN** 商户池详情返回 `routing_epoch`
|
||||
- **THEN** 页面 MUST 只读展示该值
|
||||
- **AND** 页面 MUST NOT 提供编辑或提交该字段的控件
|
||||
|
||||
### Requirement: 微信授权配置管理
|
||||
|
||||
系统 SHALL 提供当前微信授权配置的读取和保存能力,并允许管理员切换授权配置的启停状态。
|
||||
|
||||
#### Scenario: 读取当前微信授权配置状态
|
||||
- **WHEN** 管理员请求 `GET /api/admin/wechat-authorizations`
|
||||
- **THEN** 页面 MUST 展示 `enabled`、`miniapp_app_id`、`oa_app_id` 和 `oa_oauth_redirect_url` 等非敏感字段
|
||||
- **AND** 页面 MUST NOT 回填或展示 `miniapp_app_secret`、`oa_app_secret`、`oa_token` 或 `oa_aes_key` 的原始值
|
||||
|
||||
#### Scenario: 保存或切换微信授权启停状态
|
||||
- **WHEN** 管理员向 `PUT /api/admin/wechat-authorizations/current` 保存配置
|
||||
- **THEN** 请求 MUST 支持 `enabled`、`miniapp_app_id`、`miniapp_app_secret`、`oa_app_id`、`oa_app_secret`、`oa_token`、`oa_aes_key` 和 `oa_oauth_redirect_url`
|
||||
- **AND** 保存成功后页面 MUST 使用接口结果刷新启停状态
|
||||
- **AND** 敏感字段 MUST 只在当前编辑会话内存在,并在保存、取消、关闭或卸载后清空
|
||||
|
||||
#### Scenario: 未更换敏感字段时保留后端原值
|
||||
- **GIVEN** 页面只修改 `enabled` 或其他非敏感字段
|
||||
- **WHEN** 管理员提交微信授权配置
|
||||
- **THEN** 请求 MUST NOT 使用脱敏占位值覆盖后端已有敏感字段
|
||||
- **AND** 页面 MUST NOT 在提交后缓存敏感字段值
|
||||
Reference in New Issue
Block a user