- 添加 IoT 核心业务表:运营商、IoT 卡、设备、号卡、套餐、订单等 - 添加分佣系统表:分佣规则、分佣记录、运营商结算等 - 添加轮询和流量管理表:轮询配置、流量使用记录等 - 添加财务和系统管理表:佣金提现、换卡申请等 - 实现完整的 GORM 模型和常量定义 - 添加数据库迁移脚本和详细文档 - 集成 OpenSpec 工作流工具(opsx 命令和 skills) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
941 lines
23 KiB
Markdown
941 lines
23 KiB
Markdown
# IoT SIM 管理系统 - 分佣系统说明
|
||
|
||
## 概述
|
||
|
||
IoT SIM 管理系统实现了一套灵活的多级代理分佣体系,支持三种分佣模式(一次性分佣、长期分佣、组合分佣),支持阶梯奖励机制,支持自动解冻和手动审批,支持 OR 条件解冻逻辑。
|
||
|
||
---
|
||
|
||
## 分佣架构
|
||
|
||
### 多级代理树形结构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 平台(Platform) │
|
||
│ Level 0 │
|
||
└─────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────┼─────────────────┐
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||
│ 一级代理 A │ │ 一级代理 B │ │ 一级代理 C │
|
||
│ Level 1 │ │ Level 1 │ │ Level 1 │
|
||
│ Path: /A/ │ │ Path: /B/ │ │ Path: /C/ │
|
||
└──────────────┘ └──────────────┘ └──────────────┘
|
||
│ │
|
||
├────┬────┐ ├────┬────┐
|
||
▼ ▼ ▼ ▼ ▼ ▼
|
||
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐
|
||
│A-1 │ │A-2 │ │A-3 │ │B-1 │ │B-2 │ │B-3 │
|
||
│L2 │ │L2 │ │L2 │ │L2 │ │L2 │ │L2 │
|
||
└────┘ └────┘ └────┘ └────┘ └────┘ └────┘
|
||
```
|
||
|
||
**代理层级关系表**: `agent_hierarchies`
|
||
|
||
---
|
||
|
||
## 三种分佣模式
|
||
|
||
### 1. 一次性分佣 (One-time Commission)
|
||
|
||
**特点**: 订单完成后立即发放佣金
|
||
|
||
**适用场景**:
|
||
- 首次激活奖励
|
||
- 推广奖励
|
||
- 快速返佣
|
||
|
||
**示例**:
|
||
```
|
||
用户购买套餐 → 订单完成 → 立即发放佣金给上级代理
|
||
```
|
||
|
||
**配置示例**:
|
||
```sql
|
||
INSERT INTO commission_rules (
|
||
rule_name,
|
||
rule_type,
|
||
package_series_id,
|
||
commission_type,
|
||
commission_value,
|
||
status
|
||
) VALUES (
|
||
'一次性分佣-套餐激活奖励',
|
||
'one_time',
|
||
1, -- 套餐系列 ID
|
||
'fixed', -- 固定金额
|
||
10.00, -- 10元
|
||
1 -- 启用
|
||
);
|
||
```
|
||
|
||
**业务流程**:
|
||
```
|
||
订单创建 → 订单支付 → 订单完成
|
||
│
|
||
└─→ 创建分佣记录 (status=1 待发放)
|
||
│
|
||
└─→ 自动发放 (status=2 已发放)
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 长期分佣 (Long-term Commission)
|
||
|
||
**特点**: 订单完成后冻结佣金,满足解冻条件后发放
|
||
|
||
**适用场景**:
|
||
- 续费奖励
|
||
- 留存奖励
|
||
- 长期激励
|
||
|
||
**解冻条件**:
|
||
- **时间条件**: 冻结 N 天后自动解冻
|
||
- **流量条件**: IoT 卡累计使用 M MB 流量后解冻
|
||
- **OR 逻辑**: 时间到期 **OR** 流量达标,满足任一条件即可解冻
|
||
|
||
**示例**:
|
||
```
|
||
用户购买套餐 → 订单完成 → 冻结佣金 (30天或1GB流量)
|
||
↓
|
||
时间到期 OR 流量达标
|
||
↓
|
||
自动解冻发放
|
||
```
|
||
|
||
**配置示例**:
|
||
```sql
|
||
INSERT INTO commission_rules (
|
||
rule_name,
|
||
rule_type,
|
||
package_series_id,
|
||
commission_type,
|
||
commission_value,
|
||
freeze_days,
|
||
freeze_data_mb,
|
||
unfreeze_mode,
|
||
status
|
||
) VALUES (
|
||
'长期分佣-续费奖励',
|
||
'long_term',
|
||
1,
|
||
'percentage', -- 百分比
|
||
0.10, -- 10%
|
||
30, -- 冻结30天
|
||
1024, -- 或使用1GB流量
|
||
'auto', -- 自动解冻
|
||
1
|
||
);
|
||
```
|
||
|
||
**业务流程**:
|
||
```
|
||
订单创建 → 订单支付 → 订单完成
|
||
│
|
||
└─→ 创建分佣记录 (status=3 已冻结)
|
||
│
|
||
├─→ 时间检查: 30天后 → 自动解冻 (status=2 已发放)
|
||
│
|
||
└─→ 流量检查: 使用1GB流量后 → 自动解冻 (status=2 已发放)
|
||
```
|
||
|
||
**解冻条件数据结构**:
|
||
```json
|
||
{
|
||
"time_based": {
|
||
"days": 30,
|
||
"deadline": "2025-02-10T00:00:00Z"
|
||
},
|
||
"data_based": {
|
||
"data_mb": 1024,
|
||
"iot_card_id": 12345
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 组合分佣 (Combined Commission)
|
||
|
||
**特点**: 同时包含一次性分佣和长期分佣,订单完成后部分立即发放,部分冻结
|
||
|
||
**适用场景**:
|
||
- 首充奖励(立即发放) + 留存奖励(冻结发放)
|
||
- 灵活激励机制
|
||
|
||
**示例**:
|
||
```
|
||
用户购买套餐 → 订单完成 → 立即发放 5元 + 冻结 10元 (30天后发放)
|
||
```
|
||
|
||
**配置示例**:
|
||
```sql
|
||
-- 1. 创建组合分佣规则
|
||
INSERT INTO commission_rules (
|
||
rule_name,
|
||
rule_type,
|
||
package_series_id,
|
||
status
|
||
) VALUES (
|
||
'组合分佣-首充+留存',
|
||
'combined',
|
||
1,
|
||
1
|
||
);
|
||
|
||
-- 2. 配置一次性条件
|
||
INSERT INTO commission_combined_conditions (
|
||
rule_id,
|
||
condition_type,
|
||
commission_type,
|
||
commission_value
|
||
) VALUES (
|
||
1, -- 上面创建的规则 ID
|
||
'one_time',
|
||
'fixed',
|
||
5.00 -- 立即发放 5元
|
||
);
|
||
|
||
-- 3. 配置长期条件
|
||
INSERT INTO commission_combined_conditions (
|
||
rule_id,
|
||
condition_type,
|
||
commission_type,
|
||
commission_value,
|
||
freeze_days,
|
||
freeze_data_mb
|
||
) VALUES (
|
||
1,
|
||
'long_term',
|
||
'fixed',
|
||
10.00, -- 冻结 10元
|
||
30, -- 30天
|
||
1024 -- 或1GB流量
|
||
);
|
||
```
|
||
|
||
**业务流程**:
|
||
```
|
||
订单创建 → 订单支付 → 订单完成
|
||
│
|
||
├─→ 创建一次性分佣记录 (status=1 待发放) → 立即发放 (status=2)
|
||
│
|
||
└─→ 创建长期分佣记录 (status=3 已冻结) → 满足条件后解冻
|
||
```
|
||
|
||
---
|
||
|
||
## 阶梯奖励机制
|
||
|
||
### 阶梯奖励说明
|
||
|
||
阶梯奖励允许根据订单数量设置不同的分佣标准,订单数量越多,分佣越高。
|
||
|
||
**示例配置**:
|
||
```
|
||
1-10 单: 10元/单
|
||
11-50 单: 15元/单
|
||
51+ 单: 20元/单
|
||
```
|
||
|
||
### 配置示例
|
||
|
||
```sql
|
||
-- 1. 创建支持阶梯的分佣规则
|
||
INSERT INTO commission_rules (
|
||
rule_name,
|
||
rule_type,
|
||
package_series_id,
|
||
enable_ladder,
|
||
status
|
||
) VALUES (
|
||
'阶梯分佣-月度订单量',
|
||
'one_time',
|
||
1,
|
||
true, -- 启用阶梯
|
||
1
|
||
);
|
||
|
||
-- 2. 配置阶梯奖励
|
||
INSERT INTO commission_ladder (rule_id, min_quantity, max_quantity, commission_type, commission_value) VALUES
|
||
(1, 1, 10, 'fixed', 10.00),
|
||
(1, 11, 50, 'fixed', 15.00),
|
||
(1, 51, NULL, 'fixed', 20.00); -- NULL 表示无上限
|
||
```
|
||
|
||
### 阶梯计算逻辑
|
||
|
||
```go
|
||
// 伪代码
|
||
func CalculateLadderCommission(agentID uint, ruleID uint, currentMonth string) float64 {
|
||
// 1. 查询阶梯配置
|
||
ladders := db.FindCommissionLadders(ruleID)
|
||
|
||
// 2. 统计当月订单数量
|
||
orderCount := db.CountOrders(agentID, currentMonth)
|
||
|
||
// 3. 匹配阶梯
|
||
for _, ladder := range ladders {
|
||
if orderCount >= ladder.MinQuantity &&
|
||
(ladder.MaxQuantity == nil || orderCount <= ladder.MaxQuantity) {
|
||
if ladder.CommissionType == "fixed" {
|
||
return ladder.CommissionValue
|
||
} else if ladder.CommissionType == "percentage" {
|
||
orderAmount := db.GetOrderAmount(orderID)
|
||
return orderAmount * ladder.CommissionValue
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 分佣计算方式
|
||
|
||
### 1. 固定金额 (Fixed)
|
||
|
||
**说明**: 每笔订单固定分佣 N 元
|
||
|
||
**示例**:
|
||
```sql
|
||
commission_type = 'fixed'
|
||
commission_value = 10.00
|
||
```
|
||
|
||
**计算公式**:
|
||
```
|
||
分佣金额 = commission_value = 10.00 元
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 百分比 (Percentage)
|
||
|
||
**说明**: 按订单金额的 N% 分佣
|
||
|
||
**示例**:
|
||
```sql
|
||
commission_type = 'percentage'
|
||
commission_value = 0.10 -- 10%
|
||
```
|
||
|
||
**计算公式**:
|
||
```
|
||
分佣金额 = 订单金额 × commission_value
|
||
= 100.00 × 0.10
|
||
= 10.00 元
|
||
```
|
||
|
||
---
|
||
|
||
## 分佣记录表
|
||
|
||
### 表结构: `commission_records`
|
||
|
||
分佣记录表记录每笔分佣的详细信息:
|
||
|
||
```sql
|
||
CREATE TABLE commission_records (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
order_id BIGINT NOT NULL,
|
||
agent_id BIGINT NOT NULL,
|
||
rule_id BIGINT NOT NULL,
|
||
commission_type VARCHAR(50) NOT NULL,
|
||
commission_amount DECIMAL(10,2) NOT NULL,
|
||
status INT NOT NULL DEFAULT 1,
|
||
freeze_days INT DEFAULT 0,
|
||
freeze_data_mb BIGINT DEFAULT 0,
|
||
unfreeze_conditions JSONB,
|
||
unfrozen_at TIMESTAMPTZ,
|
||
distributed_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 状态说明
|
||
|
||
| status | 状态 | 说明 |
|
||
|--------|------|------|
|
||
| 1 | 待发放 | 一次性分佣,等待发放 |
|
||
| 2 | 已发放 | 已发放到代理账户 |
|
||
| 3 | 已冻结 | 长期分佣,冻结中 |
|
||
| 4 | 已取消 | 订单取消或退款,分佣取消 |
|
||
|
||
---
|
||
|
||
## OR 条件解冻逻辑
|
||
|
||
### 解冻条件设计
|
||
|
||
长期分佣支持 **OR 条件解冻**,即时间到期 **OR** 流量达标,满足任一条件即可自动解冻。
|
||
|
||
**解冻条件数据结构**:
|
||
```json
|
||
{
|
||
"time_based": {
|
||
"days": 30,
|
||
"deadline": "2025-02-10T00:00:00Z"
|
||
},
|
||
"data_based": {
|
||
"data_mb": 1024,
|
||
"iot_card_id": 12345
|
||
}
|
||
}
|
||
```
|
||
|
||
### 解冻检查逻辑
|
||
|
||
```go
|
||
// 伪代码
|
||
func CheckUnfreezeConditions(record *CommissionRecord) bool {
|
||
var conditions struct {
|
||
TimeBased struct {
|
||
Days int `json:"days"`
|
||
Deadline time.Time `json:"deadline"`
|
||
} `json:"time_based"`
|
||
DataBased struct {
|
||
DataMB int64 `json:"data_mb"`
|
||
IotCardID uint `json:"iot_card_id"`
|
||
} `json:"data_based"`
|
||
}
|
||
|
||
json.Unmarshal(record.UnfreezeConditions, &conditions)
|
||
|
||
// 检查时间条件
|
||
if time.Now().After(conditions.TimeBased.Deadline) {
|
||
return true // 时间到期,可以解冻
|
||
}
|
||
|
||
// 检查流量条件
|
||
if conditions.DataBased.IotCardID > 0 {
|
||
card := db.FindIotCardByID(conditions.DataBased.IotCardID)
|
||
if card.DataUsageMB >= conditions.DataBased.DataMB {
|
||
return true // 流量达标,可以解冻
|
||
}
|
||
}
|
||
|
||
return false // 条件均未满足
|
||
}
|
||
```
|
||
|
||
### 自动解冻定时任务
|
||
|
||
```go
|
||
// 伪代码
|
||
func UnfreezeCommissionTask() {
|
||
ticker := time.NewTicker(1 * time.Minute)
|
||
defer ticker.Stop()
|
||
|
||
for range ticker.C {
|
||
// 查询所有冻结中的分佣记录
|
||
records := db.FindCommissionRecords("status = 3")
|
||
|
||
for _, record := range records {
|
||
if CheckUnfreezeConditions(&record) {
|
||
// 解冻
|
||
record.Status = 2 // 已发放
|
||
record.UnfrozenAt = time.Now()
|
||
record.DistributedAt = time.Now()
|
||
db.Save(&record)
|
||
|
||
// 发放到代理账户
|
||
DistributeCommission(record.AgentID, record.CommissionAmount)
|
||
|
||
logger.Info("分佣解冻成功",
|
||
zap.Uint("record_id", record.ID),
|
||
zap.Uint("agent_id", record.AgentID),
|
||
zap.Float64("amount", record.CommissionAmount),
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 分佣审批流程
|
||
|
||
### 自动审批 vs 手动审批
|
||
|
||
分佣规则的 `unfreeze_mode` 字段控制解冻模式:
|
||
|
||
- **auto**: 自动解冻,满足条件后自动发放
|
||
- **manual**: 手动审批,需要人工审核通过后才能发放
|
||
|
||
### 手动审批流程
|
||
|
||
```
|
||
订单完成 → 创建分佣记录 (status=3 已冻结)
|
||
↓
|
||
满足解冻条件
|
||
↓
|
||
创建审批记录 (approval_status=1 待审批)
|
||
↓
|
||
审批人审核
|
||
├─→ 通过 (approval_status=2) → 发放佣金 (status=2 已发放)
|
||
└─→ 拒绝 (approval_status=3) → 取消分佣 (status=4 已取消)
|
||
```
|
||
|
||
### 审批表: `commission_approvals`
|
||
|
||
```sql
|
||
CREATE TABLE commission_approvals (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
commission_record_id BIGINT UNIQUE NOT NULL,
|
||
agent_id BIGINT NOT NULL,
|
||
approval_status INT NOT NULL DEFAULT 1,
|
||
approver_id BIGINT,
|
||
approval_reason TEXT,
|
||
approved_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 审批状态
|
||
|
||
| approval_status | 状态 | 说明 |
|
||
|----------------|------|------|
|
||
| 1 | 待审批 | 等待审批人审核 |
|
||
| 2 | 已通过 | 审批通过,发放佣金 |
|
||
| 3 | 已拒绝 | 审批拒绝,取消分佣 |
|
||
|
||
---
|
||
|
||
## 分佣模板
|
||
|
||
### 模板设计
|
||
|
||
分佣模板用于快速创建分佣规则,避免重复配置。
|
||
|
||
**表结构**: `commission_templates`
|
||
|
||
```sql
|
||
CREATE TABLE commission_templates (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
template_name VARCHAR(255) NOT NULL,
|
||
template_data JSONB NOT NULL,
|
||
description TEXT,
|
||
status INT NOT NULL DEFAULT 1,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 模板数据格式
|
||
|
||
```json
|
||
{
|
||
"rule_type": "combined",
|
||
"package_series_id": 1,
|
||
"conditions": [
|
||
{
|
||
"condition_type": "one_time",
|
||
"commission_type": "fixed",
|
||
"commission_value": 5.00
|
||
},
|
||
{
|
||
"condition_type": "long_term",
|
||
"commission_type": "fixed",
|
||
"commission_value": 10.00,
|
||
"freeze_days": 30,
|
||
"freeze_data_mb": 1024
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 使用模板创建规则
|
||
|
||
```go
|
||
// 伪代码
|
||
func CreateRuleFromTemplate(templateID uint, seriesID uint) error {
|
||
template := db.FindTemplateByID(templateID)
|
||
|
||
var data struct {
|
||
RuleType string `json:"rule_type"`
|
||
PackageSeriesID uint `json:"package_series_id"`
|
||
Conditions []struct {
|
||
ConditionType string `json:"condition_type"`
|
||
CommissionType string `json:"commission_type"`
|
||
CommissionValue float64 `json:"commission_value"`
|
||
FreezeDays int `json:"freeze_days"`
|
||
FreezeDataMB int64 `json:"freeze_data_mb"`
|
||
} `json:"conditions"`
|
||
}
|
||
|
||
json.Unmarshal(template.TemplateData, &data)
|
||
|
||
// 创建分佣规则
|
||
rule := CommissionRule{
|
||
RuleName: template.TemplateName,
|
||
RuleType: data.RuleType,
|
||
PackageSeriesID: seriesID,
|
||
Status: 1,
|
||
}
|
||
db.Create(&rule)
|
||
|
||
// 创建组合条件
|
||
for _, cond := range data.Conditions {
|
||
condition := CommissionCombinedCondition{
|
||
RuleID: rule.ID,
|
||
ConditionType: cond.ConditionType,
|
||
CommissionType: cond.CommissionType,
|
||
CommissionValue: cond.CommissionValue,
|
||
FreezeDays: cond.FreezeDays,
|
||
FreezeDataMB: cond.FreezeDataMB,
|
||
}
|
||
db.Create(&condition)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 运营商结算
|
||
|
||
### 结算表: `carrier_settlements`
|
||
|
||
记录与运营商的月度结算情况:
|
||
|
||
```sql
|
||
CREATE TABLE carrier_settlements (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
carrier_id BIGINT NOT NULL,
|
||
settlement_month VARCHAR(7) NOT NULL,
|
||
total_orders INT DEFAULT 0,
|
||
total_amount DECIMAL(10,2) DEFAULT 0,
|
||
settlement_status INT NOT NULL DEFAULT 1,
|
||
settled_at TIMESTAMPTZ,
|
||
paid_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 结算状态
|
||
|
||
| settlement_status | 状态 | 说明 |
|
||
|------------------|------|------|
|
||
| 1 | 待结算 | 月度未结束 |
|
||
| 2 | 已结算 | 已统计金额 |
|
||
| 3 | 已支付 | 已支付给运营商 |
|
||
|
||
### 月度结算流程
|
||
|
||
```
|
||
每月1号 → 统计上月订单数据
|
||
↓
|
||
创建结算记录 (settlement_status=1 待结算)
|
||
↓
|
||
财务审核
|
||
↓
|
||
确认结算 (settlement_status=2 已结算)
|
||
↓
|
||
支付运营商 (settlement_status=3 已支付)
|
||
```
|
||
|
||
### 结算计算逻辑
|
||
|
||
```go
|
||
// 伪代码
|
||
func GenerateCarrierSettlement(carrierID uint, month string) error {
|
||
// 1. 统计上月订单
|
||
orders := db.FindOrders("carrier_id = ? AND DATE_FORMAT(completed_at, '%Y-%m') = ?", carrierID, month)
|
||
|
||
totalOrders := len(orders)
|
||
totalAmount := 0.0
|
||
for _, order := range orders {
|
||
totalAmount += order.Amount
|
||
}
|
||
|
||
// 2. 创建结算记录
|
||
settlement := CarrierSettlement{
|
||
CarrierID: carrierID,
|
||
SettlementMonth: month,
|
||
TotalOrders: totalOrders,
|
||
TotalAmount: totalAmount,
|
||
SettlementStatus: 1, // 待结算
|
||
}
|
||
db.Create(&settlement)
|
||
|
||
return nil
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 提现管理
|
||
|
||
### 提现申请表: `commission_withdrawal_requests`
|
||
|
||
```sql
|
||
CREATE TABLE commission_withdrawal_requests (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
agent_id BIGINT NOT NULL,
|
||
withdrawal_amount DECIMAL(10,2) NOT NULL,
|
||
withdrawal_method VARCHAR(20) NOT NULL,
|
||
account_info JSONB NOT NULL,
|
||
status INT NOT NULL DEFAULT 1,
|
||
reviewer_id BIGINT,
|
||
review_reason TEXT,
|
||
reviewed_at TIMESTAMPTZ,
|
||
paid_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 提现状态
|
||
|
||
| status | 状态 | 说明 |
|
||
|--------|------|------|
|
||
| 1 | 待审核 | 等待审核 |
|
||
| 2 | 已通过 | 审核通过,等待打款 |
|
||
| 3 | 已拒绝 | 审核拒绝 |
|
||
| 4 | 已打款 | 已打款到账户 |
|
||
| 5 | 已取消 | 用户取消 |
|
||
|
||
### 提现流程
|
||
|
||
```
|
||
代理提交提现申请 (status=1 待审核)
|
||
↓
|
||
财务审核
|
||
├─→ 通过 (status=2 已通过) → 打款 (status=4 已打款)
|
||
└─→ 拒绝 (status=3 已拒绝)
|
||
```
|
||
|
||
### 提现设置表: `commission_withdrawal_settings`
|
||
|
||
```sql
|
||
CREATE TABLE commission_withdrawal_settings (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
agent_id BIGINT UNIQUE NOT NULL,
|
||
min_withdrawal_amount DECIMAL(10,2) DEFAULT 0,
|
||
max_withdrawal_amount DECIMAL(10,2) DEFAULT 0,
|
||
withdrawal_fee_rate DECIMAL(5,4) DEFAULT 0,
|
||
auto_approval_enabled BOOLEAN DEFAULT false,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 提现规则检查
|
||
|
||
```go
|
||
// 伪代码
|
||
func ValidateWithdrawalRequest(agentID uint, amount float64) error {
|
||
setting := db.FindWithdrawalSetting(agentID)
|
||
|
||
// 检查最小金额
|
||
if amount < setting.MinWithdrawalAmount {
|
||
return fmt.Errorf("提现金额不能低于 %.2f 元", setting.MinWithdrawalAmount)
|
||
}
|
||
|
||
// 检查最大金额
|
||
if setting.MaxWithdrawalAmount > 0 && amount > setting.MaxWithdrawalAmount {
|
||
return fmt.Errorf("提现金额不能高于 %.2f 元", setting.MaxWithdrawalAmount)
|
||
}
|
||
|
||
// 检查账户余额
|
||
balance := db.GetAgentBalance(agentID)
|
||
fee := amount * setting.WithdrawalFeeRate
|
||
totalAmount := amount + fee
|
||
|
||
if balance < totalAmount {
|
||
return fmt.Errorf("余额不足,需要 %.2f 元(含手续费 %.2f 元)", totalAmount, fee)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 分佣业务流程示例
|
||
|
||
### 示例 1: 一次性分佣
|
||
|
||
```
|
||
1. 用户购买套餐(100元)
|
||
↓
|
||
2. 订单完成
|
||
↓
|
||
3. 触发分佣计算
|
||
- 规则: 一次性分佣,固定金额 10元
|
||
- 创建分佣记录: agent_id=123, commission_amount=10.00, status=1 待发放
|
||
↓
|
||
4. 自动发放
|
||
- 更新分佣记录: status=2 已发放, distributed_at=NOW()
|
||
- 更新代理账户余额: balance += 10.00
|
||
↓
|
||
5. 完成
|
||
```
|
||
|
||
---
|
||
|
||
### 示例 2: 长期分佣(OR 条件解冻)
|
||
|
||
```
|
||
1. 用户购买套餐(100元)
|
||
↓
|
||
2. 订单完成
|
||
↓
|
||
3. 触发分佣计算
|
||
- 规则: 长期分佣,10%,冻结30天 OR 使用1GB流量
|
||
- 创建分佣记录: agent_id=123, commission_amount=10.00, status=3 已冻结
|
||
- 解冻条件: {"time_based": {"days": 30}, "data_based": {"data_mb": 1024}}
|
||
↓
|
||
4. 定时任务检查解冻条件
|
||
- 时间检查: 30天后 → 满足条件 → 解冻
|
||
- 流量检查: 使用1GB流量后 → 满足条件 → 解冻
|
||
↓
|
||
5. 自动解冻
|
||
- 更新分佣记录: status=2 已发放, unfrozen_at=NOW(), distributed_at=NOW()
|
||
- 更新代理账户余额: balance += 10.00
|
||
↓
|
||
6. 完成
|
||
```
|
||
|
||
---
|
||
|
||
### 示例 3: 组合分佣
|
||
|
||
```
|
||
1. 用户购买套餐(100元)
|
||
↓
|
||
2. 订单完成
|
||
↓
|
||
3. 触发分佣计算
|
||
- 规则: 组合分佣
|
||
- 一次性条件: 固定金额 5元
|
||
- 长期条件: 固定金额 10元,冻结30天
|
||
↓
|
||
4. 创建两条分佣记录
|
||
- 记录1: agent_id=123, commission_amount=5.00, status=1 待发放
|
||
- 记录2: agent_id=123, commission_amount=10.00, status=3 已冻结
|
||
↓
|
||
5. 立即发放一次性分佣
|
||
- 记录1: status=2 已发放
|
||
- 代理账户余额: balance += 5.00
|
||
↓
|
||
6. 30天后自动解冻长期分佣
|
||
- 记录2: status=2 已发放
|
||
- 代理账户余额: balance += 10.00
|
||
↓
|
||
7. 完成
|
||
```
|
||
|
||
---
|
||
|
||
## 监控和统计
|
||
|
||
### 分佣统计指标
|
||
|
||
1. **代理分佣总额**
|
||
- 待发放金额
|
||
- 已发放金额
|
||
- 已冻结金额
|
||
|
||
2. **分佣发放效率**
|
||
- 平均发放时长
|
||
- 平均解冻时长
|
||
|
||
3. **提现统计**
|
||
- 提现申请数量
|
||
- 提现成功率
|
||
- 提现金额统计
|
||
|
||
### SQL 查询示例
|
||
|
||
```sql
|
||
-- 1. 代理分佣总额统计
|
||
SELECT
|
||
agent_id,
|
||
SUM(CASE WHEN status = 1 THEN commission_amount ELSE 0 END) AS pending_amount,
|
||
SUM(CASE WHEN status = 2 THEN commission_amount ELSE 0 END) AS distributed_amount,
|
||
SUM(CASE WHEN status = 3 THEN commission_amount ELSE 0 END) AS frozen_amount
|
||
FROM commission_records
|
||
WHERE agent_id = 123
|
||
GROUP BY agent_id;
|
||
|
||
-- 2. 月度分佣统计
|
||
SELECT
|
||
DATE_FORMAT(created_at, '%Y-%m') AS month,
|
||
COUNT(*) AS total_records,
|
||
SUM(commission_amount) AS total_amount
|
||
FROM commission_records
|
||
WHERE agent_id = 123
|
||
AND status = 2
|
||
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
|
||
ORDER BY month DESC;
|
||
|
||
-- 3. 提现统计
|
||
SELECT
|
||
status,
|
||
COUNT(*) AS request_count,
|
||
SUM(withdrawal_amount) AS total_amount
|
||
FROM commission_withdrawal_requests
|
||
WHERE agent_id = 123
|
||
GROUP BY status;
|
||
```
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 合理设置冻结条件
|
||
|
||
- **过短**: 可能导致代理流失
|
||
- **过长**: 影响代理积极性
|
||
- **建议**: 根据业务特点和用户留存数据设置合理的冻结期
|
||
|
||
### 2. 使用 OR 条件解冻
|
||
|
||
- **优势**: 提高解冻灵活性,代理满足任一条件即可获得佣金
|
||
- **示例**: 30天 OR 1GB流量,满足其一即可解冻
|
||
|
||
### 3. 启用阶梯奖励
|
||
|
||
- **优势**: 激励代理提高订单量
|
||
- **示例**: 月订单量越多,单笔佣金越高
|
||
|
||
### 4. 定期审查分佣规则
|
||
|
||
- 定期分析分佣数据,优化分佣规则
|
||
- 根据代理反馈调整冻结条件和佣金比例
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
IoT SIM 管理系统的分佣系统具有以下特点:
|
||
|
||
1. **三种分佣模式**: 一次性分佣、长期分佣、组合分佣
|
||
2. **阶梯奖励机制**: 支持根据订单数量设置不同的分佣标准
|
||
3. **OR 条件解冻**: 时间到期 OR 流量达标,满足任一条件即可解冻
|
||
4. **自动 + 手动审批**: 支持自动解冻和手动审批两种模式
|
||
5. **分佣模板**: 快速创建分佣规则,避免重复配置
|
||
6. **运营商结算**: 记录与运营商的月度结算情况
|
||
7. **提现管理**: 完善的提现申请和审批流程
|
||
8. **多级代理**: 支持无限层级的代理树形结构
|
||
|
||
通过灵活配置和使用分佣系统,可以激励代理积极性,提高销售业绩,实现平台与代理的双赢。
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**最后更新**: 2026-01-12
|
||
**维护人员**: Claude Sonnet 4.5
|