diff --git a/openspec/changes/reliable-order-commission-dispatch/.openspec.yaml b/openspec/changes/reliable-order-commission-dispatch/.openspec.yaml new file mode 100644 index 0000000..b6b2d1f --- /dev/null +++ b/openspec/changes/reliable-order-commission-dispatch/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-13 diff --git a/openspec/changes/reliable-order-commission-dispatch/design.md b/openspec/changes/reliable-order-commission-dispatch/design.md new file mode 100644 index 0000000..9e29d70 --- /dev/null +++ b/openspec/changes/reliable-order-commission-dispatch/design.md @@ -0,0 +1,56 @@ +## Context + +当前订单服务在事务提交后直接向 Asynq 提交 `commission:calculate`;退款服务在审批事务提交后使用裸 goroutine 回扣佣金及处理资产。两者均不与业务事实绑定:进程、Redis 或 Worker 短暂异常会分别遗留待计算订单或 `commission_deducted=false` 的已退款单。现有 Outbox 已具备状态、租约、重试与任务投递能力;见 proposal.md。 + +## Goals / Non-Goals + +**Goals:** +- 将订单佣金计算请求纳入现有 Outbox 可靠投递链路。 +- 为遗留待计算订单提供有界、幂等的补偿入口。 +- 为遗留退款后处理提供有界、幂等的补偿入口。 +- 保持现有佣金计算与订单结果兼容。 + +**Non-Goals:** +- 不改变佣金金额、分配规则或提现逻辑。 +- 不引入新队列、中间件或外部依赖。 +- 不自动修改已完成、待人工修正或未支付订单。 +- 不改变退款金额、审批结论和既有资产后处理业务规则。 + +## Decisions + +### 使用订单级稳定事件 ID 写入现有 Outbox + +订单创建/支付成功的同一数据库事务写入 `order.commission.calculate` 事件,业务键由订单 ID 派生,建立唯一约束或既有去重语义。选择 Outbox 而不是事务后重试 Asynq,因为事件能与已支付订单原子落库并具备失败状态。 + +### Relay 投递现有佣金任务类型 + +新增 Outbox 消费者仅将结构化订单 ID 投递给既有 `commission:calculate`,不改佣金计算服务。选择复用任务处理器,避免并行的计算实现和金额语义分叉。 + +### 有界扫描补偿历史待计算订单 + + Worker 启动或既有调度器以分页上限扫描已支付、`CommissionStatusPending` 的订单;缺事件或终态失败事件时按同一业务键补写/恢复事件。选择可重复扫描而非一次性数据库修复,方便部署中断恢复;扫描只恢复投递,不直接计算。 + +### 退款后处理使用退款单级 Outbox 事件 + +退款审批成功的同一事务分别写入佣金回扣与资产后处理事件,业务键按退款单和处理类型派生。消费者调用既有幂等后处理函数。选择两个独立事件以保留“佣金已回扣但资产重置待处理”的现有可观察状态;不把长资产操作放进审批事务。 + +### 有界扫描补偿已退款未完成单 + +Worker 按分页上限扫描已退款且 `commission_deducted=false` 或 `asset_reset=false` 的退款单,并按稳定业务键恢复缺失或终态失败事件。选择状态驱动扫描而非单次修数,确保已退款订单在部署或 Worker 中断后仍可恢复。 + +### 以既有终态判断实现消费幂等 + +佣金服务已对已完成和待人工修正订单跳过。补偿和消费者只产生同一业务键事件,佣金记录仍由现有计算事务落库,防止重复余额发放。 + +## Risks / Trade-offs + +- [同一订单存在旧直投与新 Outbox 任务] → 依赖既有佣金终态短路和记录事务,部署期间允许重复请求。 +- [补偿扫描加载过多历史数据] → 固定批量上限、按状态和支付状态筛选,并输出扫描计数与失败日志。 +- [事件定义/消费者漏注册] → Worker 启动时注册并在构建与手工测试中验证事件由 Relay 投递。 +- [退款审批与旧 goroutine 并发] → 新事件和既有回扣函数均以退款标记、佣金状态与回扣流水去重,部署过渡允许重复请求。 + +## Migration Plan + +1. 先发布带事件类型、消费者和补偿扫描的新 Worker/API 版本。 +2. 部署后执行订单与退款补偿扫描,核对待计算订单、已退款未回扣退款单、投递状态及资金结果。 +3. 若需回滚代码,停止补偿扫描;已持久化事件保留,恢复新版本后继续投递,不回滚订单、退款或佣金事实。 diff --git a/openspec/changes/reliable-order-commission-dispatch/proposal.md b/openspec/changes/reliable-order-commission-dispatch/proposal.md new file mode 100644 index 0000000..eac3579 --- /dev/null +++ b/openspec/changes/reliable-order-commission-dispatch/proposal.md @@ -0,0 +1,25 @@ +## Why + +资产钱包订单已支付并激活套餐后,佣金计算目前直接提交 Asynq;退款审批后佣金回扣和资产处理则以裸 goroutine 执行。两类短暂异步故障都会遗留错误资金事实,且没有可追踪、可补偿的持久化事实。测试环境已出现已退款订单佣金未回扣,需将这些关键副作用纳入可靠投递链路。 + +## What Changes + +- 在订单支付成功事务内写入唯一的佣金计算 Outbox 事件。 +- Relay 将事件可靠投递到 `commission:calculate` 队列,并记录投递状态、重试与错误摘要。 +- 为历史和异常遗留的“待计算”已支付订单提供幂等补偿扫描与可观察结果。 +- 保持既有佣金计算、佣金记录和订单结果语义;重复投递或补偿不得重复发放佣金。 +- 在退款审批事务内写入佣金回扣和资产后处理事件,替换裸 goroutine。 +- 为已退款但佣金未回扣或资产未完成处理的退款单提供幂等补偿扫描。 + +## Capabilities + +### New Capabilities +- `order-commission-delivery`: 已支付订单佣金计算的可靠投递、状态可见与幂等补偿。 + +### Modified Capabilities +- `agent-funds-commission`: 佣金计算和退款佣金回扣从尽力而为异步提交变更为可恢复的可靠副作用。 + +## Impact + +- `internal/service/order`、`internal/service/refund`、佣金/退款任务、Outbox Relay/事件注册、Worker 启动补偿与审计。 +- 新增数据库事件类型/可能的调度入口;不新增外部 API 和依赖。 diff --git a/openspec/changes/reliable-order-commission-dispatch/specs/agent-funds-commission/spec.md b/openspec/changes/reliable-order-commission-dispatch/specs/agent-funds-commission/spec.md new file mode 100644 index 0000000..cba05e7 --- /dev/null +++ b/openspec/changes/reliable-order-commission-dispatch/specs/agent-funds-commission/spec.md @@ -0,0 +1,22 @@ +## ADDED Requirements + +### Requirement: 佣金待计算状态可恢复 +系统 SHALL 将已支付订单的佣金待计算状态与可靠投递事实关联;投递异常不得静默遗留为无法继续处理的待计算订单。 + +#### Scenario: 佣金投递链路异常 +- **WHEN** 佣金计算任务提交或消费链路发生可恢复异常 +- **THEN** 订单维持待计算且投递状态、重试次数和失败摘要可查询,恢复投递后按既有规则得出有佣金、无佣金或待人工修正结果 + +### Requirement: 退款佣金回扣可靠完成 +系统 SHALL 在退款审批生效时持久化佣金回扣请求;回扣请求的投递或处理异常不得静默遗留,且退款单在全部应回扣佣金失效并完成对应钱包流水前不得标记为已回扣。 + +#### Scenario: 已退款订单佣金回扣失败后恢复 +- **WHEN** 已退款订单的佣金回扣首次处理失败或进程中断 +- **THEN** 退款单保持佣金未回扣状态并保留可重试事实,后续成功处理后佣金记录失效、佣金钱包按既有规则扣减且退款单标记为已回扣 + +### Requirement: 退款后处理可补偿 +系统 SHALL 对已退款但佣金未回扣或资产未完成后处理的退款单提供幂等补偿;重复补偿不得重复扣减佣金钱包、重复写回扣流水或重复处理资产。 + +#### Scenario: 遗留退款单补偿 +- **WHEN** 补偿流程发现已退款且 `commission_deducted=false` 的退款单 +- **THEN** 系统恢复该退款单的唯一后处理请求,并在既有回扣成功后更新其回扣完成标记 diff --git a/openspec/changes/reliable-order-commission-dispatch/specs/order-commission-delivery/spec.md b/openspec/changes/reliable-order-commission-dispatch/specs/order-commission-delivery/spec.md new file mode 100644 index 0000000..b0ce4a6 --- /dev/null +++ b/openspec/changes/reliable-order-commission-dispatch/specs/order-commission-delivery/spec.md @@ -0,0 +1,26 @@ +## Purpose + +保证已支付订单的佣金计算请求具有可追踪的可靠投递与幂等补偿能力,避免短暂异步故障导致佣金永久遗漏。 + +## ADDED Requirements + +### Requirement: 已支付订单佣金计算可靠投递 +系统 SHALL 在已支付订单的资金、订单状态和套餐激活事实提交时,持久化唯一的佣金计算投递事实;该事实须在首次投递失败后按既有可靠投递机制重试,并保存当前状态与最后失败摘要。 + +#### Scenario: 首次投递失败后恢复 +- **WHEN** 已支付订单的佣金计算首次未能提交给异步消费者 +- **THEN** 订单保持待计算,持久化投递事实保留待重试状态,后续成功投递后订单进入既有佣金计算流程 + +### Requirement: 佣金计算重复投递幂等 +系统 SHALL 容忍同一订单的佣金计算事件被重复投递、重复消费或由补偿流程再次请求,且不得创建重复佣金记录或重复增加佣金钱包余额。 + +#### Scenario: 重复消费同一订单 +- **WHEN** 已完成佣金计算的订单再次收到佣金计算请求 +- **THEN** 系统不新增佣金记录、不改变佣金钱包余额,并保留该订单既有佣金结果 + +### Requirement: 待计算订单可补偿 +系统 SHALL 对已支付且仍处于佣金待计算状态、但缺少可投递事实或投递超过重试上限的订单提供幂等补偿;补偿结果须能区分已补发、无需补发和补发失败。 + +#### Scenario: 历史待计算订单补偿 +- **WHEN** 补偿流程发现已支付且长期待计算的订单 +- **THEN** 系统为该订单建立或恢复唯一投递事实,并使其重新进入佣金计算流程而不重复发放佣金 diff --git a/openspec/changes/reliable-order-commission-dispatch/tasks.md b/openspec/changes/reliable-order-commission-dispatch/tasks.md new file mode 100644 index 0000000..1b8a640 --- /dev/null +++ b/openspec/changes/reliable-order-commission-dispatch/tasks.md @@ -0,0 +1,21 @@ +## 1. 可靠投递事件 + +- [x] 1.1 定义订单佣金计算 Outbox 事件、稳定业务键及消费者注册,并复用现有 Relay 投递 `commission:calculate`。 +- [x] 1.2 将各已支付订单路径的佣金请求改为在订单事实事务内写入唯一 Outbox 事件,移除事务后裸 Asynq 投递。 +- [x] 1.3 保持任务载荷关联 ID 与现有佣金计算终态幂等语义,补齐中文结构化日志和审计关联。 + +## 2. 退款可靠后处理 + +- [x] 2.1 定义退款佣金回扣与资产后处理 Outbox 事件及消费者,在退款审批成功事务内原子写入并移除裸 goroutine。 +- [x] 2.2 保持退款回扣流水、佣金记录失效、退款完成标记与资产后处理的既有幂等语义和审计关联。 + +## 3. 遗留事实补偿 + +- [x] 3.1 实现有界分页的待计算已支付订单扫描,为缺失或失败的投递事实幂等恢复事件。 +- [x] 3.2 实现有界分页的已退款未完成后处理扫描,为佣金未回扣或资产未处理退款单幂等恢复事件。 +- [x] 3.3 将补偿扫描接入 Worker 的既有启动/调度边界,输出订单和退款的已补发、无需补发及失败计数。 + +## 4. 验证与文档 + +- [x] 4.1 对订单事件写入、退款事件写入、Relay 重试、重复消费及两类历史补偿执行最小可复现验证,并保留 `.lh-harness/` 外的必要命令证据。 +- [x] 4.2 运行 gofmt、`go build ./cmd/api ./cmd/worker`、`openspec validate --all` 与 `./scripts/context-health.sh`,记录结果。 diff --git a/openspec/specs/agent-funds-commission/spec.md b/openspec/specs/agent-funds-commission/spec.md index 99e17a0..331d2ba 100644 --- a/openspec/specs/agent-funds-commission/spec.md +++ b/openspec/specs/agent-funds-commission/spec.md @@ -46,6 +46,33 @@ - **WHEN** 创建对应充值 - **THEN** 边界内请求进入既有支付流程,越界请求返回参数或业务错误 +### Requirement: 佣金待计算状态可恢复 + +系统 SHALL 将已支付订单的佣金待计算状态与可靠投递事实关联;投递异常不得静默遗留为无法继续处理的待计算订单。 + +#### Scenario: 佣金投递链路异常 + +- **WHEN** 佣金计算任务提交或消费链路发生可恢复异常 +- **THEN** 订单维持待计算且投递状态、重试次数和失败摘要可查询,恢复投递后按既有规则得出有佣金、无佣金或待人工修正结果 + +### Requirement: 退款佣金回扣可靠完成 + +系统 SHALL 在退款审批生效时持久化佣金回扣请求;回扣请求的投递或处理异常不得静默遗留,且退款单在全部应回扣佣金失效并完成对应钱包流水前不得标记为已回扣。 + +#### Scenario: 已退款订单佣金回扣失败后恢复 + +- **WHEN** 已退款订单的佣金回扣首次处理失败或进程中断 +- **THEN** 退款单保持佣金未回扣状态并保留可重试事实,后续成功处理后佣金记录失效、佣金钱包按既有规则扣减且退款单标记为已回扣 + +### Requirement: 退款后处理可补偿 + +系统 SHALL 对已退款但佣金未回扣或资产未完成后处理的退款单提供幂等补偿;重复补偿不得重复扣减佣金钱包、重复写回扣流水或重复处理资产。 + +#### Scenario: 遗留退款单补偿 + +- **WHEN** 补偿流程发现已退款且 `commission_deducted=false` 的退款单 +- **THEN** 系统恢复该退款单的唯一后处理请求,并在既有回扣成功后更新其回扣完成标记 + ## 可达操作索引 本节只用于入口导航,不是行为 Requirement;业务义务以上述 Requirements 为准。 @@ -65,3 +92,30 @@ ### 提现配置管理 `GET /api/admin/commission/withdrawal-settings`(提现配置列表);`POST /api/admin/commission/withdrawal-settings`(新增提现配置);`GET /api/admin/commission/withdrawal-settings/current`(获取当前生效的提现配置)。 + +### Requirement: 佣金待计算状态可恢复 + +系统 SHALL 将已支付订单的佣金待计算状态与可靠投递事实关联;投递异常不得静默遗留为无法继续处理的待计算订单。 + +#### Scenario: 佣金投递链路异常 + +- **WHEN** 佣金计算任务提交或消费链路发生可恢复异常 +- **THEN** 订单维持待计算且投递状态、重试次数和失败摘要可查询,恢复投递后按既有规则得出有佣金、无佣金或待人工修正结果 + +### Requirement: 退款佣金回扣可靠完成 + +系统 SHALL 在退款审批生效时持久化佣金回扣请求;回扣请求的投递或处理异常不得静默遗留,且退款单在全部应回扣佣金失效并完成对应钱包流水前不得标记为已回扣。 + +#### Scenario: 已退款订单佣金回扣失败后恢复 + +- **WHEN** 已退款订单的佣金回扣首次处理失败或进程中断 +- **THEN** 退款单保持佣金未回扣状态并保留可重试事实,后续成功处理后佣金记录失效、佣金钱包按既有规则扣减且退款单标记为已回扣 + +### Requirement: 退款后处理可补偿 + +系统 SHALL 对已退款但佣金未回扣或资产未完成后处理的退款单提供幂等补偿;重复补偿不得重复扣减佣金钱包、重复写回扣流水或重复处理资产。 + +#### Scenario: 遗留退款单补偿 + +- **WHEN** 补偿流程发现已退款且 `commission_deducted=false` 的退款单 +- **THEN** 系统恢复该退款单的唯一后处理请求,并在既有回扣成功后更新其回扣完成标记 diff --git a/openspec/specs/order-commission-delivery/spec.md b/openspec/specs/order-commission-delivery/spec.md new file mode 100644 index 0000000..ae7bbc0 --- /dev/null +++ b/openspec/specs/order-commission-delivery/spec.md @@ -0,0 +1,34 @@ +# 订单佣金可靠投递当前行为 + +## Purpose + +保证已支付订单的佣金计算请求具有可追踪的可靠投递与幂等补偿能力,避免短暂异步故障导致佣金永久遗漏。 + +## Requirements + +### Requirement: 已支付订单佣金计算可靠投递 + +系统 SHALL 在已支付订单的资金、订单状态和套餐激活事实提交时,持久化唯一的佣金计算投递事实;该事实须在首次投递失败后按既有可靠投递机制重试,并保存当前状态与最后失败摘要。 + +#### Scenario: 首次投递失败后恢复 + +- **WHEN** 佣金计算首次未能提交给异步消费者 +- **THEN** 订单保持待计算,持久化投递事实保留待重试状态,后续成功投递后订单进入既有佣金计算流程 + +### Requirement: 佣金计算重复投递幂等 + +系统 SHALL 容忍同一订单的佣金计算事件被重复投递、重复消费或由补偿流程再次请求,且不得创建重复佣金记录或重复增加佣金钱包余额。 + +#### Scenario: 重复消费同一订单 + +- **WHEN** 已完成佣金计算的订单再次收到佣金计算请求 +- **THEN** 系统不新增佣金记录、不改变佣金钱包余额,并保留该订单既有佣金结果 + +### Requirement: 待计算订单可补偿 + +系统 SHALL 对已支付且仍处于佣金待计算状态、但缺少可投递事实或投递超过重试上限的订单提供幂等补偿;补偿结果须能区分已补发、无需补发和补发失败。 + +#### Scenario: 历史待计算订单补偿 + +- **WHEN** 补偿流程发现已支付且长期待计算的订单 +- **THEN** 系统为该订单建立或恢复唯一投递事实,并使其重新进入佣金计算流程而不重复发放佣金