14 KiB
订单状态与佣金系统分析报告
一、订单状态定义
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 订单支付规则
- 待支付订单自动取消: 30 分钟未支付自动取消,解冻钱包余额
- 幂等性检查: 防止同一买家对同一资源短时间内重复下单
- 强充要求: 首次购买需满足最低充值要求
8.2 佣金计算规则
- 成本价差佣金: 仅当
seller_profit > 0时才计算 - 一次性佣金: 仅非代购订单触发,且仅首次购买时触发
- 佣金链路: 沿代理层级向上分配,若中间断裂则标记为待审核
8.3 提现规则
- 冻结机制: 提现申请时冻结余额,防止重复提现
- 可用余额: balance - frozen_balance,用于验证提现金额
- 手续费: 提现时扣除手续费,实际到账 = 申请金额 - 手续费
九、相关文件索引
| 功能 | 文件 | 关键行数 |
|---|---|---|
| 订单模型 | 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 |