# 订单状态与佣金系统分析报告 ## 一、订单状态定义 ### 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 |