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