Files
huang d977000a66
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m40s
feat: 归档佣金计算触发和快照变更,同步规范文档
- 归档 OpenSpec 变更到 archive 目录
- 创建 2 个新的主规范文件:commission-trigger 和 order-commission-snapshot
- 实现订单佣金快照字段和支付自动触发
- 确保事务一致性,所有佣金操作在同一事务内完成
- 提取成本价计算为公共工具函数
2026-01-29 14:58:35 +08:00

125 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# commission-trigger Specification
## Purpose
TBD - created by archiving change fix-commission-calculation-trigger-and-snapshot. Update Purpose after archive.
## Requirements
### Requirement: 支付成功后自动入队佣金计算任务
系统 SHALL 在订单首次支付成功时自动 enqueue 佣金计算异步任务(`commission:calculate`),确保佣金及时发放。
**触发条件**
- 订单从"待支付"变为"已支付"(首次成功支付)
- 订单 `commission_status``pending`(未计算)
**任务参数**
- 任务类型:`commission:calculate`
- Payload`{"order_id": <订单ID>}`
#### Scenario: 首次支付成功触发计算
- **WHEN** 订单支付成功,订单状态从"待支付"变为"已支付"
- **THEN** 系统自动 enqueue `commission:calculate` 任务payload 包含订单 ID
- **AND** 订单 `commission_status` 保持为 `pending`(任务执行后才更新为 `calculated`
#### Scenario: 重复支付不重复触发
- **WHEN** 订单已经是"已支付"状态,再次收到支付成功通知(幂等场景)
- **THEN** 系统不重复 enqueue 佣金计算任务
- **AND** 日志记录"订单已支付,跳过重复入队"
#### Scenario: 已计算佣金的订单不触发
- **WHEN** 订单 `commission_status``calculated`(已计算)
- **THEN** 系统跳过入队操作
- **AND** 日志记录"订单佣金已计算,跳过入队"
---
### Requirement: 入队失败不影响支付主链路
系统 SHALL 确保佣金任务入队失败时不回滚订单支付成功状态,保障主业务链路稳定。
**失败处理策略**
- 入队失败时记录 ERROR 级别日志(包含订单 ID、失败原因
- 订单状态保持为"已支付",不回滚
- 订单 `commission_status` 保持为 `pending`,允许后续补偿
**补偿机制**
- 后台补偿任务扫描 `commission_status=pending` 且已支付的订单
- 人工触发佣金计算(后台接口)
- 定时任务重试入队(可选)
#### Scenario: 入队失败记录日志
- **WHEN** 佣金任务入队失败(队列服务不可用或网络超时)
- **THEN** 系统记录 ERROR 日志,包含订单 ID、失败原因、队列配置信息
- **AND** 订单支付状态保持为"已支付",不回滚
#### Scenario: 失败后允许补偿
- **WHEN** 后台补偿任务扫描到 `commission_status=pending``payment_status=paid` 的订单
- **THEN** 系统可重新 enqueue 佣金计算任务或直接执行计算
- **AND** 避免佣金永久丢失
---
### Requirement: 佣金计算任务幂等性
系统 SHALL 确保佣金计算任务可重复执行,不重复发放佣金。
**幂等检查**
- 任务执行前检查订单 `commission_status`
- 如果已为 `calculated`,跳过计算并返回成功
**状态更新**
- 计算完成后将订单 `commission_status` 更新为 `calculated`
- 状态更新与佣金记录创建在同一事务中
#### Scenario: 任务重复执行跳过计算
- **WHEN** 佣金计算任务执行时,订单 `commission_status` 已为 `calculated`
- **THEN** 系统跳过佣金计算和钱包入账操作
- **AND** 任务返回成功(避免 Asynq 重试)
- **AND** 日志记录"订单佣金已计算,跳过执行"
#### Scenario: 并发任务只有一个成功
- **WHEN** 同一订单的佣金计算任务被重复入队,两个 worker 并发执行
- **THEN** 第一个任务成功完成计算并更新状态为 `calculated`
- **AND** 第二个任务检查到状态已为 `calculated`,跳过计算
#### Scenario: 任务失败可安全重试
- **WHEN** 佣金计算任务执行失败(数据库异常、钱包服务不可用)
- **THEN** Asynq 自动重试任务
- **AND** 重试时幂等检查确保不重复发放佣金
---
### Requirement: 队列客户端依赖注入
系统 SHALL 通过依赖注入方式将队列客户端注入到订单服务,遵循现有 bootstrap 架构。
**注入位置**
- `internal/service/order/service.go``Service` 结构体
- 添加 `queueClient *asynq.Client` 字段
**注入方式**
-`internal/bootstrap/services.go` 中初始化订单服务时传入队列客户端
- 队列客户端在 `bootstrap.Bootstrap()` 中统一创建
#### Scenario: 订单服务接收队列客户端
- **WHEN** 系统启动时执行 `bootstrap.Bootstrap()`
- **THEN** 订单服务(`order.Service`)通过构造函数接收队列客户端实例
- **AND** 队列客户端可在服务内部调用 `Enqueue()` 方法
#### Scenario: 支付成功时调用队列客户端
- **WHEN** 订单支付成功,订单服务执行入队操作
- **THEN** 系统通过注入的队列客户端调用 `Enqueue("commission:calculate", payload)`
- **AND** 不在服务内部直接创建队列客户端(遵循依赖注入原则)
---