代理在线充值
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m6s

This commit is contained in:
2026-07-27 16:02:55 +08:00
parent a2166c8011
commit cbf909b878
42 changed files with 3121 additions and 195 deletions

View File

@@ -0,0 +1,327 @@
# 代理钱包扫码充值实现与接口对接说明
> 面向:产品、前端和联调人员
> 范围:代理微信/支付宝桌面扫码自充、平台线下代充来源区分、支付状态轮询
> 接口细节:以 [`docs/admin-openapi.yaml`](../../admin-openapi.yaml) 为准,本文说明前端调用顺序、字段口径和页面状态。
## 一、先看这几个关键结论
1. **同一个创建接口有两条业务路径**:代理账号使用 `wechat|alipay` 在线自充;平台或超级管理员使用 `offline` 线下代充。
2. **前端必须使用 `recharge_source` 区分来源**`platform_offline` 是平台线下代充,`agent_online` 是代理在线自充;不要根据账号名称、备注或审批字段猜测。
3. **代理不能选择充值店铺**:在线充值的店铺和主钱包由登录上下文确定,请求不得发送 `shop_id`、支付凭证或备注。
4. **二维码由前端渲染**:后端返回支付渠道原始 `qr_content`,不返回二维码图片。
5. **网络重试不能创建新请求 ID**:同一次提交重试复用原 `request_id`;用户主动发起下一笔充值时生成新的 `request_id`
6. **支付和钱包到账是两个阶段**`status=2` 表示第三方已收款、钱包入账处理中;只有 `status=3` 才表示钱包到账完成。
7. **页面轮询只调用本地状态接口**:前端不得直接调用微信、支付宝查单,也不要反复调用创建接口查询状态。
8. **在线金额单位为分**:最低 `10000`100 元),最高 `100000000`100 万元);不要提交浮点元金额。
## 二、本次需求怎么实现
| 能力 | 后端实现 | 前端要做什么 |
| --- | --- | --- |
| 可用支付方式 | 根据当前有效支付配置返回真正可用的 `wechat``alipay` | 打开充值弹窗时先查询;只展示返回数组中的方式 |
| 代理在线自充 | 从登录账号取得当前店铺和主钱包,创建充值单、支付单并向第三方预下单 | 只提交金额、支付方式和请求 ID`qr_content` 渲染二维码 |
| 平台线下代充 | 保留既有目标店铺、凭证和企业微信审批流程 | 平台页面继续提交 `offline` 请求并只读展示审批状态 |
| 充值来源 | 根据受控创建方式返回稳定来源枚举 | 列表、详情和支付状态统一展示 `recharge_source_name` |
| 支付确认 | 微信/支付宝回调确认第三方收款事实 | 前端无需调用回调接口 |
| 钱包入账 | Outbox/Worker 幂等增加代理主钱包余额 | `status=2` 展示“入账处理中”,`status=3` 展示“已到账” |
| 回调丢失恢复 | Worker 使用原支付单号主动查单并收敛状态 | 页面继续轮询本地状态,无需提供“重新查单”按钮 |
## 三、接口对接表
| 页面动作 | 接口 | 前端调用说明 |
| --- | --- | --- |
| 打开代理在线充值弹窗 | `GET /api/admin/agent-recharges/payment-methods` | 仅代理账号调用;返回支付方式和金额上下限 |
| 创建在线扫码充值 | `POST /api/admin/agent-recharges` | 代理只传 `amount + payment_method + request_id` |
| 创建平台线下代充 | `POST /api/admin/agent-recharges` | 平台/超级管理员传 `shop_id + amount + payment_method=offline + payment_voucher_key + remark` |
| 查询充值列表 | `GET /api/admin/agent-recharges` | 支持 `recharge_source=platform_offline|agent_online` 筛选 |
| 查询充值详情 | `GET /api/admin/agent-recharges/:id` | 使用返回的来源、充值状态和审批状态展示 |
| 轮询在线支付状态 | `GET /api/admin/agent-recharges/:id/payment-status` | 仅在线单使用;页面可见时每 3 秒调用,终态停止 |
| 支付回调 | 微信/支付宝既有回调地址 | 只由支付渠道调用,前端禁止调用 |
所有接口都使用 Bearer Token成功响应统一为
```json
{
"code": 0,
"msg": "success",
"data": {},
"timestamp": "2026-07-27T16:00:00+08:00"
}
```
前端应同时检查 HTTP 状态和响应体 `code`,失败时优先展示后端返回的 `msg`
## 四、代理在线扫码充值调用流程
### 4.1 查询可用支付方式
```http
GET /api/admin/agent-recharges/payment-methods
Authorization: Bearer {token}
```
响应示例:
```json
{
"code": 0,
"msg": "success",
"data": {
"methods": ["wechat", "alipay"],
"min_amount": 10000,
"max_amount": 100000000
},
"timestamp": "2026-07-27T16:00:00+08:00"
}
```
前端处理规则:
- `methods=[]`:隐藏或禁用提交按钮,提示“当前暂无可用在线支付方式”。
- 只显示返回的支付方式,不自行补充微信或支付宝。
- 金额校验直接使用返回的 `min_amount``max_amount`,提交值仍为整数分。
- 平台、超级管理员和企业账号不调用此接口。
### 4.2 创建在线充值单
```http
POST /api/admin/agent-recharges
Authorization: Bearer {token}
Content-Type: application/json
```
微信请求示例:
```json
{
"amount": 10000,
"payment_method": "wechat",
"request_id": "recharge-20260727-8f73d95d"
}
```
支付宝只需将 `payment_method` 改为 `alipay`
在线请求禁止发送:
- `shop_id`
- `payment_voucher_key`
- `remark`
- 商户号、应用 ID、密钥、支付配置 ID
成功响应示例:
```json
{
"code": 0,
"msg": "success",
"data": {
"recharge_id": 101,
"recharge_no": "ARCH20260727160000123456",
"payment_no": "PAY20260727160000987654",
"payment_method": "wechat",
"recharge_source": "agent_online",
"recharge_source_name": "代理在线自充",
"amount": 10000,
"qr_content": "weixin://wxpay/bizpayurl?pr=...",
"status": 1,
"status_name": "待支付"
},
"timestamp": "2026-07-27T16:00:00+08:00"
}
```
前端处理规则:
1.`qr_content` 原样交给二维码组件,不解析、不改写、不拼接。
2. 二维码页面保存 `recharge_id`,后续轮询使用该 ID不使用 `payment_no` 查询。
3. 同一次请求超时或网络断开时,重试必须复用原 `request_id`;后端会返回原充值单和原二维码内容。
4. 用户关闭弹窗后重新点击“立即充值”,视为新一笔主动提交,必须生成新的 `request_id`
5. 相同 `request_id` 改变金额或支付方式会返回冲突,前端不得自动覆盖原请求。
### 4.3 轮询支付和到账状态
```http
GET /api/admin/agent-recharges/101/payment-status
Authorization: Bearer {token}
```
响应示例:
```json
{
"code": 0,
"msg": "success",
"data": {
"recharge_id": 101,
"recharge_no": "ARCH20260727160000123456",
"recharge_source": "agent_online",
"recharge_source_name": "代理在线自充",
"status": 2,
"status_name": "已支付",
"payment_status": 1,
"payment_status_name": "已支付",
"paid_at": "2026-07-27 16:01:20",
"completed_at": null
},
"timestamp": "2026-07-27T16:01:21+08:00"
}
```
页面建议每 3 秒轮询一次,仅在二维码弹窗可见时执行。按以下状态展示:
| `status` | 含义 | 页面展示 | 是否继续轮询 |
| --- | --- | --- | --- |
| `1` | 待支付 | 等待用户扫码付款 | 是 |
| `2` | 已支付 | 支付成功,钱包入账处理中 | 是 |
| `3` | 已完成 | 充值成功,钱包已到账 | 否 |
| `4` | 已关闭 | 订单已关闭,请重新发起充值 | 否 |
| `5` | 已退款 | 已退款 | 否 |
| `6` | 已驳回 | 已驳回 | 否 |
`payment_status` 的值为:`0=待支付``1=已支付``2=已失败``3=已退款`。页面主流程优先根据充值 `status` 判断;`payment_status` 用于补充支付阶段文案。
进入 `status=3` 后,前端刷新钱包余额和充值列表。不要在 `status=2` 时提前增加页面余额。
## 五、平台线下代充调用流程
平台或超级管理员继续调用同一个创建接口:
```json
{
"shop_id": 20,
"amount": 50000,
"payment_method": "offline",
"payment_voucher_key": ["recharge-voucher/20260727/abc.png"],
"remark": "银行转账"
}
```
线下代充响应和后续列表/详情会返回:
```json
{
"recharge_source": "platform_offline",
"recharge_source_name": "平台线下代充",
"approval_provider": "wecom",
"approval_status": 1,
"approval_status_name": "审批中"
}
```
前端处理规则:
- 代理账号不得展示线下代充表单。
- 平台线下单展示凭证、备注和企微审批状态,不展示扫码二维码。
- 存在 `approval_instance_id``approval_provider=wecom` 时,只读展示审批进度,不显示旧的本地确认/驳回按钮。
- 在线充值的 `approval_instance_id``null`,不要显示企微审批区域。
- `/:id/payment-status` 是在线支付状态接口,线下代充详情使用 `GET /:id`,不要轮询支付状态接口。
## 六、列表和详情对接
列表请求示例:
```http
GET /api/admin/agent-recharges?page=1&page_size=20&recharge_source=agent_online&status=1
```
可用筛选参数:
| 参数 | 说明 |
| --- | --- |
| `page` | 页码,默认 1 |
| `page_size` | 每页条数,默认 20最大 100 |
| `shop_id` | 店铺 ID按既有数据权限生效 |
| `status` | 充值状态 `16` |
| `recharge_source` | `platform_offline``agent_online` |
| `start_date` | 开始日期,格式 `YYYY-MM-DD` |
| `end_date` | 结束日期,格式 `YYYY-MM-DD` |
列表成功响应的分页数据位于:
```json
{
"data": {
"items": [],
"total": 0,
"page": 1,
"size": 20
}
}
```
建议列表至少展示:充值单号、店铺、金额、`recharge_source_name`、支付方式、`status_name`、提交人和创建时间。
详情接口:
```http
GET /api/admin/agent-recharges/{id}
```
页面分支必须以 `recharge_source` 为准:
- `agent_online`:展示支付方式、第三方流水号、支付时间、完成时间;隐藏凭证和企微审批操作。
- `platform_offline`:展示支付凭证、备注、提交人和企微审批状态;隐藏二维码和在线轮询区域。
## 七、充值来源与状态字段
### 7.1 充值来源
| 字段值 | 中文名 | 创建人和路径 |
| --- | --- | --- |
| `platform_offline` | 平台线下代充 | 平台/超级管理员提交 `payment_method=offline` |
| `agent_online` | 代理在线自充 | 代理提交 `payment_method=wechat|alipay` |
前端展示中文时优先使用后端返回的 `recharge_source_name`,业务分支使用稳定枚举 `recharge_source`
### 7.2 充值状态
| 状态 | 名称 | 说明 |
| --- | --- | --- |
| `1` | 待支付 | 在线单等待扫码;线下单等待审批 |
| `2` | 已支付 | 第三方收款已确认,钱包可能仍在入账 |
| `3` | 已完成 | 钱包余额和唯一流水已提交 |
| `4` | 已关闭 | 在线支付关闭或预下单失败 |
| `5` | 已退款 | 充值已退款 |
| `6` | 已驳回 | 线下代充审批驳回 |
## 八、权限和错误处理
| 操作 | 平台/超级管理员 | 代理账号 | 企业账号 |
| --- | --- | --- | --- |
| 创建在线扫码充值 | 禁止 | 仅当前店铺主钱包 | 禁止 |
| 创建线下代充 | 允许 | 禁止 | 禁止 |
| 查询在线支付方式 | 禁止 | 允许 | 禁止 |
| 查询列表/详情 | 按既有数据范围 | 按店铺层级数据范围 | 禁止 |
| 查询在线支付状态 | 按既有数据范围 | 按店铺层级数据范围 | 禁止 |
前端不要根据错误文案判断资源是否存在。无权限或资源不可见时,后端可能统一返回“无权限操作该资源或资源不存在”。常见处理:
- `400`:保留表单输入并展示参数错误,不自动重试。
- `401`:按现有登录失效流程处理。
- `403`:关闭无权限操作入口,不尝试换账号类型绕过。
- `409` 或业务冲突码:检查是否错误复用了 `request_id`
- `500`/超时:在线创建可使用原 `request_id` 重试一次;不要生成新 ID否则可能创建第二张充值单。
## 九、前端本期最容易漏掉的工作
- 在线充值表单不要复用线下表单对象后整体序列化,避免携带 `shop_id`、凭证或备注。
- 支付方式必须来自 `/payment-methods`,不要写死微信和支付宝都可用。
- 金额显示元、提交分;禁止使用浮点数直接乘除后提交。
- 二维码内容原样渲染,不记录到埋点、错误日志或浏览器持久化缓存。
- 同一次网络重试复用 `request_id`,新一笔主动充值换新 ID。
- `status=2` 只表示已收款、入账中,不能提前刷新成“钱包已到账”。
- 列表、详情的页面分支使用 `recharge_source`,不要用 `payment_method` 或审批字段猜来源。
- 线下单不调用支付状态接口;在线单不展示企微审批按钮。
- 页面隐藏或浏览器切到后台时停止轮询,恢复可见后再继续。
- 进入终态后停止轮询并刷新钱包余额、充值列表。
## 十、联调和验收边界
- OpenAPI 已包含 `/payment-methods`、在线创建、列表来源筛选、详情来源字段和 `/payment-status`
- 后端已完成微信 Native、支付宝 PreCreate、支付确认、异步钱包入账和回调丢失恢复的代码装配。
- 当前交付已通过 `go build ./...``go vet ./...` 和 OpenSpec 严格校验。
- 按需求方要求,本次未启动 API/Worker未连接 PostgreSQL/Redis未调用真实支付渠道未发送真实回调也未运行自动化测试。
- 前端联调环境需具备完整支付配置;至少确认微信和支付宝创建响应分别返回非空 `qr_content`
- 本需求不支持 JSAPI、H5、WAP、同设备拉起支付、主动取消、支付方式切换、二维码图片接口或在线退款。