- 拆分订单创建为 CreateAdminOrder(后台一步支付)和 CreateH5Order(H5 两步支付) - 新增 CreateAdminOrderRequest DTO,后台仅允许 wallet/offline 支付方式 - 同步 delta specs 到主规格(order-payment 更新 + admin-order-creation 新增) - 归档 fix-agent-wallet-order-creation 变更 - 新增 implement-order-expiration 变更提案
10 KiB
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} - TTL:3 分钟
- 分布式锁:
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:订单 IDorder_no:订单号payment_status:支付状态(后台订单必为 2-已支付)payment_method:支付方式(wallet 或 offline)paid_at:支付时间(不为 NULL)total_amount:订单总金额actual_paid_amount:实际支付金额(仅 wallet 有值)operator_id:操作者 IDoperator_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 方法命名(
CreateAdminOrdervsCreateH5Order)清楚表明了后台和 H5 端的差异,防止误用