Files
junhong_cmp_fiber/openspec/specs/admin-order-creation/spec.md
huang 5bb0ff0ddf
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
fix: 修复代理钱包订单创建逻辑,拆分后台/H5端下单方法并归档变更
- 拆分订单创建为 CreateAdminOrder(后台一步支付)和 CreateH5Order(H5 两步支付)
- 新增 CreateAdminOrderRequest DTO,后台仅允许 wallet/offline 支付方式
- 同步 delta specs 到主规格(order-payment 更新 + admin-order-creation 新增)
- 归档 fix-agent-wallet-order-creation 变更
- 新增 implement-order-expiration 变更提案
2026-02-28 16:31:31 +08:00

249 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.
# Admin Order Creation
## Purpose
后台订单创建流程,为代理和平台账号提供订单创建功能。与 H5 端订单创建的核心区别:后台仅支持 wallet/offline 支付方式,且 wallet 支付立即完成扣款和套餐激活(一步到位),不创建待支付订单。
This capability supports:
- 参数验证和支付方式限制
- 钱包余额检查和一步扣款
- 权限校验(代理、平台、超管)
- 错误处理和防御性编程
## ADDED Requirements
### Requirement: 后台订单创建 API 参数验证
系统 SHALL 在后台订单创建 API 中强制验证请求参数,拒绝非法的支付方式。
后台订单创建使用独立的 DTO`CreateAdminOrderRequest`),仅允许 `wallet``offline` 两种支付方式。Handler 层 MUST 调用 `middleware.ValidateStruct(&req)` 验证参数,确保 DTO 的 `validate:"oneof=wallet offline"` 规则生效。
#### Scenario: DTO 验证拒绝非法支付方式
- **WHEN** 后台创建订单请求中 `payment_method``wechat``alipay`
- **THEN** 系统在 Handler 层验证失败,返回错误"请求参数解析失败"`CodeInvalidParam`),订单创建失败
#### Scenario: DTO 验证拒绝空支付方式
- **WHEN** 后台创建订单请求中缺少 `payment_method` 字段或值为空字符串
- **THEN** 系统在 Handler 层验证失败,返回错误"请求参数解析失败"`CodeInvalidParam`),订单创建失败
#### Scenario: DTO 验证允许 wallet 支付
- **WHEN** 后台创建订单请求中 `payment_method``wallet`
- **THEN** 系统通过 DTO 验证,继续后续业务逻辑
#### Scenario: DTO 验证允许 offline 支付
- **WHEN** 后台创建订单请求中 `payment_method``offline`
- **THEN** 系统通过 DTO 验证,继续后续业务逻辑
---
### Requirement: 后台订单创建权限检查
系统 SHALL 在后台订单创建时完整检查支付方式权限,所有支付方式(包括非法的)都必须经过权限校验。
权限规则:
- `offline` 支付:仅超管和平台账号可用
- `wallet` 支付:代理、平台、超管均可用
- 其他支付方式:一律拒绝(兜底检查)
#### Scenario: 超管可以使用 offline 支付
- **WHEN** 超管账号创建订单,支付方式为 `offline`
- **THEN** 系统通过权限检查,继续创建订单
#### Scenario: 平台账号可以使用 offline 支付
- **WHEN** 平台账号创建订单,支付方式为 `offline`
- **THEN** 系统通过权限检查,继续创建订单
#### Scenario: 代理账号不能使用 offline 支付
- **WHEN** 代理账号创建订单,支付方式为 `offline`
- **THEN** 系统返回错误"只有平台可以使用线下支付"`CodeForbidden`),订单创建失败
#### Scenario: 代理账号可以使用 wallet 支付
- **WHEN** 代理账号创建订单,支付方式为 `wallet`,钱包余额充足
- **THEN** 系统通过权限检查,继续创建订单
#### Scenario: 兜底检查拒绝其他支付方式
- **WHEN** 后台创建订单请求中 `payment_method``wechat`(虽然 DTO 验证应该已拒绝,但作为防御性编程)
- **THEN** 系统在 Handler 层返回错误"后台仅支持钱包支付或线下支付"`CodeInvalidParam`
---
### Requirement: 后台 wallet 订单一步到位
系统 SHALL 在后台创建 wallet 订单时立即完成余额扣款和套餐激活,不创建待支付订单。订单创建成功后 `payment_status` MUST 为 2已支付
与 H5 端的核心区别:
- **后台**:检查余额 → 扣款 → 创建已支付订单 → 激活套餐(一步完成)
- **H5 端**:冻结余额 → 创建待支付订单 → 用户调用支付接口 → 扣款 + 激活(两步流程)
#### Scenario: 后台 wallet 订单立即扣款
- **WHEN** 代理在后台创建订单,支付方式为 `wallet`,钱包余额 5000 分,订单金额 3000 分
- **THEN** 系统立即扣减钱包余额 3000 分,余额变为 2000 分,创建订单时 `payment_status` = 2`paid_at` 为当前时间
#### Scenario: 后台 wallet 订单立即激活套餐
- **WHEN** 代理在后台创建 wallet 订单成功
- **THEN** 系统在同一事务中创建 `PackageUsage` 记录,套餐状态为已激活
#### Scenario: 后台 wallet 订单不创建待支付状态
- **WHEN** 代理在后台创建 wallet 订单
- **THEN** 系统不创建 `payment_status` = 1待支付的订单订单创建后立即为已支付状态
#### Scenario: 后台 wallet 订单余额不足直接拒绝
- **WHEN** 代理在后台创建 wallet 订单,钱包余额 1000 分,订单金额 3000 分
- **THEN** 系统在事务外快速检查余额,返回错误"余额不足"`CodeInsufficientBalance`),订单创建失败
#### Scenario: 后台 wallet 订单事务保证
- **WHEN** 代理在后台创建 wallet 订单
- **THEN** 订单创建、余额扣减、套餐激活在同一事务中完成,任一步骤失败则全部回滚
---
### Requirement: 后台 offline 订单立即激活
系统 SHALL 在后台创建 offline 订单时立即激活套餐,不扣减钱包余额。订单创建成功后 `payment_status` MUST 为 2已支付
#### Scenario: 平台创建 offline 订单立即激活
- **WHEN** 平台账号创建订单,支付方式为 `offline`
- **THEN** 系统创建订单时 `payment_status` = 2`paid_at` 为当前时间,立即激活套餐
#### Scenario: offline 订单不扣钱包
- **WHEN** 平台账号创建 offline 订单
- **THEN** 系统不扣减任何钱包余额(因为是线下支付)
#### Scenario: offline 订单不检查余额
- **WHEN** 平台账号创建 offline 订单,钱包余额为 0
- **THEN** 系统仍然创建订单成功(因为线下支付不依赖钱包)
---
### Requirement: 后台订单创建错误处理
系统 SHALL 在后台订单创建失败时返回明确的错误信息,不泄露底层细节。
错误码使用规范:
- 参数验证失败:`CodeInvalidParam`(不泄露具体校验错误)
- 权限不足:`CodeForbidden`
- 余额不足:`CodeInsufficientBalance`
- 钱包不存在:`CodeWalletNotFound`
- 其他错误:`CodeInternalError`
#### Scenario: 参数验证失败不泄露细节
- **WHEN** 后台创建订单请求参数验证失败(如支付方式非法)
- **THEN** 系统返回 `CodeInvalidParam` 错误码,错误消息为通用的"请求参数解析失败",不包含具体的 validator 错误信息
#### Scenario: 钱包余额不足返回明确错误
- **WHEN** 代理创建 wallet 订单,余额不足
- **THEN** 系统返回 `CodeInsufficientBalance` 错误码,错误消息为"余额不足"
#### Scenario: 钱包不存在返回明确错误
- **WHEN** 代理创建 wallet 订单,钱包不存在
- **THEN** 系统返回 `CodeWalletNotFound` 错误码,错误消息为"钱包不存在"
#### Scenario: 套餐激活失败回滚并返回错误
- **WHEN** 后台创建订单时余额扣减成功但套餐激活失败
- **THEN** 事务回滚,钱包余额恢复,返回套餐激活失败错误(`CodeInternalError`
---
### Requirement: 后台订单创建防重复
系统 SHALL 使用幂等性检查防止同一订单重复创建和重复扣款。
幂等性策略:
- 使用 Redis 业务键:`order:idempotency:{buyer_type}:{buyer_id}:{order_type}:{carrier_type}:{carrier_id}:{sorted_package_ids}`
- TTL3 分钟
- 分布式锁:`order:create:lock:{carrier_type}:{carrier_id}`TTL 10 秒
#### Scenario: 重复创建订单返回已创建结果
- **WHEN** 代理在后台对同一张卡的同一套餐组合在 3 分钟内重复创建订单
- **THEN** 系统返回第一次创建的订单信息,不重复扣款
#### Scenario: 并发创建订单使用分布式锁
- **WHEN** 两个请求同时为同一张卡创建订单
- **THEN** 只有一个请求获取到分布式锁并创建订单,另一个请求返回"操作进行中,请勿重复提交"`CodeTooManyRequests`
#### Scenario: 幂等性 key 超时后可重新创建
- **WHEN** 订单创建成功 3 分钟后,代理再次创建相同订单
- **THEN** 系统创建新订单(因为幂等性 key 已过期)
---
### Requirement: 后台订单 API 响应格式
系统 SHALL 在后台订单创建成功后返回完整的订单信息,包含支付状态、实际支付金额、操作者信息等。
响应字段(`OrderResponse`
- `id`:订单 ID
- `order_no`:订单号
- `payment_status`:支付状态(后台订单必为 2-已支付)
- `payment_method`支付方式wallet 或 offline
- `paid_at`:支付时间(不为 NULL
- `total_amount`:订单总金额
- `actual_paid_amount`:实际支付金额(仅 wallet 有值)
- `operator_id`:操作者 ID
- `operator_type`操作者类型agent/platform
- `purchase_role`购买角色self_purchase/purchase_for_subordinate/purchased_by_platform
#### Scenario: wallet 订单响应包含实际支付金额
- **WHEN** 代理在后台创建 wallet 订单成功
- **THEN** 响应包含 `actual_paid_amount` 字段,值为实际扣减的钱包金额
#### Scenario: offline 订单响应不包含实际支付金额
- **WHEN** 平台创建 offline 订单成功
- **THEN** 响应的 `actual_paid_amount` 字段为 NULL因为线下支付不扣钱包
#### Scenario: 代购订单响应包含操作者信息
- **WHEN** 上级代理为下级代理购买套餐
- **THEN** 响应包含 `operator_id`(上级店铺 ID`operator_type` = "agent"、`purchase_role` = "purchase_for_subordinate"
---
### Requirement: 后台订单创建与 H5 端隔离
系统 SHALL 使用独立的 Service 方法处理后台订单创建,避免与 H5 端订单创建逻辑混淆。
架构设计:
- 后台:`OrderHandler.Create()``OrderService.CreateAdminOrder()`
- H5 端:`OrderHandler.Create()``OrderService.CreateH5Order()`
#### Scenario: 后台调用独立的 Service 方法
- **WHEN** 后台创建订单
- **THEN** Handler 层调用 `OrderService.CreateAdminOrder()` 方法,不调用通用的 `Create()` 方法
#### Scenario: H5 端调用独立的 Service 方法
- **WHEN** H5 端创建订单
- **THEN** Handler 层调用 `OrderService.CreateH5Order()` 方法,不影响后台订单创建逻辑
#### Scenario: Service 方法命名明确职责
- **WHEN** 开发人员查看代码
- **THEN** 方法命名(`CreateAdminOrder` vs `CreateH5Order`)清楚表明了后台和 H5 端的差异,防止误用