fix: 修复订单支付幂等性问题,防止重复激活套餐
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m22s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m22s
- 使用条件更新实现支付状态原子转换(pending -> paid) - 重复请求返回幂等成功,不再重复激活套餐 - 新增 tb_package_usage 唯一索引(order_id, package_id) - 新增幂等性和异常状态测试,测试覆盖率 71.7% - 归档 OpenSpec 变更 fix-order-activation-idempotency
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# 订单激活幂等性修复 - 实现任务
|
||||
|
||||
## 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
|
||||
|
||||
Reference in New Issue
Block a user