# 订单状态与佣金系统分析报告

## 一、订单状态定义

### 1.1 订单支付状态（Payment Status）
**文件**: `internal/model/order.go` (第 98-104 行)

```go
const (
    PaymentStatusPending   = 1 // 待支付
    PaymentStatusPaid      = 2 // 已支付
    PaymentStatusCancelled = 3 // 已取消
    PaymentStatusRefunded  = 4 // 已退款
)
```

**数据库字段**: `tb_order.payment_status` (int)

**说明**:
- 订单创建时默认为 `PaymentStatusPending`（待支付）
- 支付成功后更新为 `PaymentStatusPaid`（已支付）
- 待支付订单超时 30 分钟自动取消，状态变为 `PaymentStatusCancelled`
- 退款后状态变为 `PaymentStatusRefunded`

### 1.2 订单佣金状态（Commission Status）
**文件**: `internal/model/order.go` (第 106-110 行)

```go
const (
    CommissionStatusPending    = 1 // 待计算
    CommissionStatusCalculated = 2 // 已计算
)
```

**数据库字段**: `tb_order.commission_status` (int)

**说明**:
- 订单创建时默认为 `CommissionStatusPending`（待计算）
- 佣金计算完成后更新为 `CommissionStatusCalculated`（已计算）
- 佣金计算是异步任务，通过 Asynq 队列处理

---

## 二、订单状态流转逻辑

### 2.1 订单创建流程
**文件**: `internal/service/order/service.go`

#### 支付方式决定初始状态：

**1. 线下支付（offline）- 平台代购**
- 创建订单时直接设置 `PaymentStatus = PaymentStatusPaid`（已支付）
- 立即激活套餐
- 立即入队佣金计算任务
- 代码位置: 第 189-209 行、第 464-502 行

**2. 钱包支付（wallet）- 代理自购或代购**
- 创建订单时直接设置 `PaymentStatus = PaymentStatusPaid`（已支付）
- 在事务中完成：创建订单 → 扣款 → 激活套餐
- 代码位置: 第 210-289 行、第 504-573 行

**3. 微信/支付宝支付（wechat/alipay）- H5 端**
- 创建订单时设置 `PaymentStatus = PaymentStatusPending`（待支付）
- 设置 `ExpiresAt` 为 30 分钟后（用于自动取消）
- 支付成功后由支付回调更新状态为 `PaymentStatusPaid`
- 代码位置: 第 754-927 行

### 2.2 订单超时自动取消
**文件**: `internal/service/order/service.go` (第 1314-1349 行)

```go
func (s *Service) CancelExpiredOrders(ctx context.Context) (int, error) {
    // 查询超时订单（expires_at < now）
    orders, err := s.orderStore.FindExpiredOrders(ctx, constants.OrderExpireBatchSize)
    // 批量取消订单
    for _, order := range orders {
        s.cancelOrder(ctx, order)
    }
}
```

**取消逻辑** (第 1351-1385 行):
- 只有 `PaymentStatusPending` 的订单才能取消
- 更新状态为 `PaymentStatusCancelled`
- 清除 `ExpiresAt` 字段
- 如果是钱包支付，解冻钱包余额

**触发方式**:
- Asynq Scheduler 每分钟执行一次
- 常量: `constants.OrderExpireTimeout = 30 * time.Minute`

---

## 三、佣金系统与订单状态的关系

### 3.1 佣金计算触发条件
**文件**: `internal/service/commission_calculation/service.go` (第 74-119 行)

```go
func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error {
    // 1. 检查订单佣金状态
    if order.CommissionStatus == model.CommissionStatusCalculated {
        return nil // 已计算，跳过
    }
    
    // 2. 计算成本价差佣金
    costDiffRecords, err := s.CalculateCostDiffCommission(ctx, order)
    
    // 3. 入账佣金
    for _, record := range costDiffRecords {
        s.creditCommissionInTx(ctx, tx, record)
    }
    
    // 4. 触发一次性佣金（仅非代购订单）
    if !order.IsPurchaseOnBehalf {
        s.triggerOneTimeCommissionForCardInTx(ctx, tx, order, *order.IotCardID)
    }
    
    // 5. 更新订单佣金状态为已计算
    tx.Model(&model.Order{}).Update("commission_status", CommissionStatusCalculated)
}
```

**触发时机**:
- 线下支付（offline）订单：创建后立即入队
- 钱包支付（wallet）订单：创建后立即入队
- 微信/支付宝支付：支付成功回调后入队

### 3.2 佣金类型与订单关系

#### 成本价差佣金（Cost Diff Commission）
**文件**: `internal/service/commission_calculation/service.go` (第 121-230 行)

```go
func (s *Service) CalculateCostDiffCommission(ctx context.Context, order *model.Order) {
    // 计算销售店铺的利润
    sellerProfit := order.TotalAmount - order.SellerCostPrice
    
    // 沿着代理链向上分配佣金
    // 每一级代理的佣金 = 该级成本价 - 下级成本价
}
```

**关键字段**:
- `order.SellerShopID`: 销售店铺ID（佣金归属）
- `order.SellerCostPrice`: 销售成本价（用于计算利润）
- `order.SeriesID`: 系列ID（用于查询分配配置）

**佣金状态**: `CommissionStatusReleased`（已入账）

#### 一次性佣金（One-Time Commission）
**触发条件**:
- 仅非代购订单触发（`!order.IsPurchaseOnBehalf`）
- 单卡首次购买或设备首次购买时触发
- 代码位置: 第 102-111 行

---

## 四、钱包冻结与佣金提现流程

### 4.1 钱包冻结状态定义
**文件**: `pkg/constants/wallet.go` (第 17-22 行)

```go
const (
    AgentWalletStatusNormal = 1 // 正常
    AgentWalletStatusFrozen = 2 // 冻结（钱包整体冻结）
    AgentWalletStatusClosed = 3 // 关闭
)
```

**冻结余额字段**:
- `tb_agent_wallet.frozen_balance`: 冻结余额（分）
- `tb_agent_wallet.balance`: 总余额（分）
- 可用余额 = balance - frozen_balance

### 4.2 佣金提现流程（3 阶段）

#### 阶段 1: 代理发起提现申请
**文件**: `internal/service/shop_commission/service.go` (第 517-649 行)

```go
func (s *Service) CreateWithdrawalRequest(ctx context.Context, shopID uint, req *dto.CreateMyWithdrawalReq) {
    // 1. 验证可用余额
    if req.Amount > wallet.GetAvailableBalance() {
        return "可提现余额不足"
    }
    
    // 2. 冻结余额（在事务中）
    tx.Model(&AgentWallet{}).
        Where("id = ? AND balance - frozen_balance >= ?", wallet.ID, req.Amount).
        Updates(map[string]interface{}{
            "frozen_balance": gorm.Expr("frozen_balance + ?", req.Amount),
        })
    
    // 3. 创建提现申请记录
    withdrawalRequest = &CommissionWithdrawalRequest{
        Status: 1, // 待审核
    }
    
    // 4. 创建钱包流水
    transaction = &AgentWalletTransaction{
        TransactionType: "withdrawal",
        Amount: -req.Amount,
        Status: TransactionStatusProcessing,
    }
}
```

**状态**: `WithdrawalStatusPending`（待审核）

#### 阶段 2: 平台审核通过
**文件**: `internal/service/commission_withdrawal/service.go` (第 145-256 行)

```go
func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveWithdrawalReq) {
    // 1. 检查冻结余额
    if wallet.FrozenBalance < amount {
        return "钱包冻结余额不足"
    }
    
    // 2. 从冻结余额扣款（在事务中）
    s.agentWalletStore.DeductFrozenBalanceWithTx(ctx, tx, wallet.ID, amount)
    
    // 3. 创建提现交易流水
    transaction = &AgentWalletTransaction{
        TransactionType: "withdrawal",
        Amount: -amount,
    }
    
    // 4. 更新提现申请状态
    updates["status"] = WithdrawalStatusApproved
}
```

**状态**: `WithdrawalStatusApproved`（已通过）

#### 阶段 3: 平台审核拒绝
**文件**: `internal/service/commission_withdrawal/service.go` (第 258-310 行)

```go
func (s *Service) Reject(ctx context.Context, id uint, req *dto.RejectWithdrawalReq) {
    // 1. 解冻余额（在事务中）
    s.agentWalletStore.UnfreezeBalanceWithTx(ctx, tx, wallet.ID, withdrawal.Amount)
    
    // 2. 创建退款交易流水
    transaction = &AgentWalletTransaction{
        TransactionType: "refund",
        Amount: withdrawal.Amount, // 正数，增加可用余额
    }
    
    // 3. 更新提现申请状态
    updates["status"] = WithdrawalStatusRejected
}
```

**状态**: `WithdrawalStatusRejected`（已拒绝）

### 4.3 钱包余额计算
**文件**: `internal/service/shop_commission/service.go` (第 154-198 行)

```go
func (s *Service) buildFundSummaryItem(shop, mainWallet, commissionWallet) {
    // 佣金钱包
    balance = commissionWallet.Balance
    frozenBalance = commissionWallet.FrozenBalance
    
    // 总佣金 = 可用 + 冻结 + 已提现
    totalCommission := balance + frozenBalance + withdrawnAmount
    
    // 未提现佣金 = 总佣金 - 已提现
    unwithdrawCommission := totalCommission - withdrawnAmount
    
    // 可提现佣金 = 可用 - 提现中
    availableCommission := balance - withdrawingAmount
    
    return ShopFundSummaryItem{
        TotalCommission:       totalCommission,
        WithdrawnCommission:   withdrawnAmount,
        UnwithdrawCommission:  unwithdrawCommission,
        FrozenCommission:      frozenBalance,
        WithdrawingCommission: withdrawingAmount,
        AvailableCommission:   availableCommission,
    }
}
```

---

## 五、关键数据模型

### 5.1 订单模型
**文件**: `internal/model/order.go`

| 字段 | 类型 | 说明 |
|------|------|------|
| `payment_status` | int | 支付状态（1-待支付 2-已支付 3-已取消 4-已退款） |
| `commission_status` | int | 佣金状态（1-待计算 2-已计算） |
| `seller_shop_id` | uint | 销售店铺ID（用于成本价差佣金） |
| `seller_cost_price` | int64 | 销售成本价（分） |
| `series_id` | uint | 系列ID（用于查询分配配置） |
| `is_purchase_on_behalf` | bool | 是否代购订单 |
| `expires_at` | time.Time | 订单过期时间（待支付订单） |

### 5.2 佣金记录模型
**文件**: `internal/model/commission.go`

| 字段 | 类型 | 说明 |
|------|------|------|
| `shop_id` | uint | 店铺ID（佣金归属） |
| `order_id` | uint | 订单ID |
| `commission_source` | string | 佣金来源（cost_diff-成本价差 one_time-一次性） |
| `amount` | int64 | 佣金金额（分） |
| `status` | int | 状态（1-已入账 2-已失效） |

### 5.3 代理钱包模型
**文件**: `internal/model/agent_wallet.go`

| 字段 | 类型 | 说明 |
|------|------|------|
| `balance` | int64 | 总余额（分） |
| `frozen_balance` | int64 | 冻结余额（分） |
| `status` | int | 钱包状态（1-正常 2-冻结 3-关闭） |
| `version` | int | 版本号（乐观锁） |

### 5.4 提现申请模型
**文件**: `internal/model/commission_withdrawal_request.go`

| 字段 | 类型 | 说明 |
|------|------|------|
| `status` | int | 状态（1-待审核 2-已通过 3-已拒绝） |
| `amount` | int64 | 提现金额（分） |
| `frozen_balance` | int64 | 冻结余额（分） |

---

## 六、关键常量

### 6.1 订单相关常量
**文件**: `pkg/constants/constants.go`

```go
const (
    OrderExpireTimeout   = 30 * time.Minute // 订单超时时间
    OrderExpireBatchSize = 100              // 批量处理数量
)
```

### 6.2 钱包相关常量
**文件**: `pkg/constants/wallet.go`

```go
const (
    AgentWalletStatusNormal = 1 // 正常
    AgentWalletStatusFrozen = 2 // 冻结
    AgentWalletStatusClosed = 3 // 关闭
    
    AgentTransactionTypeWithdrawal = "withdrawal" // 提现
    TransactionStatusProcessing    = 3            // 处理中
)
```

---

## 七、订单状态与佣金的完整流程图

```
订单创建
  ├─ 线下支付（offline）
  │  ├─ PaymentStatus = PaymentStatusPaid（已支付）
  │  ├─ 立即激活套餐
  │  ├─ 入队佣金计算
  │  └─ CommissionStatus = CommissionStatusCalculated
  │
  ├─ 钱包支付（wallet）
  │  ├─ PaymentStatus = PaymentStatusPaid（已支付）
  │  ├─ 扣款 → 激活套餐（事务）
  │  ├─ 入队佣金计算
  │  └─ CommissionStatus = CommissionStatusCalculated
  │
  └─ 微信/支付宝（wechat/alipay）
     ├─ PaymentStatus = PaymentStatusPending（待支付）
     ├─ ExpiresAt = now + 30min
     ├─ 支付成功回调
     │  ├─ PaymentStatus = PaymentStatusPaid
     │  ├─ 激活套餐
     │  ├─ 入队佣金计算
     │  └─ CommissionStatus = CommissionStatusCalculated
     └─ 超时自动取消（30min）
        ├─ PaymentStatus = PaymentStatusCancelled
        ├─ 解冻钱包余额
        └─ CommissionStatus = CommissionStatusPending（不计算）

佣金计算流程
  ├─ 成本价差佣金
  │  ├─ 计算销售店铺利润
  │  ├─ 沿代理链向上分配
  │  └─ 入账到分佣钱包
  │
  └─ 一次性佣金（非代购订单）
     ├─ 单卡/设备首次购买触发
     └─ 入账到分佣钱包

提现流程（3 阶段）
  ├─ 阶段 1: 代理发起申请
  │  ├─ 验证可用余额
  │  ├─ 冻结余额（frozen_balance += amount）
  │  ├─ 创建提现申请（Status = 待审核）
  │  └─ 创建钱包流水（Status = 处理中）
  │
  ├─ 阶段 2a: 平台审核通过
  │  ├─ 从冻结余额扣款（frozen_balance -= amount）
  │  ├─ 创建提现交易流水
  │  └─ 更新提现申请（Status = 已通过）
  │
  └─ 阶段 2b: 平台审核拒绝
     ├─ 解冻余额（frozen_balance -= amount）
     ├─ 创建退款交易流水
     └─ 更新提现申请（Status = 已拒绝）
```

---

## 八、重要业务规则

### 8.1 订单支付规则
1. **待支付订单自动取消**: 30 分钟未支付自动取消，解冻钱包余额
2. **幂等性检查**: 防止同一买家对同一资源短时间内重复下单
3. **强充要求**: 首次购买需满足最低充值要求

### 8.2 佣金计算规则
1. **成本价差佣金**: 仅当 `seller_profit > 0` 时才计算
2. **一次性佣金**: 仅非代购订单触发，且仅首次购买时触发
3. **佣金链路**: 沿代理层级向上分配，若中间断裂则标记为待审核

### 8.3 提现规则
1. **冻结机制**: 提现申请时冻结余额，防止重复提现
2. **可用余额**: balance - frozen_balance，用于验证提现金额
3. **手续费**: 提现时扣除手续费，实际到账 = 申请金额 - 手续费

---

## 九、相关文件索引

| 功能 | 文件 | 关键行数 |
|------|------|---------|
| 订单模型 | `internal/model/order.go` | 9-138 |
| 订单创建 | `internal/service/order/service.go` | 114-354, 359-674, 679-928 |
| 订单超时取消 | `internal/service/order/service.go` | 1314-1385 |
| 佣金计算 | `internal/service/commission_calculation/service.go` | 74-230 |
| 提现申请 | `internal/service/shop_commission/service.go` | 517-649 |
| 提现审核 | `internal/service/commission_withdrawal/service.go` | 145-310 |
| 钱包模型 | `internal/model/agent_wallet.go` | 9-94 |
| 钱包常量 | `pkg/constants/wallet.go` | 1-234 |
| 订单常量 | `pkg/constants/constants.go` | 153-166 |

