# 代理充值管理 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 | 线下转账 |
