C9 Platform — 全端專案規格書
本文件整合後端架構、API 規格、資料庫結構、前端串接指南、功能規格與開發理念。 供前端與後端工程師共用,作為開發、串接、維護的唯一真相來源 (Single Source of Truth)。
目錄
- 1. 專案概覽
- 2. 技術架構
- 3. 開發理念與設計原則
- 4. 統一回應格式與錯誤處理
- 5. 多語系 (i18n) 系統
- 6. 前端串接指南
- 7. 資料庫結構 (Database Schema)
- 8. 功能規格
- 8.1 認證系統 (Auth)
- 8.2 遊戲系統 (Game)
- 8.3 金流系統 (Vendor + Deposit)
- 8.4 錢包系統 (Wallet)
- 8.5 VIP 等級系統
- 8.6 活動促銷系統 (Promo)
- 8.7 代理推廣系統 (Affiliate)
- 8.8 投注紀錄 (BetRecord)
- 8.9 排行榜 (Ranking)
- 8.10 站內信 (Inbox)
- 8.11 站點設定 (SiteConfig)
- 8.12 提領系統 (Withdrawal)
- 8.13 即時體育賽事 (LiveSports)
- 8.14 任務系統 (Mission)
- 8.15 吉祥物頭像系統 (Avatar)
- 8.16 後台管理系統 (Admin)
- 9. 系統自動排程
- 10. 錯誤碼完整對照表
- 11. 端點速查表
1. 專案概覽
| 項目 | 值 |
|---|---|
| 專案名稱 | C9 Platform |
| 後端版本 | c9-be 0.0.1 |
| 功能定位 | 線上博弈平台(遊戲、入金、VIP、代理推廣) |
| 端點總數 | 142(GET 50 / POST 57 / PATCH 9 / DELETE 8 + 新增 18),含 11 遊戲商 S2S 回調 |
| Base URL | http://localhost:8080/api (開發環境) |
| Swagger UI | {BASE_URL}/api/docs |
| 認證方式 | JWT Bearer Token,Authorization: Bearer <token> |
| 多語系 | Header: locales: zh-TW / en-US / zh-CN |
| Content-Type | application/json(銀行卡新增為 multipart/form-data) |
| 幣別 | 系統統一使用 USD,入金時依即時匯率自動轉換 |
核心模組
┌──────────────────────────────────────────────────────────────┐
│ C9 Platform │
├──────────┬───────────┬──────────┬───────────┬───────────────┤
│ Auth │ Game │ Deposit │ VIP │ Affiliate │
│ 認證系統 │ 遊戲系統 │ 入金系統 │ VIP 系統 │ 代理推廣 │
├──────────┼───────────┼──────────┼───────────┼───────────────┤
│ Wallet │ Promo │ Ranking │ BetRecord │ Common │
│ 錢包管理 │ 活動促銷 │ 排行榜 │ 投注紀錄 │ 共用列舉 │
├──────────┼───────────┼──────────┼───────────┼───────────────┤
│ Inbox │SiteConfig │Withdrawal│ LiveSports│ Mission │
│ 站內信 │ 站點設定 │ 提領系統 │ 即時體育 │ 任務系統 │
├──────────┼───────────┼──────────┼───────────┼───────────────┤
│ Admin │AdminGroup │ AdminLog │ │ │
│ 後台管理員│ 管理員群組 │ 操作紀錄 │ │ │
└──────────┴───────────┴──────────┴───────────┴───────────────┘2. 技術架構
後端技術棧
| 層級 | 技術 | 版本 |
|---|---|---|
| Runtime | Node.js | ES2023 |
| Framework | NestJS | v11 |
| Language | TypeScript | 5.7 (strict) |
| Database | MySQL | utf8mb4, TZ +08:00 |
| ORM | TypeORM | 0.3.28 |
| Cache | Redis | via @keyv/redis |
| Auth | JWT + Passport | 7 天過期 |
| 2FA | speakeasy (TOTP) | Google Authenticator |
| OAuth | google-auth-library | Google 登入 |
| Telegram Login | crypto (HMAC-SHA256) | Telegram Login Widget 驗證 |
| File Storage | Cloudflare R2 | S3-compatible |
| Resend API | 驗證信 | |
| i18n | nestjs-i18n | 3 語系 |
| Scheduler | @nestjs/schedule | Cron 排程 |
| Exchange Rate | tw-exchange | 台灣銀行即時匯率 |
環境變數
| 變數 | 用途 |
|---|---|
PORT | 伺服器 Port (預設 8080) |
JWT_SECRET / JWT_EXPIRES | JWT 簽名密鑰 / 過期時間 (7d) |
DB_HOST/PORT/USER/PASSWORD/DATABASE | MySQL 連線 |
REDIS_URL | Redis 連線字串 |
RESEND_API_KEY / RESEND_FROM | Email 發送 |
GOOGLE_CLIENT_ID/SECRET/REDIRECT_URI | Google OAuth |
TELEGRAM_BOT_TOKEN / TELEGRAM_BOT_USERNAME | Telegram Login Widget 驗證 |
R2_BUCKET_NAME/ENDPOINT/ACCESS_KEY_ID/SECRET_ACCESS_KEY/PUBLIC_URL | Cloudflare R2 |
API_DOMAIN | 自身域名(用於回調 URL) |
RSG_* | RSG 遊戲商 API 設定 |
BS_* | BetSolutions 遊戲商 API 設定 |
專案結構
src/
├── main.ts # Bootstrap: port 8080, /api prefix, CORS, pipes, filters
├── app.module.ts # Root module: 匯入所有模組
├── enum/ # 共用列舉
│ ├── auth/index.ts # AUTH_ENUM (登入紀錄 action)
│ ├── game/index.ts # GameType, TURNOVER_WEIGHT, BetOrderStatus
│ └── error-codes/index.ts # I18N_PATH_MAP: i18n key → API path 映射
├── utils/
│ ├── http-exception.filter.ts # 統一錯誤回應格式
│ ├── success-response.interceptor.ts # 統一成功回應格式
│ ├── i18n.ts # resolveText(), resolveGameTypeLabel()
│ └── helper.ts # toNum() 數字工具
├── i18n/{zh-TW,en-US,zh-CN}/ # 翻譯 JSON 檔
└── modules/
├── auth/ # 認證、註冊、Google OAuth、Telegram Login、2FA
├── game/ # 遊戲商管理、啟動、模擬、回調
├── common/ # 共用列舉 + 錯誤碼 API
├── deposit/ # 存款訂單、匯率
├── wallet/ # 銀行卡 / 信用卡 / 加密錢包
├── vendor/ # 金流通道、萬通、USDT
├── promo/ # 活動促銷、領取追蹤
├── vip/ # VIP 等級、反水、保級
├── ranking/ # 排行榜
├── bet-record/ # 投注紀錄
├── affiliate/ # 代理推廣、佣金、結算
├── mission/ # 任務系統(每日/週/月存款、投注任務)
├── inbox/ # 站內信、通知已讀
├── site-config/ # 站點設定、主題、吉祥物管理
├── withdrawal/ # 提領訂單、審核流程
├── live-sports/ # 即時體育賽事(API-Football + Redis 快取)
├── r2/ # Cloudflare R2 檔案上傳
└── admin/ # 後台管理(管理員、群組、操作紀錄)
scripts/
├── seed-all.ts # 全資料表假資料 (37 表)
├── generate-promo-images.ts # 活動橫幅圖片生成 + R2 上傳 (50×2 = 100 張)
└── generate-mascot-avatars.ts # 吉祥物頭像生成 + R2 上傳 (10 張 512×512)3. 開發理念與設計原則
3.1 統一幣別:一切以 USD 為核心
系統內部所有金額一律使用 USD (decimal(18,6))。使用者以 TWD、USDT 等幣別入金時,由後端於收到付款確認的當下,透過台灣銀行即時匯率 (tw-exchange) 換算為 USD 後上分。
為什麼: 多幣別入金若不統一,VIP 流水、反水計算、代理佣金結算將需要各自維護匯率快照,複雜度倍增。統一 USD 讓所有後續計算都在同一基準上進行。
3.2 多語系:DB 欄位 + i18n 翻譯雙軌制
| 場景 | 方案 | 範例 |
|---|---|---|
| 使用者可編輯的內容 | DB json 欄位 | VIP 等級名稱、活動標題、金流通道名稱 |
| 系統固定的錯誤訊息 | nestjs-i18n JSON 檔 | 「帳號已存在」「查無此遊戲商」 |
| 系統固定的 UI 標籤 | nestjs-i18n JSON 檔 | 遊戲類型名稱「老虎機」「真人」 |
DB json 欄位格式:
{"zh-TW": "青銅 I", "en-US": "Bronze I", "zh-CN": "青铜 I"}後端使用 resolveText() 根據請求的 locales header 自動解析為對應語言字串回傳。
3.3 錯誤碼集中管理
所有業務錯誤碼透過 i18n JSON 檔案定義。GET /common/enums 回傳完整的 ERROR_CODES 列舉,前端載入後依 path + code 查表顯示錯誤訊息。
前端不需硬寫任何錯誤文字,切換語系只需重新呼叫 /common/enums 即可取得對應語言的錯誤碼。
3.4 回應格式零歧義
- 成功: HTTP 200,body 固定為
{ code: 200, message: "ok", result, timestamp, path } - 業務錯誤: HTTP 200,
code ≠ 200,message為當前語系的錯誤訊息 - 未授權: HTTP 401,
code: 401,message: "Unauthorized"
前端只需判斷 HTTP status 是否 401、以及 body.code 是否為 200,即可決定後續行為。
3.5 投注後連鎖觸發 (Event-Driven)
每筆投注結算完成後,系統同步觸發兩項操作(對前端透明):
- VIP 等級重算 — 累計有效投注達門檻自動升級
- 優惠打碼量累計 — 已領取的活動獎勵打碼進度自動更新
3.6 金流商抽象層
所有金流商(萬通、USDT、未來可擴充的第三方)統一透過 VendorService 路由。前端呼叫 POST /deposit 時只需帶 channelId + paymentMethod,後端自動判斷應走哪家金流商。
3.7 遊戲商 Provider 模式
遊戲商以 providerCode 區分(目前 betsolutions、rsg),每個 provider 各自實作 callback 端點。遊戲大廳以 gameCode 為唯一識別,一個 provider 可有多個 gameCode(如 slot-betsolutions、crypto-betsolutions)。
3.8 資料精度規範
| 用途 | 型別 | 精度 |
|---|---|---|
| 金額(餘額、投注、佣金) | decimal(18,6) | 小數 6 位 |
| 匯率 | decimal(18,10) | 小數 10 位 |
| 百分比(反水率、佣金率) | decimal(5,2) | 小數 2 位 |
| 倍率 | decimal(10,2) | 小數 2 位 |
USD 金額截斷規則:Math.floor(value * 1e6) / 1e6(無條件捨去到 6 位)。
4. 統一回應格式與錯誤處理
成功回應 (HTTP 200)
{
"code": 200,
"message": "ok",
"result": { ... },
"timestamp": 1708588800000,
"path": "/api/auth/login"
}業務錯誤 (HTTP 200, code ≠ 200)
{
"code": 2001,
"message": "帳號已存在",
"data": null,
"timestamp": 1708588800000,
"path": "/api/auth/register"
}未授權 (HTTP 401)
{
"code": 401,
"message": "Unauthorized",
"data": null,
"timestamp": 1771899908020,
"path": "/api/vendor/channels"
}例外:遊戲商回調
遊戲商 callback 回傳 { StatusCode, Data } 格式時,直接 passthrough,不套用 wrapper。
錯誤回應實作邏輯
AllExceptionsFilter:
├─ HTTP 401 → 回 HTTP 401, code=401, message="Unauthorized"
└─ 其他所有 → 回 HTTP 200, code=業務碼, message=i18n訊息Service 層拋出錯誤的方式:
throw new HttpException(
{ code: 2001, message: this.i18n.t('authError.register.2001') },
HttpStatus.BAD_REQUEST,
);
// AllExceptionsFilter 會攔截,統一回 HTTP 200 + code 20015. 多語系 (i18n) 系統
支援語系
| Code | 語言 |
|---|---|
zh-TW | 繁體中文(預設) |
en-US | 英文 |
zh-CN | 簡體中文 |
語系判定優先順序
- Request header
locales - Request header
Accept-Language - Fallback:
zh-TW
i18n 檔案結構
src/i18n/
├── zh-TW/
│ ├── authError.json # 認證相關錯誤碼
│ ├── walletError.json # 錢包相關錯誤碼
│ ├── vendorError.json # 金流商相關錯誤碼
│ ├── depositError.json # 存款相關錯誤碼
│ ├── gameError.json # 遊戲相關錯誤碼
│ ├── promoError.json # 活動相關錯誤碼
│ ├── vipError.json # VIP 相關錯誤碼
│ ├── affiliateError.json # 代理相關錯誤碼
│ ├── betRecordError.json # 投注紀錄相關錯誤碼
│ ├── game.json # 遊戲類型標籤(一般翻譯)
│ └── ranking.json # 排行榜標籤(一般翻譯)
├── en-US/ # 同結構
└── zh-CN/ # 同結構錯誤碼 Key 對應 API Path
I18N_PATH_MAP 定義了 i18n key prefix 到 API path 的映射:
i18n key prefix → API path
─────────────────────────────────────────────────────
authError.register → /api/auth/register
authError.loginGoogle → /api/auth/login-google
walletError.bankCard.add → /api/wallet/bank-card/add
vendorError.channels → /api/vendor/channels
vendorError.wantong → [/api/vendor/wantong/add-atm, /api/vendor/wantong/add-card]
depositError.exchangeRate → /api/deposit/exchange-rate
gameError.launch → /api/game/launch
vipError.levels → [/api/vip/levels, /api/vip/levels/:id]
affiliateError.withdrawals.request → /api/affiliate/withdrawals/request
...CommonService.scanI18nCodes() 在啟動時遞迴掃描所有 i18n JSON 檔案,找出數字 key(錯誤碼),透過 I18N_PATH_MAP 映射後組成 ERROR_CODES 物件。
6. 前端串接指南
6.1 錯誤碼讀取規則
┌─────────────────────────────────────────────────────────────┐
│ 1. 啟動時呼叫 GET /common/enums 取得 ERROR_CODES │
│ └→ 依 locales header 回傳對應語系的錯誤訊息 │
│ └→ 切換語系後重新呼叫一次即可 │
│ │
│ 2. API 錯誤回應格式固定為: │
│ { code, message, data, timestamp, path } │
│ │
│ 3. 錯誤處理邏輯: │
│ ├─ HTTP 401 → 直接跳轉登入頁(不查 enum) │
│ └─ HTTP 200 + code ≠ 200 → 查 ERROR_CODES 顯示訊息 │
│ └→ ERROR_CODES[path][code] │
│ └→ 例: ERROR_CODES["/api/auth/register"][2001] │
│ = "帳號已存在" │
│ │
│ 4. path 含動態參數時的匹配規則: │
│ 回應 path = "/api/vip/levels/3" │
│ ERROR_CODES key = "/api/vip/levels/:id" │
│ 前端需自行做 path pattern matching │
│ (將 path 中的數字段替換為 :param 後再查) │
└─────────────────────────────────────────────────────────────┘6.2 Axios 建議設定
import axios from 'axios';
// ── 錯誤碼 enum(啟動時 / 切換語系時載入)──
let ERROR_CODES: Record<string, Record<number, string>> = {};
export async function loadErrorCodes() {
const enums = await api.get('/common/enums');
ERROR_CODES = enums.ERROR_CODES || {};
}
// ── path pattern matching(處理動態路由參數)──
function matchErrorPath(path: string): string {
if (ERROR_CODES[path]) return path;
const pattern = path.replace(/\/\d+/g, '/:id');
if (ERROR_CODES[pattern]) return pattern;
return path;
}
// ── Axios instance ──
const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api',
headers: { 'Content-Type': 'application/json' },
});
// 請求攔截器:自動加 token + 語系
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
config.headers['locales'] = localStorage.getItem('locale') || 'zh-TW';
return config;
});
// 回應攔截器:統一取出 result / 處理錯誤
api.interceptors.response.use(
(res) => {
const body = res.data;
if (body.code === 200) return body.result;
// 業務錯誤 (code ≠ 200):查 ERROR_CODES 取得 i18n 訊息
const matched = matchErrorPath(body.path);
const i18nMsg = ERROR_CODES[matched]?.[body.code];
return Promise.reject({ ...body, message: i18nMsg || body.message });
},
(err) => {
if (err.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(err.response?.data || err);
},
);
export default api;6.3 多語系欄位讀取
後端已自動處理,前端直接使用 API 回傳的字串即可:
// 後端回傳已解析好的字串
const channel = await api.get('/vendor/channels');
// channel.groupName = "預設" ← 依 locales header 自動解析
// channel.channels[0].name = "萬通" ← 同上
// 切換語系:改 localStorage + 重打 API 即可
localStorage.setItem('locale', 'en-US');
loadErrorCodes(); // 重載錯誤碼6.4 認證流程
┌─────────────────────────────────────────┐
│ 1. 一般登入 │
│ POST /auth/login │
│ → 存 token 到 localStorage │
│ → 後續 API 自動帶 Authorization │
│ │
│ 2. Google 登入 │
│ GET /auth/login-config → 取得 URL │
│ → 跳轉 Google OAuth 頁面 │
│ → Google 回調帶 code + state │
│ → POST /auth/login-google │
│ → 存 token │
│ │
│ 3. 註冊(可帶推廣碼) │
│ POST /auth/register │
│ { account, password, name, refCode } │
│ → refCode 來自推廣連結 ?ref=XXX │
│ → 自動建立 3 層代理關係 │
│ │
│ 4. Token 過期 │
│ HTTP 401 → 清 token → 跳轉登入頁 │
└─────────────────────────────────────────┘6.5 檔案上傳 (R2)
僅銀行卡新增使用 multipart/form-data,其餘所有端點皆為 application/json。
圖片欄位在 list/detail API 回傳時自動轉為完整 R2 公開 URL:
新增時回傳: "bank-card/xxx.jpg"
列表時回傳: "https://pub-xxx.r2.dev/bank-card/xxx.jpg"7. 資料庫結構 (Database Schema)
總覽:36 張資料表
| 資料表 | 用途 | 主要關聯 |
|---|---|---|
auth-user | 用戶帳號 | vendorGroupId → vendor-group |
auth-user-login-log | 登入紀錄 | userId → auth-user |
vendor-group | 金流群組 | |
vendor-channel | 金流通道 | vendorGroupId → vendor-group |
deposit-order | 存款訂單 | userId, channelId |
bank-card | 銀行卡 | userId → auth-user |
credit-card | 信用卡 | userId → auth-user |
crypto-address | 加密錢包 | userId → auth-user |
game-provider | 遊戲商 | |
game-transaction | 遊戲錢包帳本 | userId |
bet-order | 投注訂單 | userId |
bet-detail | 注單明細 | orderId → bet-order |
vip-level | VIP 等級定義 | |
vip-rebate | 反水規則 | level |
vip-rebate-log | 反水發放紀錄 | userId |
promo | 活動促銷 | |
promo-claim | 活動領取紀錄 | promoId, userId |
rank-list | 排行榜 | userId |
affiliate-commission | 佣金明細 | agentId, memberId, betOrderId, settlementId |
affiliate-settlement | 佣金結算 | agentId |
affiliate-balance | 代理佣金餘額 | agentId (unique) |
affiliate-withdrawal | 代理提款 | agentId |
affiliate-click | 推廣點擊追蹤 | agentId |
affiliate-bind-log | 綁定審計紀錄 | memberId, agentId |
affiliate-risk-log | 風控紀錄 | settlementId, agentId |
notification | 站內信通知 | userId → auth-user (nullable) |
notification-read | 通知已讀紀錄 | userId, notificationId |
site-config | 站點設定 | |
site-theme | 站點主題 | siteConfigId → site-config |
withdrawal-order | 提領訂單 | userId → auth-user |
mission | 任務定義 | unique(category, periodType, tier) |
mission-progress | 任務進度 | userId, unique(userId, periodType, periodKey) |
mission-claim | 任務領取紀錄 | missionId, userId, unique(missionId, userId, periodKey) |
admin-user | 後台管理員帳號 | groupId → admin-group |
admin-group | 管理員群組 (RBAC) | |
admin-operation-log | 管理員操作紀錄 | adminId → admin-user |
auth-user
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| account | varchar(50) | 帳號 |
| password | varchar(255) | bcrypt hash |
| name | varchar(50) | 暱稱 |
| varchar(50) unique nullable | ||
| mobile | varchar(30) unique nullable | |
| telegram | varchar(50) unique nullable | |
| varchar(50) unique nullable | Google sub ID | |
| vipLevel | varchar(4) default '1' | 當前 VIP 等級 |
| totalEffectiveBet | decimal(18,6) | 累計有效投注 (USD) |
| relegationMissCount | tinyint | 連續未達保級月數 |
| vipHold | tinyint | 保級鎖定 (VIP 5+) |
| googleAuthSecret | varchar(32) nullable | TOTP secret |
| googleAuthEnabled | tinyint default 0 | |
| tokenVersion | int default 0 | JWT 版本(強制登出用) |
| balance | decimal(18,6) | USD 餘額 |
| frozenBalance | decimal(18,6) default 0 | 凍結中金額(提領審核中) |
| withdrawalVerifyCode | varchar(6) nullable | 提領用郵箱驗證碼 |
| vendorGroupId | int nullable | 金流群組 ID |
| agentCode | varchar(20) unique nullable | 代理推廣碼 (null=非代理) |
| level1AgentId | int nullable | 直屬代理 user ID |
| level2AgentId | int nullable | 上 2 層代理 |
| level3AgentId | int nullable | 上 3 層代理 |
deposit-order
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| userId | int | |
| channelId | int | 金流通道 ID |
| channelName | varchar(30) | 通道名稱快照 |
| currency | varchar(10) | TWD / USDT / ETH |
| subOrder | varchar(24) unique | 商戶訂單號 |
| orderAmount | int | 原幣金額 |
| paymentMethod | varchar(10) | fiat / credit / crypto |
| status | varchar(20) | pending → created → paid / failed |
| payAmount | int | 實付金額 |
| payTime | varchar(50) nullable | |
| usdAmount | decimal(18,6) | 換算 USD |
| exchangeRate | decimal(18,10) | 當時匯率 |
| callbackData | json nullable | 回調原始資料 |
vendor-channel
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| name | json | 多語系名稱 |
| storeCode | varchar(30) | 金流商代碼 |
| secret1~4 | varchar(100) | 金流商認證資訊 |
| currency | varchar(10) | TWD / USDT / ETH |
| paymentMethods | simple-array | fiat / credit / crypto |
| paymentAddress | varchar(255) nullable | 加密貨幣繳費地址 |
| enabled | tinyint default 1 | |
| vendorGroupId | int nullable | FK to vendor-group |
game-provider
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| gameCode | varchar unique | 遊戲商識別碼 (e.g. slot-betsolutions) |
| providerCode | varchar(20) | 後端路由 (rsg / betsolutions) |
| gameType | int | 1=體育 2=電子 ... 10=捕魚 |
| areaBlock | tinyint default 0 | 地區封鎖 |
| maintain | tinyint default 0 | 維護中 |
| enable | tinyint default 1 |
bet-order
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| userId | int | |
| gameType | varchar(20) | "1"~"10" (存數字字串) |
| gamePlatform | varchar(20) | rsg / betsolutions / simulate |
| gameNumber | varchar(100) unique | 訂單號 |
| totalBetCount | int | 總注單數 |
| betAmount | decimal(18,6) | 投注金額 |
| betEffective | decimal(18,6) | 有效投注 = betAmount × 權重 |
| winLose | decimal(18,6) | 輸贏 |
| status | varchar(20) | valid / invalid / cancelled |
| odds | decimal(10,4) | |
| invalidReason | varchar(50) nullable | draw / cancelled / low_odds / arbitrage |
| gameName | varchar(100) | |
| betDatetime | datetime |
vip-level
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| level | int unique | 1~15 |
| name | json | 多語系名稱 |
| tier | varchar(20) | bronze / gold / platinum / diamond |
| minChip | decimal(18,6) | 升級門檻 (累計有效投注 USD) |
| relegationChip | decimal(18,6) | 月保級門檻 (USD) |
| sortOrder | int | |
| enabled | tinyint |
promo
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| title | json | 多語系標題 |
| content | json | 多語系 HTML 內容 |
| imgPc / imgMobile | varchar(255) nullable | R2 圖片 key |
| startTime / endTime | datetime | 活動期間 |
| tag | varchar(30) | 新手/回歸/VIP/節日/限時/每日/週末/月度 |
| conditionType | varchar(20) | deposit_threshold / vip_level / first_deposit |
| conditionValue | varchar(50) | 門檻值 |
| rewardAmount | decimal(18,6) | 獎勵金額 (USD) |
| turnoverMultiplier | decimal(10,2) | 打碼倍數 (0=無要求) |
| maxClaims | int | 最大領取人數 (0=無限) |
| claimedCount | int | 已領取人數 |
affiliate 相關
| 資料表 | 主要欄位 | 說明 |
|---|---|---|
affiliate-commission | agentId, memberId, agentLevel, netLoss, commissionRate, commissionAmount, weekStart | 佣金明細(每週結算) |
affiliate-settlement | agentId, weekStart, totalCommission, status, riskFlagged | 結算紀錄 |
affiliate-balance | agentId (unique), available, frozen, totalEarned, totalWithdrawn | 佣金餘額 |
affiliate-withdrawal | agentId, amount, method, status | 提款紀錄 |
affiliate-click | agentId, refCode, ip, userAgent, converted | 推廣點擊 |
affiliate-bind-log | memberId, agentId, action, source | 綁定審計 |
affiliate-risk-log | settlementId, riskType, detail | 風控紀錄 |
mission
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| category | varchar(20) | 任務類別: deposit / bet |
| periodType | varchar(20) | 週期類型: daily / weekly / monthly |
| tier | int | 階級 (1-5) |
| threshold | decimal(18,6) | 門檻金額 (USD) |
| rewardAmount | decimal(18,6) | 獎勵金額 (USD) |
| vipRequired | int default 0 | VIP 最低等級要求 (0=無) |
| turnoverMultiplier | decimal(10,2) default 0 | 打碼量倍數 |
| enabled | tinyint(1) default 1 | 是否啟用 |
| createdAt | datetime | |
| updatedAt | datetime |
UNIQUE(category, periodType, tier)
mission-progress
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| userId | int index | 用戶 ID |
| periodType | varchar(20) | 週期類型 |
| periodKey | varchar(20) | 週期 key (e.g. 2026-02-24 / 2026-W09 / 2026-02) |
| depositTotal | decimal(18,6) default 0 | 累計存款 (USD) |
| betTotal | decimal(18,6) default 0 | 累計有效投注 (USD) |
| createdAt | datetime | |
| updatedAt | datetime |
UNIQUE(userId, periodType, periodKey) 存款確認時
ON DUPLICATE KEY UPDATE depositTotal += amount投注結算時ON DUPLICATE KEY UPDATE betTotal += betEffective
mission-claim
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| missionId | int index | 任務定義 ID |
| userId | int index | 用戶 ID |
| periodKey | varchar(20) | 領取時的週期 key |
| rewardAmount | decimal(18,6) | 實際發放金額 (USD) |
| requiredTurnover | decimal(18,6) default 0 | 需完成的打碼量 |
| completedTurnover | decimal(18,6) default 0 | 已完成的打碼量 |
| turnoverCompleted | tinyint(1) default 0 | 打碼量是否已完成 |
| claimedAt | datetime | 領取時間 |
UNIQUE(missionId, userId, periodKey) — 同一任務同一期只能領一次
8. 功能規格
8.1 認證系統 (Auth)
端點: 17 個
| 功能 | 端點 | Auth |
|---|---|---|
| 註冊 | POST /auth/register | - |
| 登入 | POST /auth/login | - |
| 用戶資料 | GET /auth/user-detail | JWT |
| 國碼列表 | GET /auth/country-codes | JWT |
| 發送驗證信 | POST /auth/send-verify-email | JWT |
| 驗證 Email | POST /auth/check-verify-email | JWT |
| 產生 Google Auth | POST /auth/generate-google-auth | JWT |
| 啟用 Google Auth | POST /auth/enable-google-auth | JWT |
| 修改密碼 | POST /auth/edit-password | JWT |
| 登出 | POST /auth/logout | JWT |
| 更新語系偏好 | PATCH /auth/locale | JWT |
| 取得登入設定 | GET /auth/login-config | - |
| Google 登入 | POST /auth/login-google | - |
| Telegram 登入 | POST /auth/login-telegram | - |
| 吉祥物列表 | GET /auth/mascots | - |
| 切換頭像 | PATCH /auth/avatar | JWT |
規格要點:
- 密碼使用 bcryptjs (salt rounds: 10) 雜湊
- JWT 有效期 7 天,
tokenVersion用於強制登出 - Google 登入:使用 OAuth 2.0 code flow,回傳 token + user + google profile
- Telegram 登入:使用 Telegram Login Widget,HMAC-SHA256(
SHA256(BOT_TOKEN), data-check-string) 驗證 hash,auth_date 須在 5 分鐘內。首次登入自動建立帳號(account: tg_{telegramId}) GET /auth/login-config回傳 Google OAuth URL +telegram: { botUsername }供前端初始化 Login Widget- 2FA:基於 TOTP (speakeasy),需先 generate 再 enable
- 登入紀錄自動記錄 IP、UserAgent、時間
- 註冊時可帶
refCode自動綁定代理關係 - 註冊時依語系自動分配金流群組(zh-TW→TWD, en-US→USD, zh-CN→CNY),找第一個啟用且含該幣別通道的群組
- 登出時:從
localesheader 覆寫語系 → 重新匹配金流群組 →tokenVersion + 1使 token 失效 - 吉祥物頭像:10 組 SVG 生成的 512×512 PNG(dragon / phoenix / angel / wizard / knight / mermaid / tiger / fox / owl / wolf),存放於 R2
avatars/mascots/。PATCH /auth/avatar以mascotId切換,更新auth-user.avatar
8.2 遊戲系統 (Game)
端點: 5 個 + 11 個遊戲商回調
| 功能 | 端點 | Auth |
|---|---|---|
| 遊戲商列表 | GET /game/provider | - |
| 啟動遊戲 | POST /game/launch | JWT |
| 試玩 (Demo) | POST /game/demo | - |
| 模擬遊戲 | POST /game/simulate | JWT |
| 遊戲列表 | GET /game/list | JWT |
遊戲類型與流水權重
| gameType | 名稱 | 標籤 | 流水權重 | 說明 |
|---|---|---|---|---|
| 1 | 體育 | sports | 1.0 (100%) | |
| 2 | 電子 | slot | 1.0 (100%) | |
| 3 | 真人 | live | 1.0 (100%) | |
| 4 | 彩票 | lottery | 1.0 (100%) | |
| 5 | 棋牌 | chess | 1.0 (100%) | |
| 8 | 電競 | esports | 1.0 (100%) | |
| 9 | 加密貨幣 | crypto | 0.5 (50%) | 半權重:防止高勝率遊戲快速刷流水 |
| 10 | 捕魚 | fish | 0.5 (50%) | 半權重:同上 |
有效投注公式: betEffective = betAmount × TURNOVER_WEIGHT[gameType]
有效投注在下注時即計算(非結算時),影響 VIP 升級、反水計算、打碼量累計。
遊戲商 Provider
| gameCode | Provider | gameType | 說明 |
|---|---|---|---|
| slot-betsolutions | betsolutions | 2 (電子) | BetSolutions Slots |
| crypto-betsolutions | betsolutions | 9 (加密) | BetSolutions ProvablyFair |
| slot-rsg | rsg | 2 (電子) | RSG 瑞盛電子 |
啟動遊戲流程
前端呼叫 POST /game/launch { gameCode, productId }
│
game-provider 查表 → 取得 providerCode
│
┌─────┴─────┐
│ │
RSG BetSolutions
│ │
DES-CBC 生成 publicToken (5min TTL)
加密 + MD5 → 組裝 URL 含 Token
→ 回傳 URL → 回傳 URL- RSG:DES-CBC 加密 JSON payload + MD5 簽名(
clientId + clientSecret + timestamp + encrypted),userId 格式為c9_{userId} - BetSolutions:生成 publicToken 存 cache(5min TTL),遊戲端首次回調時交換為 privateToken(2hr TTL),後續所有回調使用 privateToken + SHA256 hash 驗證
試玩模式 (Demo)
- 僅支援 BetSolutions(
IsFreeplay=1),RSG 不支援試玩 - 免登入,不需 JWT
模擬遊戲 (Simulate) — RTP 97%
投注金額限制:0.01 ~ 10,000 USD
完整流程:
- 扣除
betAmount,建立 game-transaction (type=bet) - 隨機開獎(累積機率閾值):
| result | 倍率 | 累積機率 | 區間 |
|---|---|---|---|
| lose | 0x | 60% | 0 ~ 60% |
| small | 0.5x | 82% | 60 ~ 82% |
| medium | 2x | 92% | 82 ~ 92% |
| good | 4x | 97% | 92 ~ 97% |
| big | 8x | 99% | 97 ~ 99% |
| huge | 20x | 99.8% | 99 ~ 99.8% |
| mega | 70x | 100% | 99.8 ~ 100% |
- 派彩
winAmount = betAmount × 倍率,建立 game-transaction (type=win) - 計算
betEffective = betAmount × getTurnoverWeight(gameType) - 建立 bet-order + bet-detail(
winLose = winAmount - betAmount) - 觸發
afterBetSettle()
投注結算後連鎖觸發 (afterBetSettle)
每筆投注結算完成後(模擬 / RSG 回調 / BS 回調),同步執行:
afterBetSettle(userId, betEffective)
├─ VipService.recalculateUserVip(userId) → 重算 VIP 等級(只升不降)
└─ PromoService.updatePromoTurnover(userId, betEffective) → 累計優惠打碼量注意: RSG 和 BS 的 afterBetSettle 在 handleBet() 時觸發(下注時),不是在 handleBetResult/handleWin() 時。
遊戲錢包帳本 (game-transaction)
每一筆遊戲資金異動記錄一筆 game-transaction:
| type | 說明 | 餘額影響 |
|---|---|---|
| bet | 下注扣款 | balance -= amount |
| win | 派彩入帳 | balance += amount |
| cancel | 取消退款 | balance += amount |
| jackpot | Jackpot 獎勵 | balance += amount |
transactionId以 provider 前綴區分:rsg_{id}、bs_{id}、sim_{uuid}- 重複 transactionId 檢查防止 callback 重放(冪等性)
RSG 回調流程
RSG → POST /game/rsg/Bet → 下注扣款 + 建 bet-order + afterBetSettle
RSG → POST /game/rsg/BetResult → 派彩入帳 + 更新 bet-order (winLose, odds)
RSG → POST /game/rsg/CancelBet → 退款 (type=cancel)
RSG → POST /game/rsg/JackpotResult → Jackpot 入帳 (type=jackpot)
RSG → POST /game/rsg/GetBalance → 查詢餘額bet-order 匹配規則:gameNumber LIKE 'rsg_{SequenceNumber}_%'
BetSolutions 回調流程
BS → POST /game/betsolutions/auth → publicToken 交換 privateToken
BS → POST /game/betsolutions/getBalance → 查詢餘額 (SHA256 驗證)
BS → POST /game/betsolutions/bet → 下注扣款 + 建 bet-order + afterBetSettle
BS → POST /game/betsolutions/win → 派彩入帳 + 更新 bet-order (winLose, odds)
BS → POST /game/betsolutions/cancelBet → 退款
BS → POST /game/betsolutions/getPlayerInfo → 回傳玩家資訊- gameType 判斷:
productId === 3→ crypto (gameType=9),其他 → slot (gameType=2) - bet-order 匹配規則:
gameNumber LIKE 'bs_{roundId}_%' - SHA256 驗證:所有參數以
|串接 + privateKey,計算 SHA256 比對
注單狀態
| status | 說明 |
|---|---|
| valid | 有效注單(參與流水、反水計算) |
| invalid | 無效(原因:draw / cancelled / low_odds / arbitrage) |
| cancelled | 已取消 |
前端遊戲整合流程
1. GET /game/provider → 取得遊戲商列表,依 gameType 分頁
2. GET /game/list?gameCode= → 取得該商的遊戲清單
3. POST /game/launch → 正式遊戲(iframe/window.open)
3. POST /game/demo → 試玩(免登入,僅 BetSolutions)
3. POST /game/simulate → 模擬遊戲(一鍵扣款→開獎→派彩→觸發 VIP+打碼量)
4. GET /bet-record → 查看投注紀錄8.3 金流系統 (Vendor + Deposit)
金流通道端點: 1 (vendor) + 4 (wantong) + 1 (usdt) + 4 (deposit)
金流架構
用戶
│
POST /deposit
(channelId + paymentMethod)
│
建立 deposit-order (status: pending)
│
依 channel.paymentMethods 路由
│
┌──────┴──────┐
│ │
fiat/credit crypto
│ │
萬通金流 USDT 入金
(wantong.service) (usdt.service)
│ │
┌────────┴────────┐ └→ 回傳繳費地址 + 鏈路
│ │ 用戶自行轉帳
ATM建單 信用卡建單 │
│ │ pay callback
萬通API 萬通API (pending → paid)
│ │
add callback add callback ← S2S: pending → created
pay callback pay callback ← S2S: created → paid
│ │
匯率轉換 → creditBalance() → user.balance += USD
└→ recalculateUserVip()法幣存款 vs USDT 存款比較
| 面向 | 法幣 (萬通) | USDT (加密) |
|---|---|---|
| 建單 | 呼叫萬通 API 取得繳費資訊 | 直接回傳預設錢包地址 |
| 狀態流轉 | pending → created → paid(2 次 callback) | pending → paid(1 次 callback) |
| 驗證方式 | MD5 checksum | 外部區塊鏈確認 |
| 匯率轉換 | TWD → USD(台灣銀行即時匯率) | USDT → USD(匯率 ≈ 1:1) |
| callbackData | {add: dto, pay: dto} | {pay: dto} |
存款訂單生命週期
| 狀態 | 觸發 | 說明 |
|---|---|---|
pending | POST /deposit | 本地訂單建立 |
created | wantong callback/add | 金流商確認建單(僅萬通) |
paid | callback/pay | 付款成功,已上分至 balance |
failed | 超時/異常 | 訂單失敗 |
匯率系統
- 來源: 台灣銀行即時匯率 (
tw-exchange) - 基底: USD(API 回傳所有幣種對 USD 的匯率)
- 快取: cache-manager,TTL 60 秒,key 格式
deposit:exchange-rate:{bankCode}:{base}:{currency}:v1 - 去重:
inFlightPromise 防止並發時重複呼叫外部 API - 匯率優先序:
sell→bksell→cashsell(賣出方向)
USD 轉換公式:
usdAmount = payAmount / exchangeRate
精度截斷:Math.floor(usdAmount * 1e6) / 1e6creditBalance 原子操作
付款回調確認後的上分流程:
1. 取得幣別匯率(cache → tw-exchange API)
2. usdAmount = Math.floor(payAmount / rate * 1e6) / 1e6
3. 原子更新:UPDATE auth-user SET balance = balance + {usdAmount} WHERE id = {userId}
4. 更新 deposit-order:status='paid', usdAmount, exchangeRate, payAmount, payTime
5. 觸發 VipService.recalculateUserVip(userId) → 重算 VIP 等級萬通 Checksum 驗證
raw = `${subOrder}${secret1}${storeCode}`.toUpperCase()
checkSum = MD5(raw).toUpperCase()- 用於 add/pay callback 的來源驗證
- checksum 不符時 log 警告但不阻擋處理(容錯)
金流通道分配
- 用戶註冊時依語系自動分配
vendorGroupId(zh-TW→TWD, en-US→USD, zh-CN→CNY) - 僅顯示用戶所屬 vendor-group 中
enabled=1的通道 - USDT 通道的
secret1欄位儲存鏈路名稱(如 TRC-20)
冪等性保護
- add callback:訂單不存在時靜默成功(防止重試失敗)
- pay callback:已為
paid狀態時直接回傳成功(不重複上分)
8.4 錢包系統 (Wallet)
| 功能 | 端點 | 特殊 |
|---|---|---|
| 銀行卡 | /wallet/bank-card/add, list, :id | multipart/form-data,含圖片上傳 |
| 信用卡 | /wallet/credit-card/add, list, :id | JSON |
| 加密錢包 | /wallet/crypto-address/add, list, :id | JSON |
錢包狀態: 0=待審核 → 1=已通過 → 2=已拒絕
- 代理提款時的
targetId需對應 status=1(已通過審核)的錢包
8.5 VIP 等級系統
端點: 12 個
資料表
vip-level:
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| level | int, UNIQUE | VIP 等級編號 (1~15) |
| name | json | 多語系名稱 {"zh-TW":"青銅 I","en-US":"Bronze I","zh-CN":"青铜 I"} |
| tier | varchar(20) | 階層標籤:bronze / gold / platinum / diamond |
| minChip | decimal(18,6) default 0 | 升級門檻:累計有效投注 (USD) |
| relegationChip | decimal(18,6) default 0 | 月保級門檻:當月有效投注 (USD) |
| sortOrder | int default 1 | 前端顯示排序 |
| enabled | tinyint(1) default 1 | 是否啟用 |
| createdAt / updatedAt | datetime |
vip-rebate:
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| level | int, INDEX | VIP 等級 |
| gameType | varchar(20) | 遊戲類型標籤 (sports/slot/live/lottery/chess/esports/crypto/fish) |
| rebateRate | decimal(5,2) default 0 | 反水率 (%),如 0.50 = 0.50% |
| createdAt | datetime |
UNIQUE(level, gameType) — 每等級每遊戲類型一筆反水率
vip-rebate-log:
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| userId | int | |
| settleDate | date | 結算日期 (如 2026-02-23) |
| vipLevel | int | 結算當時的 VIP 等級 |
| gameType | varchar(20) | 遊戲類型標籤 |
| dailyEffective | decimal(18,6) | 當日該遊戲類型有效投注 |
| rebateRate | decimal(5,2) | 適用反水率 (%) |
| rebateAmount | decimal(18,6) | 發放的反水金額 (USD) |
| createdAt | datetime |
INDEX(userId, settleDate)
15 級制
| 等級 | 名稱 | Tier | 升級門檻 (USD) | 月保級門檻 (USD) |
|---|---|---|---|---|
| 1 | 青銅 I | bronze | 0 | 0 |
| 2 | 青銅 II | bronze | 3,600 | 200 |
| 3 | 青銅 III | bronze | 12,000 | 600 |
| 4 | 青銅 IV | bronze | 36,000 | 1,800 |
| 5 | 青銅 V | bronze | 120,000 | 6,000 |
| 6 | 青銅 VI | bronze | 360,000 | 18,000 |
| 7 | 黃金 I | gold | 1,200,000 | 60,000 |
| 8 | 黃金 II | gold | 2,400,000 | 120,000 |
| 9 | 黃金 III | gold | 5,000,000 | 250,000 |
| 10 | 鉑金 I | platinum | 12,000,000 | 600,000 |
| 11 | 鉑金 II | platinum | 30,000,000 | 1,500,000 |
| 12 | 鉑金 III | platinum | 60,000,000 | 3,000,000 |
| 13 | 鑽石 I | diamond | 120,000,000 | 6,000,000 |
| 14 | 鑽石 II | diamond | 480,000,000 | 24,000,000 |
| 15 | 鑽石 III | diamond | 960,000,000 | 48,000,000 |
等級門檻數值儲存於
vip-level資料表,管理員可透過 API 調整。上表為預設初始值。
VIP 升級演算法 — recalculateUserVip(userId)
觸發時機: 每次投注結算完成後同步呼叫(非排程)
1. 取得所有啟用的 VipLevel,按 level ASC 排序
2. 查詢 SUM(betEffective) FROM bet_order WHERE userId AND status='valid'
→ totalEffective(全歷史累計有效投注)
3. 由低到高遍歷等級表,取最後一個 totalEffective >= minChip 的等級
→ matchedLevel
4. 「只升不降」: newLevel = MAX(matchedLevel.level, user.currentVipLevel)
5. 計算升級進度:
- nextLevel = 下一個等級
- 若有 nextLevel: progress = MIN(totalEffective / nextLevel.minChip, 1.0)
- 若已滿級: progress = 1.0
6. 寫入 auth_user:
- vipLevel = String(newLevel)
- vipProgress = String(Math.floor(progress × 100)) // 整數 0~100
- totalEffectiveBet = totalEffective.toFixed(6)重點:此函數永遠不會降級,降級僅由月度保級檢查(
checkMonthlyRelegation)執行。
月度保級檢查 — checkMonthlyRelegation()
排程: 0 0 1 1 * * → 每月 1 號 01:00:00
狀態機:
通過本月保級 → missCount = 0 (歸零)
第 1 個月未達 → missCount = 1 (警告,不降級)
連續第 2 月未達 → 降 1 級,missCount = 0
VIP 5+ 且 vipHold = 1 → 完全跳過檢查演算法:
1. 取得所有啟用的 VipLevel → levelMap<level, VipLevel>
2. 計算上月日期範圍: lastMonthStart ~ lastMonthEnd (23:59:59)
3. 查詢 auth_user WHERE CAST(vipLevel AS UNSIGNED) >= 2
4. 逐一檢查:
a. 若 user.vipHold === 1 且 vipLevel >= 5 → 跳過
b. 查表 levelConfig = levelMap[userLevel],若 relegationChip <= 0 → 跳過
c. 計算 SUM(betEffective) FROM bet_order WHERE userId AND 上月範圍 AND status='valid'
→ monthlyEffective
d. 判定:
- monthlyEffective >= relegationChip → 通過,missCount 歸零
- monthlyEffective < relegationChip:
- newMissCount = missCount + 1
- newMissCount >= 2 → 降級: vipLevel = MAX(level - 1, 1), missCount = 0
- newMissCount == 1 → 警告: missCount = 1(不降級)
5. 回傳 { checked, warned, demoted }VIP Hold 保級鎖定
- 僅限 VIP 5 以上 的用戶可設定
- 管理員透過
PATCH /vip/users/:userId/hold設定{ hold: 0|1 } vipHold = 1的用戶在月度保級檢查時完全跳過,不會被降級- 錯誤碼:2001 用戶不存在、2002 用戶 VIP 等級低於 5
每日反水結算 — settleDailyRebate(targetDate?)
排程: 0 5 0 * * * → 每日 00:05:00
演算法:
1. 確定結算日期(預設昨日): dayStart = 'YYYY-MM-DD 00:00:00', dayEnd = 'YYYY-MM-DD 23:59:59'
2. 查詢所有有效投注:
SELECT userId, gameType, SUM(betEffective) as dailyEffective
FROM bet_order
WHERE settledAt BETWEEN dayStart AND dayEnd AND status = 'valid'
GROUP BY userId, gameType
3. 建立反水率查詢表:
rebateMap = Map<"${level}-${gameTypeLabel}", rebateRate%>
(從 vip-rebate 全表載入)
4. 逐用戶處理:
a. 取得 user.vipLevel
b. 對每個 (gameType, dailyEffective):
- gameTypeLabel = GAME_TYPE_LABELS[gameType] // 數字→文字
- rate = rebateMap["${level}-${gameTypeLabel}"] || 0
- ★ rebateAmount = Math.floor(dailyEffective × (rate / 100) × 1e6) / 1e6
↑ 截斷至 6 位小數(非四捨五入)
- 若 rebateAmount > 0 → 累計到 userRebate,建立 rebate-log 記錄
c. 若 userRebate > 0:
- rounded = Math.floor(userRebate × 1e6) / 1e6
- UPDATE auth_user SET balance = balance + rounded WHERE id = userId
- INSERT INTO vip-rebate-log (批量寫入)
5. 回傳 { usersProcessed, totalRebate }精度規則: 所有中間值與最終值均使用 Math.floor(x × 1e6) / 1e6 截斷至 6 位小數,絕不四捨五入。
反水率範例 (%):
| Level | sports | slot | live | crypto |
|---|---|---|---|---|
| 1 | 0.20 | 0.50 | 0.50 | 0.50 |
| 7 | 0.50 | 0.80 | 0.70 | 0.70 |
| 15 | 0.90 | 1.50 | 1.00 | 1.10 |
反水率儲存於
vip-rebate資料表,管理員可透過 API 調整。每等級每遊戲類型一筆。
VIP 狀態查詢 — GET /vip/status
回傳結構:
{
"level": 1,
"name": "Bronze I",
"tier": "bronze",
"totalEffectiveBet": "1200.000000",
"currentChip": "0.000000",
"nextLevelMinChip": "3600.000000",
"progress": "0.333333",
"relegationChip": "0.000000",
"monthlyEffective": "450.000000",
"relegationMissCount": 0,
"vipHold": 0,
"rebates": [
{ "gameType": "sports", "rebateRate": "0.50" }
],
"allLevels": [
{ "level": 1, "name": "Bronze I", "tier": "bronze",
"minChip": "0.000000", "relegationChip": "0.000000" }
]
}端點
| Method | Path | Auth | 說明 |
|---|---|---|---|
| GET | /vip/levels | — | 取得所有啟用的 VIP 等級表 (sortOrder ASC) |
| GET | /vip/rebates | — | 取得所有反水率規則 (level ASC, gameType ASC) |
| GET | /vip/status | JWT | 取得當前用戶完整 VIP 狀態 |
| POST | /vip/settlement/daily-rebate | JWT | 手動觸發每日反水結算 |
| POST | /vip/settlement/monthly-relegation | JWT | 手動觸發月度保級檢查 |
| PATCH | /vip/users/:userId/hold | JWT | 設定/取消 VIP Hold |
| POST | /vip/levels | JWT | 新增 VIP 等級 |
| PATCH | /vip/levels/:id | JWT | 修改 VIP 等級 |
| DELETE | /vip/levels/:id | JWT | 刪除 VIP 等級 |
| POST | /vip/rebates | JWT | 新增反水率規則 |
| PATCH | /vip/rebates/:id | JWT | 修改反水率規則 |
| DELETE | /vip/rebates/:id | JWT | 刪除反水率規則 |
8.6 活動促銷系統 (Promo)
端點: 7 個
資料表
promo:
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| title | json | 多語系標題 {"zh-TW":"...","en-US":"...","zh-CN":"..."} |
| imgPc | varchar(255) nullable | 電腦版圖片 (R2 key) |
| imgMobile | varchar(255) nullable | 手機版圖片 (R2 key) |
| content | json | 多語系 HTML 內容 |
| actionHtml | text nullable | 渲染連結/按鈕 (HTML) |
| startTime | datetime | 活動開始時間 |
| endTime | datetime | 活動結束時間 |
| tag | varchar(30) | 活動標籤(篩選用) |
| enabled | tinyint(1) default 1 | 0=關 1=開 |
| conditionType | varchar(20) | 領取條件類型 |
| conditionValue | varchar(50) default '0' | 門檻值 |
| rewardAmount | decimal(18,6) | 獎勵金額 (USD) |
| turnoverMultiplier | decimal(10,2) default 0 | 打碼量倍數 (0=無要求) |
| maxClaims | int default 0 | 最大領取總數 (0=無限) |
| claimedCount | int default 0 | 已領取次數 |
| createdAt / updatedAt | datetime |
promo-claim:
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| promoId | int, INDEX | 對應 promo.id |
| userId | int, INDEX | 對應 auth_user.id |
| rewardAmount | decimal(18,6) | 實際發放金額 |
| requiredTurnover | decimal(18,6) default 0 | 需完成的打碼量 (USD) |
| completedTurnover | decimal(18,6) default 0 | 已完成的打碼量 |
| turnoverCompleted | tinyint(1) default 0 | 0=進行中 1=已完成 |
| claimedAt | datetime auto | 領取時間 |
UNIQUE(promoId, userId) — 每用戶每活動只能領取一次
領取條件類型 — checkCondition(userId, promo)
| conditionType | 判定邏輯 | conditionValue 用途 |
|---|---|---|
deposit_threshold | SUM(deposit_order.payAmount) WHERE userId AND status='paid' >= conditionValue | 門檻金額 (USD) |
vip_level | user.vipLevel >= conditionValue | 最低 VIP 等級 |
first_deposit | 用戶首筆 paid 存單的 createdAt 在 [promo.startTime, promo.endTime] 範圍內 | 不使用(設 0) |
未知的 conditionType 一律回傳 false(不可領取)
領取流程 — claimPromo(userId, promoId)
1. 查詢 promo → 不存在 → 2001
2. 檢查活動有效: enabled=1 且 now 在 [startTime, endTime] 內 → 否 → 2001
3. 檢查重複領取: promo-claim(promoId, userId) 已存在 → 2001
4. 檢查領取額度: maxClaims > 0 且 claimedCount >= maxClaims → 2002
5. 檢查領取條件: checkCondition(userId, promo) → false → 2003
6. 發放獎勵:
reward = Number(promo.rewardAmount)
UPDATE auth_user SET balance = balance + reward WHERE id = userId
7. 計算打碼量:
multiplier = Number(promo.turnoverMultiplier || 0)
requiredTurnover = multiplier > 0 ? reward × multiplier : 0
8. 建立領取紀錄:
INSERT INTO promo-claim {
promoId, userId,
rewardAmount: promo.rewardAmount,
requiredTurnover: requiredTurnover.toFixed(6),
completedTurnover: '0.000000',
turnoverCompleted: (requiredTurnover <= 0) ? 1 : 0 // 無打碼要求直接完成
}
9. 累計已領取: UPDATE promo SET claimedCount = claimedCount + 1
10. 回傳 { rewardAmount, newBalance }打碼量累計 — updatePromoTurnover(userId, betAmount)
觸發時機: 每次投注結算完成後同步呼叫(非排程)
1. 若 betAmount <= 0 → 直接返回(no-op)
2. 查詢 promo-claim WHERE userId AND turnoverCompleted = 0(所有未完成的領取紀錄)
3. 逐筆更新:
- required = Number(claim.requiredTurnover)
- 若 required <= 0 → 跳過
- completed = MIN(claim.completedTurnover + betAmount, claim.requiredTurnover)
↑ 封頂於 requiredTurnover,不會超過
- UPDATE: completedTurnover = completed.toFixed(6)
- 若 completed >= required → turnoverCompleted = 1重點: 單次投注的 betAmount 同時套用到所有未完成的領取紀錄。每筆紀錄獨立追蹤進度,各自封頂於 requiredTurnover。
前端計算欄位(列表 API 回傳)
| 欄位 | 計算邏輯 |
|---|---|
isActive | promo.enabled === 1 AND startTime <= now AND endTime >= now |
isClaimed | 用戶已登入且 promo-claim(promoId, userId) 存在 |
isClaimable | 用戶已登入 AND isActive AND !isClaimed AND checkCondition() === true |
未登入時
isClaimed和isClaimable一律為 false
圖片上傳
- Content-Type:
multipart/form-data - 欄位:
imgPc(電腦版)、imgMobile(手機版),各最多 1 張 - 限制: 僅
image/*MIME,單檔 5 MB - 儲存: Cloudflare R2,上傳後回傳 key,前端透過公開 URL 存取
- 更新時上傳新圖會先刪除舊 R2 物件;刪除活動時兩張圖同步刪除
端點
| Method | Path | Auth | 說明 |
|---|---|---|---|
| GET | /promo | 可選 JWT | 活動列表(分頁,?tag, ?activeOnly=1) |
| GET | /promo/claims | JWT | 用戶領取紀錄(?tab=all/pending/completed, ?startDate, ?endDate) |
| GET | /promo/:id | 可選 JWT | 單一活動詳情(含計算欄位) |
| POST | /promo | JWT | 新增活動 (multipart/form-data) |
| PATCH | /promo/:id | JWT | 修改活動 (部分更新,可上傳新圖) |
| DELETE | /promo/:id | JWT | 刪除活動(同步刪除 R2 圖片) |
| POST | /promo/:id/claim | JWT | 領取活動獎勵 |
8.7 代理推廣系統 (Affiliate + Alliance)
端點: 39 個(原 21 + 新增 18)
3 層代理架構
代理 A (level1AgentId)
├── 會員 X → A 的直接下線 (Level 1)
│ ├── 會員 Y → A 的 Level 2
│ │ └── 會員 Z → A 的 Level 3代理階層制度
| 階層 | tierCode | 升級門檻 (累計佣金) | 最低直屬下線 |
|---|---|---|---|
| 青銅 | bronze | $0 | 0 |
| 白銀 | silver | $1,000 | 10 |
| 黃金 | gold | $5,000 | 50 |
| 鉑金 | platinum | $20,000 | 200 |
佣金比例(DB 可配置,依遊戲類型 + 代理階層 + 代理層級)
查詢邏輯:精確匹配 (agentTier, agentLevel, gameType) → 找不到時 fallback (agentTier, agentLevel, null)。
預設佣金比例(Bronze 階層為例):
| 層級 | 預設比例 | 計算基礎 |
|---|---|---|
| Level 1 | 30% | 直接下線淨虧損 |
| Level 2 | 10% | 間接下線淨虧損 |
| Level 3 | 5% | 間接下線淨虧損 |
可依遊戲類型設定差異化比例(如 crypto 遊戲較低、sports 較高)。
淨虧損計算: netLoss = max(0, -winLose)(只有下線虧損時代理才有佣金)
VIP 里程碑獎勵
當被推薦會員升級 VIP 等級時,L1 代理獲得額外獎金:
| VIP 等級 | 獎勵 (USD) |
|---|---|
| VIP 2 | $0.50 |
| VIP 3 | $1 |
| VIP 4 | $2 |
| VIP 5 | $5 |
| VIP 6 | $10 |
| VIP 7 | $20 |
| VIP 8 | $50 |
| VIP 9 | $100 |
| VIP 10 | $200 |
| VIP 11~15 | $300~$1,000 |
- 每位會員每個 VIP 等級只發放一次(unique constraint 冪等保護)
- 獎金直接入帳
AffiliateBalance.available - VipService 重算後自動觸發(best-effort,不阻擋 VIP 重算)
多推廣碼
- 每位代理最多 10 個自訂推廣碼(3-30 英數字)
- 可標記渠道標籤(YouTube、Telegram、Twitter 等)
- 自動追蹤每個碼的轉換人數
- 推廣碼全域唯一(同時檢查 auth_user.agentCode + alliance-referral-code)
- 註冊綁定流程:先查 alliance-referral-code,再查 auth_user.agentCode
佣金結算
雙軌結算:
- 週結算:每週一 03:00 自動結算上一週(與現有一致)
- 日結算:每日 03:30 自動結算前一日
結算流程:
1. 載入 rateMap(所有佣金比例 → Map)
2. 查詢注單(含 gameType)
3. Pre-load 所有代理的 agentTier
4. 逐單計算: resolve gameType → lookup rate → calc commission
5. 寫入 settlement (含 gameTypeBreakdown JSON)
6. 寫入 commission 明細 (含 gameType)
7. 風控檢測結算生命週期:
結算 Cron → 計算佣金明細 → 風控檢測
├─ 無風控 → status: pending
└─ 有風控 → status: pendingReview
Admin 審核 ──┬─ approved → 佣金入帳 available
└─ rejected → 不發放風控規則: same_ip / same_device / agent_member_ip / agent_member_device
代理佣金餘額流轉
totalEarned ← 累計所有已核准佣金 + VIP 里程碑獎勵
available ← 可提款餘額
frozen ← 提款審核中凍結金額
totalWithdrawn ← 歷史已提款總額
agentTier ← 代理階層 (bronze|silver|gold|platinum)
available → (提款申請) → frozen → (出款完成) → totalWithdrawn
└→ (審核拒絕) → available前端推廣流程
1. 用戶進入 /?ref=MYCODE2026
2. 前端呼叫 POST /affiliate/track-click { refCode: "MYCODE2026" }
→ resolveRefCode 解析(alliance-referral-code 優先 → auth_user.agentCode fallback)
3. 用戶註冊時帶入 POST /auth/register { ..., refCode: "MYCODE2026" }
4. 自動建立 3 層代理關係 + increment convertCount(best-effort)資料表
alliance-commission-rate:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK | |
| agentTier | varchar(20) | bronze|silver|gold|platinum |
| agentLevel | tinyint | 1=直屬 2=二級 3=三級 |
| gameType | varchar(20) nullable | sports|slot|live|...,null=全部 |
| commissionRate | decimal(5,2) | 佣金比例 (%) |
| enabled | tinyint(1) | |
| UNIQUE | (agentTier, agentLevel, gameType) |
alliance-agent-tier:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK | |
| tierCode | varchar(20) unique | 階層碼 |
| tierName | varchar(50) | 顯示名稱 |
| minTotalEarned | decimal(18,6) | 升級門檻 |
| minActiveMembers | int | 最低直屬下線 |
| sortOrder | int |
alliance-vip-milestone:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK | |
| vipLevel | int unique | 觸發 VIP 等級 |
| bonusAmount | decimal(18,6) | 獎勵金額 (USD) |
| description | varchar(100) nullable | |
| enabled | tinyint(1) |
alliance-vip-milestone-log:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK | |
| agentId | int | 代理 ID |
| memberId | int | 會員 ID |
| vipLevel | int | 觸發等級 |
| bonusAmount | decimal(18,6) | 發放金額 |
| milestoneId | int | FK |
| UNIQUE | (agentId, memberId, vipLevel) | 冪等 |
alliance-referral-code:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK | |
| agentId | int | 代理 ID |
| code | varchar(30) unique | 推廣碼 |
| label | varchar(50) nullable | 渠道標籤 |
| enabled | tinyint(1) | |
| convertCount | int default 0 | 轉換人數 |
affiliate-commission (新增欄位):
| 新增欄位 | 型態 | 說明 |
|---|---|---|
| gameType | varchar(20) nullable | 遊戲類型 |
affiliate-settlement (新增欄位):
| 新增欄位 | 型態 | 說明 |
|---|---|---|
| gameTypeBreakdown | json nullable | 各遊戲類型佣金分解 |
| periodType | varchar(10) default 'weekly' | weekly|daily |
affiliate-balance (新增欄位):
| 新增欄位 | 型態 | 說明 |
|---|---|---|
| agentTier | varchar(20) default 'bronze' | 代理階層 |
8.8 投注紀錄 (BetRecord)
端點: 2 個
| 功能 | 端點 |
|---|---|
| 列表 (含匯總) | GET /bet-record |
| 小注單明細 | GET /bet-record/:orderId/details |
匯總欄位 (totalBetCount, betAmount, betEffective, winLose) 為該用戶全量統計(status=valid),不受分頁影響。
注單狀態:
valid:有效invalid:無效(原因:draw/cancelled/low_odds/arbitrage)cancelled:已取消
8.9 排行榜 (Ranking)
端點: 1 個 — GET /ranking
| type | 說明 | 排序 |
|---|---|---|
realtime | 最新投注 | 時間倒序 |
daily | 今日 | 支付金額 |
weekly | 本週 | 支付金額 |
monthly | 本月 | 支付金額 |
total | 累積提領排名 | 總金額 |
匿名顯示: isAnonymous=true 時 playerName 顯示為 i18n 翻譯的「隱身」文字。
8.10 站內信 (Inbox)
端點: 7 個 — User 4 + Admin 3
支援「個人通知」(userId 指定) 與「全站通知」(userId=NULL),分類為 system / promo。 全站通知不複製到每個用戶,使用獨立 notification-read 表追蹤已讀。 title / content 為多語系 JSON,User 端回傳會經 resolveText() 轉為當前語系字串。
資料表
notification:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK auto | |
| userId | int nullable, index | NULL = 全站通知 |
| title | json | {"zh-TW":"...","en-US":"...","zh-CN":"..."} |
| content | json | 多語系 HTML |
| category | varchar(20) | system / promo |
| createdAt | datetime | |
| updatedAt | datetime |
notification-read:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK auto | |
| userId | int, index | |
| notificationId | int, index | |
| readAt | datetime (auto) | |
| UNIQUE | (userId, notificationId) |
User 端點
| Method | Path | 說明 |
|---|---|---|
| GET | /inbox | 站內信列表(個人+全站,LEFT JOIN 已讀,分頁) |
| GET | /inbox/unread-count | 未讀數量 |
| POST | /inbox/:id/read | 標記單則已讀 |
| POST | /inbox/read-all | 全部已讀 |
Admin 端點
| Method | Path | 說明 |
|---|---|---|
| POST | /inbox/admin/send | 發送通知(userId 不傳 = 全站) |
| GET | /inbox/admin/list | 通知列表(原始多語系 JSON) |
| DELETE | /inbox/admin/:id | 刪除通知(一併清除已讀紀錄) |
核心查詢
SELECT n.*, r.id AS readId
FROM notification n
LEFT JOIN `notification-read` r
ON r.notificationId = n.id AND r.userId = ?
WHERE (n.userId = ? OR n.userId IS NULL)
ORDER BY n.createdAt DESCisRead = !!readId- 未讀數量: 同上 +
AND r.id IS NULL+COUNT - 全部已讀: 查出未讀 IDs → 批次 INSERT
notification-read
8.11 站點設定 (SiteConfig)
端點: 8 個 — Public 1 + Admin 7
支援多站點架構,透過 SITE_CODE 環境變數區分當前站點(預設 C9)。 站點名稱 / 介紹為多語系 JSON,Public 端回傳經 resolveText() 轉為當前語系字串。 每個站點可設定多組主題色(site-theme),透過 activeThemeId 指定當前使用的主題。
資料表
site-config:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK auto | |
| siteCode | varchar(30) unique | 站點代碼 (e.g. C9) |
| siteName | json | {"zh-TW":"...","en-US":"...","zh-CN":"..."} |
| siteDescription | json | 多語系站點介紹 |
| supportedLocales | json | ["zh-TW","en-US","zh-CN"] |
| activeThemeId | int nullable | 當前使用的主題 (FK site-theme) |
| enabled | tinyint(1) | 0=關 1=開 |
| createdAt | datetime | |
| updatedAt | datetime |
site-theme:
| 欄位 | 型態 | 說明 |
|---|---|---|
| id | int PK auto | |
| themeId | varchar(50) unique | 主題識別碼 (e.g. default-emerald) |
| themeName | json | 多語系主題名稱 |
| primary | json | 主色系 {base, dark, light, glow} |
| accent | json | 強調色 {gold, info, violet, cyan, error} |
| surface | json | 表面色 {page, navbar, card, modal, sidebar} |
| text | json | 文字色 {primary, secondary, muted, hint} |
| border | json | 邊框色 {subtle, default, strong} |
| enabled | tinyint(1) | 0=關 1=開 |
| siteConfigId | int FK | 所屬站點設定 |
| createdAt | datetime | |
| updatedAt | datetime |
Public 端點
| Method | Path | 說明 |
|---|---|---|
| GET | /site-config | 取得當前站點設定(含 activeTheme 完整色號 + availableThemes 列表) |
Admin 端點
| Method | Path | 說明 |
|---|---|---|
| GET | /site-config/admin/list | 取得所有站點設定(含主題列表) |
| PATCH | /site-config/admin/:id | 更新站點設定(含 activeThemeId) |
| GET | /site-config/admin/:siteConfigId/themes | 主題列表 |
| POST | /site-config/admin/:siteConfigId/themes | 新增主題 |
| PATCH | /site-config/admin/themes/:id | 更新主題 |
| DELETE | /site-config/admin/themes/:id | 刪除主題 |
| PATCH | /site-config/admin/:siteConfigId/mascots | 更新吉祥物列表(全量替換) |
8.12 提領系統 (Withdrawal)
USDT 提領模組。用戶需已綁定信箱與手機,擁有已審核通過的加密錢包,並透過郵箱驗證碼確認身份後方可提交提領。
資料表
withdrawal-order:
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| userId | int, INDEX | |
| amount | decimal(18,6) | 提領金額 (USD) |
| cryptoAddressId | int | 提領目標錢包 ID |
| address | varchar(255) | 快照:錢包地址(提交時快照,防後續修改) |
| network | varchar(20) | 快照:鏈路 (如 TRC-20) |
| status | varchar(20) default 'pending' | pending / approved / rejected / completed |
| rejectReason | varchar(255) nullable | 拒絕原因 |
| reviewedBy | varchar(50) nullable | 審核人帳號 |
| reviewedAt | datetime nullable | 審核時間 |
| completedAt | datetime nullable | 完成時間 |
| createdAt / updatedAt | datetime |
INDEX(userId, createdAt)
auth-user 相關欄位:
frozenBalancedecimal(18,6) default 0 — 凍結中金額(提領審核中)withdrawalVerifyCodevarchar(6) nullable — 提領用郵箱驗證碼
提領狀態機
pending ──┬── approve ──→ approved ──→ complete ──→ completed
│ (frozenBalance 扣除,金額離開系統)
└── reject ──→ rejected
(frozenBalance 退回 balance)餘額帳務變化
| 事件 | balance | frozenBalance |
|---|---|---|
| 提交提領 | -amount | +amount |
| 審核拒絕 (reject) | +amount | -amount |
| 確認出款 (complete) | 不變 | -amount |
所有金額操作使用
Math.floor(value × 1e6) / 1e6截斷至 6 位小數
流程一:發送驗證碼 — POST /withdrawal/send-code
1. 檢查 user.email 不為 null → 否 → 2001 (信箱未驗證)
2. 檢查 user.mobile 不為 null → 否 → 2002 (手機未驗證)
3. 產生 6 位驗證碼(排除全同數字如 111111、連續遞增/遞減如 123456/654321)
4. 透過 Resend 發送郵件到 user.email
5. 儲存至 auth_user.withdrawalVerifyCode驗證碼產生規則:
- 隨機產生 6 位數字 (000000~999999)
- 排除黑名單:
000000 - 排除全同:
111111,222222... 等 - 排除連續遞增/遞減:
123456,654321等 - 最多嘗試 30 次,fallback 僅排除黑名單(保證終止)
流程二:提交提領 — POST /withdrawal/request
DTO: { amount: number, cryptoAddressId: number, verifyCode: string }
驗證管線(依序執行):
Step 1 — 驗證碼檢查
├ user.withdrawalVerifyCode 為 null → 2001 (驗證碼未發送)
└ dto.verifyCode !== withdrawalVerifyCode → 2002 (驗證碼錯誤)
Step 2 — 綁定檢查
├ user.email 為 null → 2003 (信箱未驗證)
└ user.mobile 為 null → 2004 (手機未驗證)
Step 3 — 錢包檢查
SELECT * FROM crypto_address
WHERE id = cryptoAddressId AND userId = userId AND status = 1
→ 無結果 → 2005 (錢包不存在或未審核)
Step 4 — 金額檢查
dto.amount <= 0 → 2006 (金額需大於 0)
Step 5 — 優惠打碼量檢查
SELECT COUNT(*) FROM promo_claim
WHERE userId AND turnoverCompleted = 0 AND requiredTurnover > 0
→ count > 0 → 2008 (優惠打碼量未完成)
Step 6 — 存款打碼量檢查
totalEffectiveBet = user.totalEffectiveBet
totalDeposits = SUM(deposit_order.usdAmount) WHERE userId AND status='paid'
requiredTurnover = totalDeposits × DEPOSIT_TURNOVER_MULTIPLIER (=1)
→ totalEffectiveBet < requiredTurnover → 2009 (存款打碼量不足)
Step 7 — 原子凍結餘額
rounded = Math.floor(dto.amount × 1e6) / 1e6
UPDATE auth_user
SET balance = balance - rounded, frozenBalance = frozenBalance + rounded
WHERE id = userId AND balance >= rounded
→ affected = 0 → 2007 (餘額不足)
Step 8 — 建立提領單
INSERT withdrawal-order {
userId, amount: rounded.toFixed(6),
cryptoAddressId: wallet.id,
address: wallet.address, // 快照
network: wallet.network, // 快照
status: 'pending'
}
Step 9 — 清除驗證碼
UPDATE auth_user SET withdrawalVerifyCode = NULL流程三:Admin 審核 — POST /withdrawal/admin/:id/review
DTO: { action: 'approve' | 'reject', rejectReason?: string }
前置: 查單 → 不存在 → 2001;status !== 'pending' → 2002
approve:
UPDATE withdrawal-order SET status='approved', reviewedBy, reviewedAt=NOW()
reject:
rounded = Math.floor(order.amount × 1e6) / 1e6
UPDATE auth_user SET frozenBalance = frozenBalance - rounded, balance = balance + rounded // 解凍退回
UPDATE withdrawal-order SET status='rejected', rejectReason, reviewedBy, reviewedAt=NOW()流程四:Admin 確認出款 — POST /withdrawal/admin/:id/complete
前置: 查單 → 不存在 → 2001;status !== 'approved' → 2002
rounded = Math.floor(order.amount × 1e6) / 1e6
UPDATE auth_user SET frozenBalance = frozenBalance - rounded // 金額已離開系統
UPDATE withdrawal-order SET status='completed', completedAt=NOW()打碼量檢查規則
提領前系統自動檢查兩項打碼量條件,任一未通過即拒絕提領:
1. 優惠打碼量(Promo Turnover)
條件: 所有已領取優惠的打碼量需全部完成 (turnoverCompleted = 1)
檢查: COUNT(promo_claim) WHERE userId AND turnoverCompleted=0 AND requiredTurnover>0
通過: count === 0打碼量累計由 PromoService.updatePromoTurnover() 在每次投注結算時同步更新。
2. 存款打碼量(Deposit Turnover)
公式: totalEffectiveBet >= totalDeposits × DEPOSIT_TURNOVER_MULTIPLIER
常數: DEPOSIT_TURNOVER_MULTIPLIER = 1 (1 倍打碼)
totalEffectiveBet = auth_user.totalEffectiveBet (全歷史累計有效投注)
totalDeposits = SUM(deposit_order.usdAmount WHERE status='paid')有效投注權重:
| 遊戲類型 | TURNOVER_WEIGHT | 說明 |
|---|---|---|
| sports | 1.0 | 100% 計入 |
| slot | 1.0 | 100% 計入 |
| live | 1.0 | 100% 計入 |
| lottery | 1.0 | 100% 計入 |
| chess | 1.0 | 100% 計入 |
| esports | 1.0 | 100% 計入 |
| crypto | 0.5 | 50% 計入 |
| fish | 0.5 | 50% 計入 |
betEffective = betAmount × TURNOVER_WEIGHT[gameType],存於 bet_order.betEffective
打碼量狀態查詢 — GET /withdrawal/turnover-status
供前端查詢當前用戶的打碼量完成進度,依據 canWithdraw 控制提領按鈕狀態。
回傳結構:
{
"canWithdraw": false,
"deposit": {
"totalDeposits": "1000.000000",
"multiplier": 1,
"requiredTurnover": "1000.000000",
"completedTurnover": "750.000000",
"remaining": "250.000000",
"completed": false
},
"promo": {
"pendingCount": 1,
"completed": false,
"items": [
{
"promoId": 1,
"promoTitle": "新手首存禮",
"rewardAmount": "10.000000",
"requiredTurnover": "50.000000",
"completedTurnover": "30.000000",
"remaining": "20.000000"
}
]
}
}canWithdraw = deposit.completed && promo.completeddeposit.completedTurnover封頂於requiredTurnover(不超過目標值)promo.items僅列出未完成的優惠打碼量(turnoverCompleted = 0),JOIN promo 表取多語系標題
端點
| Method | Path | Auth | 說明 |
|---|---|---|---|
| POST | /withdrawal/send-code | JWT | 發送提領驗證碼到用戶信箱 |
| POST | /withdrawal/request | JWT | 提交提領申請(含驗證碼、金額、錢包 ID) |
| GET | /withdrawal/list | JWT | 用戶提領紀錄(?page, ?pageSize, ?status, ?startDate, ?endDate) |
| GET | /withdrawal/turnover-status | JWT | 查詢打碼量狀態 |
| GET | /withdrawal/admin/list | JWT | [Admin] 提領列表(?page, ?pageSize, ?status, ?startDate, ?endDate) |
| POST | /withdrawal/admin/:id/review | JWT | [Admin] 審核提領 (approve/reject) |
| POST | /withdrawal/admin/:id/complete | JWT | [Admin] 確認出款完成 |
8.13 即時體育賽事 (LiveSports)
端點: 1 個
資料來源: API-Football (api-sports.io),Free plan 每日 100 次請求
快取策略:
- Cron 每 30 分鐘抓取一次(
0 */30 * * * *) - Redis 快取 TTL 35 分鐘
- 每次抓取:2 requests(live fixtures + today's upcoming)+ 最多 5 requests(odds)
- Quota 感知:當
x-ratelimit-requests-remaining < 10時跳過 odds 抓取
回傳格式:
每個 BannerItem 包含:fixtureId, kickoffAt, status(short/long/elapsed), league(id/name/country/logo/round), home(id/name/logo/score), away(id/name/logo/score), odds(home/draw/away/extraCount) 或 null
前端顯示邏輯:
- 進行中的賽事(status.short ∈ [1H, HT, 2H, ET, P])顯示「現場」Badge
- 排序:進行中優先 → 按開賽時間
- 最多回傳 20 場賽事
端點:
| Method | Path | Auth | 說明 |
|---|---|---|---|
| GET | /live-sports | — | 取得即時體育賽事 Banner(公開) |
8.14 任務系統 (Mission)
端點: 3 個
| 功能 | 端點 | Auth |
|---|---|---|
| 取得任務列表 | GET /mission | OptionalJWT |
| 取得領取紀錄 | GET /mission/claims | JWT |
| 領取任務獎勵 | POST /mission/:id/claim | JWT |
任務分類:
| category | periodType | tier 1-5 | 說明 |
|---|---|---|---|
deposit | daily / weekly / monthly | 5 階 | 存款達門檻即可領取 |
bet | daily / weekly / monthly | 5 階 | 投注有效投注額達門檻即可領取 |
規格要點:
- 每個分類 × 週期類型共 30 個任務(2 類 × 3 週期 × 5 階)
- 進度在存款確認 (
updateDepositProgress) 和投注結算 (updateBetProgress) 時自動累加 - 每期(日/週/月)自動重置(透過
periodKey區分,如2026-02-24、2026-W09、2026-02) - 同一任務同一期只能領取一次(UNIQUE constraint on
missionId + userId + periodKey) - 領取時檢查:
!isClaimed && currentProgress >= threshold && meetsVip vipRequired > 0時需 VIP 等級達標才可領取- 領取獎勵直接加入用戶 USD 餘額
- 領取時依
turnoverMultiplier計算打碼量(requiredTurnover = rewardAmount × turnoverMultiplier)
前端顯示邏輯:
- 未登入時:
currentProgress = null,isClaimed = false,isClaimable = false isClaimable = true時顯示「領取」按鈕isClaimed = true時顯示「已領取」meetsVip = false時顯示 VIP 等級不足提示
8.15 吉祥物頭像系統 (Avatar)
端點: 3 個(Auth 2 + SiteConfig Admin 1)
| 功能 | 端點 | Auth |
|---|---|---|
| 取得吉祥物列表 | GET /auth/mascots | - |
| 切換頭像 | PATCH /auth/avatar | JWT |
| [Admin] 更新吉祥物列表 | PATCH /site-config/admin/:siteConfigId/mascots | JWT |
10 組預設吉祥物:
| ID | Label | 主題 |
|---|---|---|
| c9-dragon | 翡翠龍 | 翡翠綠漸層 |
| c9-phoenix | 鳳凰 | 火紅漸層 |
| c9-angel | 天使 | 天藍漸層 |
| c9-wizard | 巫師 | 紫色漸層 |
| c9-knight | 騎士 | 銀灰漸層 |
| c9-mermaid | 美人魚 | 海藍漸層 |
| c9-tiger | 白虎 | 金橘漸層 |
| c9-fox | 狐狸 | 橘紅漸層 |
| c9-owl | 貓頭鷹 | 深藍漸層 |
| c9-wolf | 灰狼 | 暗灰漸層 |
規格要點:
- 頭像為 512×512 PNG,SVG 生成後經 sharp 轉檔
- 存放路徑:R2
avatars/mascots/{mascotId}.png GET /auth/mascots從 SiteConfig 讀取當前站點的 mascots 列表PATCH /auth/avatar以mascotId找到對應 URL,更新auth-user.avatar欄位- Admin 可透過
PATCH /site-config/admin/:siteConfigId/mascots全量替換吉祥物列表 - 生成腳本:
npx ts-node scripts/generate-mascot-avatars.ts
8.16 後台管理系統 (Admin)
端點: 13 個
| 功能 | 端點 | Auth |
|---|---|---|
| 管理員登入 | POST /admin/login | - |
| 取得個人資料 | GET /admin/profile | AdminJWT |
| 管理員列表 | GET /admin/list | AdminJWT |
| 取得單一管理員 | GET /admin/:id | AdminJWT |
| 建立管理員 | POST /admin/create | AdminJWT |
| 更新管理員 | PATCH /admin/:id | AdminJWT |
| 刪除管理員 | DELETE /admin/:id | AdminJWT |
| 群組列表 | GET /admin/groups/list | AdminJWT |
| 取得單一群組 | GET /admin/groups/:id | AdminJWT |
| 建立群組 | POST /admin/groups/create | AdminJWT |
| 更新群組 | PATCH /admin/groups/:id | AdminJWT |
| 刪除群組 | DELETE /admin/groups/:id | AdminJWT |
| 操作紀錄列表 | GET /admin/logs/list | AdminJWT |
架構設計
- 獨立認證系統:管理員帳號存儲於
admin-user表,與前台auth-user完全獨立 - 獨立 JWT 策略:使用
admin-jwtPassport 策略,JWT payload 包含role: 'admin'標識 - AdminJwtAuthGuard:專屬 Guard,確保只有管理員 token 可存取後台 API
- 操作紀錄全記錄:所有增刪改操作自動記錄至
admin-operation-log,含操作者 IP、User-Agent、變更詳情
資料表結構
admin-user (管理員帳號)
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| account | varchar(50) unique | 管理員帳號 |
| password | varchar(255) | 密碼 (bcrypt) |
| name | varchar(50) | 管理員名稱 |
| groupId | int nullable FK | 所屬群組 → admin-group.id |
| status | tinyint(1) default 1 | 啟用狀態 (1=啟用, 0=停用) |
| lastLoginIp | varchar(45) nullable | 最後登入 IP |
| lastLoginAt | datetime nullable | 最後登入時間 |
| tokenVersion | int default 0 | Token 版本 (變更密碼時遞增) |
| createdAt | datetime | 建立時間 |
| updatedAt | datetime | 更新時間 |
admin-group (管理員群組)
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| name | varchar(50) | 群組名稱 |
| permissions | json nullable | 權限列表 (e.g. ["admin:read", "admin:write"]) |
| description | varchar(200) nullable | 群組描述 |
| status | tinyint(1) default 1 | 啟用狀態 |
| createdAt | datetime | 建立時間 |
| updatedAt | datetime | 更新時間 |
admin-operation-log (管理員操作紀錄)
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int PK auto | |
| adminId | int FK | 操作者 → admin-user.id |
| module | varchar(50) | 操作模組 (admin / admin-group / deposit / ...) |
| action | varchar(50) | 操作動作 (login / create / update / delete) |
| targetId | int nullable | 被操作對象 ID |
| ip | varchar(45) | 操作者 IP (IPv4/IPv6) |
| userAgent | varchar(500) nullable | 瀏覽器 User-Agent |
| method | varchar(20) | HTTP Method |
| path | varchar(255) | 請求路徑 |
| detail | json nullable | 操作詳情 / 變更內容 |
| summary | varchar(500) nullable | 操作摘要 |
| createdAt | datetime | 操作時間 |
權限系統 (RBAC)
權限使用 module:action 格式,存儲於群組的 permissions JSON 欄位:
| 權限 Key | 說明 |
|---|---|
admin:read | 查看管理員列表 |
admin:write | 新增/編輯/刪除管理員 |
admin-group:read | 查看群組列表 |
admin-group:write | 新增/編輯/刪除群組 |
admin-log:read | 查看操作紀錄 |
deposit:read | 查看存款列表 |
deposit:write | 存款操作 |
withdrawal:read | 查看提領列表 |
withdrawal:write | 審核/出款操作 |
user:read | 查看用戶列表 |
user:write | 編輯用戶 |
promo:read | 查看活動列表 |
promo:write | 新增/編輯活動 |
vip:read | 查看 VIP 設定 |
vip:write | 編輯 VIP 設定 |
game:read | 查看遊戲設定 |
game:write | 編輯遊戲設定 |
affiliate:read | 查看代理資料 |
affiliate:write | 代理操作 |
inbox:read | 查看站內信 |
inbox:write | 發送/刪除站內信 |
site-config:read | 查看站點設定 |
site-config:write | 編輯站點設定 |
report:read | 查看報表 |
9. 系統自動排程
| 排程 | 時間 | 服務 | 說明 |
|---|---|---|---|
| 每日反水結算 | 00:05 | VipService | 結算昨日各用戶各遊戲類型的有效投注反水,發放到餘額 |
| 月度保級檢查 | 每月 1 號 01:00 | VipService | 檢查 VIP 2+ 用戶上月投注是否達保級門檻 |
| 代理佣金週結 | 每週一 03:00 | AffiliateSettlementService | 結算上週各代理佣金,含風控檢測 |
| 代理佣金日結算 | 每日 03:30 | AffiliateSettlementService.handleDailySettlementCron | 代理佣金日結算 |
| 即時賽事快取更新 | 每 30 分鐘 | LiveSportsService | 抓取 API-Football 即時/今日賽事 + 賠率,快取至 Redis |
手動觸發端點(測試/補結算用):
POST /vip/settlement/daily-rebatePOST /vip/settlement/monthly-relegationPOST /affiliate/admin/trigger-settlement
投注後同步觸發(非排程,即時):
投注結算完成
├─ VipService.recalculateUserVip() → 自動升級
├─ PromoService.updatePromoTurnover() → 累計打碼量
└─ MissionService.updateBetProgress() → 累計任務投注進度
存款確認完成
└─ MissionService.updateDepositProgress() → 累計任務存款進度10. 錯誤碼完整對照表
所有錯誤碼由
GET /common/enums的ERROR_CODES動態回傳,以下為完整參考。
Auth
| API Path | Code | zh-TW |
|---|---|---|
/api/auth/register | 2001 | 帳號已存在 |
/api/auth/register | 2002 | 推廣碼不存在 |
/api/auth/login | 2001 | 帳號或密碼錯誤 |
/api/auth/send-verify-email | 2001 | 此信箱已被其他帳號使用 |
/api/auth/check-verify-email | 2001 | 查無驗證資訊 |
/api/auth/check-verify-email | 2002 | 驗證碼錯誤 |
/api/auth/enable-google-auth | 2001 | 查無驗證資訊 |
/api/auth/enable-google-auth | 2002 | 6 位數密碼輸入錯誤 |
/api/auth/edit-password | 2001 | 當前密碼錯誤 |
/api/auth/edit-password | 2002 | 舊密碼與新密碼不符合 |
/api/auth/locale | 2001 | 不支援的語系 |
/api/auth/login-config | 2001 | 尚未設置 GOOGLE_CLIENT_ID |
/api/auth/login-google | 2001 | 尚未設置 GOOGLE_CLIENT_ID |
/api/auth/login-google | 2002 | GOOGLE_CLIENT_ID 驗證失敗 |
/api/auth/login-google | 2003 | Google Payload 加密失敗 |
/api/auth/login-google | 2004 | Google Payload 解析失敗 |
/api/auth/login-google | 2005 | 本次操作時效已過期, 請重新登入 |
/api/auth/login-google | 2006 | Google Code 驗證錯誤 |
/api/auth/login-google | 2007 | Google Api 響應過程錯誤 |
/api/auth/login-google | 2008 | Google Ticket 解析失敗 |
/api/auth/login-google | 2009 | Google Ticket Payload 解析失敗 |
/api/auth/login-telegram | 2001 | 尚未設置 TELEGRAM_BOT_TOKEN |
/api/auth/login-telegram | 2002 | Telegram 驗證失敗 |
/api/auth/login-telegram | 2003 | 本次操作時效已過期, 請重新登入 |
/api/auth/avatar | 2001 | 無效的頭像 ID |
Mission
| API Path | Code | zh-TW |
|---|---|---|
/api/mission/:id/claim | 3001 | 查無此任務 |
/api/mission/:id/claim | 3002 | 此任務本期已領取過 |
/api/mission/:id/claim | 3003 | VIP 等級不足 |
/api/mission/:id/claim | 3004 | 尚未達成任務目標 |
Wallet
| API Path | Code | zh-TW |
|---|---|---|
/api/wallet/bank-card/add | 2001 | 請上傳身分證正面 |
/api/wallet/bank-card/add | 2002 | 請上傳身分證反面 |
/api/wallet/bank-card/add | 2003 | 請上傳銀行存摺封面 |
/api/wallet/bank-card/add | 2004 | 此銀行卡已存在 |
/api/wallet/bank-card/:id | 2001 | 查無此銀行卡 |
/api/wallet/credit-card/add | 2001 | 此信用卡已綁定 |
/api/wallet/credit-card/:id | 2001 | 查無此信用卡 |
/api/wallet/crypto-address/add | 2001 | 此錢包地址已綁定 |
/api/wallet/crypto-address/:id | 2001 | 查無此錢包地址 |
Vendor
| API Path | Code | zh-TW |
|---|---|---|
/api/vendor/channels | 2001 | 用戶未分配金流群組 |
/api/vendor/channels | 2002 | 金流群組不存在或已停用 |
/api/vendor/channels | 2003 | 金流通道不存在或不屬於此群組 |
/api/vendor/wantong/add-atm | 2001 | 查無可用的萬通金流通道 |
/api/vendor/wantong/add-atm | 2002 | 建立 ATM 訂單失敗 |
/api/vendor/wantong/add-atm | 2003 | ATM 訂單請求異常 |
/api/vendor/wantong/add-card | 2001 | 查無可用的萬通金流通道 |
/api/vendor/wantong/add-card | 2002 | 建立信用卡訂單失敗 |
/api/vendor/wantong/add-card | 2003 | 信用卡訂單請求異常 |
Deposit
| API Path | Code | zh-TW |
|---|---|---|
/api/deposit/exchange-rate | 2001 | 銀行代碼格式錯誤 |
/api/deposit/exchange-rate | 2002 | 匯率解析失敗 |
/api/deposit | 2001 | 查無可用的 USDT 金流通道 |
/api/deposit | 2002 | 該通道尚未設定繳費地址 |
/api/deposit | 2004 | 不支援的支付方式 |
/api/deposit | 2005 | 匯率轉換失敗,無法取得有效匯率 |
/api/deposit | 2006 | 金流通道尚未建置完成 |
Game
| API Path | Code | zh-TW |
|---|---|---|
/api/game/launch | 5001 | 查無此遊戲商 |
/api/game/launch | 5002 | 遊戲商已停用 |
/api/game/launch | 5003 | 遊戲商維護中 |
/api/game/launch | 5004 | 遊戲商 API 回傳錯誤 |
/api/game/launch | 5005 | 未設定遊戲商 API |
/api/game/demo | 5001 | 查無此遊戲商 |
/api/game/demo | 5006 | 該遊戲商不支援試玩模式 |
/api/game/simulate | 5001 | 查無此遊戲商 |
/api/game/simulate | 5010 | 投注金額需在 0.01 ~ 10000 之間 |
/api/game/simulate | 5011 | 餘額不足 |
/api/game/simulate | 2001 | 用戶不存在 |
/api/game/list | 5001 | 查無此遊戲商 |
Promo
| API Path | Code | zh-TW |
|---|---|---|
/api/promo/:id | 2001 | 查無此活動 |
/api/promo/:id/claim | 2001 | 此活動已領取過 |
/api/promo/:id/claim | 2002 | 此活動領取名額已滿 |
/api/promo/:id/claim | 2003 | 尚未滿足領取條件 |
VIP
| API Path | Code | zh-TW |
|---|---|---|
/api/vip/levels | 2001 | 查無此 VIP 等級 |
/api/vip/levels | 2003 | 此 VIP 等級已存在 |
/api/vip/levels/:id | 2001 | 查無此 VIP 等級 |
/api/vip/levels/:id | 2003 | 此 VIP 等級已存在 |
/api/vip/rebates | 2002 | 查無此返水規則 |
/api/vip/rebates | 2004 | 此等級的遊戲類型返水規則已存在 |
/api/vip/rebates/:id | 2002 | 查無此返水規則 |
/api/vip/rebates/:id | 2004 | 此等級的遊戲類型返水規則已存在 |
/api/vip/users/:userId/hold | 2001 | 用戶不存在 |
/api/vip/users/:userId/hold | 2002 | VIP 等級未達 5,無法設定保級鎖定 |
BetRecord
| API Path | Code | zh-TW |
|---|---|---|
/api/bet-record/:orderId/details | 2001 | 訂單不存在或不屬於該用戶 |
Affiliate
| API Path | Code | zh-TW |
|---|---|---|
/api/affiliate/track-click | 2001 | 推廣碼不存在 |
/api/affiliate/dashboard | 2001 | 您不是代理身份 |
/api/affiliate/downline | 2001 | 您不是代理身份 |
/api/affiliate/settlements/:id | 2001 | 結算紀錄不存在 |
/api/affiliate/withdrawals/request | 2001 | 提款金額須大於零 |
/api/affiliate/withdrawals/request | 2002 | 提款方式無效或尚未審核通過 |
/api/affiliate/withdrawals/request | 2003 | 提款冷卻中,請稍後再試 |
/api/affiliate/withdrawals/request | 2004 | 佣金餘額不足 |
/api/affiliate/withdrawals/request | 2005 | 您不是代理身份 |
/api/affiliate/admin/create-agent | 2001 | 用戶不存在 |
/api/affiliate/admin/create-agent | 2002 | 推廣碼已被使用 |
/api/affiliate/admin/create-agent | 2003 | 此用戶已是代理 |
/api/affiliate/admin/settlements/:id/review | 2001 | 結算紀錄不存在 |
/api/affiliate/admin/settlements/:id/review | 2002 | 該結算已審核完成 |
/api/affiliate/admin/withdrawals/:id/review | 2001 | 提款紀錄不存在 |
/api/affiliate/admin/withdrawals/:id/review | 2002 | 該提款已審核完成 |
/api/affiliate/admin/withdrawals/:id/complete | 2001 | 提款紀錄不存在 |
/api/affiliate/admin/withdrawals/:id/complete | 2002 | 狀態須為已審核通過 |
/api/affiliate/admin/bind | 2001 | 會員不存在 |
/api/affiliate/admin/bind | 2002 | 代理不存在 |
Inbox
| API Path | Code | zh-TW |
|---|---|---|
/api/inbox/:id/read | 2001 | 查無此通知 |
/api/inbox/admin/:id | 2001 | 查無此通知 |
SiteConfig
| API Path | Code | zh-TW |
|---|---|---|
/api/site-config | 2001 | 查無此站點設定 |
/api/site-config/admin/:id | 2001 | 查無此站點設定 |
/api/site-config/admin/:siteConfigId/themes | 2001 | 查無此站點設定 |
/api/site-config/admin/themes/:id | 2002 | 查無此主題 |
Withdrawal
| API Path | Code | zh-TW |
|---|---|---|
/api/withdrawal/send-code | 2001 | 用戶信箱未驗證 |
/api/withdrawal/send-code | 2002 | 用戶手機未驗證 |
/api/withdrawal/request | 2001 | 驗證碼未發送 |
/api/withdrawal/request | 2002 | 驗證碼錯誤 |
/api/withdrawal/request | 2003 | 用戶信箱未驗證 |
/api/withdrawal/request | 2004 | 用戶手機未驗證 |
/api/withdrawal/request | 2005 | 該錢包不存在或未通過審核 |
/api/withdrawal/request | 2006 | 提領金額需大於 0 |
/api/withdrawal/request | 2007 | 餘額不足 |
/api/withdrawal/request | 2008 | 優惠打碼量未完成 |
/api/withdrawal/request | 2009 | 存款打碼量不足 |
/api/withdrawal/admin/:id/review | 2001 | 查無此提領單 |
/api/withdrawal/admin/:id/review | 2002 | 該提領單狀態不允許審核 |
/api/withdrawal/admin/:id/complete | 2001 | 查無此提領單 |
/api/withdrawal/admin/:id/complete | 2002 | 該提領單狀態不允許完成 |
Admin
| API Path | Code | zh-TW |
|---|---|---|
/api/admin/login | 2001 | 帳號或密碼錯誤 |
/api/admin/login | 2002 | 帳號已停用 |
/api/admin/profile | 2003 | 管理員不存在 |
/api/admin/create | 2001 | 帳號已存在 |
/api/admin/create | 2002 | 群組不存在 |
/api/admin/:id (PATCH) | 2001 | 管理員不存在 |
/api/admin/:id (DELETE) | 2001 | 管理員不存在 |
/api/admin/:id (DELETE) | 2002 | 不可刪除自己 |
/api/admin/groups/:id | 2001 | 群組不存在 |
/api/admin/groups/:id (DELETE) | 2002 | 群組下仍有管理員,無法刪除 |
11. 端點速查表
| Method | Path | Auth | 說明 |
|---|---|---|---|
| GET | / | - | Health check |
| POST | /auth/register | - | 註冊 |
| POST | /auth/login | - | 登入 |
| GET | /auth/user-detail | JWT | 取得用戶資料 |
| GET | /auth/country-codes | JWT | 取得國碼列表 |
| POST | /auth/send-verify-email | JWT | 發送驗證信 |
| POST | /auth/check-verify-email | JWT | 驗證 Email |
| POST | /auth/generate-google-auth | JWT | 產生 Google Auth |
| POST | /auth/enable-google-auth | JWT | 啟用 Google Auth |
| POST | /auth/edit-password | JWT | 修改密碼 |
| POST | /auth/logout | JWT | 登出 (語系 + 金流群組重匹配 + token 失效) |
| PATCH | /auth/locale | JWT | 更新語系偏好 |
| GET | /auth/login-config | - | 取得登入設定 (Google + Telegram) |
| POST | /auth/login-google | - | Google 登入 |
| POST | /auth/login-telegram | - | Telegram 登入 |
| GET | /auth/mascots | - | 取得吉祥物頭像列表 |
| PATCH | /auth/avatar | JWT | 切換用戶頭像 |
| GET | /game/provider | - | 取得遊戲商列表 |
| POST | /game/launch | JWT | 啟動遊戲 |
| POST | /game/demo | - | 試玩遊戲 |
| POST | /game/simulate | JWT | 模擬遊戲一輪 |
| GET | /game/list | JWT | 取得遊戲列表 |
| POST | /game/rsg/GetBalance | S2S | [RSG] 查詢餘額 |
| POST | /game/rsg/Bet | S2S | [RSG] 下注扣款 |
| POST | /game/rsg/BetResult | S2S | [RSG] 結算派彩 |
| POST | /game/rsg/CancelBet | S2S | [RSG] 取消注單 |
| POST | /game/rsg/JackpotResult | S2S | [RSG] Jackpot 派彩 |
| POST | /game/betsolutions/auth | S2S | [BetSolutions] 驗證 token |
| POST | /game/betsolutions/getBalance | S2S | [BetSolutions] 查詢餘額 |
| POST | /game/betsolutions/bet | S2S | [BetSolutions] 下注扣款 |
| POST | /game/betsolutions/win | S2S | [BetSolutions] 派彩 |
| POST | /game/betsolutions/cancelBet | S2S | [BetSolutions] 取消注單 |
| POST | /game/betsolutions/getPlayerInfo | S2S | [BetSolutions] 取得玩家資訊 |
| GET | /common/enums | - | 取得共用列舉 + 錯誤碼 |
| POST | /wallet/bank-card/add | JWT | 新增銀行卡 |
| GET | /wallet/bank-card/list | JWT | 取得銀行卡列表 |
| DELETE | /wallet/bank-card/:id | JWT | 刪除銀行卡 |
| POST | /wallet/credit-card/add | JWT | 新增信用卡 |
| GET | /wallet/credit-card/list | JWT | 取得信用卡列表 |
| DELETE | /wallet/credit-card/:id | JWT | 刪除信用卡 |
| POST | /wallet/crypto-address/add | JWT | 新增加密錢包 |
| GET | /wallet/crypto-address/list | JWT | 取得加密錢包列表 |
| DELETE | /wallet/crypto-address/:id | JWT | 刪除加密錢包 |
| GET | /vendor/channels | JWT | 取得用戶金流通道列表 |
| POST | /vendor/wantong/add-atm | JWT | 萬通 ATM 建單 |
| POST | /vendor/wantong/add-card | JWT | 萬通信用卡建單 |
| POST | /vendor/wantong/callback/add | S2S | 萬通建單回調 |
| POST | /vendor/wantong/callback/pay | S2S | 萬通銷案回調 |
| POST | /vendor/usdt/callback/pay | S2S | USDT 付款確認回調 |
| GET | /deposit/exchange-rate | - | 取得匯率 |
| GET | /deposit/channels | JWT | 取得金流通道 (含匯率) |
| GET | /deposit/orders | JWT | 取得存款訂單列表 |
| POST | /deposit | JWT | 建立存款訂單 |
| GET | /promo | - | 取得活動列表 |
| GET | /promo/claims | JWT | 取得領取紀錄(?tab, ?startDate, ?endDate) |
| GET | /promo/:id | - | 取得活動詳情 |
| POST | /promo | JWT | 建立活動 |
| PATCH | /promo/:id | JWT | 更新活動 |
| DELETE | /promo/:id | JWT | 刪除活動 |
| POST | /promo/:id/claim | JWT | 領取活動獎勵 |
| GET | /vip/levels | - | 取得 VIP 等級列表 |
| GET | /vip/rebates | - | 取得返水規則 |
| GET | /vip/status | JWT | 取得用戶 VIP 狀態 |
| POST | /vip/levels | JWT | 建立 VIP 等級 |
| PATCH | /vip/levels/:id | JWT | 更新 VIP 等級 |
| DELETE | /vip/levels/:id | JWT | 刪除 VIP 等級 |
| POST | /vip/rebates | JWT | 建立返水規則 |
| PATCH | /vip/rebates/:id | JWT | 更新返水規則 |
| DELETE | /vip/rebates/:id | JWT | 刪除返水規則 |
| POST | /vip/settlement/daily-rebate | JWT | 手動觸發每日反水結算 |
| POST | /vip/settlement/monthly-relegation | JWT | 手動觸發月度保級檢查 |
| PATCH | /vip/users/:userId/hold | JWT | 設定 VIP 保級鎖定 |
| GET | /ranking | - | 取得排行榜 |
| GET | /bet-record | JWT | 投注紀錄列表 |
| GET | /bet-record/:orderId/details | JWT | 訂單小注單明細 |
| POST | /affiliate/track-click | - | 記錄推廣連結點擊 |
| GET | /affiliate/dashboard | JWT | 代理儀表板 |
| GET | /affiliate/promo-link | JWT | 取得推廣連結 |
| GET | /affiliate/downline | JWT | 下線列表 |
| GET | /affiliate/click-stats | JWT | 點擊統計 |
| GET | /affiliate/commissions | JWT | 佣金明細 |
| GET | /affiliate/settlements | JWT | 結算紀錄 |
| GET | /affiliate/settlements/:id | JWT | 結算詳情 |
| GET | /affiliate/balance | JWT | 佣金餘額 |
| GET | /affiliate/withdrawals | JWT | 提款紀錄 |
| POST | /affiliate/withdrawals/request | JWT | 發起提款 |
| POST | /affiliate/admin/create-agent | JWT | [Admin] 建立代理 |
| GET | /affiliate/admin/settlements | JWT | [Admin] 全部結算列表 |
| POST | /affiliate/admin/settlements/:id/review | JWT | [Admin] 審核結算 |
| GET | /affiliate/admin/settlements/:id/risk-logs | JWT | [Admin] 結算風控紀錄 |
| GET | /affiliate/admin/withdrawals | JWT | [Admin] 全部提款列表 |
| POST | /affiliate/admin/withdrawals/:id/review | JWT | [Admin] 審核提款 |
| POST | /affiliate/admin/withdrawals/:id/complete | JWT | [Admin] 確認出款完成 |
| POST | /affiliate/admin/bind | JWT | [Admin] 人工綁定/解綁/轉移 |
| GET | /affiliate/admin/bind-logs | JWT | [Admin] 綁定審計紀錄 |
| POST | /affiliate/admin/trigger-settlement | JWT | [Admin] 手動觸發週結算 |
| GET | /inbox | JWT | 取得站內信列表 |
| GET | /inbox/unread-count | JWT | 取得未讀通知數量 |
| POST | /inbox/:id/read | JWT | 標記單則通知為已讀 |
| POST | /inbox/read-all | JWT | 全部標記為已讀 |
| POST | /inbox/admin/send | JWT | [Admin] 發送通知 |
| GET | /inbox/admin/list | JWT | [Admin] 通知列表 |
| DELETE | /inbox/admin/:id | JWT | [Admin] 刪除通知 |
| GET | /site-config | - | 取得當前站點設定 (含主題) |
| GET | /site-config/admin/list | JWT | [Admin] 取得所有站點設定 (含主題) |
| PATCH | /site-config/admin/:id | JWT | [Admin] 更新站點設定 |
| GET | /site-config/admin/:siteConfigId/themes | JWT | [Admin] 主題列表 |
| POST | /site-config/admin/:siteConfigId/themes | JWT | [Admin] 新增主題 |
| PATCH | /site-config/admin/themes/:id | JWT | [Admin] 更新主題 |
| DELETE | /site-config/admin/themes/:id | JWT | [Admin] 刪除主題 |
| PATCH | /site-config/admin/:siteConfigId/mascots | JWT | [Admin] 更新吉祥物列表 |
| POST | /withdrawal/send-code | JWT | 發送提領驗證碼 |
| POST | /withdrawal/request | JWT | 提交提領申請 |
| GET | /withdrawal/list | JWT | 用戶提領紀錄 |
| GET | /withdrawal/turnover-status | JWT | 查詢打碼量狀態 |
| GET | /withdrawal/admin/list | JWT | [Admin] 提領列表 |
| POST | /withdrawal/admin/:id/review | JWT | [Admin] 審核提領 |
| POST | /withdrawal/admin/:id/complete | JWT | [Admin] 確認出款完成 |
| GET | /live-sports | - | 取得即時體育賽事 Banner |
| GET | /mission | OptionalJWT | 取得任務列表(含進度) |
| GET | /mission/claims | JWT | 取得任務領取紀錄 |
| POST | /mission/:id/claim | JWT | 領取任務獎勵 |
| POST | /admin/login | - | 管理員登入 |
| GET | /admin/profile | AdminJWT | 取得管理員個人資料 |
| GET | /admin/list | AdminJWT | 管理員列表 |
| GET | /admin/:id | AdminJWT | 取得單一管理員 |
| POST | /admin/create | AdminJWT | 建立管理員 |
| PATCH | /admin/:id | AdminJWT | 更新管理員 |
| DELETE | /admin/:id | AdminJWT | 刪除管理員 |
| GET | /admin/groups/list | AdminJWT | 管理員群組列表 |
| GET | /admin/groups/:id | AdminJWT | 取得單一群組 |
| POST | /admin/groups/create | AdminJWT | 建立群組 |
| PATCH | /admin/groups/:id | AdminJWT | 更新群組 |
| DELETE | /admin/groups/:id | AdminJWT | 刪除群組 |
| GET | /admin/logs/list | AdminJWT | 管理員操作紀錄列表 |
JWT = 需要
Authorization: Bearer <token>/ AdminJWT = 需要管理員 JWT Token / OptionalJWT = 有 token 時回傳個人化資料 / S2S = Server-to-Server 回調 (不需 JWT)