# 累计触发一次性佣金逻辑流程 ## 概述 一次性佣金支持两种触发方式: 1. **单次充值触发**:单笔订单金额达到阈值即发放 2. **累计充值触发**:累计多次充值金额达到阈值后发放(本文档重点) ## 累计触发流程 ``` ┌─────────────────────────────────────────────────────────────┐ │ 用户购买套餐 │ └────────────────────┬────────────────────────────────────────┘ │ ┌────────────▼──────────────┐ │ 支付成功触发佣金计算 │ └────────────┬──────────────┘ │ ┌────────────▼──────────────┐ │ 读取一次性佣金配置 │ │ - 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` ```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. 阈值判断与发放 ```go // 判断是否已发放过 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 | ❌ | 已发放过,不重复 | ## 注意事项 1. **每次支付都写回累计金额**:即使未达阈值,也要更新 `accumulated_recharge` 2. **仅发放一次**:通过 `first_commission_paid` 标记防止重复发放 3. **事务保证一致性**:累计金额更新、佣金记录创建、标记更新都在同一事务中 4. **适用于单卡和设备**:`IotCard` 和 `Device` 模型都有相同字段,逻辑完全一致 ## 测试覆盖 单元测试:`tests/unit/commission_calculation_service_test.go` - ✅ 第一次支付:累计金额更新为 3000,未发放 - ✅ 第二次支付:累计金额更新为 7000,未发放 - ✅ 第三次支付:累计金额更新为 11000,发放佣金 500 - ✅ 第四次支付:累计金额更新为 14000,不重复发放 集成测试:`tests/integration/shop_series_allocation_test.go` - ✅ 一次性佣金配置落库(固定类型) - ✅ 一次性佣金配置落库(梯度类型) - ✅ 启用一次性佣金但未提供配置应失败 ## 相关文档 - [API 文档](../api-documentation-guide.md):自动生成的 OpenAPI 文档包含所有参数说明 - [DTO 规范](../dto-standards.md):一次性佣金配置的请求/响应结构 - [Model 规范](../model-standards.md):数据库字段定义