Files
junhong_cmp_fiber/docs/7月迭代/新增需求/05-代理钱包扫码充值.md
2026-07-16 15:08:07 +08:00

15 KiB
Raw Blame History

新增需求 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 中直接更新充值单、钱包和流水,业务状态和幂等边界没有收口为独立用例。
  • 前端技术方案只写了“保留收款码和支付状态页面”,没有支付方式选择、最低金额、二维码过期和支付成功状态细节。

现有评审中“代理在线充值不审批”的结论保持不变,本次补齐的是扫码支付和最低金额的完整技术方案。

三、业务流程

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,充值详情固定返回:

{
  "approval_source": "none",
  "approval": null
}

四、DDD 设计

4.1 目录

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 单号前缀判断支付渠道。

新增支付业务类型:

const PaymentOrderTypeAgentRecharge = "agent_recharge"

充值单和支付单在同一事务创建:

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 下单:

微信支付 v3 TransactionNative
  -> 返回 code_url

现有微信 SDK 已包含 TransactionNative,需要在项目支付 Adapter 中封装,不在 Handler 直接调用 SDK。

若当前生效支付配置为:

  • wechat:使用微信 v3 Native。
  • wechat_v2:补充 v2 Native 统一下单实现。
  • fuiou:只有现有富友配置明确支持后台扫码产品时才返回微信可用;不支持时前端隐藏微信扫码入口,不擅自用 JSAPI 代替。

5.3 支付宝扫码

支付宝使用当前 SDK 已提供的:

alipay.trade.precreate
  -> 返回 qr_code

不复用现有 WAP 支付 URL。创建时校验当前支付配置中的

ali_app_id
ali_private_key
ali_public_key
ali_notify_url

配置不完整时支付宝方式显示为不可用,不能创建只有本地记录而没有有效二维码的充值单。

5.4 二维码响应

后端统一返回二维码内容,不返回二维码图片:

{
  "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_codeApplication 统一映射为 qr_content

六、支付回调与直接入账

6.1 回调校验

微信和支付宝回调继续执行现有签名、安全和金额校验,并增加代理充值分发:

payment.order_type = agent_recharge
  -> ConfirmAgentRechargePayment

必须校验:

  • 支付单存在且支付方式与回调渠道一致。
  • payment_config_id 与创建支付单时配置一致。
  • 回调金额等于支付单和充值单金额。
  • 第三方交易号没有被其他支付单占用。
  • 充值单属于支付单中的 order_id,不能只依赖订单号前缀。

6.2 回调事务

1. 按 payment_no 加载支付单
2. 支付单 pending -> paid 条件更新
3. 充值单 1 -> 2 条件更新
4. 加载代理主钱包并执行 CreditRecharge
5. 钱包余额和 version 更新
6. 创建唯一钱包流水
7. 充值单 2 -> 3写 paid_at/completed_at
8. 写 Audit Event

幂等键:

agent_recharge:{recharge_id}:credit

数据库还需要保证钱包流水 reference_type=agent_recharge + reference_id=recharge_id 唯一。Redis 只用于削减重复并发,不能替代数据库幂等。

6.3 回调失败恢复

  • 第三方校验失败:拒绝回调,不改变业务状态。
  • 支付状态已成功、钱包事务失败:充值单保持已支付,写 Outbox/恢复任务继续入账。
  • 钱包流水已存在但充值单未完成:恢复任务只补齐充值单状态。
  • 重复回调:查询到支付单或充值单已完成后直接返回渠道成功报文。
  • 本功能不引入审批,也不会因为支付金额较大转入审批。

七、金额与权限

7.1 金额

const AgentRechargeMinAmount int64 = 10000
  • 单位固定为分。
  • DTO 使用 min=10000Service/Application 必须再次校验。
  • 前端输入单位为元,提交前转换为分。
  • 最大金额继续沿用现有系统上限,后续调整单独配置。
  • 禁止前端通过浮点数直接计算金额,元转分使用字符串或十进制定点处理。

7.2 权限

  • 代理只能为当前登录账号所属店铺的主钱包充值。
  • 后端从登录上下文校验 shop_id,不能只相信请求参数。
  • 平台和超级管理员可以查看全部充值记录;是否允许代代理发起在线扫码充值保持现有权限。
  • 企业账号无权访问代理充值接口。
  • 代理不能查看其他店铺的充值单、支付状态或二维码。

八、API 设计

8.1 可用支付方式

GET /api/admin/agent-recharges/payment-methods

响应:

{
  "items": [
    {"method": "wechat", "name": "微信支付", "enabled": true},
    {"method": "alipay", "name": "支付宝", "enabled": true}
  ],
  "min_amount": 10000,
  "max_amount": 100000000
}

该路由必须先于 /:id 动态路由注册。

8.2 创建扫码充值

沿用现有接口:

POST /api/admin/agent-recharges
{
  "shop_id": 101,
  "amount": 10000,
  "payment_method": "wechat",
  "request_id": "01J2RECHARGE..."
}

payment_method 调整为:

wechat / alipay / offline

其中 offline 仅平台员工线下代充值使用,并继续走企微审批;代理用户只能选择 wechat/alipay

request_id 用于防止前端重复点击创建多个二维码订单,同一店铺下建立业务唯一约束或 Redis 防重键。

8.3 查询支付状态

GET /api/admin/agent-recharges/{id}/payment-status
{
  "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 二维码状态

创建成功后展示:

充值金额
支付方式
二维码
二维码剩余有效时间
支付状态
取消/重新生成
  • 前端使用 qr_content 生成二维码,不请求后端图片文件。
  • 页面可见时每 3 秒查询一次轻量支付状态。
  • 页面隐藏时暂停轮询,恢复可见时立即查询。
  • 状态变为已完成、已关闭或离开页面时停止轮询。
  • 二维码过期后禁用原二维码,提供“重新生成”命令,重新创建充值单和支付单。
  • 支付完成后关闭二维码区域,刷新钱包余额并展示充值成功结果。
  • 全流程不展示审批状态或企微审批区块。

9.3 列表和详情

代理充值列表增加支付方式和支付状态:

充值单号 | 金额 | 支付方式 | 支付状态 | 充值状态 | 创建时间 | 完成时间

在线充值详情返回 approval_source=none。平台员工线下代充值详情继续展示企微审批信息,两类记录按 payment_method 区分。

十、审计与通知

统一审计至少记录:

代理创建充值单
支付预下单成功/失败
微信/支付宝支付回调成功/失败
钱包入账成功/失败
重复回调被幂等忽略
充值单超时关闭

支付渠道交互写 tb_integration_log,钱包余额变化写关键 Audit Event,并关联充值单、支付单、代理钱包和钱包流水。

在线充值不产生审批通知。充值成功后可以生成普通资金结果站内通知,但不能显示“审批通过”。

十一、代码迁移范围

11.1 必须修改

  • AgentRechargeMinAmount1 调整为 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. 验证平台员工线下代充值仍按原企微审批方案执行,不受在线充值改造影响。