fix: 修复代理钱包订单创建逻辑,拆分后台/H5端下单方法并归档变更
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
- 拆分订单创建为 CreateAdminOrder(后台一步支付)和 CreateH5Order(H5 两步支付) - 新增 CreateAdminOrderRequest DTO,后台仅允许 wallet/offline 支付方式 - 同步 delta specs 到主规格(order-payment 更新 + admin-order-creation 新增) - 归档 fix-agent-wallet-order-creation 变更 - 新增 implement-order-expiration 变更提案
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 订单状态流转
|
||||
|
||||
系统 SHALL 管理订单的状态流转,确保状态变更符合业务规则。**新增订单超时自动取消的详细场景。**
|
||||
|
||||
**状态定义**:
|
||||
- **1-待支付**: 订单已创建,等待用户支付
|
||||
- **2-已支付**: 用户已支付,等待系统处理
|
||||
- **3-已完成**: 订单已完成(激活/发货等)
|
||||
- **4-已取消**: 订单已取消
|
||||
- **5-已退款**: 订单已退款
|
||||
|
||||
**状态流转规则**:
|
||||
- 待支付(1) → 已支付(2): 用户完成支付
|
||||
- 待支付(1) → 已取消(4): 用户手动取消订单或订单超时(30 分钟)
|
||||
- 已支付(2) → 已完成(3): 系统完成订单处理(激活/发货)
|
||||
- 已支付(2) → 已退款(5): 用户申请退款且审核通过
|
||||
- 已完成(3) → 已退款(5): 用户申请退款且审核通过(特殊情况)
|
||||
|
||||
#### Scenario: 用户支付订单
|
||||
|
||||
- **WHEN** 用户支付待支付订单(ID 为 10001),支付金额为 30.00 元
|
||||
- **THEN** 系统将订单状态从 1(待支付) 变更为 2(已支付),`paid_at` 记录支付时间
|
||||
|
||||
#### Scenario: 单卡套餐订单完成
|
||||
|
||||
- **WHEN** 系统处理完单卡套餐订单(ID 为 10001),激活 IoT 卡并分配套餐
|
||||
- **THEN** 系统将订单状态从 2(已支付) 变更为 3(已完成),`completed_at` 记录完成时间
|
||||
|
||||
#### Scenario: 设备级套餐订单完成
|
||||
|
||||
- **WHEN** 系统处理完设备级套餐订单(ID 为 10002),为设备绑定的所有 IoT 卡分配套餐
|
||||
- **THEN** 系统将订单状态从 2(已支付) 变更为 3(已完成),`completed_at` 记录完成时间
|
||||
|
||||
#### Scenario: 用户手动取消订单
|
||||
|
||||
- **WHEN** 用户手动取消待支付订单(ID 为 10003)
|
||||
- **THEN** 系统将订单状态从 1(待支付) 变更为 4(已取消),`expires_at` 设置为 NULL,如有钱包预扣则解冻余额
|
||||
|
||||
#### Scenario: 订单超时自动取消
|
||||
|
||||
- **WHEN** 订单创建后 30 分钟未支付,定时任务扫描到该订单
|
||||
- **THEN** 系统自动将订单状态从 1(待支付) 变更为 4(已取消),`expires_at` 设置为 NULL,如有钱包预扣则解冻余额
|
||||
|
||||
#### Scenario: 订单超时自动取消(混合支付)
|
||||
|
||||
- **WHEN** 混合支付订单创建后 30 分钟未完成在线支付,钱包已预扣 2000 分
|
||||
- **THEN** 系统自动取消订单,解冻钱包余额 2000 分
|
||||
|
||||
#### Scenario: 订单超时自动取消(纯在线支付)
|
||||
|
||||
- **WHEN** 纯在线支付订单创建后 30 分钟未支付
|
||||
- **THEN** 系统自动取消订单,无需钱包解冻操作
|
||||
@@ -0,0 +1,237 @@
|
||||
# Order Expiration
|
||||
|
||||
## Purpose
|
||||
|
||||
自动管理订单的超时失效,确保待支付订单在超时后自动取消,防止"僵尸订单"堆积,并自动释放已冻结的资源(如钱包余额)。
|
||||
|
||||
This capability supports:
|
||||
- 订单超时时间配置和管理
|
||||
- 定时扫描和自动取消超时订单
|
||||
- 钱包余额自动解冻
|
||||
- 过期订单查询和筛选
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 订单过期时间字段
|
||||
|
||||
系统 SHALL 为每个订单设置过期时间字段(`expires_at`),用于判断订单是否超时。
|
||||
|
||||
**字段定义**:
|
||||
- `expires_at`:订单过期时间(TIMESTAMP,可为 NULL)
|
||||
- 创建时自动设置:`expires_at = created_at + 30分钟`(仅待支付订单)
|
||||
- 已支付/已取消/已退款订单的 `expires_at` 为 NULL
|
||||
|
||||
**索引设计**:
|
||||
- 复合索引:`idx_order_expires(expires_at, payment_status)` 优化定时任务查询
|
||||
|
||||
#### Scenario: 创建待支付订单时设置过期时间
|
||||
|
||||
- **WHEN** 用户创建订单,支付方式为 wechat 或 alipay,订单状态为待支付(payment_status = 1)
|
||||
- **THEN** 系统设置 `expires_at = created_at + 30分钟`
|
||||
|
||||
#### Scenario: 创建钱包支付订单(后台)不设置过期时间
|
||||
|
||||
- **WHEN** 代理在后台创建订单,支付方式为 wallet,订单立即支付成功(payment_status = 2)
|
||||
- **THEN** 系统不设置 `expires_at`,字段值为 NULL
|
||||
|
||||
#### Scenario: 订单支付成功后清除过期时间
|
||||
|
||||
- **WHEN** 待支付订单支付成功,状态变更为已支付(payment_status = 2)
|
||||
- **THEN** 系统将 `expires_at` 设置为 NULL
|
||||
|
||||
#### Scenario: 订单取消后清除过期时间
|
||||
|
||||
- **WHEN** 订单被取消(payment_status = 3)
|
||||
- **THEN** 系统将 `expires_at` 设置为 NULL
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 订单超时自动取消
|
||||
|
||||
系统 SHALL 通过定时任务自动扫描并取消超时订单。任务每分钟执行一次,批量处理超时订单。
|
||||
|
||||
**任务配置**:
|
||||
- 任务类型:`TaskTypeOrderExpire = "order:expire"`
|
||||
- 执行频率:每分钟
|
||||
- 单批处理量:最多 100 条
|
||||
- 超时时间:`OrderExpireTimeout = 30 * time.Minute`
|
||||
|
||||
**任务逻辑**:
|
||||
1. 查询条件:`expires_at <= NOW() AND payment_status = 1`
|
||||
2. 批量取消订单:更新 `payment_status = 3`,`expires_at = NULL`
|
||||
3. 钱包余额解冻(如果订单涉及钱包预扣)
|
||||
4. 记录日志
|
||||
|
||||
#### Scenario: 定时任务扫描超时订单
|
||||
|
||||
- **WHEN** 定时任务执行,当前时间为 2026-02-28 10:30:00
|
||||
- **THEN** 系统查询 `expires_at <= '2026-02-28 10:30:00' AND payment_status = 1` 的订单,最多 100 条
|
||||
|
||||
#### Scenario: 批量取消超时订单
|
||||
|
||||
- **WHEN** 查询到 50 条超时订单
|
||||
- **THEN** 系统批量更新订单状态为已取消(payment_status = 3),`expires_at = NULL`
|
||||
|
||||
#### Scenario: 钱包余额解冻(混合支付)
|
||||
|
||||
- **WHEN** 超时订单使用了混合支付,钱包预扣 2000 分
|
||||
- **THEN** 系统解冻钱包余额 2000 分(`frozen_balance` 减少 2000)
|
||||
|
||||
#### Scenario: 钱包余额解冻(纯钱包支付,H5 端)
|
||||
|
||||
- **WHEN** 超时订单使用了钱包支付(H5 端创建待支付订单),钱包预扣 3000 分
|
||||
- **THEN** 系统解冻钱包余额 3000 分
|
||||
|
||||
#### Scenario: 无需解冻钱包(在线支付)
|
||||
|
||||
- **WHEN** 超时订单使用了纯在线支付(wechat/alipay),没有钱包预扣
|
||||
- **THEN** 系统不执行钱包解冻操作
|
||||
|
||||
#### Scenario: 任务执行日志
|
||||
|
||||
- **WHEN** 定时任务执行完成
|
||||
- **THEN** 系统记录日志:处理订单数量、解冻钱包次数、执行耗时
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 订单过期状态查询
|
||||
|
||||
系统 SHALL 支持按过期状态筛选订单,便于运营人员查询和分析超时订单。
|
||||
|
||||
**查询条件**(新增):
|
||||
- `is_expired`(布尔值):
|
||||
- `true`:查询已过期的待支付订单(`expires_at <= NOW() AND payment_status = 1`)
|
||||
- `false`:查询未过期的待支付订单(`expires_at > NOW() AND payment_status = 1`)
|
||||
- 不传:不按过期状态筛选
|
||||
|
||||
#### Scenario: 查询已过期的待支付订单
|
||||
|
||||
- **WHEN** 运营人员查询订单列表,筛选 `is_expired = true`
|
||||
- **THEN** 系统返回 `expires_at <= NOW() AND payment_status = 1` 的订单列表
|
||||
|
||||
#### Scenario: 查询未过期的待支付订单
|
||||
|
||||
- **WHEN** 运营人员查询订单列表,筛选 `is_expired = false`
|
||||
- **THEN** 系统返回 `expires_at > NOW() AND payment_status = 1` 的订单列表
|
||||
|
||||
#### Scenario: 订单详情显示过期状态
|
||||
|
||||
- **WHEN** 查询订单详情,订单为待支付且已超时
|
||||
- **THEN** 响应包含 `is_expired = true`,`expires_at` 字段显示过期时间
|
||||
|
||||
#### Scenario: 订单列表响应包含过期时间
|
||||
|
||||
- **WHEN** 查询订单列表
|
||||
- **THEN** 每个订单响应包含 `expires_at` 字段(可为 NULL)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 钱包余额解冻逻辑
|
||||
|
||||
系统 SHALL 在订单取消(手动或自动)时,根据支付方式自动解冻钱包余额。
|
||||
|
||||
**解冻规则**:
|
||||
- 钱包支付(H5 端待支付订单):解冻 `total_amount`
|
||||
- 混合支付:解冻 `wallet_payment_amount`
|
||||
- 纯在线支付:无需解冻
|
||||
- 后台钱包一步支付:无需解冻(订单创建时已完成支付)
|
||||
|
||||
#### Scenario: 手动取消订单,解冻钱包
|
||||
|
||||
- **WHEN** 用户手动取消待支付订单,订单使用混合支付,钱包预扣 2000 分
|
||||
- **THEN** 系统解冻钱包余额 2000 分,订单状态变更为已取消
|
||||
|
||||
#### Scenario: 自动取消订单,解冻钱包
|
||||
|
||||
- **WHEN** 定时任务自动取消超时订单,订单使用钱包支付,钱包预扣 3000 分
|
||||
- **THEN** 系统解冻钱包余额 3000 分,订单状态变更为已取消
|
||||
|
||||
#### Scenario: 取消订单,无钱包预扣
|
||||
|
||||
- **WHEN** 用户取消待支付订单,订单使用纯在线支付(wechat)
|
||||
- **THEN** 系统不执行钱包解冻操作
|
||||
|
||||
#### Scenario: 钱包解冻事务保证
|
||||
|
||||
- **WHEN** 订单取消涉及钱包解冻
|
||||
- **THEN** 订单状态更新和钱包余额解冻在同一事务中完成,任一失败则全部回滚
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 超时配置常量
|
||||
|
||||
系统 SHALL 定义订单超时相关常量,统一管理超时时间和任务类型。
|
||||
|
||||
**常量定义**(`pkg/constants/constants.go`):
|
||||
- `OrderExpireTimeout = 30 * time.Minute`:订单超时时间(30 分钟)
|
||||
- `TaskTypeOrderExpire = "order:expire"`:订单超时取消任务类型
|
||||
|
||||
#### Scenario: 使用常量设置过期时间
|
||||
|
||||
- **WHEN** 创建待支付订单
|
||||
- **THEN** 系统使用 `constants.OrderExpireTimeout` 计算 `expires_at`
|
||||
|
||||
#### Scenario: 使用常量注册任务
|
||||
|
||||
- **WHEN** 注册 Asynq 定时任务
|
||||
- **THEN** 系统使用 `constants.TaskTypeOrderExpire` 作为任务类型
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 性能优化
|
||||
|
||||
系统 SHALL 通过索引优化和批量处理确保超时任务的性能符合要求。
|
||||
|
||||
**性能指标**:
|
||||
- 定时任务查询耗时 < 50ms
|
||||
- 单批次处理耗时 < 5s
|
||||
- 单批处理量:100 条
|
||||
|
||||
**优化措施**:
|
||||
- 使用复合索引 `idx_order_expires(expires_at, payment_status)` 优化查询
|
||||
- 批量更新订单状态(单 SQL 语句)
|
||||
- 钱包解冻支持批量操作(单事务)
|
||||
|
||||
#### Scenario: 复合索引优化查询
|
||||
|
||||
- **WHEN** 定时任务查询超时订单
|
||||
- **THEN** 数据库使用 `idx_order_expires` 索引,查询耗时 < 50ms
|
||||
|
||||
#### Scenario: 批量处理限制
|
||||
|
||||
- **WHEN** 超时订单数量超过 100 条
|
||||
- **THEN** 系统单次最多处理 100 条,剩余订单下次执行时处理
|
||||
|
||||
#### Scenario: 任务执行时间限制
|
||||
|
||||
- **WHEN** 定时任务执行
|
||||
- **THEN** 单批次处理耗时 < 5s,包括查询、更新、解冻、日志记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 数据库迁移
|
||||
|
||||
系统 SHALL 提供数据库迁移脚本,添加 `expires_at` 字段和索引。
|
||||
|
||||
**迁移内容**:
|
||||
- 添加字段:`ALTER TABLE tb_order ADD COLUMN expires_at TIMESTAMP NULL COMMENT '订单过期时间'`
|
||||
- 添加索引:`CREATE INDEX idx_order_expires ON tb_order(expires_at, payment_status)`
|
||||
|
||||
**回滚脚本**:
|
||||
- 删除索引:`DROP INDEX idx_order_expires ON tb_order`
|
||||
- 删除字段:`ALTER TABLE tb_order DROP COLUMN expires_at`
|
||||
|
||||
#### Scenario: 迁移脚本执行成功
|
||||
|
||||
- **WHEN** 执行 `migrate up`
|
||||
- **THEN** `tb_order` 表新增 `expires_at` 字段和 `idx_order_expires` 索引
|
||||
|
||||
#### Scenario: 回滚脚本执行成功
|
||||
|
||||
- **WHEN** 执行 `migrate down`
|
||||
- **THEN** `tb_order` 表删除 `expires_at` 字段和 `idx_order_expires` 索引
|
||||
|
||||
#### Scenario: 迁移对现有数据的影响
|
||||
|
||||
- **WHEN** 执行迁移脚本
|
||||
- **THEN** 已存在的订单 `expires_at` 字段值为 NULL,不影响现有业务
|
||||
@@ -0,0 +1,67 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 订单支付处理
|
||||
|
||||
系统 SHALL 根据支付方式正确处理订单支付,包括钱包扣款、在线支付、混合支付等。**新增订单取消(手动或自动)时的钱包余额解冻逻辑。**
|
||||
|
||||
**钱包支付流程**:
|
||||
1. 检查钱包可用余额是否充足
|
||||
2. 冻结钱包余额(`frozen_balance` 增加)
|
||||
3. 创建订单,状态为"待支付"
|
||||
4. 订单完成后,扣减钱包余额(`balance` 减少,`frozen_balance` 减少),创建钱包明细记录
|
||||
5. 订单取消时(手动或自动),解冻钱包余额(`frozen_balance` 减少)
|
||||
|
||||
**在线支付流程**:
|
||||
1. 创建订单,状态为"待支付"
|
||||
2. 调用第三方支付接口
|
||||
3. 用户完成支付后,订单状态变更为"已支付"
|
||||
4. 订单完成后,订单状态变更为"已完成"
|
||||
|
||||
**混合支付流程**:
|
||||
1. 检查钱包可用余额是否充足(钱包支付部分)
|
||||
2. 冻结钱包余额
|
||||
3. 创建订单,状态为"待支付"
|
||||
4. 调用第三方支付接口(在线支付部分)
|
||||
5. 用户完成在线支付后,扣减钱包余额,订单状态变更为"已支付"
|
||||
6. 订单完成后,订单状态变更为"已完成"
|
||||
7. 订单取消时(手动或自动),解冻钱包余额
|
||||
|
||||
#### Scenario: 钱包支付订单完成
|
||||
|
||||
- **WHEN** 用户使用钱包支付购买套餐,订单金额为 3000 分
|
||||
- **THEN** 系统:
|
||||
1. 创建订单,状态为"待支付",冻结钱包余额 3000 分
|
||||
2. 订单处理完成后,扣减钱包余额 3000 分,解冻 3000 分,创建钱包明细记录(类型为"扣款"),订单状态变更为"已完成"
|
||||
|
||||
#### Scenario: 混合支付订单完成
|
||||
|
||||
- **WHEN** 用户使用混合支付购买套餐,钱包支付 2000 分 + 在线支付 3000 分
|
||||
- **THEN** 系统:
|
||||
1. 创建订单,状态为"待支付",冻结钱包余额 2000 分
|
||||
2. 用户完成在线支付 3000 分后,扣减钱包余额 2000 分,解冻 2000 分,创建钱包明细记录,订单状态变更为"已支付"
|
||||
3. 订单处理完成后,订单状态变更为"已完成"
|
||||
|
||||
#### Scenario: 订单手动取消,解冻钱包余额
|
||||
|
||||
- **WHEN** 用户使用钱包支付创建订单,订单金额为 3000 分,然后手动取消订单
|
||||
- **THEN** 系统解冻钱包余额 3000 分(`frozen_balance` 减少 3000),订单状态变更为"已取消"
|
||||
|
||||
#### Scenario: 订单超时自动取消,解冻钱包余额
|
||||
|
||||
- **WHEN** 用户使用混合支付创建订单,钱包预扣 2000 分,30 分钟后订单超时
|
||||
- **THEN** 系统自动取消订单,解冻钱包余额 2000 分(`frozen_balance` 减少 2000),订单状态变更为"已取消"
|
||||
|
||||
#### Scenario: 订单取消(纯在线支付),无需解冻
|
||||
|
||||
- **WHEN** 用户使用纯在线支付创建订单,30 分钟后订单超时
|
||||
- **THEN** 系统自动取消订单,不执行钱包解冻操作(因为没有钱包预扣)
|
||||
|
||||
#### Scenario: 钱包解冻事务保证
|
||||
|
||||
- **WHEN** 订单取<E58D95><E58F96>涉及钱包解冻
|
||||
- **THEN** 订单状态更新(`payment_status = 3`、`expires_at = NULL`)和钱包余额解冻在同一事务中完成,任一失败则全部回滚
|
||||
|
||||
#### Scenario: 钱包解冻失败回滚
|
||||
|
||||
- **WHEN** 订单取消时,钱包解冻失败(如钱包不存在、冻结余额不足)
|
||||
- **THEN** 事务回滚,订单状态不变,返回错误信息"订单取消失败"
|
||||
Reference in New Issue
Block a user