# 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,不影响现有业务