This commit is contained in:
456
docs/order_status_commission_analysis.md
Normal file
456
docs/order_status_commission_analysis.md
Normal 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 |
|
||||
|
||||
Reference in New Issue
Block a user