# 订单激活幂等性修复 - 实现任务

## 1. 状态原子转换

- [x] 1.1 在 `internal/service/order/service.go` 中修改钱包支付和支付回调逻辑
  - 将支付状态更新改为条件更新：`WHERE id = ? AND payment_status = ?`（只允许 pending -> paid）
  - 使用 `result.RowsAffected == 0` 检查是否已被其他请求处理
- [x] 1.2 实现重复请求处理逻辑
  - 当 `RowsAffected == 0` 时，重新查询订单当前状态
  - 订单状态为 `model.PaymentStatusPaid`：返回 `nil`（幂等成功）
  - 订单状态为 `model.PaymentStatusCancelled`：返回 `errors.New(errors.CodeInvalidStatus, "订单已取消，无法支付")`
  - 订单状态为 `model.PaymentStatusRefunded`：返回 `errors.New(errors.CodeInvalidStatus, "订单已退款，无法支付")`

## 2. 激活幂等与事务一致性

- [x] 2.1 调整 `activatePackage` 调用时机
  - 只在条件更新成功（`RowsAffected > 0`）后执行
  - 幂等场景（订单已支付）直接返回，不再调用 `activatePackage`
- [x] 2.2 审查 `activatePackage` 内部数据访问
  - 确保所有订单明细、套餐信息查询使用事务 `tx` 参数
  - 避免使用非事务的 `s.db` 或 `s.orderStore` 直接查询（应使用 `tx` 包装的 Store）

## 3. 防御性约束（可选）

- [x] 3.1 创建数据库迁移文件
  - 在 `migrations/` 目录创建迁移文件对（up/down）
  - 文件命名：`000033_add_unique_index_package_usage_order_package.up.sql`
  - 文件命名：`000033_add_unique_index_package_usage_order_package.down.sql`
  - Up 内容：`CREATE UNIQUE INDEX idx_package_usage_order_package ON tb_package_usage(order_id, package_id) WHERE deleted_at IS NULL;`
  - Down 内容：`DROP INDEX IF EXISTS idx_package_usage_order_package;`
- [x] 3.2 代码中处理唯一冲突
  - 在 `activatePackage` 插入 `tb_package_usage` 前，先查询是否已存在
  - 若已存在：记录警告日志，返回成功（防御性幂等）
  - 若插入失败且为唯一冲突错误：返回成功（数据库层防御）

## 4. 测试与验证

- [x] 4.1 新增集成测试到 `internal/service/order/service_test.go`
  - **幂等支付测试**：串行多次调用 `WalletPay`，验证只生成一条 `PackageUsage` 记录
  - **重复回调测试**：连续多次调用支付回调，验证返回成功且不重复插入
  - **已取消订单支付**：创建已取消订单，调用支付接口，验证返回错误码 1050
  - **已取消订单回调**：创建已取消订单，调用支付回调，验证返回错误码 1050
- [x] 4.2 运行测试并验证
  - 执行 `source .env.local && go test -v ./internal/service/order/...`
  - 确保所有测试通过
  - 测试覆盖率：71.7%

## 5. 文档更新

- [x] 5.1 创建功能总结文档
  - 创建目录：`docs/fix-order-activation-idempotency/`
  - 创建文件：`docs/fix-order-activation-idempotency/功能总结.md`
  - 内容包含：问题背景、解决方案、技术实现、测试验证
- [x] 5.2 更新 README.md（如有必要）
  - 内部幂等性修复，无需更新 README.md

