All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m45s
- 归档 fix-one-time-commission-config-and-accumulation 到 archive/2026-01-29-* - 同步 delta specs 到主规范(one-time-commission-trigger、commission-calculation) - 新增累计触发逻辑文档和测试用例 - 修复一次性佣金配置落库和累计充值更新逻辑
8.1 KiB
8.1 KiB
累计触发一次性佣金逻辑流程
概述
一次性佣金支持两种触发方式:
- 单次充值触发:单笔订单金额达到阈值即发放
- 累计充值触发:累计多次充值金额达到阈值后发放(本文档重点)
累计触发流程
┌─────────────────────────────────────────────────────────────┐
│ 用户购买套餐 │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────▼──────────────┐
│ 支付成功触发佣金计算 │
└────────────┬──────────────┘
│
┌────────────▼──────────────┐
│ 读取一次性佣金配置 │
│ - EnableOneTimeCommission │
│ - Trigger = accumulated │
│ - Threshold 阈值 │
└────────────┬──────────────┘
│
┌────────────▼──────────────┐
│ 累计金额 += 本次订单金额 │
│ 写回 AccumulatedRecharge │
└────────────┬──────────────┘
│
┌────────────▼──────────────┐
│ 累计金额 >= 阈值? │
└─────┬───────────┬──────────┘
│ 否 │ 是
│ │
│ ┌────▼───────────────┐
│ │ FirstCommissionPaid│
│ │ 已标记? │
│ └────┬───────┬───────┘
│ │ 是 │ 否
│ │ │
│ ┌────▼────┐ │
│ │ 跳过 │ │
│ └─────────┘ │
│ │
│ ┌────▼────────┐
│ │ 计算佣金金额 │
│ └────┬────────┘
│ │
│ ┌────▼────────┐
│ │ 创建佣金记录 │
│ │ 佣金入账 │
│ └────┬────────┘
│ │
│ ┌────▼────────┐
│ │ 标记 │
│ │FirstCommission│
│ │Paid = true │
│ └────┬────────┘
│ │
└───────────────────▼
│
┌────────────▼──────────────┐
│ 完成 │
└───────────────────────────┘
关键字段
IotCard / Device 模型
| 字段 | 类型 | 说明 |
|---|---|---|
accumulated_recharge |
int64 | 累计充值金额(分),每次支付成功都会累加 |
first_commission_paid |
bool | 一次性佣金是否已发放,防止重复发放 |
series_allocation_id |
uint | 关联的系列分配ID,用于获取配置 |
ShopSeriesAllocation 模型
| 字段 | 类型 | 说明 |
|---|---|---|
enable_one_time_commission |
bool | 是否启用一次性佣金 |
one_time_commission_trigger |
string | 触发方式:single_recharge 或 accumulated_recharge |
one_time_commission_threshold |
int64 | 最低阈值(分) |
one_time_commission_type |
string | 佣金类型:fixed 或 tiered |
one_time_commission_mode |
string | 返佣模式:fixed 或 percent |
one_time_commission_value |
int64 | 佣金值(分或千分比) |
核心逻辑实现
1. 累计金额更新
位置:internal/service/commission_calculation/service.go
// 累计充值触发场景:每次支付都写回累计金额
if allocation.OneTimeCommissionTrigger == model.OneTimeCommissionTriggerAccumulatedRecharge {
newAccumulated := card.AccumulatedRecharge + order.TotalAmount
if err := tx.Model(&model.IotCard{}).Where("id = ?", cardID).
Update("accumulated_recharge", newAccumulated).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新卡累计充值金额失败")
}
card.AccumulatedRecharge = newAccumulated
}
2. 阈值判断与发放
// 判断是否已发放过
if card.FirstCommissionPaid {
return nil // 已发放,跳过
}
// 根据触发方式选择充值金额
var rechargeAmount int64
switch allocation.OneTimeCommissionTrigger {
case model.OneTimeCommissionTriggerSingleRecharge:
rechargeAmount = order.TotalAmount // 单次金额
case model.OneTimeCommissionTriggerAccumulatedRecharge:
rechargeAmount = card.AccumulatedRecharge // 累计金额
default:
return nil
}
// 判断是否达到阈值
if rechargeAmount < allocation.OneTimeCommissionThreshold {
return nil // 未达阈值,不发放
}
// 计算佣金、创建记录、入账
commissionAmount, err := s.calculateOneTimeCommission(ctx, allocation, order.TotalAmount)
// ...
// 标记已发放
if err := tx.Model(&model.IotCard{}).Where("id = ?", cardID).
Update("first_commission_paid", true).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新卡佣金发放状态失败")
}
典型场景示例
场景:阈值 10000 分(100元),佣金 500 分(5元)
| 支付次数 | 订单金额 | 累计金额 | 是否发放佣金 | 说明 |
|---|---|---|---|---|
| 第1次 | 3000 | 3000 | ❌ | 未达阈值 |
| 第2次 | 4000 | 7000 | ❌ | 未达阈值 |
| 第3次 | 4000 | 11000 | ✅ 发放500 | 达到阈值,首次发放 |
| 第4次 | 3000 | 14000 | ❌ | 已发放过,不重复 |
| 第5次 | 5000 | 19000 | ❌ | 已发放过,不重复 |
注意事项
- 每次支付都写回累计金额:即使未达阈值,也要更新
accumulated_recharge - 仅发放一次:通过
first_commission_paid标记防止重复发放 - 事务保证一致性:累计金额更新、佣金记录创建、标记更新都在同一事务中
- 适用于单卡和设备:
IotCard和Device模型都有相同字段,逻辑完全一致
测试覆盖
单元测试:tests/unit/commission_calculation_service_test.go
- ✅ 第一次支付:累计金额更新为 3000,未发放
- ✅ 第二次支付:累计金额更新为 7000,未发放
- ✅ 第三次支付:累计金额更新为 11000,发放佣金 500
- ✅ 第四次支付:累计金额更新为 14000,不重复发放
集成测试:tests/integration/shop_series_allocation_test.go
- ✅ 一次性佣金配置落库(固定类型)
- ✅ 一次性佣金配置落库(梯度类型)
- ✅ 启用一次性佣金但未提供配置应失败