Files
huang 817d0d6e04
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 46s
更新openspec
2026-03-17 14:22:01 +08:00

599 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 代理充值管理 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 | 线下转账 |