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