Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
228 lines
7.6 KiB
Markdown
228 lines
7.6 KiB
Markdown
# 代理预充值功能
|
||
|
||
## 功能概述
|
||
|
||
代理商(店铺)余额钱包的在线充值系统,支持微信在线支付和线下转账两种充值方式,具备完整的 Service/Handler/回调处理链路。充值仅针对余额钱包(`wallet_type=main`),佣金钱包通过分佣自动入账。
|
||
|
||
### 背景与动机
|
||
|
||
原有 `tb_agent_recharge_record` 表和 Store 层骨架已存在,但缺少 Service 层和 Handler 层,无法通过 API 发起充值。本次补全完整实现,并集成至支付配置管理体系(按 `payment_config_id` 动态路由至微信直连或富友通道)。
|
||
|
||
## 核心流程
|
||
|
||
### 在线充值流程(微信)
|
||
|
||
```
|
||
代理/平台 → POST /api/admin/agent-recharges
|
||
│
|
||
├─ 验证权限:代理只能充自己店铺,平台可指定任意店铺
|
||
├─ 验证金额范围(100 元~100 万元)
|
||
├─ 查找目标店铺的 main 钱包
|
||
├─ 查询 active 支付配置 → 无配置则拒绝(返回 1175)
|
||
├─ 记录 payment_config_id
|
||
└─ 创建充值订单(status=1 待支付)
|
||
└─ 返回订单信息(客户端支付发起【留桩】)
|
||
|
||
支付成功 → POST /api/callback/wechat-pay 或 /api/callback/fuiou-pay
|
||
│
|
||
├─ 按订单号前缀 "ARCH" 识别为代理充值
|
||
├─ 查询充值记录,取 payment_config_id
|
||
├─ 按配置验签
|
||
└─ agentRechargeService.HandlePaymentCallback()
|
||
├─ 幂等检查(WHERE status = 1)
|
||
├─ 更新充值记录状态 → 2(已完成)
|
||
├─ 代理主钱包余额增加(乐观锁防并发)
|
||
└─ 创建钱包流水记录
|
||
```
|
||
|
||
### 线下充值流程(仅平台)
|
||
|
||
```
|
||
平台 → POST /api/admin/agent-recharges
|
||
└─ payment_method = "offline"
|
||
└─ 创建充值订单(status=1 待支付)
|
||
|
||
平台确认 → POST /api/admin/agent-recharges/:id/offline-pay
|
||
├─ 验证操作密码(二次鉴权)
|
||
└─ 事务内:
|
||
├─ 更新充值记录状态 → 2(已完成)
|
||
├─ 记录 paid_at、completed_at
|
||
├─ 代理主钱包余额增加(乐观锁 version 字段)
|
||
├─ 创建钱包流水记录
|
||
└─ 记录审计日志
|
||
```
|
||
|
||
## 接口说明
|
||
|
||
### 基础路径
|
||
|
||
`/api/admin/agent-recharges`
|
||
|
||
**权限要求**:企业账号(`user_type=4`)在路由层被拦截,返回 `1005`。
|
||
|
||
### 接口列表
|
||
|
||
| 方法 | 路径 | 说明 | 权限 |
|
||
|------|------|------|------|
|
||
| POST | `/api/admin/agent-recharges` | 创建充值订单 | 代理(自己店铺)/ 平台(任意店铺)|
|
||
| GET | `/api/admin/agent-recharges` | 查询充值记录列表 | 代理(自己店铺)/ 平台(全部)|
|
||
| GET | `/api/admin/agent-recharges/:id` | 查询充值记录详情 | 代理(自己店铺)/ 平台(全部)|
|
||
| POST | `/api/admin/agent-recharges/:id/offline-pay` | 确认线下充值到账 | 仅平台 |
|
||
|
||
### 创建充值订单
|
||
|
||
**请求体示例(在线充值)**
|
||
|
||
```json
|
||
{
|
||
"shop_id": 101,
|
||
"amount": 50000,
|
||
"payment_method": "wechat"
|
||
}
|
||
```
|
||
|
||
**请求体示例(线下充值)**
|
||
|
||
```json
|
||
{
|
||
"shop_id": 101,
|
||
"amount": 200000,
|
||
"payment_method": "offline"
|
||
}
|
||
```
|
||
|
||
**请求字段**
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| shop_id | integer | 是 | 目标店铺 ID(代理只能填自己所属店铺)|
|
||
| amount | integer | 是 | 充值金额(单位:分),范围 10000~100000000 |
|
||
| payment_method | string | 是 | `wechat`(在线)/ `offline`(线下,仅平台)|
|
||
|
||
**成功响应**
|
||
|
||
```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"
|
||
}
|
||
```
|
||
|
||
### 线下充值确认
|
||
|
||
**请求体**
|
||
|
||
```json
|
||
{
|
||
"operation_password": "Abc123456"
|
||
}
|
||
```
|
||
|
||
操作密码验证通过后,事务内同步完成:余额到账 + 钱包流水 + 审计日志。
|
||
|
||
## 权限控制矩阵
|
||
|
||
| 操作 | 平台账号 | 代理账号 | 企业账号 |
|
||
|------|----------|----------|----------|
|
||
| 创建充值(在线) | ✅ 任意店铺 | ✅ 仅自己店铺 | ❌ |
|
||
| 创建充值(线下) | ✅ 任意店铺 | ❌ | ❌ |
|
||
| 线下充值确认 | ✅ | ❌ | ❌ |
|
||
| 查询充值列表 | ✅ 全部 | ✅ 仅自己店铺 | ❌ |
|
||
| 查询充值详情 | ✅ 全部 | ✅ 仅自己店铺 | ❌ |
|
||
|
||
**越权统一响应**:代理访问他人店铺充值记录时,返回 `1121 CodeRechargeNotFound`(不区分不存在与无权限)
|
||
|
||
## 数据模型
|
||
|
||
### `tb_agent_recharge_record` 新增字段
|
||
|
||
| 字段 | 类型 | 可空 | 说明 |
|
||
|------|------|------|------|
|
||
| `payment_config_id` | bigint | 是 | 关联支付配置 ID(线下充值为 NULL,在线充值记录实际使用的配置)|
|
||
|
||
### 充值订单状态枚举
|
||
|
||
| 值 | 含义 |
|
||
|----|------|
|
||
| 1 | 待支付 |
|
||
| 2 | 已完成 |
|
||
| 3 | 已取消 |
|
||
|
||
### 支付方式与通道
|
||
|
||
| payment_method | payment_channel | 说明 |
|
||
|---------------|----------------|------|
|
||
| wechat | wechat_direct | 微信直连通道(provider_type=wechat)|
|
||
| wechat | fuyou | 富友通道(provider_type=fuiou)|
|
||
| offline | offline | 线下转账 |
|
||
|
||
> 前端统一显示"微信支付",后端根据生效配置的 `provider_type` 自动路由,前端不感知具体通道。
|
||
|
||
### 充值单号规则
|
||
|
||
前缀 `ARCH`,全局唯一,用于回调时识别订单类型。
|
||
|
||
## 幂等性设计
|
||
|
||
- 回调处理使用状态条件更新:`WHERE status = 1`
|
||
- `RowsAffected == 0` 时说明已被处理,直接返回成功,不重复入账
|
||
- 钱包余额更新使用乐观锁(`version` 字段),并发冲突时最多重试 3 次
|
||
|
||
## 审计日志
|
||
|
||
线下充值确认(`OfflinePay`)操作记录审计日志,字段包括:
|
||
|
||
| 字段 | 值 |
|
||
|------|-----|
|
||
| `operator_id` | 当前操作人 ID |
|
||
| `operation_type` | `offline_recharge` |
|
||
| `operation_desc` | `确认代理充值到账:充值单号 {recharge_no},金额 {amount} 分` |
|
||
| `before_data` | 操作前余额和充值记录状态 |
|
||
| `after_data` | 操作后余额和充值记录状态 |
|
||
|
||
## 涉及文件
|
||
|
||
### 新增文件
|
||
|
||
| 层级 | 文件 | 说明 |
|
||
|------|------|------|
|
||
| DTO | `internal/model/dto/agent_recharge_dto.go` | 请求/响应 DTO |
|
||
| Service | `internal/service/agent_recharge/service.go` | 充值业务逻辑 |
|
||
| Handler | `internal/handler/admin/agent_recharge.go` | 4 个 Handler 方法 |
|
||
| 路由 | `internal/routes/agent_recharge.go` | 路由注册 |
|
||
|
||
### 修改文件
|
||
|
||
| 文件 | 变更说明 |
|
||
|------|---------|
|
||
| `internal/model/agent_wallet.go` | 新增 `PaymentConfigID *uint` 字段 |
|
||
| `internal/handler/callback/payment.go` | 新增 "ARCH" 前缀分发 → agentRechargeService.HandlePaymentCallback() |
|
||
| `internal/bootstrap/` 系列 | 注册 AgentRechargeService、AgentRechargeHandler |
|
||
| `cmd/api/docs.go` / `cmd/gendocs/main.go` | 注册 AgentRechargeHandler |
|
||
| `migrations/000081_add_payment_config_id_to_agent_recharge.up.sql` | tb_agent_recharge_record 新增 payment_config_id 列 |
|
||
|
||
## 常量定义
|
||
|
||
```go
|
||
// pkg/constants/wallet.go
|
||
AgentRechargeOrderPrefix = "ARCH" // 充值单号前缀
|
||
AgentRechargeMinAmount = 10000 // 最小充值:100 元(单位:分)
|
||
AgentRechargeMaxAmount = 100000000 // 最大充值:100 万元(单位:分)
|
||
```
|
||
|
||
## 已知限制(留桩)
|
||
|
||
**客户端支付发起未实现**:在线充值(`payment_method=wechat`)创建订单成功后,前端获取支付参数的接口本次未实现。充值回调处理已完整实现——等支付发起改造完成后,完整的充值支付闭环即可联通。
|