docs: 新增 OpenSpec 提案 add-payment-config-management

包含 proposal.md、design.md、tasks.md 及各模块 spec 文件(微信配置管理、富友支付、代理充值、订单支付、资产充值适配、微信支付留桩)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-16 23:30:39 +08:00
parent 429edf0d19
commit 63ca12393b
10 changed files with 2797 additions and 0 deletions

View File

@@ -0,0 +1,598 @@
# 代理充值管理 API 规范
## ADDED Requirements
---
### Requirement: 创建代理充值订单
**接口描述**:代理或平台账号发起代理余额钱包充值,创建充值订单。
**HTTP 方法与路径**
```
POST /api/admin/agent-recharges
```
**鉴权**
- 需要登录态Bearer Token
- 代理账号只能为自己所属店铺的主钱包wallet_type=main充值
- 平台账号:可指定任意店铺
---
**请求体示例(在线充值 - 微信)**
```json
{
"shop_id": 101,
"amount": 50000,
"payment_method": "wechat"
}
```
**请求体示例(线下充值 - 仅平台)**
```json
{
"shop_id": 101,
"amount": 200000,
"payment_method": "offline"
}
```
**请求字段说明**
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| shop_id | integer | 是 | 目标店铺 ID。代理账号只能填写自己所属店铺 ID |
| amount | integer | 是 | 充值金额单位。范围10000~100000000即 100 元~100 万元) |
| payment_method | string | 是 | 支付方式。可选值:`wechat`(在线微信支付)、`offline`(线下转账,仅平台可用) |
**业务规则**
- `amount` 最小值为 `AgentRechargeMinAmount`10000 分 = 100 元),最大值为 `AgentRechargeMaxAmount`100000000 分 = 100 万元)
- `payment_method=wechat` 时,系统根据当前激活的支付配置自动路由至微信直连或富友通道,并记录 `payment_config_id`客户端发起支付的具体流程本期暂不实现Stub
- `payment_method=offline` 仅平台账号可使用,代理账号调用此方式将返回 `1005 CodeForbidden`
- 订单创建后状态为 `1`(待支付)
- 充值单号前缀为 `ARCH`,全局唯一
---
**成功响应示例**
```json
{
"code": 0,
"msg": "success",
"data": {
"id": 88,
"recharge_no": "ARCH20260316100001",
"shop_id": 101,
"amount": 50000,
"payment_method": "wechat",
"payment_channel": "wechat_direct",
"payment_config_id": 3,
"status": 1,
"created_at": "2026-03-16T10:00:00+08:00"
},
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
**响应字段说明**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | integer | 充值记录 ID |
| recharge_no | string | 充值单号ARCH 前缀) |
| shop_id | integer | 店铺 ID |
| amount | integer | 充值金额(分) |
| payment_method | string | 支付方式 |
| payment_channel | string | 实际支付通道wechat_direct / fuyou / offline |
| payment_config_id | integer\|null | 关联的支付配置 ID线下充值为 null |
| status | integer | 订单状态1=待支付2=已完成3=已取消 |
| created_at | string | 创建时间RFC3339 |
---
**错误响应示例**
金额超出范围:
```json
{
"code": 1001,
"msg": "充值金额超出允许范围100元~100万元",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
代理账号使用线下充值:
```json
{
"code": 1005,
"msg": "只有平台账号可以使用线下充值",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
钱包不存在:
```json
{
"code": 1053,
"msg": "钱包不存在",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
无可用支付配置:
```json
{
"code": 1175,
"msg": "当前无可用的支付配置,请联系管理员",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
越权访问(代理操作他人店铺):
```json
{
"code": 1005,
"msg": "无权限操作该资源或资源不存在",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
### Requirement: 线下充值确认
**接口描述**:平台账号确认线下转账已到账,完成充值并为代理钱包增加余额。
**HTTP 方法与路径**
```
POST /api/admin/agent-recharges/:id/offline-pay
```
**鉴权**
- 需要登录态Bearer Token
- 仅平台账号可调用,其他账号类型返回 `1005 CodeForbidden`
---
**请求体示例**
```json
{
"operation_password": "Abc123456"
}
```
**请求字段说明**
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| operation_password | string | 是 | 操作密码,用于二次身份验证 |
**路径参数说明**
| 参数名 | 类型 | 说明 |
|--------|------|------|
| id | integer | 充值记录 ID |
**业务规则**
- 操作密码验证失败返回 `1043 CodeInvalidOldPassword`
- 充值记录必须存在且 `payment_method=offline`,否则返回 `1121 CodeRechargeNotFound`
- 充值记录状态必须为 `1`(待支付),否则返回 `1050 CodeInvalidStatus`
- 确认成功后:
1. 充值记录状态更新为 `2`(已完成),记录 `paid_at``completed_at`
2. 代理主钱包余额增加对应金额(使用乐观锁 version 字段防并发)
3. 创建钱包流水记录
4. 记录审计日志(操作人、操作前后数据)
---
**成功响应示例**
```json
{
"code": 0,
"msg": "success",
"data": {
"id": 88,
"recharge_no": "ARCH20260316100001",
"shop_id": 101,
"amount": 200000,
"payment_method": "offline",
"payment_channel": "offline",
"payment_config_id": null,
"status": 2,
"paid_at": "2026-03-16T11:00:00+08:00",
"completed_at": "2026-03-16T11:00:00+08:00",
"created_at": "2026-03-16T10:00:00+08:00"
},
"timestamp": "2026-03-16T11:00:00+08:00"
}
```
---
**错误响应示例**
操作密码错误:
```json
{
"code": 1043,
"msg": "操作密码错误",
"data": null,
"timestamp": "2026-03-16T11:00:00+08:00"
}
```
充值记录不存在:
```json
{
"code": 1121,
"msg": "充值记录不存在",
"data": null,
"timestamp": "2026-03-16T11:00:00+08:00"
}
```
充值记录状态不允许操作:
```json
{
"code": 1050,
"msg": "当前充值记录状态不允许此操作",
"data": null,
"timestamp": "2026-03-16T11:00:00+08:00"
}
```
非平台账号调用:
```json
{
"code": 1005,
"msg": "只有平台账号可以使用线下充值",
"data": null,
"timestamp": "2026-03-16T11:00:00+08:00"
}
```
---
### Requirement: 代理充值查询
#### 接口一:充值记录列表
**接口描述**:分页查询代理充值记录,支持按店铺、状态、日期范围过滤。
**HTTP 方法与路径**
```
GET /api/admin/agent-recharges
```
**鉴权**
- 需要登录态Bearer Token
- 代理账号:只能查看自己所属店铺的充值记录
- 平台账号:可查看所有店铺的充值记录
---
**请求参数Query String**
```
GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=2&start_date=2026-03-01&end_date=2026-03-31
```
**请求参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | integer | 否 | 页码,默认 1 |
| page_size | integer | 否 | 每页条数,默认 20最大 100 |
| shop_id | integer | 否 | 按店铺 ID 过滤(平台账号可用) |
| status | integer | 否 | 按状态过滤1=待支付2=已完成3=已取消 |
| start_date | string | 否 | 创建时间起始日期,格式 `YYYY-MM-DD` |
| end_date | string | 否 | 创建时间截止日期,格式 `YYYY-MM-DD` |
---
**成功响应示例**
```json
{
"code": 0,
"msg": "success",
"data": {
"total": 56,
"page": 1,
"page_size": 20,
"list": [
{
"id": 88,
"recharge_no": "ARCH20260316100001",
"shop_id": 101,
"shop_name": "测试店铺A",
"amount": 50000,
"payment_method": "wechat",
"payment_channel": "wechat_direct",
"payment_config_id": 3,
"status": 2,
"paid_at": "2026-03-16T10:05:00+08:00",
"completed_at": "2026-03-16T10:05:00+08:00",
"created_at": "2026-03-16T10:00:00+08:00"
},
{
"id": 87,
"recharge_no": "ARCH20260315090001",
"shop_id": 101,
"shop_name": "测试店铺A",
"amount": 200000,
"payment_method": "offline",
"payment_channel": "offline",
"payment_config_id": null,
"status": 2,
"paid_at": "2026-03-15T11:00:00+08:00",
"completed_at": "2026-03-15T11:00:00+08:00",
"created_at": "2026-03-15T09:00:00+08:00"
}
]
},
"timestamp": "2026-03-16T12:00:00+08:00"
}
```
**列表项字段说明**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | integer | 充值记录 ID |
| recharge_no | string | 充值单号 |
| shop_id | integer | 店铺 ID |
| shop_name | string | 店铺名称 |
| amount | integer | 充值金额(分) |
| payment_method | string | 支付方式 |
| payment_channel | string | 实际支付通道 |
| payment_config_id | integer\|null | 关联支付配置 ID |
| status | integer | 状态1=待支付2=已完成3=已取消 |
| paid_at | string\|null | 支付时间 |
| completed_at | string\|null | 完成时间 |
| created_at | string | 创建时间 |
---
**错误响应示例**
参数错误:
```json
{
"code": 1001,
"msg": "参数验证失败",
"data": null,
"timestamp": "2026-03-16T12:00:00+08:00"
}
```
---
#### 接口二:充值记录详情
**接口描述**:查询单条充值记录的完整详情。
**HTTP 方法与路径**
```
GET /api/admin/agent-recharges/:id
```
**鉴权**
- 需要登录态Bearer Token
- 代理账号:只能查看自己所属店铺的充值记录,否则返回 `1121 CodeRechargeNotFound`
- 平台账号:可查看任意充值记录
---
**路径参数说明**
| 参数名 | 类型 | 说明 |
|--------|------|------|
| id | integer | 充值记录 ID |
---
**成功响应示例**
```json
{
"code": 0,
"msg": "success",
"data": {
"id": 88,
"recharge_no": "ARCH20260316100001",
"shop_id": 101,
"shop_name": "测试店铺A",
"agent_wallet_id": 55,
"amount": 50000,
"payment_method": "wechat",
"payment_channel": "wechat_direct",
"payment_config_id": 3,
"payment_transaction_id": "wx_txn_20260316_abc123",
"status": 2,
"paid_at": "2026-03-16T10:05:00+08:00",
"completed_at": "2026-03-16T10:05:00+08:00",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:05:00+08:00"
},
"timestamp": "2026-03-16T12:00:00+08:00"
}
```
**详情字段说明**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | integer | 充值记录 ID |
| recharge_no | string | 充值单号 |
| shop_id | integer | 店铺 ID |
| shop_name | string | 店铺名称 |
| agent_wallet_id | integer | 代理钱包 ID |
| amount | integer | 充值金额(分) |
| payment_method | string | 支付方式 |
| payment_channel | string | 实际支付通道 |
| payment_config_id | integer\|null | 关联支付配置 ID |
| payment_transaction_id | string\|null | 第三方支付流水号 |
| status | integer | 状态1=待支付2=已完成3=已取消 |
| paid_at | string\|null | 支付时间 |
| completed_at | string\|null | 完成时间 |
| created_at | string | 创建时间 |
| updated_at | string | 最后更新时间 |
---
**错误响应示例**
充值记录不存在或无权限:
```json
{
"code": 1121,
"msg": "充值记录不存在",
"data": null,
"timestamp": "2026-03-16T12:00:00+08:00"
}
```
---
### Requirement: 代理充值回调处理
**接口描述**:接收第三方支付平台(微信直连 / 富友)的异步支付结果通知,完成充值订单状态更新和钱包余额增加。
**HTTP 方法与路径**
回调地址由支付配置中的 `notify_url` 字段决定,格式示例:
```
POST /api/payment/callback/agent-recharge/{payment_channel}
```
其中 `payment_channel``wechat_direct``fuyou`
**鉴权**
- 无需登录态
- 通过签名验证确认请求来源合法性
---
**处理流程**
```
1. 接收回调请求
2. 根据 payment_channel 确定验签方式
3. 通过 recharge_no充值单号查找充值记录
4. 幂等性检查:若记录状态已为 2已完成直接返回成功
5. 使用充值记录中的 payment_config_id 查找对应支付配置
6. 使用支付配置的密钥验证签名
7. 验签通过后,在事务中执行:
a. 更新充值记录状态为 2已完成记录 payment_transaction_id、paid_at、completed_at
b. 代理主钱包余额增加充值金额(乐观锁 version 字段防并发)
c. 创建钱包流水记录(类型:充值入账)
8. 返回支付平台要求的成功响应格式
```
**幂等性保障**
- 使用充值记录状态作为幂等判断依据(状态条件更新:`WHERE status = 1`
- `RowsAffected == 0` 时说明已被处理,直接返回成功,不重复入账
**签名验证**
- 根据充值记录的 `payment_config_id` 查找对应支付配置
- 使用该配置的密钥(`api_key` / `app_secret`)按对应通道规则验签
- 验签失败时记录错误日志,返回失败响应(不更新订单状态)
**回调响应**
- 微信直连:返回 `{"code": "SUCCESS", "message": "成功"}`
- 富友:按富友协议返回对应成功标识
- 处理失败时返回对应通道的失败标识,触发第三方平台重试
**异常处理**
- 充值记录不存在:记录警告日志,返回失败(触发重试,等待数据一致)
- 签名验证失败:记录错误日志(含完整请求体),返回失败
- 钱包余额更新失败(乐观锁冲突):最多重试 3 次,仍失败则记录告警日志并返回失败
---
### Requirement: 权限控制
**账号类型与操作权限矩阵**
| 操作 | 平台账号 | 代理账号 | 企业账号 |
|------|----------|----------|----------|
| 创建充值订单(在线) | ✅ 任意店铺 | ✅ 仅自己店铺 | ❌ |
| 创建充值订单(线下) | ✅ 任意店铺 | ❌ | ❌ |
| 线下充值确认 | ✅ | ❌ | ❌ |
| 查询充值列表 | ✅ 全部 | ✅ 仅自己店铺 | ❌ |
| 查询充值详情 | ✅ 全部 | ✅ 仅自己店铺 | ❌ |
**越权防护规则**
1. **路由层**:企业账号访问代理充值相关接口,统一返回 `1005 CodeForbidden`
2. **Service 层**
- 代理账号创建充值时,验证 `shop_id` 必须属于自己所属店铺
- 代理账号查询详情时,验证充值记录的 `shop_id` 必须属于自己所属店铺
3. **越权统一响应**:不区分"不存在"和"无权限",统一返回 `1005` 或对应资源不存在错误,防止信息泄露
**线下充值操作密码**
- 平台账号执行线下充值确认时,必须提供操作密码
- 操作密码验证失败返回 `1043 CodeInvalidOldPassword`
- 操作密码不在响应中返回,不记录到日志明文中
---
## 数据模型补充说明
**tb_agent_recharge_record 新增字段**
| 字段名 | 类型 | 可空 | 说明 |
|--------|------|------|------|
| payment_config_id | bigint | 是 | 关联支付配置 ID线下充值为 NULL在线充值记录实际使用的支付配置 |
**充值状态枚举**
| 值 | 含义 |
|----|------|
| 1 | 待支付(订单已创建,等待支付) |
| 2 | 已完成(支付成功,余额已到账) |
| 3 | 已取消(超时未支付或主动取消) |
**支付方式枚举**
| 值 | 含义 |
|----|------|
| wechat | 微信在线支付(自动路由至微信直连或富友) |
| offline | 线下转账(仅平台账号可用) |
**支付通道枚举**
| 值 | 含义 |
|----|------|
| wechat_direct | 微信直连通道 |
| fuyou | 富友通道 |
| offline | 线下转账 |