Files
junhong_cmp_fiber/openspec/specs/order-payment/spec.md
huang 8ed3d9da93
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m0s
feat: 实现代理钱包订单创建和订单角色追踪功能
新增功能:
- 代理在后台使用 wallet 支付时,订单直接完成(扣款 + 激活套餐)
- 支持代理自购和代理代购场景
- 新增订单角色追踪字段(operator_id、operator_type、actual_paid_amount、purchase_role)
- 订单查询支持 OR 逻辑(buyer_id 或 operator_id)
- 钱包流水记录交易子类型和关联店铺
- 佣金逻辑调整:代理代购不产生佣金

数据库变更:
- 订单表新增 4 个字段和 2 个索引
- 钱包流水表新增 2 个字段
- 包含迁移脚本和回滚脚本

文档:
- 功能总结文档
- 部署指南
- OpenAPI 文档更新
- Specs 同步(新增 agent-order-role-tracking capability)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 14:11:42 +08:00

269 lines
10 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.
## ADDED Requirements
### Requirement: 线下支付方式
系统 SHALL 支持线下支付方式offline仅用于代购订单。线下支付的订单创建后直接标记为已支付跳过支付流程。
#### Scenario: 创建线下支付订单
- **WHEN** 平台账号创建订单时选择支付方式为 offline
- **THEN** 系统创建订单payment_status 直接设为 2已支付payment_method = "offline"
#### Scenario: 线下支付权限限制
- **WHEN** 非平台账号(代理/个人客户)尝试使用线下支付
- **THEN** 系统返回错误 "只有平台账号可以使用线下支付"
#### Scenario: 线下支付订单自动激活套餐
- **WHEN** 创建线下支付订单成功
- **THEN** 系统自动激活套餐,创建 PackageUsage 记录
#### Scenario: 线下支付不扣钱包
- **WHEN** 订单使用线下支付
- **THEN** 系统不扣减任何钱包余额
---
### Requirement: 钱包支付
系统 SHALL 支持使用钱包余额支付订单。支付成功后 MUST 扣减钱包余额并激活套餐。
#### Scenario: 钱包余额充足
- **WHEN** 买家使用钱包支付,余额充足
- **THEN** 系统扣减钱包余额,更新订单状态为已支付,创建套餐使用记录
#### Scenario: 钱包余额不足
- **WHEN** 买家使用钱包支付,余额不足
- **THEN** 系统返回错误 "钱包余额不足"
#### Scenario: 订单已支付
- **WHEN** 买家尝试支付已支付的订单
- **THEN** 系统返回错误 "订单已支付"
#### Scenario: 订单已取消
- **WHEN** 买家尝试支付已取消的订单
- **THEN** 系统返回错误 "订单已取消"
---
### Requirement: 第三方支付回调
系统 SHALL 处理微信支付和支付宝的支付回调,支持订单支付和钱包充值两种场景。回调处理 MUST 幂等。
#### Scenario: 微信支付成功回调(订单)
- **WHEN** 收到微信支付成功回调,订单号格式为 ORD 开头
- **THEN** 系统验证签名,更新订单状态,激活套餐,返回成功响应
#### Scenario: 微信支付成功回调(充值)
- **WHEN** 收到微信支付成功回调,订单号格式为 RCH 开头
- **THEN** 系统验证签名,更新充值订单状态,增加钱包余额,更新累计充值,触发佣金判断,返回成功响应
#### Scenario: 支付宝成功回调(订单)
- **WHEN** 收到支付宝支付成功回调,订单号格式为 ORD 开头
- **THEN** 系统验证签名,更新订单状态,激活套餐,返回成功响应
#### Scenario: 支付宝成功回调(充值)
- **WHEN** 收到支付宝支付成功回调,订单号格式为 RCH 开头
- **THEN** 系统验证签名,更新充值订单状态,增加钱包余额,更新累计充值,触发佣金判断,返回成功响应
#### Scenario: 重复回调
- **WHEN** 收到已处理订单的重复回调
- **THEN** 系统返回成功响应,不重复处理
#### Scenario: 签名验证失败
- **WHEN** 回调签名验证失败
- **THEN** 系统拒绝处理,返回失败响应
---
### Requirement: 套餐激活
支付成功后系统 MUST 激活套餐,创建 PackageUsage 记录。代购订单也需激活套餐,但不更新累计充值。
#### Scenario: 单卡套餐激活
- **WHEN** 单卡订单支付成功
- **THEN** 系统创建 PackageUsageusage_type 为 single_card关联 iot_card_id
#### Scenario: 设备套餐激活
- **WHEN** 设备订单支付成功
- **THEN** 系统创建 PackageUsageusage_type 为 device关联 device_id
#### Scenario: 套餐有效期计算
- **WHEN** 套餐激活
- **THEN** 有效期 = 激活时间 + 套餐时长(月)
#### Scenario: 代购订单激活套餐
- **WHEN** 代购订单is_purchase_on_behalf = true创建成功
- **THEN** 系统激活套餐,但不更新卡/设备的 accumulated_recharge
---
### Requirement: 支付事务保证
钱包支付 MUST 在事务中完成:余额扣减、订单状态更新、套餐激活。任一步骤失败则全部回滚。
#### Scenario: 事务成功
- **WHEN** 所有步骤成功
- **THEN** 事务提交,支付完成
#### Scenario: 余额扣减后套餐激活失败
- **WHEN** 余额扣减成功但套餐激活失败
- **THEN** 事务回滚,余额恢复,订单状态不变
## ADDED Requirements
### Requirement: 后台钱包一步支付
系统 SHALL 支持后台订单创建时使用钱包支付立即完成订单,无需后续调用支付接口。
#### Scenario: 后台订单创建时钱包支付
- **WHEN** 代理在后台创建订单,支付方式为 wallet钱包余额充足
- **THEN** 系统创建订单,立即扣减钱包余额,订单状态为已支付(`payment_status` = 2激活套餐
#### Scenario: 后台钱包支付余额不足
- **WHEN** 代理在后台创建订单,支付方式为 wallet钱包余额不足
- **THEN** 系统返回错误"余额不足",订单创建失败
#### Scenario: 后台钱包支付订单响应
- **WHEN** 后台钱包支付订单创建成功
- **THEN** API 响应包含已支付的订单信息,`payment_status` = 2`payment_method` = "wallet"`paid_at` 为当前时间
#### Scenario: 后台钱包支付不创建待支付订单
- **WHEN** 代理在后台创建 wallet 订单
- **THEN** 系统不创建待支付订单(`payment_status` != 1直接完成支付
---
### Requirement: H5 钱包两步支付保持不变
系统 SHALL 保持 H5 端钱包支付的两步流程(创建待支付订单 → 调用支付接口)。
#### Scenario: H5 创建待支付订单
- **WHEN** 个人客户在 H5 端创建订单,支付方式为 wallet
- **THEN** 系统创建订单,`payment_status` = 1待支付不扣减钱包余额
#### Scenario: H5 调用 WalletPay 接口支付
- **WHEN** 个人客户调用 WalletPay 接口支付待支付订单
- **THEN** 系统扣减钱包余额,更新订单状态为已支付,激活套餐
#### Scenario: H5 和后台钱包支付流程独立
- **WHEN** H5 端创建 wallet 订单
- **THEN** 不影响后台 wallet 订单的一步支付逻辑
---
### Requirement: 钱包流水记录扩展
系统 SHALL 在钱包流水中记录交易子类型和关联店铺,支持按场景筛选。
#### Scenario: 自购钱包流水
- **WHEN** 代理为自己的资源购买套餐,使用 wallet
- **THEN** 钱包流水的 `transaction_subtype` = "self_purchase"`related_shop_id` 为 NULL`remark` = "购买套餐"
#### Scenario: 代购钱包流水
- **WHEN** 代理为下级代理购买套餐,使用 wallet
- **THEN** 钱包流水的 `transaction_subtype` = "purchase_for_subordinate"`related_shop_id` = 下级代理店铺 ID`remark` = "为下级代理【XX】购买套餐"
#### Scenario: 钱包流水查询店铺名称
- **WHEN** 创建代购钱包流水
- **THEN** 系统查询下级店铺名称,填充到 `remark` 字段
#### Scenario: 钱包流水筛选
- **WHEN** 代理查询钱包流水,筛选 `transaction_subtype` = "purchase_for_subordinate"
- **THEN** 系统返回所有为下级代理购买的流水记录
---
### Requirement: 钱包支付乐观锁
系统 SHALL 使用乐观锁防止钱包并发扣款导致余额不一致。
#### Scenario: 钱包扣款使用 version 字段
- **WHEN** 扣减钱包余额
- **THEN** SQL 语句包含 `WHERE balance >= ? AND version = ?`,更新时 `version + 1`
#### Scenario: 钱包并发扣款失败
- **WHEN** 两个请求同时扣减同一钱包
- **THEN** 只有一个请求成功,另一个返回"余额不足或并发冲突"
#### Scenario: 乐观锁重试逻辑
- **WHEN** 钱包扣款因 version 冲突失败
- **THEN** 系统不自动重试,返回错误(由客户端决定是否重试)
---
### Requirement: 钱包支付幂等性
系统 SHALL 防止同一订单重复创建和重复扣款。
#### Scenario: 订单创建幂等性检查
- **WHEN** 同一买家对同一载体的同一套餐组合在短时间内重复创建订单
- **THEN** 系统返回已创建的订单,不重复扣款
#### Scenario: 幂等性使用 Redis 业务键
- **WHEN** 检查订单幂等性
- **THEN** 系统使用 Redis key `order:idempotency:{buyer_type}:{buyer_id}:{order_type}:{carrier_type}:{carrier_id}:{sorted_package_ids}`
#### Scenario: 幂等性 TTL
- **WHEN** 订单创建成功后标记幂等性
- **THEN** Redis key 的 TTL 为 3 分钟
#### Scenario: 分布式锁防止并发
- **WHEN** 订单创建前检查幂等性
- **THEN** 系统使用分布式锁 `order:create:lock:{carrier_type}:{carrier_id}`TTL 10 秒
---
### Requirement: 后台订单 API 响应扩展
系统 SHALL 在后台订单创建和查询 API 响应中包含钱包支付相关字段。
#### Scenario: 订单响应包含实际支付金额
- **WHEN** 查询钱包支付的订单
- **THEN** 响应包含 `actual_paid_amount` 字段
#### Scenario: 订单响应包含操作者信息
- **WHEN** 查询代购订单
- **THEN** 响应包含 `operator_id``operator_type``operator_name` 字段
#### Scenario: 订单响应包含购买备注
- **WHEN** 查询上级代理购买的订单
- **THEN** 响应包含 `purchase_remark` 字段,如"由上级代理【XX】购买"
---
### Requirement: 钱包支付错误处理
系统 SHALL 在钱包支付失败时返回明确的错误信息。
#### Scenario: 钱包不存在
- **WHEN** 钱包支付时钱包不存在
- **THEN** 系统返回错误"钱包不存在"`CodeWalletNotFound`
#### Scenario: 余额不足
- **WHEN** 钱包支付时余额不足
- **THEN** 系统返回错误"余额不足"`CodeInsufficientBalance`
#### Scenario: 并发冲突
- **WHEN** 钱包扣款因 version 冲突失败
- **THEN** 系统返回错误"余额不足或并发冲突"`CodeInsufficientBalance`
#### Scenario: 套餐激活失败
- **WHEN** 钱包扣款成功但套餐激活失败
- **THEN** 事务回滚,钱包余额恢复,返回激活失败错误
---
### Requirement: 钱包支付与第三方支付的区别
系统 SHALL 区分后台钱包支付和第三方支付的业务逻辑。
#### Scenario: 后台不支持第三方支付
- **WHEN** 代理在后台创建订单时选择 wechat 或 alipay
- **THEN** 系统返回错误"后台只支持 wallet 和 offline 支付方式"
#### Scenario: H5 支持第三方支付
- **WHEN** 个人客户在 H5 端创建订单时选择 wechat 或 alipay
- **THEN** 系统创建待支付订单返回支付参数prepay_id 或 h5_url
#### Scenario: 钱包支付不需要支付参数
- **WHEN** 后台钱包支付订单创建成功
- **THEN** 响应不包含 prepay_id、h5_url 等第三方支付参数