## 赛事结算全流程与资金链路分析报告

---

### 一、赛事生命周期

比赛经历以下状态流转：

```
DRAFT → PUBLISHED → CLOSED → PENDING_SETTLEMENT → SETTLED
                         ↓
                     CANCELLED（取消，全部退款）
```

| 状态转换 | 触发方式 |
|---------|---------|
| DRAFT → PUBLISHED | 管理员发布比赛 |
| PUBLISHED → CLOSED | 每分钟自动 cron 检测开赛时间到达，或管理员手动关闭 |
| CLOSED → PENDING_SETTLEMENT | 管理员录入比分 |
| PENDING_SETTLEMENT → SETTLED | 管理员确认结算 |
| 任意 → CANCELLED | 管理员取消比赛，所有未结算注单全额退款 |

玩家端看到三种简化状态：`open`（可投注）、`closed_pending`（封盘待结算）、`settled`（已结算）。

---

### 二、结算操作流程（三步走）

结算完全由管理员手动触发，分三个阶段：

**第一步：录入比分**
- 接口：`POST /admin/matches/:id/settlement/score`
- 管理员输入全场比分（主队/客队半场和全场进球），冠军盘只需选择获胜队伍
- 系统创建 `MatchScore` 记录，比赛状态变为 `PENDING_SETTLEMENT`

**第二步：预览结算**
- 接口：`POST /admin/matches/:id/settlement/preview`
- 系统自动计算所有该场次的 PENDING 注单结果（赢/输/走水/半赢/半输）
- 创建 `SettlementBatch`（状态 PREVIEW），汇总总注单数、总派彩、总退款
- 管理员可分页查看每笔注单的结算预览

**第三步：确认结算**
- 接口：`POST /admin/settlement/:batchId/confirm`
- 在单个数据库事务中：逐笔结算、更新钱包、标记比赛为 SETTLED
- 结算后触发所有相关代理的信用额度重算

---

### 三、单关 vs 串关结算差异

#### 单关投注（Single Bet）

每笔注单只有一个选项，直接根据比分计算结果：

| 结果 | 派彩公式 |
|------|---------|
| WIN（全赢） | stake × odds |
| HALF_WIN（半赢） | stake/2 × odds + stake/2 |
| PUSH（走水） | stake（全额退还） |
| HALF_LOSE（半输） | stake / 2 |
| LOSE（全输） | 0 |

#### 串关投注（Parlay Bet）

串关跨多场比赛，结算逻辑更复杂：

1. **结算某场比赛时**：只计算该场比赛中涉及的"腿"（leg），其他场次的腿等待后续结算
2. **所有腿都有结果后**：按串关规则合并计算总赔率
3. **任一腿为 LOSE**：整单作废，派彩 = 0
4. **全部腿为 PUSH/VOID**：整单走水，退还本金
5. **混合结果**：
   - WIN：乘以该腿赔率
   - HALF_WIN：乘以 `(odds + 1) / 2`
   - HALF_LOSE：乘以 `0.5`
   - PUSH/VOID：乘以 `1.0`（跳过）
   - 最终派彩 = stake × 所有腿赔率的连乘

#### 亚盘让球/大小球（Quarter Line）

对于 `.25` 或 `.75` 盘口，系统拆分为两条半盘独立结算，再合并：
- 两半都赢 → WIN
- 两半都输 → LOSE
- 一赢一平 → HALF_WIN
- 一输一平 → HALF_LOSE
- 一赢一输 → PUSH

---

### 四、支持的盘口类型

| 盘口类型 | 结算依据 |
|---------|---------|
| FT_1X2（全场胜平负） | 全场比分 |
| HT_1X2（半场胜平负） | 半场比分 |
| FT_ODD_EVEN（全场单双） | 全场总进球奇偶（0-0 = 双） |
| FT_HANDICAP（全场让球） | 全场比分 + 让球盘 |
| HT_HANDICAP（半场让球） | 半场比分 + 让球盘 |
| FT_OVER_UNDER（全场大小） | 全场总进球 + 大小盘 |
| HT_OVER_UNDER（半场大小） | 半场总进球 + 大小盘 |
| FT_CORRECT_SCORE（波胆） | 精确比分匹配 |
| HT_CORRECT_SCORE（半场波胆） | 半场精确比分 |
| OUTRIGHT_WINNER（冠军） | 获胜队伍编号 |

---

### 五、资金链路全景图

```
                        充值（Admin/Agent → 玩家）
                    ┌──────────────────────────────┐
                    │ availableBalance += 金额       │
                    │ 流水: MANUAL_DEPOSIT (+)       │
                    │ Agent.usedCredit 重算          │
                    └──────────────────────────────┘
                              │
                              ▼
                        下注（玩家 → 系统）
                    ┌──────────────────────────────┐
                    │ availableBalance -= stake      │
                    │ frozenBalance += stake         │
                    │ 流水: BET_FREEZE (-stake)      │
                    │ 注单状态: PENDING              │
                    └──────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
         赢了 (WIN)       输了 (LOSE)      走水 (PUSH)
    ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
    │ frozen -= stk│ │ frozen -= stk│ │ frozen -= stk    │
    │ avail += stk×odds     │ │ avail += 0   │ │ avail += stk     │
    │ 流水: BET_SETTLE_WIN  │ │ 流水: BET_SETTLE_LOSE   │ │ 流水: BET_SETTLE_PUSH │
    │ 注单: WON    │ │ 注单: LOST   │ │ 注单: PUSH       │
    └──────────────┘ └──────────────┘ └──────────────────┘

                        提现（玩家 → Admin/Agent）
                    ┌──────────────────────────────┐
                    │ availableBalance -= 金额       │
                    │ 流水: MANUAL_WITHDRAW (-)      │
                    │ Agent.usedCredit 重算          │
                    └──────────────────────────────┘

                        返水/返佣（系统 → 玩家）
                    ┌──────────────────────────────┐
                    │ availableBalance += 金额       │
                    │ 流水: CASHBACK_DEPOSIT (+)     │
                    │ 金额 = 有效投注额 × 返水率      │
                    └──────────────────────────────┘

                        重新结算（比分纠正）
                    ┌──────────────────────────────┐
                    │ availableBalance += delta      │
                    │ delta = 新派彩 - 旧派彩        │
                    │ 流水: BET_SETTLE_WIN 或        │
                    │       RESETTLE_REVERSE         │
                    │ （delta 可为负数，余额可变负）   │
                    └──────────────────────────────┘
```

---

### 六、钱包模型

每个玩家有一个钱包，包含两个余额字段：

- **availableBalance**（可用余额）：可自由使用的金额
- **frozenBalance**（冻结余额）：已下注未结算的金额

每次余额变动都会创建一条 `WalletTransaction` 记录，包含变动前后的快照（balanceBefore/After, frozenBefore/After），形成完整的审计链。

#### 所有交易类型

| 类别 | 类型 | 方向 |
|------|------|------|
| 充值 | MANUAL_DEPOSIT | 入账 (+) |
| 提现 | MANUAL_WITHDRAW | 出账 (-) |
| 下注冻结 | BET_FREEZE | available → frozen |
| 结算赢 | BET_SETTLE_WIN | frozen 释放 + 派彩入账 |
| 结算输 | BET_SETTLE_LOSE | frozen 释放，无入账 |
| 结算走水 | BET_SETTLE_PUSH | frozen 释放 + 退还本金 |
| 比赛取消退款 | BET_VOID_REFUND | frozen 释放 + 退还本金 |
| 重新结算调整 | RESETTLE_REVERSE | delta 调整（可正可负） |
| 返水 | CASHBACK_DEPOSIT | 入账 (+) |

---

### 七、代理信用体系

代理（Agent）没有真实钱包，采用**信用额度**模型：

```
可用信用 = creditLimit - usedCredit
```

其中 `usedCredit = directPlayerLiability + childAgentExposure`：
- `directPlayerLiability`：所有直属玩家的（可用余额 + 冻结余额）之和
- `childAgentExposure`：所有子代理的 max(creditLimit, usedCredit) 之和

#### 约束规则
- 子代理的 creditLimit 不能超过父代理
- 子代理的 cashbackRate 不能超过父代理
- 给玩家充值前，检查代理可用信用 ≥ 充值金额
- 每次充值/提现/结算后都会重算代理信用

---

### 八、返水（Cashback）系统

#### 返水率解析优先级（从高到低）
1. 玩家个人规则（targetType = USER）
2. 代理规则（targetType = AGENT）
3. 全局规则（targetType = GLOBAL）
4. 代理默认 cashbackRate

#### 返水流程
1. **预览**：加载周期内所有 WON/LOST 的注单，计算有效投注额 × 返水率
2. **确认**：逐笔调用 `wallet.deposit()` 发放返水，交易类型 `CASHBACK_DEPOSIT`
3. **取消**：仅 PREVIEW 状态可取消

#### 返水公式
```
返水金额 = 有效投注额 × 返水率
```
有效投注额 = 周期内所有已结算（WON/LOST）注单的投注金额之和

---

### 九、安全与并发控制

| 机制 | 说明 |
|------|------|
| 行级锁 | 每次钱包操作使用 `SELECT ... FOR UPDATE`，PostgreSQL 悲观锁串行化 |
| 幂等性 | 注单表有 `UNIQUE(userId, requestId)` 约束，重复提交直接返回已有注单 |
| 乐观版本 | `Wallet.version` 每次变动递增，提供审计轨迹 |
| 事务完整性 | 下注+冻结在同一事务；一场比赛所有注单结算在同一事务 |
| 余额校验 | 下注前检查 availableBalance ≥ stake，提现前检查 availableBalance ≥ amount |
| 重新结算 | delta 可为负数，允许追扣（余额可能变负） |

---

### 十、重新结算（纠错机制）

当管理员发现比分录入错误时：

1. **预览重新结算**：用新比分重新计算所有注单结果，对比新旧派彩差额
2. **确认重新结算**：
   - delta > 0：补发差额（BET_SETTLE_WIN）
   - delta < 0：追扣差额（RESETTLE_REVERSE），余额可能变负
   - 注单标记 `settlementStatus = RESETTLED`
   - 代理信用重算

---

### 十一、典型流程示例

**示例：玩家投注主胜 100 元，赔率 2.0**

| 步骤 | availableBalance | frozenBalance | 说明 |
|------|-----------------|---------------|------|
| 初始 | 1,000 | 0 | - |
| 下注 | 900 | 100 | BET_FREEZE |
| 结算（赢） | 1,100 | 0 | BET_SETTLE_WIN: +200 |
| 净收益 | +100 | - | 派彩 200 - 本金 100 |

**示例：串关 3 串 1，本金 100 元，赔率 2.0 × 3.0 × 1.5 = 9.0**

| 步骤 | 说明 |
|------|------|
| 下注 | 冻结 100 元 |
| 第一场结算（赢） | 该腿 WIN，注单仍 PENDING |
| 第二场结算（输） | 该腿 LOSE，整单 LOST，派彩 = 0 |
| 第三场 | 无需结算，注单已终结 |
| 结果 | 冻结释放，派彩 0，玩家亏损 100 元 |
