Files
junhong_cmp_fiber/docs/order_status_commission_analysis.md
huang 8f66ca7bcb
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m14s
文档提交
2026-04-11 11:27:43 +08:00

14 KiB
Raw Permalink Blame History

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

一、订单状态定义

1.1 订单支付状态Payment Status

文件: internal/model/order.go (第 98-104 行)

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 行)

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 行)

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 行)

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 行)

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 行)

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 行)

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 行)

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 行)

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 行)

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

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

6.2 钱包相关常量

文件: pkg/constants/wallet.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