文档提交
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m14s

This commit is contained in:
2026-04-11 11:27:43 +08:00
parent e9ff14df0e
commit 8f66ca7bcb
15 changed files with 3775 additions and 3 deletions

View File

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