Files
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

10 KiB
Raw Permalink Blame History

Admin Order Creation

Purpose

后台订单创建流程,为代理和平台账号提供订单创建功能。与 H5 端订单创建的核心区别:后台仅支持 wallet/offline 支付方式,且 wallet 支付立即完成扣款和套餐激活(一步到位),不创建待支付订单。

This capability supports:

  • 参数验证和支付方式限制
  • 钱包余额检查和一步扣款
  • 权限校验(代理、平台、超管)
  • 错误处理和防御性编程

ADDED Requirements

Requirement: 后台订单创建 API 参数验证

系统 SHALL 在后台订单创建 API 中强制验证请求参数,拒绝非法的支付方式。

后台订单创建使用独立的 DTOCreateAdminOrderRequest),仅允许 walletoffline 两种支付方式。Handler 层 MUST 调用 middleware.ValidateStruct(&req) 验证参数,确保 DTO 的 validate:"oneof=wallet offline" 规则生效。

Scenario: DTO 验证拒绝非法支付方式

  • WHEN 后台创建订单请求中 payment_methodwechatalipay
  • THEN 系统在 Handler 层验证失败,返回错误"请求参数解析失败"CodeInvalidParam),订单创建失败

Scenario: DTO 验证拒绝空支付方式

  • WHEN 后台创建订单请求中缺少 payment_method 字段或值为空字符串
  • THEN 系统在 Handler 层验证失败,返回错误"请求参数解析失败"CodeInvalidParam),订单创建失败

Scenario: DTO 验证允许 wallet 支付

  • WHEN 后台创建订单请求中 payment_methodwallet
  • THEN 系统通过 DTO 验证,继续后续业务逻辑

Scenario: DTO 验证允许 offline 支付

  • WHEN 后台创建订单请求中 payment_methodoffline
  • 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_methodwechat(虽然 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 = 2paid_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 = 2paid_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(上级店铺 IDoperator_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 端的差异,防止误用