Files
junhong_cmp_fiber/openspec/specs/wallet-recharge/spec.md
huang b9733c4913
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m12s
fix: 修正零售价架构错误 + 清理旧微信配置 + 归档提案 + 前端接口文档
1. 修正 retail_price 架构:
   - 删除 batch-pricing 接口的 pricing_target 字段和 retail_price 分支
     (上级只能改下级成本价,不能改零售价)
   - 新增 PATCH /api/admin/packages/:id/retail-price 接口
     (代理自己改自己的零售价,校验 retail_price >= cost_price)

2. 清理旧微信 YAML 配置(已全部迁移到数据库 tb_wechat_config):
   - 删除 config.yaml 中 wechat.official_account 配置节
   - 删除 NewOfficialAccountApp() 旧工厂函数
   - 清理 personal_customer service 中的死代码(旧登录/绑定微信方法)
   - 清理 docker-compose.prod.yml 中旧微信环境变量和证书挂载注释

3. 归档四个已完成提案到 openspec/changes/archive/

4. 新增前端接口变更说明文档(docs/前端接口变更说明.md)

5. 修正归档提案和 specs 中关于 pricing_target 的错误描述
2026-03-19 17:39:43 +08:00

243 lines
9.9 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.
# Capability: 钱包充值
## Purpose
本 capability 定义钱包充值功能,允许个人客户为卡/设备钱包充值,支持强充验证、第三方支付和充值后的累计充值更新与一次性佣金触发。
## Requirements
### Requirement: 创建钱包充值订单
系统 SHALL 允许个人客户创建钱包充值订单。创建前 MUST 验证强充要求,强充场景下充值金额必须等于要求的强充金额。
#### Scenario: 无强充要求时自由充值
- **WHEN** 个人客户为卡/设备创建充值订单,该卡/设备无强充要求,充值金额 100 元
- **THEN** 系统创建充值订单,状态为待支付,金额 10000 分
#### Scenario: 首次充值强充
- **WHEN** 卡关联系列配置为首次充值触发,阈值 100 元,客户尝试充值 100 元
- **THEN** 系统验证通过,创建充值订单,金额 10000 分
#### Scenario: 首次充值金额不符
- **WHEN** 卡关联系列配置为首次充值触发,阈值 100 元,客户尝试充值 50 元
- **THEN** 系统返回错误 "必须充值100元"
#### Scenario: 累计充值启用强充
- **WHEN** 卡关联系列配置为累计充值触发,启用强充,强充金额 100 元,客户尝试充值 100 元
- **THEN** 系统验证通过,创建充值订单
#### Scenario: 累计充值强充金额不符
- **WHEN** 卡关联系列配置为累计充值触发,启用强充,强充金额 100 元,客户尝试充值 50 元
- **THEN** 系统返回错误 "必须充值100元"
#### Scenario: 累计充值未启用强充
- **WHEN** 卡关联系列配置为累计充值触发,未启用强充,客户充值任意金额
- **THEN** 系统创建充值订单
#### Scenario: 充值订单号唯一
- **WHEN** 创建充值订单
- **THEN** 系统生成唯一充值单号,格式为 RCH + 14位时间戳 + 6位随机数
---
### Requirement: 查询充值订单列表
系统 SHALL 提供充值订单列表查询,支持按状态筛选、时间范围筛选。
#### Scenario: 查询个人客户的充值订单
- **WHEN** 个人客户查询充值订单列表
- **THEN** 系统返回该客户的所有充值订单
#### Scenario: 按状态筛选
- **WHEN** 客户指定充值状态筛选(待支付/已支付/已完成)
- **THEN** 系统只返回匹配状态的充值订单
#### Scenario: 分页查询
- **WHEN** 查询充值订单列表
- **THEN** 系统使用分页返回,默认每页 20 条,最大 100 条
---
### Requirement: 查询充值订单详情
系统 SHALL 允许个人客户查询充值订单详情。
#### Scenario: 查询自己的充值订单
- **WHEN** 客户查询自己的充值订单详情
- **THEN** 系统返回订单信息(充值单号、金额、支付方式、状态、时间等)
#### Scenario: 查询他人充值订单
- **WHEN** 客户尝试查询不属于自己的充值订单
- **THEN** 系统返回 "充值订单不存在" 错误
---
### Requirement: 充值支付(微信/支付宝)
系统 SHALL 支持通过微信支付和支付宝支付完成充值。
#### Scenario: 微信 JSAPI 支付
- **WHEN** 客户在微信内选择充值,使用微信支付
- **THEN** 系统调用微信支付 JSAPI 接口,返回支付参数
#### Scenario: 微信 H5 支付
- **WHEN** 客户在浏览器内选择充值,使用微信支付
- **THEN** 系统调用微信支付 H5 接口,返回支付跳转 URL
#### Scenario: 支付宝支付
- **WHEN** 客户选择支付宝支付充值
- **THEN** 系统调用支付宝接口,返回支付参数
---
### Requirement: 充值支付回调处理
系统 SHALL 处理微信和支付宝的支付回调,验证签名,更新充值订单状态,增加钱包余额。
#### Scenario: 微信支付回调成功
- **WHEN** 收到微信支付成功回调,验证签名通过
- **THEN** 系统更新充值订单状态为已支付
- **AND** 增加对应钱包余额
- **AND** 创建钱包交易记录
- **AND** 返回成功响应给微信
#### Scenario: 支付宝回调成功
- **WHEN** 收到支付宝支付成功回调,验证签名通过
- **THEN** 系统更新充值订单状态为已支付
- **AND** 增加对应钱包余额
- **AND** 创建钱包交易记录
#### Scenario: 签名验证失败
- **WHEN** 收到支付回调,签名验证失败
- **THEN** 系统记录错误日志,不处理订单,返回失败响应
#### Scenario: 重复回调幂等处理
- **WHEN** 收到同一充值订单的重复支付回调
- **THEN** 系统检查订单状态,如果已支付则直接返回成功,不重复处理
---
### Requirement: 充值成功更新累计充值金额
充值支付成功后系统 SHALL 更新卡/设备的累计充值金额AccumulatedRecharge
#### Scenario: 充值成功累加充值金额
- **WHEN** 卡钱包充值 100 元成功,当前累计充值 200 元
- **THEN** 系统更新卡的累计充值为 300 元200 + 100
#### Scenario: 设备充值成功累加充值金额
- **WHEN** 设备钱包充值 200 元成功,当前累计充值 500 元
- **THEN** 系统更新设备的累计充值为 700 元500 + 200
#### Scenario: 使用原子操作更新
- **WHEN** 更新累计充值金额
- **THEN** 系统使用 SQL 原子操作或 GORM 乐观锁确保并发安全
---
### Requirement: 充值成功触发一次性佣金判断
充值支付成功后系统 SHALL 检查是否达到一次性佣金阈值,如果达到则触发佣金计算。
#### Scenario: 首次充值达到阈值
- **WHEN** 卡配置为首次充值触发,阈值 100 元,客户充值 100 元
- **THEN** 系统触发一次性佣金计算,发放佣金
#### Scenario: 累计充值达到阈值
- **WHEN** 卡配置为累计充值触发,阈值 1000 元,累计充值已达到 1000 元
- **THEN** 系统触发一次性佣金计算,发放佣金
#### Scenario: 未达阈值不触发
- **WHEN** 充值后累计充值未达到阈值
- **THEN** 系统不触发一次性佣金计算
#### Scenario: 已发放过不重复触发
- **WHEN** 卡的一次性佣金已发放过first_commission_paid = true
- **THEN** 系统不触发一次性佣金计算
---
### Requirement: 充值订单状态流转
充值订单状态 SHALL 按以下流程流转:待支付 → 已支付 → 已完成。
#### Scenario: 正常流转
- **WHEN** 创建充值订单 → 支付成功 → 钱包余额增加完成
- **THEN** 订单状态依次为1待支付→ 2已支付→ 3已完成
#### Scenario: 超时未支付
- **WHEN** 充值订单创建 30 分钟后仍未支付
- **THEN** 系统标记订单为已关闭(状态 4
---
### Requirement: 充值金额限制
系统 SHALL 限制单次充值金额范围。
#### Scenario: 充值金额范围
- **WHEN** 创建充值订单
- **THEN** 充值金额必须在 1 元到 100000 元之间
#### Scenario: 充值金额过小
- **WHEN** 客户尝试充值 0.5 元
- **THEN** 系统返回错误 "充值金额不能小于1元"
#### Scenario: 充值金额过大
- **WHEN** 客户尝试充值 200000 元
- **THEN** 系统返回错误 "单次充值金额不能超过100000元"
### Requirement: 充值回调事务一致性
`HandlePaymentCallback` 内的 `UpdateStatusWithOptimisticLock``UpdatePaymentInfo` MUST 使用同一个事务内 `tx` 执行,保证充值状态与支付信息的原子性。
#### Scenario: 回调处理中状态更新与支付信息更新同事务
- **WHEN** 收到支付成功回调并进入 `HandlePaymentCallback`
- **THEN** 系统 MUST 在同一事务 `tx` 内执行 `UpdateStatusWithOptimisticLock`
- **THEN** 系统 MUST 在同一事务 `tx` 内执行 `UpdatePaymentInfo`
#### Scenario: 事务失败整体回滚
- **WHEN** 回调处理中任一步骤失败
- **THEN** 系统 MUST 回滚该事务,保证订单状态与支付信息不出现部分成功
---
### Requirement: Store 方法签名支持事务参数
系统 MUST 调整充值相关 Store 方法签名,支持显式传入 `*gorm.DB tx` 参数,以保证事务边界可控。
#### Scenario: Service 传入事务句柄
- **WHEN** Service 在事务上下文调用 Store 更新充值记录
- **THEN** Store 方法 MUST 接收并使用传入的 `tx` 执行数据库操作
---
### Requirement: 充值回调采用两阶段处理
系统 MUST 将强充场景的充值回调改为两阶段:第一阶段同步事务内完成入账与状态更新,第二阶段异步执行自动购买。第一阶段 SHALL 包含:更新充值状态、钱包加款、累计充值更新、首充佣金判断。第二阶段 SHALL 通过 Asynq 任务执行钱包扣款、创建套餐订单、激活套餐。该改造适用于客户端触发的强充路径,且不影响非强充充值主流程。
#### Scenario: 强充回调同步入账成功并触发异步任务
- **WHEN** 强充充值支付回调验签成功
- **THEN** 系统在事务内完成钱包入账与充值单状态更新
- **AND** 入队 `AutoPurchaseAfterRecharge` 异步任务
---
### Requirement: 充值记录新增 auto_purchase_status 状态追踪
系统 MUST 在 `AssetRechargeRecord` 增加 `auto_purchase_status` 字段,用于追踪强充后二阶段自动购买状态。状态集 SHALL 至少包括:`pending``success``failed`。创建强充充值单时 MUST 初始化为 `pending`;异步购买成功后 MUST 更新为 `success`;重试耗尽后 MUST 更新为 `failed`
#### Scenario: 强充充值单创建时默认 pending
- **WHEN** 系统创建与套餐联动的强充充值单
- **THEN** 充值记录 `auto_purchase_status` 初始化为 `pending`
---
### Requirement: 异步自动购买失败处理规范
系统 SHALL 对 `AutoPurchaseAfterRecharge` 失败场景执行统一处理:任务 MUST 自动重试(最多 3 次);全部失败后 MUST 记录错误日志并将 `auto_purchase_status` 置为 `failed`;用户资金 SHALL 保留在钱包中,允许后续手动购买,不得回滚已成功的充值入账。
#### Scenario: 异步任务最终失败
- **WHEN** 自动购买任务连续失败并达到最大重试次数
- **THEN** 系统将 `auto_purchase_status` 标记为 `failed`
- **AND** 钱包余额保持可用,用户可手动下单