feat: 充值订单审计与重新申请,优化赛事展示和余额刷新

- 新增 deposit_order_audit_logs 表,记录提交/审批/拒绝/撤销/重提全链路
- 管理端充值单页增加审计历史;玩家端充值历史支持时间线与重新申请
- 已拒绝订单可原单号重提;撤销入账使用 PLAYER_DEPOSIT_REVERSAL 并加强幂等
- 结算后清除热门标记,允许归档已结算赛事,完善今日赛事时区窗口
- 足球页今日/早盘独立折叠;资料与余额在进入钱包/个人页及下注后自动刷新
- 补充投注玩法、结算返水规则文档;新增 smoke/settlement CLI 脚本

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-15 14:52:05 +08:00
parent 73a94e6be3
commit afb5c5437e
55 changed files with 3050 additions and 160 deletions

430
docs/投注玩法说明.md Normal file
View File

@@ -0,0 +1,430 @@
# 投注玩法说明
本文档为 **Explanation说明**:集中描述 thebet365 第一版**足球赛前盘**的每种玩法如何下注、如何判赢。
派彩金额公式见 [结算与返水金额规则.md](./结算与返水金额规则.md);结算操作流程见 [settlement-and-fund-flow-analysis.md](./settlement-and-fund-flow-analysis.md)。
---
## 1. 范围与注单类型
### 1.1 产品范围v1
| 项 | 说明 |
|----|------|
| 运动 | 仅足球(`SPORT_TYPE_FOOTBALL` |
| 时段 | **赛前盘**:开球时间之前可下注(`isPreMatchKickoff` |
| 不含 | 滚球、Cash Out、改单、系统串关 |
与玩家端「我的 → 投注规则」文案一致(`apps/player/src/i18n/zh-CN.ts` `rules_p1``rules_p5`)。
### 1.2 注单类型
| 类型 | 枚举 | 说明 |
|------|------|------|
| 单关 | `SINGLE` | 1 个选项1 笔本金 |
| 串关 | `PARLAY` | 25 腿(`PARLAY_MIN_LEGS=2``PARLAY_MAX_LEGS=5`),赔率连乘 |
**数据模型**:库表为 `Bet`(注单)+ `BetSelection`(选项腿)。玩家端 Pinia store 名 `betSlip`,逻辑上等价于「投注单」。
### 1.3 API 入口
| 操作 | 路径 |
|------|------|
| 单关下注 | `POST /api/player/bets/single` |
| 串关下注 | `POST /api/player/bets/parlay` |
---
## 2. 玩法总览表
权威目录:`packages/shared/src/market-catalog.ts``FOOTBALL_MARKET_CATALOG`**共 18 种**足球盘口)。
| marketType | 中文名 | 时段 | 主要选项 | 默认线 | 单关 | 串关 | 串关序 | 结算分类 | 结算依据 | 默认 seed |
|------------|--------|------|----------|--------|------|------|--------|----------|----------|-----------|
| `FT_1X2` | 全场 1X2 | FT | HOME / DRAW / AWAY | — | ✓ | ✓ | 3 | SCORE | 全场比分 | ✓ |
| `FT_HANDICAP` | 全场让球 | FT | HOME / AWAY | -0.5 | ✓ | ✓ | 1 | HANDICAP | 全场比分 + 让球线 | ✓ |
| `FT_OVER_UNDER` | 全场大小 | FT | OVER / UNDER | 2.5 | ✓ | ✓ | 2 | TOTAL | 全场总进球 + 大小线 | ✓ |
| `FT_ODD_EVEN` | 全场单双 | FT | ODD / EVEN | — | ✓ | ✓ | 4 | ODD_EVEN | 全场总进球奇偶 | ✓ |
| `HT_1X2` | 半场 1X2 | HT | HOME / DRAW / AWAY | — | ✓ | ✓ | 7 | SCORE | 半场比分 | ✓ |
| `HT_HANDICAP` | 半场让球 | HT | HOME / AWAY | -0.5 | ✓ | ✓ | 5 | HANDICAP | 半场比分 + 让球线 | ✓ |
| `HT_OVER_UNDER` | 半场大小 | HT | OVER / UNDER | 1.5 | ✓ | ✓ | 6 | TOTAL | 半场总进球 + 大小线 | ✓ |
| `FT_CORRECT_SCORE` | 全场波胆 | FT | 见 §3.E | — | ✓ | ✗ | — | CORRECT_SCORE | 全场精确比分 | ✓ |
| `HT_CORRECT_SCORE` | 上半场波胆 | HT | 见 §3.E | — | ✓ | ✗ | — | CORRECT_SCORE | 半场精确比分 | ✗ |
| `SH_CORRECT_SCORE` | 下半场波胆 | SH | 见 §3.E | — | ✓ | ✗ | — | CORRECT_SCORE | 下半场比分FTHT | ✗ |
| `OUTRIGHT_WINNER` | 冠军 | OUTRIGHT | 球队 code | — | ✓ | ✗ | — | OUTRIGHT | 冠军球队 code | ✗* |
| `FT_TEAM_TOTAL_HOME` | 主队进球大小 | FT | OVER / UNDER | 1.5 | ✓ | ✓ | 8 | TOTAL | 主队全场进球 | ✓ |
| `FT_TEAM_TOTAL_AWAY` | 客队进球大小 | FT | OVER / UNDER | 1.5 | ✓ | ✓ | 9 | TOTAL | 客队全场进球 | ✓ |
| `HT_FT` | 半全场 | FT | 9 组合,见 §3.F | — | ✓ | ✓ | 10 | SCORE | 半场结果 + 全场结果 | ✓ |
| `FT_TOTAL_GOALS` | 总进球数 | FT | TG_0_1 … TG_7_PLUS | — | ✓ | ✓ | 11 | SCORE | 全场总进球区间 | ✓ |
| `FT_CORNERS_HANDICAP` | 全场角球让球 | FT | HOME / AWAY | -0.5 | ✓ | ✓ | 12 | MANUAL_STATS | 主客角球 + 让球线 | ✓ |
| `FT_CORNERS_OVER_UNDER` | 全场角球大小 | FT | OVER / UNDER | 8.5 | ✓ | ✓ | 13 | MANUAL_STATS | 角球总数 + 大小线 | ✓ |
| `FT_CARDS_OVER_UNDER` | 全场罚牌大小 | FT | OVER / UNDER | 3.5 | ✓ | ✓ | 14 | MANUAL_STATS | 罚牌总数 + 大小线 | ✓ |
\* `OUTRIGHT_WINNER` 不在常规赛事 seed 模板中,由世界杯 48 强等单独同步,见 [默认数据说明.md](./默认数据说明.md)。
**默认 seed 刻意不含**`HT_CORRECT_SCORE``SH_CORRECT_SCORE`(系统仍识别并可结算)。
---
## 3. 按类别详解
以下判赢逻辑对应 `apps/api/src/domains/settlement/domain/settlement-calculator.ts``settleSelection()`
腿级结果:`WIN` | `HALF_WIN` | `PUSH` | `HALF_LOSE` | `LOSE` | `VOID`
### 3.A 独赢盘1X2
**结算分类**`SCORE`
#### FT_1X2全场 1X2 / Full Time 1X2
| 项 | 内容 |
|----|------|
| 玩法说明 | 预测**全场 90 分钟**(含补时,不含加时/点球)结束时的赛果 |
| 选项 | `HOME` 主胜 · `DRAW` 和局 · `AWAY` 客胜 |
| 判赢 | 比较 `ftHome``ftAway`:主胜 / 和 / 客胜 |
| 示例 | 全场 2:1`HOME`**WIN**;选 `DRAW`**LOSE** |
| 串关 | 允许(`parlayOrder=3` |
| 快照字段 | 无盘口线 |
#### HT_1X2半场 1X2
| 项 | 内容 |
|----|------|
| 玩法说明 | 预测**上半场**结束时的赛果 |
| 选项 | `HOME` / `DRAW` / `AWAY` |
| 判赢 | 比较 `htHome``htAway` |
| 示例 | 半场 0:0、全场 1:0选半场 `DRAW`**WIN** |
| 串关 | 允许(`parlayOrder=7` |
---
### 3.B 让球盘(亚洲盘)
**结算分类**`HANDICAP`(角球盘为 `MANUAL_STATS`,算法同为让球)
**通用规则**
- 选项:`HOME`(主队受让/让球侧)/ `AWAY`(客队)
- 让球线存于 `BetSelection.handicapLine`(下注时快照)
- 主队视角:`adj = 本队进球 + handicapLine 对手进球`
- `adj > 0` → WIN`adj = 0` → PUSH`adj < 0` → LOSE
- 客队选项使用 **相反符号** 的让球线(代码内对客队取 `-line`
- 四分之一盘(`.25` / `.75`)可产生半赢半输,见 [§5](#5-四分之一盘25--75)
#### FT_HANDICAP全场让球
| 项 | 内容 |
|----|------|
| 默认线 | -0.5(表示主队让半球,即主队需净胜至少 1 球才全赢) |
| 统计 | `ftHome``ftAway` |
| 串关 | 允许;**禁止** `.25`/`.75` 线进入串关 |
**示例**(线 -0.5,选主队 `HOME`
| 比分 | 结果 |
|------|------|
| 2:1 | WIN2 + (-0.5) 1 = 0.5 > 0 |
| 1:1 | LOSE |
| 1:0 | WIN |
#### HT_HANDICAP半场让球
| 项 | 内容 |
|----|------|
| 默认线 | -0.5 |
| 统计 | `htHome``htAway` |
| 串关 | 允许(禁止四分之一线) |
#### FT_CORNERS_HANDICAP全场角球让球
| 项 | 内容 |
|----|------|
| 默认线 | -0.5 |
| 统计 | 管理员录入 `homeCorners``awayCorners`;缺失时结算报错 `SETTLEMENT_STAT_MISSING` |
| 判赢 | 与足球让球相同,但用角球数代替进球 |
| 串关 | 允许(禁止四分之一线) |
---
### 3.C 大小盘Over/Under
**结算分类**`TOTAL`(角球/罚牌为 `MANUAL_STATS`,算法同为大小)
**通用规则**
- 选项:`OVER`(大)/ `UNDER`(小)
- 大小线存于 `BetSelection.totalLine`
- 大球:`总进球 > line` → WIN`= line` → PUSH`< line` → LOSE
- 小球:相反
- 支持四分之一盘半赢半输;串关禁止 `.25`/`.75` 线
| marketType | 中文名 | 默认线 | 统计对象 |
|------------|--------|--------|----------|
| `FT_OVER_UNDER` | 全场大小 | 2.5 | `ftHome + ftAway` |
| `HT_OVER_UNDER` | 半场大小 | 1.5 | `htHome + htAway` |
| `FT_TEAM_TOTAL_HOME` | 主队进球大小 | 1.5 | `ftHome` |
| `FT_TEAM_TOTAL_AWAY` | 客队进球大小 | 1.5 | `ftAway` |
| `FT_CORNERS_OVER_UNDER` | 全场角球大小 | 8.5 | `homeCorners + awayCorners` |
| `FT_CARDS_OVER_UNDER` | 全场罚牌大小 | 3.5 | `homeCards + awayCards` |
**示例**`FT_OVER_UNDER` 线 2.5,选 `OVER`
| 比分 | 总进球 | 结果 |
|------|--------|------|
| 2:1 | 3 | WIN |
| 1:1 | 2 | LOSE |
| 2:0 | 2 | LOSE2 不大于 2.5 |
角球/罚牌盘:结算前须在管理端录入对应统计字段,否则无法确认结算。
---
### 3.D 单双Odd/Even
#### FT_ODD_EVEN全场单双
| 项 | 内容 |
|----|------|
| 选项 | `ODD` 单 · `EVEN` 双 |
| 判赢 | 全场总进球 `ftHome + ftAway` 为奇数或偶数 |
| 示例 | 0:0 → 总进球 0 → **双EVEN 赢)** |
| 串关 | 允许(`parlayOrder=4` |
---
### 3.E 波胆Correct Score— 仅单关
**结算分类**`CORRECT_SCORE`
**串关**:全部 `allowParlay: false`,不可进入串关。
| marketType | 比分来源 |
|------------|----------|
| `FT_CORRECT_SCORE` | 全场 `ftHome:ftAway` |
| `HT_CORRECT_SCORE` | 半场 `htHome:htAway` |
| `SH_CORRECT_SCORE` | 下半场 `(ftHomehtHome):(ftAwayhtAway)` |
**选项类型**
1. **精确比分**code 形如 `SCORE_2_1`(表示 2:1
2. **其它主胜** `OTHER_HOME`:主胜,且精确比分不在模板列表中
3. **其它和局** `OTHER_DRAW`:和局,且精确比分不在模板列表中
4. **其它客胜** `OTHER_AWAY`:客胜,且精确比分不在模板列表中
**模板列表**`market-catalog.ts`
- 全场:`FT_CORRECT_SCORE_TEMPLATE`28 项,含 3 个 OTHER_*
- 半场/下半场:`HT_CORRECT_SCORE_TEMPLATE`17 项)
**判赢逻辑**
-`SCORE_h_a`:实际比分完全相等 → WIN否则 LOSE
- 若实际比分在模板中有对应 `SCORE_*` 项,则 OTHER_* 选项 LOSE
- 若实际比分**不在**模板中,则按主胜/和/客胜归到对应 OTHER_* → WIN
**示例**(全场波胆,实际 4:2
- 模板含 `SCORE_4_2` → 选 `SCORE_4_2` **WIN**,选 `OTHER_HOME` **LOSE**
- 若模板不含 4:2 → 选 `OTHER_HOME` **WIN**(主队胜)
---
### 3.F 组合 / 区间
#### HT_FT半全场 / Half TimeFull Time
| 项 | 内容 |
|----|------|
| 玩法说明 | 同时预测**半场结果**与**全场结果** |
| 选项 code | `{半场}_{全场}`,各为 `HOME` / `DRAW` / `AWAY`,共 9 种 |
| 判赢 | 实际组合须与选项完全一致 |
| code | 含义 |
|------|------|
| `HOME_HOME` | 半场主胜 + 全场主胜 |
| `HOME_DRAW` | 半场主胜 + 全场和 |
| `HOME_AWAY` | 半场主胜 + 全场客胜 |
| `DRAW_HOME` | 半场和 + 全场主胜 |
| `DRAW_DRAW` | 半场和 + 全场和 |
| `DRAW_AWAY` | 半场和 + 全场客胜 |
| `AWAY_HOME` | 半场客胜 + 全场主胜 |
| `AWAY_DRAW` | 半场客胜 + 全场和 |
| `AWAY_AWAY` | 半场客胜 + 全场客胜 |
**示例**:半场 1:0、全场 1:1 → 半场 HOME、全场 DRAW → 选 `HOME_DRAW` **WIN**
#### FT_TOTAL_GOALS总进球数区间
| 选项 code | 区间 |
|-----------|------|
| `TG_0_1` | 01 球 |
| `TG_2_3` | 23 球 |
| `TG_4_6` | 46 球 |
| `TG_7_PLUS` | 7 球及以上 |
判赢:按全场 `ftHome + ftAway` 落入区间。
**示例**3:2 → 总进球 5 → 选 `TG_4_6` **WIN**
---
### 3.G 冠军Outright
#### OUTRIGHT_WINNER
| 项 | 内容 |
|----|------|
| 玩法说明 | 预测联赛/赛事**最终冠军**(如世界杯夺冠球队) |
| 选项 | 各参赛队 `selectionCode` = 球队 code`FRA``BRA` |
| 判赢 | `selectionCode === winnerTeamCode`(管理员指定冠军) |
| 串关 | **禁止** |
| 前置条件 | 联赛内常规赛事须全部 `SETTLED``CANCELLED` 后才可结算冠军盘 |
| 玩家入口 | `/bet` →「优胜冠军」→ `OutrightBetModal`**不走** BetSlip 串关) |
---
## 4. 串关规则
实现:`packages/shared/src/betting-rules.ts``canSelectForParlay`)、`apps/api/src/domains/betting/bets.service.ts``apps/player/src/stores/betSlip.ts`
| 规则 | 说明 |
|------|------|
| 腿数 | **25 腿** |
| 同场限制 | **每场比赛最多 1 项**(前端 `SAME_MATCH`;后端 `PARLAY_SAME_MATCH_FORBIDDEN` |
| 禁止玩法 | 波胆3 种)、冠军盘 |
| 禁止盘口线 | 让球/大小族(含队进球、角球、罚牌)的 **`.25` / `.75` 线**`QUARTER_LINE` |
| 可串关玩法 | `PARLAY_MARKET_TYPES`**14 种**(见总览表「串关=✓」行) |
| 串关列表 API | `GET /api/player/matches?scope=parlay` 仅返回含可串关盘口的赛事 |
**串关派彩**(连乘有效因子、任一脚 LOSE 整单 LOST 等)见 [结算与返水金额规则.md §3](./结算与返水金额规则.md)。
---
## 5. 四分之一盘(.25 / .75
让球/大小盘盘口线为 **0.25 或 0.75** 的整数倍时(如 -0.25、2.75),系统将盘口**拆成两条半盘**分别判定,再合并为腿级结果:
| 两半组合 | 合并结果 |
|----------|----------|
| 两赢 | WIN |
| 两输 | LOSE |
| 一赢一走 | HALF_WIN |
| 一输一走 | HALF_LOSE |
| 一赢一输 | PUSH |
| 场景 | 单关 | 串关 |
|------|------|------|
| 四分之一线 | **允许**下注 | **禁止**选入(`QUARTER_LINE` |
**示例 1** — 全场让球 **-0.25**,选主队,比分 **0:0**
- 拆为 -0 与 -0.5 两半:-0 → PUSH-0.5 → LOSE
- 合并 → **HALF_LOSE**(退一半本金,见派彩文档)
**示例 2** — 全场大小 **2.25**,选小球,总进球 **2**
- 拆为 2.0 与 2.5:对 2.0 小球 PUSH对 2.5 小球 WIN
- 合并 → **HALF_WIN**
---
## 6. 下注校验与限额
### 6.1 下注校验(`BetsService.validateSelection`
| 校验项 | 说明 |
|--------|------|
| 盘口状态 | 选项与盘口均为 `OPEN` |
| 玩家可见 | `showOnPlayer = true` |
| 赛事 | `PUBLISHED`;非冠军盘须未开球 |
| 运动 | 仅足球 |
| 赔率版本 | 请求 `oddsVersion` 须与库内一致 |
| 单关/串关 | 单关要求 `allowSingle`;串关走 `canSelectForParlay` |
| 资金 | 通过 `FundsPostingService.freezeBet` 冻结本金 |
### 6.2 默认限额(`betting-limits.service.ts`
| 配置项 | 默认值 |
|--------|--------|
| 最小单注 | 1 |
| 单关最大投注 | 50,000 |
| 串关最大投注 | 20,000 |
| 单关最高派彩 | 500,000 |
| 串关最高派彩 | 1,000,000 |
| 玩家每日投注上限 | 200,000 |
可在管理端 `systemConfig` 覆盖;`potentialReturn` 超限会拒单(`MAX_PAYOUT`)。
### 6.3 其它
- **未知 marketType** 结算时默认 **LOSE**
- **选项 code 回退**:若快照无 code可从中文名推断`settlement-helpers.ts`
- **玩家选盘**:赛事详情 → 点赔率 → `BetSlipDrawer`;波胆用 `CorrectScorePanel`
---
## 7. 下注与结算流程摘要
```mermaid
sequenceDiagram
participant Player
participant API
participant Admin
Player->>API: POST /player/bets/single 或 parlay
API->>API: validateSelection + freezeBet
Admin->>API: recordScore
Admin->>API: previewSettlement
Admin->>API: confirmSettlement
API->>API: settleSelection 逐腿判赢
API->>API: calculatePayout 或 calculateParlayPayout
API->>Player: 钱包 settleBet 入账
```
| 阶段 | 说明 |
|------|------|
| 下注 | 创建 `Bet` + `BetSelection`,冻结 `stake` |
| 录入比分 | 赛事 → `PENDING_SETTLEMENT` |
| 预览 | 生成 `SettlementBatch`PREVIEW可含角球/罚牌统计 |
| 确认 | 写入腿级 `resultStatus`、注单 `status`/`actualReturn`,解冻并派彩 |
**跨场串关**:每场结算时只更新该场腿的 `resultStatus`;全部腿有结果后才调用 `calculateParlayPayout` 一次。
详细流程见 [settlement-and-fund-flow-analysis.md](./settlement-and-fund-flow-analysis.md)。
---
## 8. 代码真源与相关文档
### 8.1 代码索引
| 主题 | 文件 |
|------|------|
| 玩法目录18 种) | `packages/shared/src/market-catalog.ts` |
| 串关选盘规则 | `packages/shared/src/betting-rules.ts` |
| 下注与校验 | `apps/api/src/domains/betting/bets.service.ts` |
| 下注限额 | `apps/api/src/domains/betting/betting-limits.service.ts` |
| 腿级结算 | `apps/api/src/domains/settlement/domain/settlement-calculator.ts` |
| 结算流程 | `apps/api/src/domains/settlement/settlement.service.ts` |
| 结算辅助 | `apps/api/src/domains/settlement/domain/settlement-helpers.ts` |
| 单元测试 | `apps/api/src/domains/settlement/domain/settlement-calculator.spec.ts` |
| Smoke 用例 | `apps/api/src/domains/operations/smoke-tests/smoke-test.cases.ts` |
| 玩家投注单 | `apps/player/src/stores/betSlip.ts` |
### 8.2 相关文档
| 文档 | 内容 |
|------|------|
| [结算与返水金额规则.md](./结算与返水金额规则.md) | 单关/串关派彩公式、四分之一盘金额、返水 |
| [settlement-and-fund-flow-analysis.md](./settlement-and-fund-flow-analysis.md) | 结算三步、钱包流水 |
| [默认数据说明.md](./默认数据说明.md) | seed 盘口范围、48 强冠军盘 |
| [UAT_CHECKLIST.md](./UAT_CHECKLIST.md) | 投注与串关 UAT 项 |
### 8.3 文档分工
| 本文档 | 结算金额文档 |
|--------|--------------|
| 每种玩法怎么选、怎么判赢 | stake × odds 怎么算 |
| 串关能否选、同场限制 | 串关连乘因子与 payout |
| 角球/罚牌统计要求 | HALF_WIN 派彩数值示例 |
---
*最后更新:与 `FOOTBALL_MARKET_CATALOG`18 种)及 `settlement-calculator.ts` 实现对齐。*