Files
junhong_cmp_fiber/docs/7月迭代/独立方案/新增需求/05-代理钱包扫码充值.md
2026-07-17 16:39:41 +08:00

447 lines
15 KiB
Markdown
Raw 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.
# 新增需求 05代理钱包扫码充值
> 状态:已合并至标准评审稿,本文保留为实施明细。
> 评审主文档:`../../7月迭代技术方案-标准评审稿.md`
> 范围:代理在后台使用微信或支付宝扫码充值代理主钱包。
## 一、已确认决策
1. 代理在后台为自己的店铺主钱包充值。
2. 支付方式支持微信扫码和支付宝扫码。
3. 单笔最低充值金额为 100 元,即 `10000` 分。
4. 代理在线充值不进入企业微信审批,支付成功后直接幂等增加代理主钱包余额。
5. 平台员工线下代充值仍按 `02-企业微信审批接入.md` 走企微审批,与本方案隔离。
6. 后端返回支付二维码内容,前端使用现有二维码组件渲染,不由后端生成或保存二维码图片文件。
7. 微信使用 Native 支付,支付宝使用 `alipay.trade.precreate` 当面付预创建。
8. 支付回调、钱包入账、钱包流水和审计必须幂等,重复回调不能重复加钱。
9. 当前代理充值复杂写逻辑迁移到 Application/Domain旧 Service 不再保留另一套在线入账逻辑。
## 二、现状与缺口
现有代码已经具备代理充值记录、微信/富友回调和钱包入账骨架,但还不能满足本需求:
- `CreateAgentRechargeRequest` 只允许 `wechat/offline`,没有支付宝。
- `AgentRechargeMinAmount=1`,当前最低金额是 1 分。
- 创建接口只生成充值记录,没有创建统一 `tb_payment` 支付单,也没有真正向支付渠道预下单获取二维码。
- 微信支付仅保存当前生效支付配置,响应中没有 `code_url`
- 支付宝回调只分发套餐订单和客户资产钱包充值,没有代理充值订单类型。
- `HandlePaymentCallback` 在旧 Service 中直接更新充值单、钱包和流水,业务状态和幂等边界没有收口为独立用例。
- 前端技术方案只写了“保留收款码和支付状态页面”,没有支付方式选择、最低金额、二维码过期和支付成功状态细节。
现有评审中“代理在线充值不审批”的结论保持不变,本次补齐的是扫码支付和最低金额的完整技术方案。
## 三、业务流程
```mermaid
sequenceDiagram
actor Agent as 代理用户
participant Web as 代理后台
participant API as Recharge Application
participant DB as PostgreSQL
participant Pay as 微信/支付宝
participant Callback as 支付回调
participant Wallet as AgentWallet Domain
Agent->>Web: 输入金额并选择支付方式
Web->>API: 创建扫码充值单
API->>DB: 创建充值单和支付单
API->>Pay: Native/PreCreate 预下单
Pay-->>API: 二维码内容
API-->>Web: 返回二维码和过期时间
Web-->>Agent: 展示二维码并轮询支付状态
Agent->>Pay: 扫码完成支付
Pay->>Callback: 异步支付通知
Callback->>API: ConfirmAgentRechargePayment
API->>DB: 校验支付单、金额和状态
API->>Wallet: CreditRecharge
Wallet->>DB: 同事务增加余额、写流水、完成充值单
API-->>Pay: 返回成功
Web->>API: 查询到已完成
Web-->>Agent: 展示最新钱包余额
```
支付成功不创建 `wecom_approval_instance`,充值详情固定返回:
```json
{
"approval_source": "none",
"approval": null
}
```
## 四、DDD 设计
### 4.1 目录
```text
internal/
├── domain/agentwallet/
│ ├── wallet.go 代理主钱包聚合及余额不变量
│ ├── recharge.go 充值单状态转换
│ ├── events.go 充值到账领域事件
│ └── repository.go 钱包与充值聚合仓储接口
├── application/agentrecharge/
│ ├── create_qr_recharge.go 创建充值单和扫码支付
│ ├── confirm_payment.go 支付回调确认并入账
│ ├── close_expired.go 关闭过期未支付充值单
│ └── get_payment_status.go 轻量支付状态查询
├── infrastructure/adapter/payment/
│ ├── wechat_native.go 微信 Native 预下单
│ └── alipay_precreate.go 支付宝当面付预创建
├── infrastructure/persistence/
│ └── agent_recharge_repository.go
└── query/agentrecharge/
├── list.go
└── detail.go
```
### 4.2 领域状态
继续使用现有充值状态,不为在线充值增加审批状态:
| 状态 | 含义 | 在线充值使用方式 |
|---:|---|---|
| 1 | 待支付 | 已创建二维码,等待扫码 |
| 2 | 已支付 | 支付已确认,钱包入账事务处理中 |
| 3 | 已完成 | 钱包余额和流水已完成 |
| 4 | 已关闭 | 超时未支付或主动取消 |
| 5 | 已退款 | 历史或后续人工退款结果 |
| 6 | 已驳回 | 仅旧数据或线下审批兼容,在线充值不产生 |
`status=2` 是短暂业务处理状态。支付回调事务正常完成时直接推进到 `3`;若钱包入账发生可恢复错误,则保持 `2` 并由可靠任务继续处理。
### 4.3 钱包入账不变量
`AgentWallet.CreditRecharge` 必须保证:
- 只允许向目标店铺的 `wallet_type=main` 钱包入账。
- 入账金额必须等于充值单金额和支付单金额。
- 同一充值单只能生成一条成功钱包流水。
- 钱包余额、`version`、充值单状态和钱包流水在同一数据库事务更新。
- 钱包乐观锁冲突时由 Application 重新加载后有限重试,不能重复创建流水。
- 支付渠道成功不等于业务已经完成;只有钱包事务成功后充值单才变为已完成。
## 五、支付预下单
### 5.1 统一支付单
代理在线充值必须创建 `tb_payment` 记录,不能只依赖 `ARCH` 单号前缀判断支付渠道。
新增支付业务类型:
```go
const PaymentOrderTypeAgentRecharge = "agent_recharge"
```
充值单和支付单在同一事务创建:
```text
tb_agent_recharge_record.status = 1
tb_payment.status = 0
tb_payment.order_type = agent_recharge
tb_payment.order_id = recharge_id
tb_payment.payment_method = wechat / alipay
tb_payment.amount = recharge_amount
tb_payment.payment_config_id = 创建时使用的配置ID
```
先完成本地事务,再调用第三方预下单。预下单失败时把支付单标记为失败并关闭本次充值单,代理重新创建,不复用来源不明确的旧二维码。
### 5.2 微信扫码
微信使用 Native 下单:
```text
微信支付 v3 TransactionNative
-> 返回 code_url
```
现有微信 SDK 已包含 `TransactionNative`,需要在项目支付 Adapter 中封装,不在 Handler 直接调用 SDK。
若当前生效支付配置为:
- `wechat`:使用微信 v3 Native。
- `wechat_v2`:补充 v2 Native 统一下单实现。
- `fuiou`:只有现有富友配置明确支持后台扫码产品时才返回微信可用;不支持时前端隐藏微信扫码入口,不擅自用 JSAPI 代替。
### 5.3 支付宝扫码
支付宝使用当前 SDK 已提供的:
```text
alipay.trade.precreate
-> 返回 qr_code
```
不复用现有 WAP 支付 URL。创建时校验当前支付配置中的
```text
ali_app_id
ali_private_key
ali_public_key
ali_notify_url
```
配置不完整时支付宝方式显示为不可用,不能创建只有本地记录而没有有效二维码的充值单。
### 5.4 二维码响应
后端统一返回二维码内容,不返回二维码图片:
```json
{
"recharge_id": 88,
"recharge_no": "ARCH20260715143000000001",
"payment_no": "ARCH20260715143000000001",
"payment_method": "wechat",
"amount": 10000,
"qr_content": "weixin://wxpay/bizpayurl?...",
"expires_at": "2026-07-15T15:00:00+08:00",
"status": 1,
"status_name": "待支付"
}
```
微信返回 `code_url`、支付宝返回 `qr_code`Application 统一映射为 `qr_content`
## 六、支付回调与直接入账
### 6.1 回调校验
微信和支付宝回调继续执行现有签名、安全和金额校验,并增加代理充值分发:
```text
payment.order_type = agent_recharge
-> ConfirmAgentRechargePayment
```
必须校验:
- 支付单存在且支付方式与回调渠道一致。
- `payment_config_id` 与创建支付单时配置一致。
- 回调金额等于支付单和充值单金额。
- 第三方交易号没有被其他支付单占用。
- 充值单属于支付单中的 `order_id`,不能只依赖订单号前缀。
### 6.2 回调事务
```text
1. 按 payment_no 加载支付单
2. 支付单 pending -> paid 条件更新
3. 充值单 1 -> 2 条件更新
4. 加载代理主钱包并执行 CreditRecharge
5. 钱包余额和 version 更新
6. 创建唯一钱包流水
7. 充值单 2 -> 3写 paid_at/completed_at
8. 写 Audit Event
```
幂等键:
```text
agent_recharge:{recharge_id}:credit
```
数据库还需要保证钱包流水 `reference_type=agent_recharge + reference_id=recharge_id` 唯一。Redis 只用于削减重复并发,不能替代数据库幂等。
### 6.3 回调失败恢复
- 第三方校验失败:拒绝回调,不改变业务状态。
- 支付状态已成功、钱包事务失败:充值单保持已支付,写 Outbox/恢复任务继续入账。
- 钱包流水已存在但充值单未完成:恢复任务只补齐充值单状态。
- 重复回调:查询到支付单或充值单已完成后直接返回渠道成功报文。
- 本功能不引入审批,也不会因为支付金额较大转入审批。
## 七、金额与权限
### 7.1 金额
```go
const AgentRechargeMinAmount int64 = 10000
```
- 单位固定为分。
- DTO 使用 `min=10000`Service/Application 必须再次校验。
- 前端输入单位为元,提交前转换为分。
- 最大金额继续沿用现有系统上限,后续调整单独配置。
- 禁止前端通过浮点数直接计算金额,元转分使用字符串或十进制定点处理。
### 7.2 权限
- 代理只能为当前登录账号所属店铺的主钱包充值。
- 后端从登录上下文校验 `shop_id`,不能只相信请求参数。
- 平台和超级管理员可以查看全部充值记录;是否允许代代理发起在线扫码充值保持现有权限。
- 企业账号无权访问代理充值接口。
- 代理不能查看其他店铺的充值单、支付状态或二维码。
## 八、API 设计
### 8.1 可用支付方式
```http
GET /api/admin/agent-recharges/payment-methods
```
响应:
```json
{
"items": [
{"method": "wechat", "name": "微信支付", "enabled": true},
{"method": "alipay", "name": "支付宝", "enabled": true}
],
"min_amount": 10000,
"max_amount": 100000000
}
```
该路由必须先于 `/:id` 动态路由注册。
### 8.2 创建扫码充值
沿用现有接口:
```http
POST /api/admin/agent-recharges
```
```json
{
"shop_id": 101,
"amount": 10000,
"payment_method": "wechat",
"request_id": "01J2RECHARGE..."
}
```
`payment_method` 调整为:
```text
wechat / alipay / offline
```
其中 `offline` 仅平台员工线下代充值使用,并继续走企微审批;代理用户只能选择 `wechat/alipay`
`request_id` 用于防止前端重复点击创建多个二维码订单,同一店铺下建立业务唯一约束或 Redis 防重键。
### 8.3 查询支付状态
```http
GET /api/admin/agent-recharges/{id}/payment-status
```
```json
{
"status": 3,
"status_name": "已完成",
"payment_status": 1,
"payment_status_name": "已支付",
"paid_at": "2026-07-15T14:35:00+08:00",
"completed_at": "2026-07-15T14:35:01+08:00",
"wallet_balance": 510000
}
```
该接口只返回当前登录代理有权访问的充值单,不返回支付密钥、签名参数或其他店铺余额。
## 九、前端方案
### 9.1 入口
代理后台钱包页面保留“充值”按钮,点击后打开充值弹窗或抽屉,不新增营销页面。
控件:
- 金额使用数字输入框,单位为元,明确最低 100 元。
- 支付方式使用微信/支付宝分段控件,带对应图标。
- 不可用渠道禁用并显示简短原因。
- 主按钮为“生成支付二维码”。
### 9.2 二维码状态
创建成功后展示:
```text
充值金额
支付方式
二维码
二维码剩余有效时间
支付状态
取消/重新生成
```
- 前端使用 `qr_content` 生成二维码,不请求后端图片文件。
- 页面可见时每 3 秒查询一次轻量支付状态。
- 页面隐藏时暂停轮询,恢复可见时立即查询。
- 状态变为已完成、已关闭或离开页面时停止轮询。
- 二维码过期后禁用原二维码,提供“重新生成”命令,重新创建充值单和支付单。
- 支付完成后关闭二维码区域,刷新钱包余额并展示充值成功结果。
- 全流程不展示审批状态或企微审批区块。
### 9.3 列表和详情
代理充值列表增加支付方式和支付状态:
```text
充值单号 | 金额 | 支付方式 | 支付状态 | 充值状态 | 创建时间 | 完成时间
```
在线充值详情返回 `approval_source=none`。平台员工线下代充值详情继续展示企微审批信息,两类记录按 `payment_method` 区分。
## 十、审计与通知
统一审计至少记录:
```text
代理创建充值单
支付预下单成功/失败
微信/支付宝支付回调成功/失败
钱包入账成功/失败
重复回调被幂等忽略
充值单超时关闭
```
支付渠道交互写 `tb_integration_log`,钱包余额变化写关键 `Audit Event`,并关联充值单、支付单、代理钱包和钱包流水。
在线充值不产生审批通知。充值成功后可以生成普通资金结果站内通知,但不能显示“审批通过”。
## 十一、代码迁移范围
### 11.1 必须修改
- `AgentRechargeMinAmount``1` 调整为 `10000`
- `CreateAgentRechargeRequest.payment_method` 增加 `alipay`,金额校验改为 `min=10000`
- 创建代理在线充值时同时创建 `tb_payment` 记录。
- 新增 `PaymentOrderTypeAgentRecharge`
- 微信支付 Adapter 增加 Native 预下单。
- 支付宝 Adapter 增加 `TradePreCreate`
- 支付宝回调增加代理充值分发。
- 微信/富友代理充值回调统一改为按支付单分发,不只依赖 `ARCH` 前缀。
-`agent_recharge.Service.HandlePaymentCallback` 迁入 `ConfirmAgentRechargePayment` 用例。
- 前端增加支付方式查询、二维码展示和支付状态轮询。
### 11.2 保持不变
- 代理在线充值不进入企微审批。
- 平台员工线下代充值继续走企微审批。
- 代理只能充值自己的店铺主钱包。
- 钱包流水仍是资金变化权威记录。
- 现有支付回调验签和金额校验原则保持不变。
## 十二、发布与人工验证
停机发布API 与回调服务同时切换:
1. 验证 99.99 元被后端拒绝100 元可以创建充值单。
2. 验证代理只能为自己的店铺创建微信或支付宝充值。
3. 验证微信 Native 返回有效 `code_url`,前端能够扫码支付。
4. 验证支付宝 PreCreate 返回有效 `qr_code`,前端能够扫码支付。
5. 验证支付回调通过支付单类型分发到代理充值用例。
6. 验证微信、支付宝回调金额不一致时不会增加钱包余额。
7. 验证支付成功后不创建企微审批实例,直接完成钱包入账。
8. 验证重复回调只产生一条钱包流水,余额只增加一次。
9. 验证支付成功但钱包事务暂时失败时能够恢复完成,不需要代理重复支付。
10. 验证二维码过期后充值单关闭,旧二维码不能继续显示为有效。
11. 验证代理充值详情固定返回 `approval_source=none`,不展示审批区域。
12. 验证平台员工线下代充值仍按原企微审批方案执行,不受在线充值改造影响。