C9 Backend API Reference
此文件供前端專案 Claude Code 讀取後直接串接使用。 Swagger UI:
{BASE_URL}/api/docs端點總數:142(GET 50 / POST 57 / PATCH 9 / DELETE 8 + 新增 18),含 11 遊戲商 S2S 回調
目錄
- 基本設定
- 統一回應格式
- API 端點列表
- App
- Auth — 認證 (17 端點)
- Game — 遊戲 (5 端點 + 11 回調)
- Common — 共用 (1 端點)
- Wallet - BankCard — 銀行卡 (3 端點)
- Wallet - CreditCard — 信用卡 (3 端點)
- Wallet - CryptoAddress — 加密貨幣錢包 (3 端點)
- Vendor — 金流通道 (1 端點)
- Vendor - Wantong — 萬通金流 (4 端點)
- Vendor - USDT — 加密貨幣入金 (1 端點)
- Deposit — 存款 (4 端點)
- Promo — 活動促銷 (7 端點)
- VIP — VIP 等級系統 (12 端點)
- Ranking — 排行榜 (1 端點)
- BetRecord — 投注紀錄 (2 端點)
- Affiliate — 代理推廣 (39 端點)
- Inbox — 站內信 (7 端點)
- SiteConfig — 站點設定 (8 端點)
- Withdrawal — 提領 (7 端點)
- LiveSports — 即時體育 (1 端點)
- Mission — 任務 (3 端點)
- Admin — 後台管理 (13 端點)
- 遊戲類型與流水權重
- 存款訂單生命週期
- 投注後自動觸發機制
- 系統自動排程
- 錯誤碼對照表
基本設定
| 項目 | 值 |
|---|---|
| Base URL | http://localhost:8080/api (開發環境) |
| Content-Type | application/json(銀行卡新增為 multipart/form-data) |
| 認證方式 | JWT Bearer Token,Header: Authorization: Bearer <token> |
| 多語系 | Header: locales: zh-TW / en-US / zh-CN |
統一回應格式
成功回應 (HTTP 200)
{
"code": 200,
"message": "ok",
"result": { ... },
"timestamp": 1708588800000,
"path": "/api/auth/login"
}錯誤回應
後端錯誤分為兩種 HTTP Status:
HTTP 401 — 未授權(Token 過期 / 無效 / 未帶)
{
"code": 401,
"message": "Unauthorized",
"data": null,
"timestamp": 1771899908020,
"path": "/api/vendor/channels"
}401 固定回
"Unauthorized",前端收到後直接跳轉登入頁,不需查 ERROR_CODES。
HTTP 200 — 業務錯誤(code ≠ 200)
{
"code": 2001,
"message": "帳號已存在",
"data": null,
"timestamp": 1708588800000,
"path": "/api/auth/register"
}
code為業務錯誤碼,path為觸發錯誤的 API 路徑。 前端用ERROR_CODES[path][code]即可取得當前語系的錯誤訊息(見下方說明)。
前端錯誤碼讀取規則
┌─────────────────────────────────────────────────────────────┐
│ 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 後再查) │
└─────────────────────────────────────────────────────────────┘前端 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 {
// /api/vip/levels/3 → /api/vip/levels/:id
// /api/bet-record/42/details → /api/bet-record/:orderId/details
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) {
// 401 未授權 → 清 token,跳轉登入頁
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(err.response?.data || err);
},
);
export default api;API 端點列表
App
GET / — Health check
// Response result
"Hello World!"Auth — 認證
POST /auth/register — 註冊
// Request Body
{
"account": "user001",
"password": "pass1234",
"name": "John",
"refCode": "AGENT001" // optional — 代理推廣碼,註冊時綁定代理關係
}
// Response result
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": { "id": 1, "name": "John" },
"vendorGroupId": 1 // null = 無符合的金流群組
}錯誤碼:
2001帳號已存在 /2002推廣碼不存在自動分配金流群組: 依
localesheader 語系對應幣別(zh-TW→TWD, en-US→USD, zh-CN→CNY),自動指派第一個啟用中且含該幣別通道的金流群組。vendorGroupId為 null 代表無符合群組,前端可據此提示用戶。
POST /auth/login — 登入
// Request Body
{
"account": "user001",
"password": "pass1234"
}
// Response result
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"account": "user001",
"name": "John",
"email": null,
"mobile": null,
"vipLevel": "1",
"balance": "0.000000",
"vendorGroupId": null,
"googleAuthEnabled": 0,
"tokenVersion": 1,
"createdAt": "2026-02-22T00:00:00.000Z"
}
}錯誤碼:
2001帳號或密碼錯誤
GET /auth/user-detail — 取得用戶資料 🔒
Query Params (optional):
related: "loginLogs" — 帶入可一併取得最近 20 筆登入紀錄// Response result
{
"id": 1,
"account": "user001",
"name": "John",
"email": "user@example.com",
"mobile": null,
"telegram": null,
"vipLevel": "1",
"vipProgress": "0",
"totalEffectiveBet": "1200.000000",
"relegationMissCount": 0,
"vipHold": 0,
"googleAuthEnabled": 0,
"balance": "100.000000",
"vendorGroupId": 1,
"createdAt": "2026-02-22T00:00:00.000Z",
"loginLogs": [
{
"id": 1,
"userId": 1,
"ip": "127.0.0.1",
"device": "Mozilla/5.0...",
"action": "LOGIN",
"lastUse": "2026-02-22T12:00:00.000Z"
}
]
}GET /auth/country-codes — 取得國碼列表 🔒
// Response result
[
{ "country": "TW", "callingCode": "886", "name": "台灣" },
{ "country": "US", "callingCode": "1", "name": "美國" }
]POST /auth/send-verify-email — 發送驗證信 🔒
// Request Body
{
"email": "user@example.com",
"subject": "C9邀請您驗證信箱" // optional
}
// Response result (Resend API 回應)
{ "id": "email_id_xxx" }錯誤碼:
2001此信箱已被其他帳號使用
POST /auth/check-verify-email — 驗證 Email 🔒
// Request Body
{
"code": "123456",
"email": "user@example.com"
}
// Response result
null錯誤碼:
2001查無驗證資訊 /2002驗證碼錯誤
POST /auth/generate-google-auth — 產生 Google Authenticator 🔒
// Request Body: 無 (僅需 JWT)
// Response result
{
"secret": "JBSWY3DPEHPK3PXP",
"qrCode": "data:image/png;base64,..."
}POST /auth/enable-google-auth — 啟用 Google Authenticator 🔒
// Request Body
{ "code": "123456" }
// Response result
{ "affected": 1 }錯誤碼:
2001查無驗證資訊 /20026 位數密碼輸入錯誤
POST /auth/edit-password — 修改密碼 🔒
// Request Body
{
"password": "oldPass123",
"newPassword": "newPass456",
"confirmPassword": "newPass456"
}
// Response result
{ "affected": 1 }錯誤碼:
2001當前密碼錯誤 /2002舊密碼與新密碼不符合
POST /auth/logout — 登出 🔒
// Response result
{ "locale": "en-US", "vendorGroupId": 4 }從
localesheader 取語系覆寫auth-user.locale,並根據語系對應幣別重新匹配金流群組。同時tokenVersion + 1使現有 token 失效。
PATCH /auth/locale — 更新語系偏好 🔒
// Request Body
{ "locale": "zh-TW" }
// Response result
{ "locale": "zh-TW" }錯誤碼:
2001不支援的語系
GET /auth/login-config — 取得登入設定 (Google OAuth + Telegram)
// Response result
{
"google": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&response_type=code&scope=openid+email+profile&state=...",
"telegram": { "botUsername": "c9_game_bot" }
}錯誤碼:
2001尚未設置 GOOGLE_CLIENT_ID
POST /auth/login-google — Google 登入
// Request Body
{
"code": "4/0AQSTgQ...",
"state": "eyJhbGci..."
}
// Response result
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"account": "google_123456",
"name": "John Doe",
"email": "john@gmail.com"
},
"google": {
"sub": "123456",
"email": "john@gmail.com",
"name": "John Doe",
"picture": "https://lh3.googleusercontent.com/..."
}
}錯誤碼:
2001尚未設置 GOOGLE_CLIENT_ID /2002GOOGLE_CLIENT_ID 驗證失敗 /2003Google Payload 加密失敗 /2004Google Payload 解析失敗 /2005本次操作時效已過期, 請重新登入 /2006Google Code 驗證錯誤 /2007Google Api 響應過程錯誤 /2008Google Ticket 解析失敗 /2009Google Ticket Payload 解析失敗
POST /auth/login-telegram — Telegram 登入
透過 Telegram Login Widget 資料驗證並登入。首次登入自動建立帳號(account: tg_{telegramId})。
// Request Body
{
"id": 123456789,
"first_name": "John",
"last_name": "Doe",
"username": "johndoe",
"photo_url": "https://t.me/i/userpic/...",
"auth_date": 1708862400,
"hash": "a1b2c3d4e5f6..."
}
// Response result
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"account": "tg_123456789",
"name": "John Doe",
"telegram": "123456789"
}
}驗證流程:使用 HMAC-SHA256(
SHA256(BOT_TOKEN), data-check-string) 驗證 hash,auth_date 須在 5 分鐘內。 錯誤碼:2001尚未設置 TELEGRAM_BOT_TOKEN /2002Telegram 驗證失敗 /2003本次操作時效已過期, 請重新登入
GET /auth/mascots — 取得吉祥物頭像列表
回傳 10 張可選的吉祥物頭像,供用戶選擇更換頭像。
// Response result
[
{ "id": "c9-dragon", "label": "翡翠龍", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-dragon.png" },
{ "id": "c9-phoenix", "label": "鳳凰", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-phoenix.png" },
{ "id": "c9-angel", "label": "天使", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-angel.png" },
{ "id": "c9-wizard", "label": "巫師", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-wizard.png" },
{ "id": "c9-knight", "label": "騎士", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-knight.png" },
{ "id": "c9-mermaid", "label": "美人魚", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-mermaid.png" },
{ "id": "c9-tiger", "label": "白虎", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-tiger.png" },
{ "id": "c9-fox", "label": "狐狸", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-fox.png" },
{ "id": "c9-owl", "label": "貓頭鷹", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-owl.png" },
{ "id": "c9-wolf", "label": "灰狼", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-wolf.png" }
]頭像圖片為 512×512 PNG,存放於 R2
avatars/mascots/路徑下。
PATCH /auth/avatar 🔒 — 切換用戶頭像
// Request Body
{
"mascotId": "c9-dragon"
}
// Response result
{
"avatar": "https://pub-xxx.r2.dev/avatars/mascots/c9-dragon.png"
}錯誤碼:
2001無效的頭像 ID
Game — 遊戲
GET /game/provider — 取得遊戲商列表
前端進入遊戲大廳時先呼叫此 API,取得所有遊戲商(含維護狀態),可依 gameType 篩選分類頁籤。
Query Params (optional):
gameType: number — 遊戲類型篩選 (1=體育 2=電子 3=真人 4=彩票 5=棋牌 8=電競 9=加密貨幣 10=捕魚)// Response result
[
{
"id": 1,
"gameCode": "slot-betsolutions",
"providerCode": "betsolutions",
"gameType": 2,
"gameTypeLabel": "slot",
"areaBlock": false,
"maintain": false,
"enable": true,
"createdAt": "2026-02-22T00:00:00.000Z"
},
{
"id": 2,
"gameCode": "crypto-betsolutions",
"providerCode": "betsolutions",
"gameType": 9,
"gameTypeLabel": "crypto",
"areaBlock": false,
"maintain": false,
"enable": true,
"createdAt": "2026-02-22T00:00:00.000Z"
},
{
"id": 3,
"gameCode": "slot-rsg",
"providerCode": "rsg",
"gameType": 2,
"gameTypeLabel": "slot",
"areaBlock": false,
"maintain": false,
"enable": true,
"createdAt": "2026-02-22T00:00:00.000Z"
}
]前端使用重點:
gameCode是啟動遊戲時的 key(傳給/game/launch、/game/simulate、/game/demo)providerCode是後端路由用的,前端不需關心maintain=true時顯示維護中,不允許點擊進入enable=false時不顯示此遊戲商
POST /game/launch — 啟動遊戲 (取得遊戲 URL) 🔒
玩家點擊遊戲後呼叫此 API,後端回傳遊戲商的遊戲頁面 URL,前端用 iframe 或 window.open 開啟。
Headers:
Authorization: Bearer <token>
locales: string — 語系 (zh-TW | zh-CN | en-US | ko-KR | ja-JP | vi-VN | th-TH),預設 zh-TW
Body (JSON):
device: string — 裝置類型:desktop | mobile (必填)
gameCode: string — 遊戲商代碼,對應 game-provider.gameCode (必填)
productId: number — 遊戲商內的遊戲 ID (必填)// Request example
{
"device": "mobile",
"gameCode": "slot-betsolutions",
"productId": 1
}
// Response result
{
"url": "https://auth-staging.betsolutions.com/auth/auth?MerchantId=105&Lang=zh-TW&GameId=1&ProductId=2&Token=xxx"
}前端使用重點:
gameCode從/game/provider拿productId是該遊戲商內的遊戲 ID(從/game/list拿,或前端寫死常用遊戲 ID)device依螢幕寬度判斷,影響遊戲商回傳的 UI 版本- 語系自動從
localesheader 取得,不需額外傳參 - 回傳的
url直接用 iframe / window.open 開啟
錯誤碼:
5001查無此遊戲商 /5002遊戲商已停用 /5003遊戲商維護中 /5004遊戲商 API 回傳錯誤 /5005未設定遊戲商 API
POST /game/demo — 試玩遊戲 (免登入 Demo)
不需要登入即可試玩,目前僅支援 BetSolutions 遊戲。前端可在遊戲列表上加「試玩」按鈕。
Headers:
locales: string — 語系,預設 zh-TW
Body (JSON):
device: string — desktop | mobile (必填)
gameCode: string — 遊戲商代碼 (必填)
productId: number — 遊戲 ID (必填)// Request example
{
"device": "mobile",
"gameCode": "slot-betsolutions",
"productId": 1
}
// Response result
{
"url": "https://auth-staging.betsolutions.com/auth/auth?MerchantId=105&Lang=zh-TW&GameId=1&ProductId=2&IsFreeplay=1&Platform=mobile"
}前端使用重點:
- 不需要 JWT Token,訪客也能試玩
- 試玩模式使用虛擬籌碼,不影響真實餘額
- 僅支援 BetSolutions 遊戲商,其他遊戲商會回傳
5006錯誤
錯誤碼:
5001查無此遊戲商 /5006該遊戲商不支援試玩模式
POST /game/simulate — 模擬遊戲一輪 🔒
這是前端按鈕觸發的核心 API。 模擬遊戲商回調流程:扣款 → 隨機開獎 (RTP 97%) → 派彩。 玩家餘額會即時變動,交易紀錄 + 投注紀錄都會同步寫入。
Headers:
Authorization: Bearer <token>
Body (JSON):
gameCode: string — 遊戲商代碼 (必填)
productId: number — 遊戲 ID (必填)
betAmount: number — 投注金額 0.01 ~ 10000 (必填)// Request example
{
"gameCode": "slot-betsolutions",
"productId": 1,
"betAmount": 10
}
// Response result — 贏
{
"roundId": "SIM_1708888888888_abc123",
"orderId": 428,
"betAmount": 10,
"multiplier": 2,
"winAmount": 20,
"profit": 10,
"result": "medium",
"balanceBefore": 200,
"balanceAfter": 210
}
// Response result — 輸
{
"roundId": "SIM_1708888899999_def456",
"orderId": 429,
"betAmount": 10,
"multiplier": 0,
"winAmount": 0,
"profit": -10,
"result": "lose",
"balanceBefore": 210,
"balanceAfter": 200
}回傳欄位說明:
| 欄位 | 說明 |
|---|---|
roundId | 本輪唯一識別碼 |
orderId | 對應 bet-order 的 ID,可用 /bet-record/:orderId/details 查明細 |
betAmount | 投注金額 |
multiplier | 開獎倍率 (0 = 全輸) |
winAmount | 贏回金額 (betAmount × multiplier) |
profit | 盈虧 = winAmount - betAmount (負數 = 虧損) |
result | 結果標籤:lose / small / medium / good / big / huge / mega |
balanceBefore | 投注前餘額 |
balanceAfter | 結算後餘額 |
開獎機率表 (RTP 97%):
| result | 倍率 | 機率 | 說明 |
|---|---|---|---|
| lose | 0x | 60% | 全輸 |
| small | 0.5x | 22% | 小獎 |
| medium | 2x | 10% | 中獎 |
| good | 4x | 5% | 好獎 |
| big | 8x | 2% | 大獎 |
| huge | 20x | 0.8% | 巨獎 |
| mega | 70x | 0.2% | 超級大獎 |
前端使用重點:
- 每次呼叫 = 完整的一輪遊戲(扣款 + 開獎 + 派彩一次完成)
- 回傳後用
balanceAfter更新頁面餘額顯示 - 用
result+multiplier做動畫 / 音效判斷 profit > 0顯示贏,profit < 0顯示輸,profit = 0不太可能出現- 投注紀錄會自動出現在
/bet-record列表中 - 投注後自動觸發: VIP 等級重算 + 優惠打碼量累計(詳見下方「投注後自動觸發機制」)
錯誤碼:
5001查無此遊戲商 /5010投注金額需在 0.01 ~ 10000 之間 /5011餘額不足 /2001用戶不存在
GET /game/list — 取得遊戲商的遊戲列表 🔒
呼叫遊戲商 API 取得該商下所有可用遊戲。如果遊戲商 API 無法取得遊戲(403、逾時、空列表等),自動回傳測試遊戲列表,格式一致。
Query Params:
gameCode: string — 遊戲商代碼,對應 game-provider.gameCode (必填)// Request example
GET /game/list?gameCode=slot-betsolutions
// Response result — 正式遊戲(遊戲商 API 正常)
{
"games": [
{ "ProductId": 2, "GameId": 1, "GameName": "Lucky Clover", "CategoryId": 1, "Category": "Slots", "HasDemo": true, "MobileAvailable": true, "DesktopAvailable": true, "RTP": "97.00" },
{ "ProductId": 2, "GameId": 2, "GameName": "Book of Ra", "CategoryId": 1, "Category": "Slots", "HasDemo": true, "MobileAvailable": true, "DesktopAvailable": true, "RTP": "96.50" }
],
"fallback": false
}
// Response result — 測試遊戲(遊戲商 API 不可用時自動降級)
{
"games": [
{ "ProductId": 2, "GameId": 1, "GameName": "[測試] Fortune Slot", "CategoryId": 1, "Category": "Slots", "HasDemo": true, "MobileAvailable": true, "DesktopAvailable": true, "RTP": "97.00" },
{ "ProductId": 2, "GameId": 2, "GameName": "[測試] Golden Dragon", "CategoryId": 1, "Category": "Slots", "HasDemo": true, "MobileAvailable": true, "DesktopAvailable": true, "RTP": "96.50" },
{ "ProductId": 2, "GameId": 3, "GameName": "[測試] Lucky Spin", "CategoryId": 1, "Category": "Slots", "HasDemo": true, "MobileAvailable": true, "DesktopAvailable": true, "RTP": "95.80" },
{ "ProductId": 3, "GameId": 101, "GameName": "[測試] Crypto Dice", "CategoryId": 2, "Category": "ProvablyFair", "HasDemo": true, "MobileAvailable": true, "DesktopAvailable": true, "RTP": "97.00" },
{ "ProductId": 3, "GameId": 102, "GameName": "[測試] Hash Crash", "CategoryId": 2, "Category": "ProvablyFair", "HasDemo": true, "MobileAvailable": true, "DesktopAvailable": true, "RTP": "97.00" }
],
"fallback": true
}回傳欄位說明:
| 欄位 | 說明 |
|---|---|
games | 遊戲列表陣列,格式依遊戲商而異(BetSolutions / RSG 各自格式) |
fallback | false=正式遊戲商資料,true=測試遊戲(遊戲商 API 不可用時降級) |
BetSolutions 遊戲欄位:
| 欄位 | 說明 |
|---|---|
GameId | 遊戲 ID — 即為 /game/launch 與 /game/simulate 的 productId |
ProductId | 產品分類:2=Slots, 3=ProvablyFair |
GameName | 遊戲名稱(測試遊戲帶 [測試] 前綴) |
Category | 分類名稱 |
HasDemo | 是否支援試玩 |
RTP | 回報率 |
前端使用重點:
- 統一從
result.games取遊戲列表,不需判斷遊戲商格式差異 fallback=true時可在 UI 顯示「測試模式」提示- BetSolutions 的
GameId即為/game/launch與/game/simulate的productId - 測試遊戲名稱帶
[測試]前綴,方便辨識
錯誤碼:
5001查無此遊戲商
遊戲商回調端點 (Callback — 遊戲商 Server-to-Server)
以下端點由遊戲商主動呼叫,前端不需要觸發,僅供後端對接用。 每次回調會同步寫入:game-transaction(錢包帳本)+ bet-order(投注訂單)+ bet-detail(注單明細)。 投注結算後自動觸發 VIP 等級重算 + 優惠打碼量累計。
| 端點 | 說明 |
|---|---|
POST /game/rsg/GetBalance | RSG 查詢玩家餘額 |
POST /game/rsg/Bet | RSG 下注扣款 → 建立 bet-order + bet-detail |
POST /game/rsg/BetResult | RSG 結算派彩 → 更新 bet-order winLose → 觸發 VIP + 打碼量 |
POST /game/rsg/CancelBet | RSG 取消注單退款 |
POST /game/rsg/JackpotResult | RSG 彩金派獎 |
POST /game/betsolutions/auth | BS Token 交換 |
POST /game/betsolutions/getBalance | BS 查詢餘額 |
POST /game/betsolutions/bet | BS 下注扣款 → 建立 bet-order + bet-detail → 觸發 VIP + 打碼量 |
POST /game/betsolutions/win | BS 派彩 → 更新 bet-order winLose |
POST /game/betsolutions/cancelBet | BS 取消注單 |
POST /game/betsolutions/getPlayerInfo | BS 查詢玩家資料 |
遊戲模組前端整合流程
┌─────────────────────────────────────────────────────────────┐
│ 遊戲大廳頁面 │
│ │
│ 1. GET /game/provider → 取得遊戲商列表 │
│ └→ 依 gameType 分頁籤 (電子/真人/體育...) │
│ └→ maintain=true 顯示維護中 │
│ │
│ 2. 點擊遊戲商 → 進入遊戲選擇 │
│ └→ GET /game/list?gameCode=xxx → 取得遊戲清單 │
│ │
│ 3a. 正式遊戲(需登入): │
│ POST /game/launch { device, gameCode, productId } │
│ └→ 取得 URL → iframe 開啟遊戲 │
│ └→ 遊戲商會自動回調我們的 callback → 餘額即時變動 │
│ │
│ 3b. 試玩模式(免登入): │
│ POST /game/demo { device, gameCode, productId } │
│ └→ 取得 URL → iframe 開啟 Demo 遊戲 │
│ └→ 使用虛擬籌碼,不影響餘額 │
│ │
│ 3c. 模擬遊玩(需登入,前端按鈕觸發): │
│ POST /game/simulate { gameCode, productId, betAmount } │
│ └→ 一鍵完成:扣款→開獎→派彩 │
│ └→ 回傳結果 → 更新餘額 → 顯示動畫 │
│ └→ 投注紀錄自動寫入 bet-order │
│ └→ 自動觸發:VIP 等級重算 + 優惠打碼量累計 │
│ │
│ ⚡ 投注後自動觸發(對前端透明,無需額外呼叫): │
│ ├→ VIP 等級重算(累計有效投注 ≥ minChip → 自動升級) │
│ └→ 優惠打碼量累計(已領取的活動獎勵打碼進度自動更新) │
│ │
│ 4. 查看投注紀錄: │
│ GET /bet-record → 大訂單列表 (含匯總) │
│ GET /bet-record/:orderId/details → 展開小注單明細 │
└─────────────────────────────────────────────────────────────┘模擬遊玩按鈕 UI 建議:
┌──────────────────────────────────────────┐
│ 🎰 模擬遊戲 │
│ │
│ 遊戲商:[slot-betsolutions ▾] │
│ 遊戲ID:[1 ] │
│ 投注額:[10 ] USD │
│ │
│ [ 🎲 開始遊戲 ] │
│ │
│ ───────────────────────────────── │
│ 結果:medium (2x) │
│ 投注:10 USD → 贏回:20 USD │
│ 盈虧:+10 USD ✅ │
│ 餘額:200 → 210 USD │
│ ───────────────────────────────── │
│ │
│ 最近紀錄: │
│ #429 lose 0x -10 USD │
│ #428 small 0.5x -5 USD │
│ #427 big 8x +70 USD │
└──────────────────────────────────────────┘前端點擊「開始遊戲」按鈕:
- 呼叫
POST /game/simulate - 依
result播放對應動畫/音效 - 用
balanceAfter更新 header 餘額 - 將結果 push 到最近紀錄列表
Common — 共用
GET /common/enums — 取得共用列舉 (依 locales header 回傳對應語系)
// Response result (locales: zh-TW)
{
"AUTH_ENUM": {
"RELATED": { "LOGIN_LOG": "LOGIN_LOG" },
"LOGIN_LOG": { "ACTION": { "LOGIN": "登入", "LOGOUT": "登出", "DEL": "移除", "UNCAPTURED": "未補獲" } }
},
"ERROR_CODES": {
"/api/auth/register": { "2001": "帳號已存在", "2002": "推廣碼不存在" },
"/api/auth/login": { "2001": "帳號或密碼錯誤" },
"/api/auth/send-verify-email": { "2001": "此信箱已被其他帳號使用" },
"/api/auth/check-verify-email": { "2001": "查無驗證資訊", "2002": "驗證碼錯誤" },
"/api/auth/enable-google-auth": { "2001": "查無驗證資訊", "2002": "6 位數密碼輸入錯誤" },
"/api/auth/edit-password": { "2001": "當前密碼錯誤", "2002": "舊密碼與新密碼不符合" },
"/api/auth/locale": { "2001": "不支援的語系" },
"/api/auth/login-config": { "2001": "尚未設置 GOOGLE_CLIENT_ID" },
"/api/auth/login-google": {
"2001": "尚未設置 GOOGLE_CLIENT_ID",
"2002": "GOOGLE_CLIENT_ID 驗證失敗",
"2003": "Google Payload 加密失敗",
"2004": "Google Payload 解析失敗",
"2005": "本次操作時效已過期, 請重新登入",
"2006": "Google Code 驗證錯誤",
"2007": "Google Api 響應過程錯誤",
"2008": "Google Ticket 解析失敗",
"2009": "Google Ticket Payload 解析失敗"
},
"/api/auth/login-telegram": {
"2001": "尚未設置 TELEGRAM_BOT_TOKEN",
"2002": "Telegram 驗證失敗",
"2003": "本次操作時效已過期, 請重新登入"
},
"/api/auth/avatar": {
"2001": "無效的頭像 ID"
},
"/api/mission/:id/claim": {
"3001": "查無此任務",
"3002": "此任務本期已領取過",
"3003": "VIP 等級不足",
"3004": "尚未達成任務目標"
},
"/api/wallet/bank-card/add": { "2001": "請上傳身分證正面", "2002": "請上傳身分證反面", "2003": "請上傳銀行存摺封面", "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": "查無此錢包地址" },
"/api/vendor/channels": { "2001": "用戶未分配金流群組", "2002": "金流群組不存在或已停用", "2003": "金流通道不存在或不屬於此群組" },
"/api/vendor/wantong/add-atm": { "2001": "查無可用的萬通金流通道", "2002": "建立 ATM 訂單失敗", "2003": "ATM 訂單請求異常" },
"/api/vendor/wantong/add-card": { "2001": "查無可用的萬通金流通道", "2002": "建立信用卡訂單失敗", "2003": "信用卡訂單請求異常" },
"/api/deposit/exchange-rate": { "2001": "銀行代碼格式錯誤", "2002": "匯率解析失敗" },
"/api/deposit": { "2001": "查無可用的 USDT 金流通道", "2002": "該通道尚未設定繳費地址", "2004": "不支援的支付方式", "2005": "匯率轉換失敗,無法取得有效匯率", "2006": "金流通道尚未建置完成" },
"/api/promo/:id": { "2001": "查無此活動" },
"/api/promo/:id/claim": { "2001": "此活動已領取過", "2002": "此活動領取名額已滿", "2003": "尚未滿足領取條件" },
"/api/vip/levels": { "2001": "查無此 VIP 等級", "2003": "此 VIP 等級已存在" },
"/api/vip/levels/:id": { "2001": "查無此 VIP 等級", "2003": "此 VIP 等級已存在" },
"/api/vip/rebates": { "2002": "查無此返水規則", "2004": "此等級的遊戲類型返水規則已存在" },
"/api/vip/rebates/:id": { "2002": "查無此返水規則", "2004": "此等級的遊戲類型返水規則已存在" },
"/api/vip/users/:userId/hold": { "2001": "用戶不存在", "2002": "VIP 等級未達 5,無法設定保級鎖定" },
"/api/game/launch": { "5001": "查無此遊戲商", "5002": "遊戲商已停用", "5003": "遊戲商維護中", "5004": "遊戲商 API 回傳錯誤", "5005": "未設定遊戲商 API" },
"/api/game/demo": { "5001": "查無此遊戲商", "5006": "該遊戲商不支援試玩模式" },
"/api/game/simulate": { "5001": "查無此遊戲商", "5010": "投注金額需在 0.01 ~ 10000 之間", "5011": "餘額不足", "2001": "用戶不存在" },
"/api/game/list": { "5001": "查無此遊戲商" },
"/api/bet-record/:orderId/details": { "2001": "訂單不存在或不屬於該用戶" },
"/api/affiliate/track-click": { "2001": "推廣碼不存在" },
"/api/affiliate/dashboard": { "2001": "您不是代理身份" },
"/api/affiliate/downline": { "2001": "您不是代理身份" },
"/api/affiliate/settlements/:id": { "2001": "結算紀錄不存在" },
"/api/affiliate/withdrawals/request": { "2001": "提款金額須大於零", "2002": "提款方式無效或尚未審核通過", "2003": "提款冷卻中,請稍後再試", "2004": "佣金餘額不足", "2005": "您不是代理身份" },
"/api/affiliate/admin/create-agent": { "2001": "用戶不存在", "2002": "推廣碼已被使用", "2003": "此用戶已是代理" },
"/api/affiliate/admin/settlements/:id/review": { "2001": "結算紀錄不存在", "2002": "該結算已審核完成" },
"/api/affiliate/admin/withdrawals/:id/review": { "2001": "提款紀錄不存在", "2002": "該提款已審核完成" },
"/api/affiliate/admin/withdrawals/:id/complete": { "2001": "提款紀錄不存在", "2002": "狀態須為已審核通過" },
"/api/affiliate/admin/bind": { "2001": "會員不存在", "2002": "代理不存在" }
}
}ERROR_CODES 的文字會依
localesheader 動態從 i18n 取得,切換語系即回傳對應語言。 key 格式與錯誤回應的path欄位完全一致(含/api前綴)。
前端錯誤碼使用方式:
// 錯誤回應中有 code + path,可直接 map 到 ERROR_CODES
api.interceptors.response.use(
(res) => { ... },
(err) => {
const body = err.response?.data || err;
const msg = ERROR_CODES[body.path]?.[body.code]; // path="/api/auth/login-google", code=2002 → "GOOGLE_CLIENT_ID 驗證失敗"
},
);Wallet - BankCard — 銀行卡 🔒
此分類所有端點都需要 JWT Token
POST /wallet/bank-card/add — 新增銀行卡
Content-Type: multipart/form-data
Fields:
bankCode: string — 銀行代碼 (e.g. "004")
bankAccount: string — 帳號 (純數字,min 8 碼)
branch: string — 分行名稱
holderName: string — 持卡人姓名 (min 2 字)
idCardFront: File — 身分證正面 (image/*, max 5MB)
idCardBack: File — 身分證背面 (image/*, max 5MB)
passbookCover: File — 存摺封面 (image/*, max 5MB)// Response result
{
"id": 1,
"userId": 1,
"bankCode": "004",
"bankAccount": "12345678901234",
"branch": "台北分行",
"holderName": "王小明",
"idCardFront": "bank-card/xxx.jpg",
"idCardBack": "bank-card/xxx.jpg",
"passbookCover": "bank-card/xxx.jpg",
"status": 0,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}錯誤碼:
2001請上傳身分證正面 /2002請上傳身分證反面 /2003請上傳銀行存摺封面 /2004此銀行卡已存在
GET /wallet/bank-card/list — 取得銀行卡列表
// Response result (圖片欄位為完整 R2 公開 URL)
[
{
"id": 1,
"userId": 1,
"bankCode": "004",
"bankAccount": "12345678901234",
"branch": "台北分行",
"holderName": "王小明",
"idCardFront": "https://pub-xxx.r2.dev/bank-card/xxx.jpg",
"idCardBack": "https://pub-xxx.r2.dev/bank-card/xxx.jpg",
"passbookCover": "https://pub-xxx.r2.dev/bank-card/xxx.jpg",
"status": 0,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
]status: 0=待審核, 1=已通過, 2=已拒絕
DELETE /wallet/bank-card/:id — 刪除銀行卡
Path Param: id (number) — 銀行卡 ID
// Response result
null錯誤碼:
2001查無此銀行卡
Wallet - CreditCard — 信用卡 🔒
此分類所有端點都需要 JWT Token
POST /wallet/credit-card/add — 新增信用卡
// Request Body
{
"cardNumber": "4111111111111111",
"holderName": "王小明",
"cvv": "123",
"expiryDate": "12/27"
}
// Response result
{
"id": 1,
"userId": 1,
"cardNumber": "4111111111111111",
"holderName": "王小明",
"cvv": "123",
"expiryDate": "12/27",
"status": 0,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}cardNumber: 13-19 位純數字
cvv: 3-4 位純數字
expiryDate: MM/YY 格式錯誤碼:
2001此信用卡已綁定
GET /wallet/credit-card/list — 取得信用卡列表
// Response result
[
{
"id": 1,
"userId": 1,
"cardNumber": "4111111111111111",
"holderName": "王小明",
"cvv": "123",
"expiryDate": "12/27",
"status": 0,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
]status: 0=待審核, 1=已通過, 2=已拒絕
DELETE /wallet/credit-card/:id — 刪除信用卡
Path Param: id (number) — 信用卡 ID
// Response result
null錯誤碼:
2001查無此信用卡
Wallet - CryptoAddress — 加密貨幣錢包 🔒
POST /wallet/crypto-address/add — 新增加密貨幣錢包地址
// Request Body
{
"walletName": "otis的USDT錢包",
"currency": "USDT",
"network": "TRC-20",
"address": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo"
}
// currency 預設 USDT,network 預設 TRC-20(皆可省略)
// Response result
{
"id": 1,
"userId": 1,
"walletName": "otis的USDT錢包",
"currency": "USDT",
"network": "TRC-20",
"address": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo",
"status": 0,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}錯誤碼:
2001此錢包地址已綁定
GET /wallet/crypto-address/list — 取得加密貨幣錢包列表
// Response result
[
{
"id": 1,
"userId": 1,
"walletName": "otis的USDT錢包",
"currency": "USDT",
"network": "TRC-20",
"address": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo",
"status": 0,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
]DELETE /wallet/crypto-address/:id — 刪除加密貨幣錢包地址
// Response result
null錯誤碼:
2001查無此錢包地址
Vendor — 金流通道 🔒
GET /vendor/channels — 取得用戶金流通道列表
// Response result (locales: zh-TW)
{
"groupId": 1,
"groupName": "預設",
"channels": [
{ "id": 1, "name": "萬通", "currency": "TWD", "paymentMethods": ["fiat", "credit"], "paymentAddress": null, "enabled": 1 },
{ "id": 2, "name": "USDT", "currency": "USDT", "paymentMethods": ["crypto"], "paymentAddress": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo", "network": "TRC20", "enabled": 1 }
]
}
groupName和name為多語系欄位,DB 儲存 JSON{"zh-TW":"...","en-US":"...","zh-CN":"..."},API 依localesheader 自動解析回傳對應語言文字。 crypto 通道會額外回傳network欄位(區塊鏈網路,e.g. TRC20, ERC20)。 錯誤碼:2001用戶未分配金流群組 /2002金流群組不存在或已停用 /2003金流通道不存在或不屬於此群組
Vendor - Wantong — 萬通金流
POST /vendor/wantong/add-atm — 萬通 ATM 建單 🔒
// Request Body
{
"subOrder": "ORD20260222001",
"orderAmount": 1000,
"expectedCode": "004",
"expectedAccount": "12345678901234",
"productDes": "儲值",
"msg": "",
"payerName": "王小明",
"payerMobile": "0912345678",
"payerEmail": "user@example.com"
}
// addCallback / payCallback 由後端自動帶入 (API_DOMAIN + callback path)
// Response result (萬通 API 原始回應)
{ "status": true, "sub_order": "ORD20260222001" }錯誤碼:
2001查無可用的萬通金流通道 /2002建立 ATM 訂單失敗 /2003ATM 訂單請求異常
POST /vendor/wantong/add-card — 萬通信用卡建單 🔒
// Request Body
{
"subOrder": "ORD20260222002",
"orderAmount": 1000,
"userCardLastValue": "1234",
"productDes": "儲值",
"msg": "",
"payerName": "王小明",
"payerMobile": "0912345678",
"payerEmail": "user@example.com"
}
// addCallback / payCallback 由後端自動帶入 (API_DOMAIN + callback path)
// Response result (萬通 API 原始回應)
{ "status": true, "sub_order": "ORD20260222002" }錯誤碼:
2001查無可用的萬通金流通道 /2002建立信用卡訂單失敗 /2003信用卡訂單請求異常
POST /vendor/wantong/callback/add — 萬通建單回調 (Server-to-Server,不需 JWT)
// 由萬通 POST 過來
// Request Body (snake_case)
{
"sub_order": "ORD20260222001",
"check_sum": "A1B2C3D4E5F6...",
"expire_date": "2026-02-23 23:59:59",
"order_amount": 1000,
"receiving_code": "004",
"receiving_account": "12345678901234"
}
// Response
{ "received": true }POST /vendor/wantong/callback/pay — 萬通銷案回調 (Server-to-Server,不需 JWT)
// 由萬通 POST 過來,收到此回調 = 付款成功
// 後端會自動轉匯率上分到用戶 USD 餘額
// Request Body (snake_case)
{
"sub_order": "ORD20260222001",
"check_sum": "A1B2C3D4E5F6...",
"pay_amount": 1000,
"pay_time": "2026-02-22 14:30:00",
"payment_account": "9876543210",
"expected_code": "004",
"expected_account": "12345678901234",
"vertify_pay_account": 1,
"vertify_pay_amount": 1
}
// Card 回調額外欄位:
// "user_pay_card": "1234", "vertify_card_last_value": 1
// Response
{ "received": true }Vendor - USDT — 加密貨幣入金
USDT 入金不需打外部 API,建單後回傳繳費地址讓用戶自行轉帳,付款確認由回調或後台處理。
POST /vendor/usdt/callback/pay — USDT 付款確認回調 (Server-to-Server,不需 JWT)
// Request Body
{
"subOrder": "ORD20260222001",
"payAmount": 100,
"txHash": "0xabc123...",
"payTime": "2026-02-22 14:30:00"
}
// Response
{ "received": true }錯誤碼:
2001查無可用的 USDT 金流通道 /2002該通道尚未設定繳費地址
Deposit — 存款
GET /deposit/exchange-rate — 取得匯率 (以 USD 為基底)
Query Params (all optional):
currency: string — 幣別 (e.g. "TWD"),不帶則回傳全部
bankCode: string — 銀行代碼 (預設 "twbk")// Response result (全部幣別)
{
"time": "2026/02/22 14:30",
"base": "USD",
"USD": { "buy": 1, "sell": 1 },
"TWD": { "buy": 31.82, "sell": 32.15 },
"JPY": { "buy": 148.32, "sell": 150.12 }
}
// Response result (指定幣別 ?currency=TWD)
{
"time": "2026/02/22 14:30",
"base": "USD",
"TWD": { "buy": 31.82, "sell": 32.15 }
}錯誤碼:
2001銀行代碼格式錯誤 /2002匯率解析失敗
GET /deposit/channels — 取得金流通道 (含匯率) 🔒
// Response result (locales: zh-TW)
{
"groupId": 1,
"groupName": "預設",
"channels": [
{
"id": 1,
"name": "萬通",
"currency": "TWD",
"paymentMethods": ["fiat", "credit"],
"paymentAddress": null,
"enabled": 1,
"exchangeRate": { "buy": 31.82, "sell": 32.15 }
},
{
"id": 2,
"name": "USDT",
"currency": "USDT",
"paymentMethods": ["crypto"],
"paymentAddress": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo",
"network": "TRC20",
"enabled": 1,
"exchangeRate": null
}
]
}POST /deposit — 建立存款訂單 🔒
// Request Body
{
"channelId": 1,
"paymentMethod": "fiat",
"subOrder": "ORD20260222001",
"orderAmount": 1000,
"expectedCode": "004",
"expectedAccount": "12345678901234",
"productDes": "儲值",
"msg": "",
"payerName": "王小明",
"payerMobile": "0912345678",
"payerEmail": "user@example.com"
}paymentMethod: "fiat" | "credit" | "crypto"
fiat 必填: expectedCode, expectedAccount
credit 選填: userCardLastValue
crypto: 不需額外欄位// Response result — wantong (建單成功,實際上分需等萬通 pay callback)
{ "status": true, "sub_order": "ORD20260222001" }
// Response result — usdt (回傳繳費地址,用戶自行轉帳)
{ "paymentAddress": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo", "network": "TRC20", "currency": "USDT", "orderAmount": 100, "subOrder": "ORD20260222001" }錯誤碼:
2004不支援的支付方式 /2005匯率轉換失敗,無法取得有效匯率 /2006金流通道尚未建置完成
GET /deposit/orders — 取得存款訂單列表 🔒
Query Params (all optional):
page: number — 頁碼 (預設 1)
pageSize: number — 每頁筆數 (預設 10,最大 50)
status: string — 訂單狀態篩選 (pending|created|paid|failed)
startDate: string — 開始日期 (YYYY-MM-DD),篩選 createdAt >= startDate 00:00:00
endDate: string — 結束日期 (YYYY-MM-DD),篩選 createdAt <= endDate 23:59:59// Response result
{
"items": [
{
"id": 1,
"channelName": "萬通",
"currency": "TWD",
"subOrder": "ORD20260222001",
"orderAmount": 1000,
"paymentMethod": "fiat",
"status": "paid",
"payAmount": 1000,
"payTime": "2026-02-22 15:30:00",
"usdAmount": "31.250000",
"exchangeRate": "32.0000000000",
"createdAt": "2026-02-22T07:00:00.000Z",
"updatedAt": "2026-02-22T07:30:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 1,
"totalPages": 1
}
}不含 callbackData(僅供後端使用的回調原始資料)
Promo — 活動促銷 🔒
活動列表與詳情不需登入即可讀取(有登入時額外回傳 isClaimed / isClaimable),其餘端點需要 JWT Token
title和content為多語系欄位,DB 儲存 JSON{"zh-TW":"...","en-US":"...","zh-CN":"..."},API 讀取時依Accept-Language/localesheader 自動回傳對應語系文字
GET /promo — 取得活動列表
Query Params (all optional):
page: number — 頁碼 (預設 1)
pageSize: number — 每頁筆數 (預設 10,最大 50)
tag: string — 活動標籤篩選 (e.g. "新手")
activeOnly: string — 僅顯示進行中 ("1"=是)// Response result (imgPc / imgMobile 為完整 R2 URL,無圖片時為 null)
{
"items": [
{
"id": 1,
"title": "新手首存禮",
"imgPc": "https://pub-xxx.r2.dev/promo/pc-xxx.jpg",
"imgMobile": "https://pub-xxx.r2.dev/promo/mobile-xxx.jpg",
"content": "<p>首次存款即送 10 USD</p>",
"actionHtml": "<a href=\"/deposit\">立即存款</a>",
"startTime": "2026-03-01T00:00:00.000Z",
"endTime": "2026-04-01T00:00:00.000Z",
"tag": "新手",
"enabled": 1,
"conditionType": "first_deposit",
"conditionValue": "0",
"rewardAmount": "10.000000",
"turnoverMultiplier": "3.00",
"maxClaims": 100,
"claimedCount": 23,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z",
"isActive": true,
"isClaimed": false,
"isClaimable": true
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}計算欄位:
isActive活動期間內且已開啟 /isClaimed當前用戶是否已領取 /isClaimable是否滿足領取條件 turnoverMultiplier = 打碼量倍數(0=無要求)。領取後需完成 rewardAmount × turnoverMultiplier 的投注量
GET /promo/:id — 取得活動詳情
Path Param: id (number) — 活動 ID// Response result (同列表單筆格式,含 isActive / isClaimed / isClaimable 計算欄位)
{
"id": 1,
"title": "新手首存禮",
"imgPc": "https://pub-xxx.r2.dev/promo/pc/promo-1-pc.png",
"imgMobile": "https://pub-xxx.r2.dev/promo/mobile/promo-1-mobile.png",
"content": "<p>首次存款即送 10 USD</p>",
"actionHtml": "<a href=\"/deposit\">立即存款</a>",
"startTime": "2026-03-01T00:00:00.000Z",
"endTime": "2026-04-01T00:00:00.000Z",
"tag": "新手",
"enabled": 1,
"conditionType": "first_deposit",
"conditionValue": "0",
"rewardAmount": "10.000000",
"turnoverMultiplier": "3.00",
"maxClaims": 100,
"claimedCount": 23,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z",
"isActive": true,
"isClaimed": false,
"isClaimable": true
}錯誤碼:
2001查無此活動
POST /promo — 建立活動 🔒
Content-Type: multipart/form-data
Fields:
imgPc: File — 電腦版圖片 (image/*, max 5MB, optional)
imgMobile: File — 手機版圖片 (image/*, max 5MB, optional)
title: string — 活動標題 (多語系 JSON 字串,e.g. '{"zh-TW":"新手首存禮","en-US":"Welcome Bonus","zh-CN":"新手首存礼"}')
content: string — 活動內容 (多語系 HTML JSON 字串,e.g. '{"zh-TW":"<p>首存送10USD</p>","en-US":"<p>First deposit bonus 10 USD</p>","zh-CN":"<p>首存送10USD</p>"}')
actionHtml: string — 渲染按鈕/連結 (HTML, optional)
startTime: string — 活動開始時間 (ISO 8601)
endTime: string — 活動結束時間 (ISO 8601)
tag: string — 活動標籤
enabled: number — 0=關 1=開 (預設 1, optional)
conditionType: string — "deposit_threshold" | "vip_level" | "first_deposit"
conditionValue: string — 門檻值 (預設 "0", optional)
rewardAmount: number — 獎勵金額 (USD)
maxClaims: number — 最大領取總數 (0=無限,預設 0, optional)
turnoverMultiplier: number — 打碼量倍數 (0=無要求,預設 0, optional)conditionType 說明:
deposit_threshold — conditionValue 為累計存款金額門檻
vip_level — conditionValue 為最低 VIP 等級
first_deposit — 用戶首次存款在活動期間內即符合 (conditionValue 無作用)PATCH /promo/:id — 更新活動 🔒
Content-Type: multipart/form-data
Path Param: id (number) — 活動 ID
所有欄位皆 optional,只傳需要更新的欄位。
上傳新圖片會取代對應的舊圖 (imgPc / imgMobile 各自獨立)。
欄位定義同 POST /promo。錯誤碼:
2001查無此活動
DELETE /promo/:id — 刪除活動 🔒
Path Param: id (number) — 活動 ID// Response result (同時刪除 R2 上的活動圖片)
null錯誤碼:
2001查無此活動
POST /promo/:id/claim — 領取活動獎勵 🔒
Path Param: id (number) — 活動 ID// Response result (獎勵會直接加到用戶 USD 餘額,同時寫入領取紀錄)
{
"rewardAmount": "10.000000",
"newBalance": "110.000000"
}檢查流程:活動進行中 → 用戶未領取 → 名額未滿 → 滿足條件 → 發放獎勵 若活動有設定打碼量(turnoverMultiplier > 0),領取後需完成對應投注量才算真正到帳 錯誤碼:
2001此活動已領取過 /2002此活動領取名額已滿 /2003尚未滿足領取條件
GET /promo/claims — 取得領取紀錄 🔒
Query Params (all optional):
page: number — 頁碼 (預設 1)
pageSize: number — 每頁筆數 (預設 10,最大 50)
tab: string — 篩選標籤:all(預設)| pending(打碼中)| completed(已完成)
startDate: string — 開始日期 (YYYY-MM-DD),篩選 claimedAt >= startDate
endDate: string — 結束日期 (YYYY-MM-DD),篩選 claimedAt <= endDate 23:59:59// Response result
{
"items": [
{
"id": 1,
"promoId": 1,
"promoTitle": "新手首存禮",
"promoTag": "新手",
"rewardAmount": "10.000000",
"requiredTurnover": "30.000000",
"completedTurnover": "18.500000",
"turnoverCompleted": 0,
"claimedAt": "2026-02-22T10:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}回傳當前用戶的活動獎勵領取紀錄,JOIN promo 表取得活動標題與標籤 requiredTurnover = 需完成打碼量 (rewardAmount × turnoverMultiplier) completedTurnover = 已完成打碼量(由投注自動累計) turnoverCompleted = 是否已完成 (0=未完成, 1=已完成)
VIP — VIP 等級系統
等級列表與返水規則不需登入即可讀取,其餘端點需要 JWT Token
GET /vip/levels — 取得 VIP 等級列表
// Response result (locales: zh-TW)
[
{
"id": 1,
"level": 1,
"name": "青銅 I",
"tier": "bronze",
"minChip": "0.000000",
"relegationChip": "0.000000",
"sortOrder": 1,
"enabled": 1,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
]
name為多語系欄位,依localesheader 自動解析回傳對應語言文字。 minChip = 升級所需累計有效投注流水 (USD) relegationChip = 每月保級所需有效投注 (USD)
GET /vip/rebates — 取得返水規則
// Response result (15 等級 × 8 遊戲類型 = 120 筆)
[
{
"id": 1,
"level": 1,
"gameType": "sports",
"rebateRate": "0.20",
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
]gameType: sports | slot | live | lottery | chess | esports | crypto | fish
GET /vip/status — 取得用戶 VIP 狀態 🔒
// Response result
{
"level": 1,
"name": "青銅 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.20" },
{ "gameType": "slot", "rebateRate": "0.50" },
{ "gameType": "live", "rebateRate": "0.50" },
{ "gameType": "lottery", "rebateRate": "0.50" },
{ "gameType": "chess", "rebateRate": "0.50" },
{ "gameType": "esports", "rebateRate": "0.30" },
{ "gameType": "crypto", "rebateRate": "0.50" },
{ "gameType": "fish", "rebateRate": "0.50" }
],
"allLevels": [
{
"level": 1,
"name": "青銅 I",
"tier": "bronze",
"minChip": "0.000000",
"relegationChip": "0.000000"
}
]
}totalEffectiveBet = 累積有效投注流水 (USD),progress = totalEffectiveBet / 下一級 minChip(最高級時 = 1) monthlyEffective = 本月有效投注(保級參考) relegationMissCount = 連續未達保級月數 (0=正常, 1=警告中, 2+=降級) vipHold = VIP 保級鎖定 (1=手動鎖定,跳過保級檢查,僅 VIP 5+)
POST /vip/levels — 建立 VIP 等級 🔒
// Request Body
{
"level": 1,
"name": { "zh-TW": "青銅 I", "en-US": "Bronze I", "zh-CN": "青铜 I" },
"tier": "bronze",
"minChip": 0,
"relegationChip": 0,
"sortOrder": 1,
"enabled": 1
}name: 多語系 JSON 物件
{"zh-TW":"...", "en-US":"...", "zh-CN":"..."}tier: "bronze" | "gold" | "platinum" | "diamond" sortOrder / enabled 為 optional 錯誤碼:2003此 VIP 等級已存在
PATCH /vip/levels/:id — 更新 VIP 等級 🔒
Path Param: id (number) — VIP 等級 ID
所有欄位皆 optional,只傳需要更新的欄位。錯誤碼:
2001查無此 VIP 等級
DELETE /vip/levels/:id — 刪除 VIP 等級 🔒
Path Param: id (number) — VIP 等級 ID// Response result
null錯誤碼:
2001查無此 VIP 等級
POST /vip/rebates — 建立返水規則 🔒
// Request Body
{
"level": 1,
"gameType": "live",
"rebateRate": 0.5
}gameType: "sports" | "slot" | "live" | "lottery" | "chess" | "esports" | "crypto" | "fish" 錯誤碼:
2004此等級的遊戲類型返水規則已存在
PATCH /vip/rebates/:id — 更新返水規則 🔒
Path Param: id (number) — 返水規則 ID
所有欄位皆 optional,只傳需要更新的欄位。錯誤碼:
2002查無此返水規則
DELETE /vip/rebates/:id — 刪除返水規則 🔒
Path Param: id (number) — 返水規則 ID// Response result
null錯誤碼:
2002查無此返水規則
POST /vip/settlement/daily-rebate — 手動觸發每日反水結算 🔒
// Response result
{
"usersProcessed": 25,
"totalRebate": "3.120000"
}結算昨日各用戶的反水金額並發放到餘額。自動排程:每日 00:05。
POST /vip/settlement/monthly-relegation — 手動觸發月度保級檢查 🔒
// Response result
{
"checked": 10,
"warned": 2,
"demoted": 1
}檢查 VIP 2+ 用戶上月有效投注是否達保級門檻。連續 1 月未達→警告,連續 2 月→降 1 級。 自動排程:每月 1 號 01:00。
PATCH /vip/users/:userId/hold — 設定 VIP 保級鎖定 🔒
// Request Body
{ "hold": 1 }
// Response result
{ "userId": 1, "vipHold": 1 }hold: 1=鎖定(跳過保級檢查), 0=解鎖 錯誤碼:
2001用戶不存在 /2002VIP 等級未達 5
Ranking — 排行榜
公開端點,不需登入
GET /ranking — 取得排行榜
Query Params (all optional):
type: string — realtime | daily | weekly | monthly | total(預設 realtime)
limit: number — 筆數(預設 20,最大 50)// Response result (type=realtime / daily / weekly / monthly)
[
{
"id": 1,
"gameName": "VIP Blackjack 7",
"playerName": "隐身",
"time": "2026-02-22T14:27:00.000Z",
"betAmount": "1875.000000",
"multiplier": "1.20",
"payout": "2250.000000",
"isAnonymous": true
}
]
// Response result (type=total — 累積提領)
[
{
"playerAccount": "user001",
"totalPayout": "50000.000000"
}
]type 說明:
realtime— 最新投注,按時間倒序daily— 今日投注,按支付金額排序weekly— 本週投注,按支付金額排序monthly— 本月投注,按支付金額排序total— 各用戶累積支付金額排名,回傳帳號 + 總金額
BetRecord — 投注紀錄
需登入(JWT)
GET /bet-record — 投注紀錄列表(分頁)
Query Params (all optional):
page: number — 頁碼(預設 1)
pageSize: number — 每頁筆數(預設 10,最大 50)
status: string — 注單狀態篩選:valid | invalid | cancelled
gameType: string — 遊戲類型篩選:1=體育 | 2=電子 | 3=真人 | 4=彩票 | 5=棋牌 | 8=電競 | 9=加密 | 10=捕魚
startDate: string — 開始日期 (YYYY-MM-DD),篩選 betDatetime >= startDate 00:00:00
endDate: string — 結束日期 (YYYY-MM-DD),篩選 betDatetime <= endDate 23:59:59// Response result
{
"totalBetCount": 79,
"betAmount": "13.800000",
"betEffective": "13.800000",
"winLose": "-1.220000",
"items": [
{
"id": 1,
"gameType": "2",
"gamePlatform": "rsg",
"gameNumber": "N1s50_n1s5076021_349009404986",
"totalBetCount": 13,
"betAmount": "0.400000",
"betEffective": "0.400000",
"winLose": "5.950000",
"status": "valid",
"odds": "0.0000",
"invalidReason": null,
"betDatetime": "2026-02-11 20:39:00",
"gameName": "RSG電子 - 雷神之錘"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 6,
"totalPages": 1
}
}匯總欄位(totalBetCount / betAmount / betEffective / winLose)為該用戶全量統計(僅計 status=valid),不受分頁影響。 status: valid=有效 / invalid=無效 / cancelled=已取消 invalidReason: draw=和局 / cancelled=取消 / low_odds=低賠率 / arbitrage=套利
GET /bet-record/:orderId/details — 訂單小注單明細
// Response result
[
{
"id": 1,
"roundNo": 1,
"betAmount": "0.020000",
"winLose": "0.500000",
"createdAt": "2026-02-11T12:39:01.000Z"
}
]錯誤碼:
2001訂單不存在或不屬於該用戶
Affiliate — 代理推廣
3 層代理推廣系統(含 Alliance Program)。代理端點需要 JWT Token 且用戶須具備代理身份(agentCode 不為 null)。 佣金週結:每週一 03:00 自動結算上週各代理的佣金;佣金日結:每日 03:30 自動結算前一日,經風控檢測後由管理員審核發放。 佣金比例依代理階層 (bronze/silver/gold/platinum) + 層級 + 遊戲類型由 DB 可配置(預設 Bronze: L1=30%, L2=10%, L3=5%)。
POST /affiliate/track-click — 記錄推廣連結點擊
前端用戶透過推廣連結進入時呼叫,記錄點擊追蹤資料。
// Request Body
{
"refCode": "AGENT001"
}
// Response result
{ "tracked": true }錯誤碼:
2001推廣碼不存在
GET /affiliate/dashboard — 代理儀表板 🔒
取得代理的總覽資料:下線人數、佣金統計、餘額等。
// Response result
{
"agentCode": "AGENT001",
"totalDownline": { "level1": 50, "level2": 120, "level3": 80 },
"balance": {
"available": "500.000000",
"frozen": "100.000000",
"totalEarned": "2000.000000",
"totalWithdrawn": "1400.000000"
},
"thisWeek": {
"activeMemberCount": 30,
"totalNetLoss": "1500.000000",
"estimatedCommission": "450.000000"
}
}錯誤碼:
2001您不是代理身份
GET /affiliate/promo-link — 取得推廣連結 🔒
// Response result
{
"agentCode": "AGENT001",
"promoUrl": "https://c9.com/?ref=AGENT001"
}GET /affiliate/downline — 下線列表 🔒
Query Params (all optional):
page: number — 頁碼 (預設 1)
pageSize: number — 每頁筆數 (預設 10,最大 50)
level: number — 層級篩選 (1/2/3)// Response result
{
"items": [
{
"userId": 10,
"account": "user010",
"level": 1,
"registeredAt": "2026-02-20T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 50, "totalPages": 5 }
}錯誤碼:
2001您不是代理身份
GET /affiliate/click-stats — 點擊統計 🔒
Query Params (optional):
startDate: string — 開始日期 (YYYY-MM-DD)
endDate: string — 結束日期 (YYYY-MM-DD)// Response result
{
"totalClicks": 150,
"uniqueIps": 120,
"registrations": 30,
"daily": [
{ "date": "2026-02-22", "clicks": 25, "uniqueIps": 20, "registrations": 5 }
]
}GET /affiliate/commissions — 佣金明細 🔒
Query Params (all optional):
page: number — 頁碼 (預設 1)
pageSize: number — 每頁筆數 (預設 10,最大 50)
weekStart: string — 週起始日期篩選 (YYYY-MM-DD)// Response result
{
"items": [
{
"id": 1,
"memberId": 10,
"memberAccount": "user010",
"agentLevel": 1,
"netLoss": "100.000000",
"commissionRate": "30.00",
"commissionAmount": "30.000000",
"weekStart": "2026-02-17",
"weekEnd": "2026-02-23"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}GET /affiliate/settlements — 結算紀錄 🔒
Query Params (all optional):
page: number — 頁碼 (預設 1)
pageSize: number — 每頁筆數 (預設 10,最大 50)// Response result
{
"items": [
{
"id": 1,
"weekStart": "2026-02-17",
"weekEnd": "2026-02-23",
"activeMemberCount": 30,
"totalNetLoss": "5000.000000",
"level1Commission": "1500.000000",
"level2Commission": "500.000000",
"level3Commission": "250.000000",
"totalCommission": "2250.000000",
"status": "approved",
"riskFlagged": 0,
"createdAt": "2026-02-24T03:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}status:
pending待撥款 /pendingReview需人工審核 (風控命中) /approved已通過 /rejected已拒絕
GET /affiliate/settlements/:id — 結算詳情 (含佣金明細) 🔒
// Response result (結算資訊 + 該週所有佣金明細)
{
"settlement": { /* 同上 */ },
"commissions": [
{
"memberId": 10,
"memberAccount": "user010",
"agentLevel": 1,
"netLoss": "100.000000",
"commissionRate": "30.00",
"commissionAmount": "30.000000"
}
]
}錯誤碼:
2001結算紀錄不存在
GET /affiliate/balance — 佣金餘額 🔒
// Response result
{
"available": "500.000000",
"frozen": "100.000000",
"totalEarned": "2000.000000",
"totalWithdrawn": "1400.000000"
}GET /affiliate/withdrawals — 提款紀錄 🔒
Query Params (all optional):
page: number — 頁碼
pageSize: number — 每頁筆數// Response result
{
"items": [
{
"id": 1,
"amount": "500.000000",
"method": "bank_card",
"targetId": 3,
"status": "completed",
"remark": null,
"createdAt": "2026-02-22T10:00:00.000Z",
"reviewedAt": "2026-02-22T12:00:00.000Z",
"completedAt": "2026-02-22T14:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}status:
pending待審核 /approved已通過待出款 /rejected已拒絕 /completed已出款
POST /affiliate/withdrawals/request — 發起提款 🔒
// Request Body
{
"amount": 500,
"method": "bank_card",
"targetId": 3
}
// Response result
{
"id": 1,
"amount": "500.000000",
"status": "pending"
}method: "bank_card" | "crypto"
targetId: 對應 bank-card.id 或 crypto-address.id(需狀態=1 已通過審核)錯誤碼:
2001提款金額須大於零 /2002提款方式無效或尚未審核通過 /2003提款冷卻中,請稍後再試 /2004佣金餘額不足 /2005您不是代理身份
POST /affiliate/admin/create-agent — [Admin] 建立代理 🔒
// Request Body
{
"userId": 10,
"agentCode": "AGENT001"
}
// Response result
{
"userId": 10,
"agentCode": "AGENT001"
}錯誤碼:
2001用戶不存在 /2002推廣碼已被使用 /2003此用戶已是代理
GET /affiliate/admin/settlements — [Admin] 全部結算列表 🔒
Query Params (all optional):
page: number — 頁碼
pageSize: number — 每頁筆數
status: string — 狀態篩選 (pending|pendingReview|approved|rejected)
weekStart: string — 週起始日期篩選POST /affiliate/admin/settlements/:id/review — [Admin] 審核結算 🔒
// Request Body
{
"action": "approved",
"remark": "已確認無異常"
}action: "approved" | "rejected" approved 後佣金自動入帳到代理的 AffiliateBalance.available 錯誤碼:
2001結算紀錄不存在 /2002該結算已審核完成
GET /affiliate/admin/settlements/:id/risk-logs — [Admin] 結算風控紀錄 🔒
// Response result
[
{
"id": 1,
"settlementId": 1,
"ruleType": "same_ip",
"detail": { "ip": "1.2.3.4", "memberIds": [10, 11, 12] },
"createdAt": "2026-02-24T03:00:00.000Z"
}
]ruleType:
same_ip多位 member 共用 IP /same_device共用 device /agent_member_ip代理與 member 同 IP /agent_member_device代理與 member 同 device
GET /affiliate/admin/withdrawals — [Admin] 全部提款列表 🔒
Query Params (all optional):
page: number — 頁碼
pageSize: number — 每頁筆數
status: string — 狀態篩選 (pending|approved|rejected|completed)POST /affiliate/admin/withdrawals/:id/review — [Admin] 審核提款 🔒
// Request Body
{
"action": "approved",
"remark": "確認出款"
}action: "approved" | "rejected" rejected → frozen 退回 available 錯誤碼:
2001提款紀錄不存在 /2002該提款已審核完成
POST /affiliate/admin/withdrawals/:id/complete — [Admin] 確認出款完成 🔒
// Response result
{ "id": 1, "status": "completed" }確認實際出款完成後呼叫,frozen 轉為 totalWithdrawn 錯誤碼:
2001提款紀錄不存在 /2002狀態須為已審核通過
POST /affiliate/admin/bind — [Admin] 人工綁定/解綁/轉移 🔒
// Request Body
{
"memberId": 10,
"agentId": 5,
"action": "bind"
}
// Response result
{ "success": true }action: "bind" | "unbind" | "transfer" 所有異動會寫入 AffiliateBindLog 審計紀錄 錯誤碼:
2001會員不存在 /2002代理不存在
GET /affiliate/admin/bind-logs — [Admin] 綁定審計紀錄 🔒
Query Params (all optional):
page: number — 頁碼
pageSize: number — 每頁筆數
memberId: number — 會員 ID 篩選
agentId: number — 代理 ID 篩選// Response result
{
"items": [
{
"id": 1,
"memberId": 10,
"oldAgentId": null,
"newAgentId": 5,
"action": "bind",
"source": "admin",
"operatorAccount": "admin001",
"createdAt": "2026-02-22T10:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}POST /affiliate/admin/trigger-settlement — [Admin] 手動觸發週結算 (測試用) 🔒
手動觸發上週的佣金結算流程,用於測試或補結算。
// Response result (結算結果摘要)
{
"settlementsCreated": 5,
"totalCommission": "3500.000000",
"riskFlagged": 1
}GET /affiliate/alliance-info — 聯盟計劃公開資訊
取得聯盟計劃的公開說明資訊(代理階層制度、佣金比例、VIP 里程碑獎勵等),無需登入。
// Response result
{
"tiers": [
{ "tierCode": "bronze", "tierName": "青銅", "minTotalEarned": "0", "minActiveMembers": 0 },
{ "tierCode": "silver", "tierName": "白銀", "minTotalEarned": "1000", "minActiveMembers": 10 }
],
"commissionRates": [
{ "agentTier": "bronze", "agentLevel": 1, "gameType": null, "commissionRate": "30.00" },
{ "agentTier": "bronze", "agentLevel": 2, "gameType": null, "commissionRate": "10.00" },
{ "agentTier": "bronze", "agentLevel": 3, "gameType": null, "commissionRate": "5.00" }
],
"vipMilestones": [
{ "vipLevel": 2, "bonusAmount": "0.50" },
{ "vipLevel": 3, "bonusAmount": "1.00" }
]
}GET /affiliate/referral-codes — 推廣碼列表 🔒
取得代理的所有自訂推廣碼列表(最多 10 個)。
// Response result
{
"items": [
{
"id": 1,
"code": "MYCODE2026",
"label": "YouTube",
"enabled": 1,
"convertCount": 35,
"createdAt": "2026-01-01T00:00:00.000Z"
}
]
}錯誤碼:
2001您不是代理身份
POST /affiliate/referral-codes — 新增推廣碼 🔒
新增自訂推廣碼,推廣碼全域唯一(3-30 英數字)。
// Request Body
{
"code": "MYCODE2026",
"label": "YouTube"
}
// Response result
{
"id": 2,
"code": "MYCODE2026",
"label": "YouTube",
"enabled": 1,
"convertCount": 0
}錯誤碼:
2001您不是代理身份 /2002推廣碼已被使用 /2003已達推廣碼上限 (10個) /2004推廣碼格式不符(3-30 英數字)
DELETE /affiliate/referral-codes/:id — 刪除推廣碼 🔒
刪除指定推廣碼。
// Response result
{ "success": true }錯誤碼:
2001推廣碼不存在或無權限
GET /affiliate/vip-milestones — VIP 里程碑進度 🔒
取得代理的 VIP 里程碑獎勵發放紀錄(哪些下線會員已達哪個 VIP 等級並已發放獎金)。
// Response result
{
"items": [
{
"id": 1,
"memberId": 10,
"memberAccount": "user010",
"vipLevel": 3,
"bonusAmount": "1.000000",
"createdAt": "2026-02-20T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 5, "totalPages": 1 }
}錯誤碼:
2001您不是代理身份
GET /affiliate/tier-info — 代理階層資訊 🔒
取得當前代理的階層資訊及下一階層升級進度。
// Response result
{
"currentTier": {
"tierCode": "bronze",
"tierName": "青銅"
},
"nextTier": {
"tierCode": "silver",
"tierName": "白銀",
"minTotalEarned": "1000.000000",
"minActiveMembers": 10
},
"progress": {
"totalEarned": "350.000000",
"activeMembers": 4
}
}錯誤碼:
2001您不是代理身份
GET /affiliate/admin/commission-rates — [Admin] 佣金比例列表 🔒
取得所有佣金比例設定(依代理階層、層級、遊戲類型)。
// Response result
{
"items": [
{
"id": 1,
"agentTier": "bronze",
"agentLevel": 1,
"gameType": null,
"commissionRate": "30.00",
"enabled": 1
}
]
}POST /affiliate/admin/commission-rates — [Admin] 新增/更新佣金比例 🔒
新增或更新佣金比例設定(依 agentTier + agentLevel + gameType 唯一索引做 upsert)。
// Request Body
{
"agentTier": "bronze",
"agentLevel": 1,
"gameType": "sports",
"commissionRate": 35,
"enabled": 1
}
// Response result
{ "id": 2, "agentTier": "bronze", "agentLevel": 1, "gameType": "sports", "commissionRate": "35.00", "enabled": 1 }錯誤碼:
2001agentTier 不存在 /2002agentLevel 須為 1~3
DELETE /affiliate/admin/commission-rates/:id — [Admin] 刪除佣金比例 🔒
// Response result
{ "success": true }錯誤碼:
2001佣金比例設定不存在
GET /affiliate/admin/vip-milestones — [Admin] VIP 里程碑列表 🔒
取得所有 VIP 里程碑獎勵設定。
// Response result
{
"items": [
{ "id": 1, "vipLevel": 2, "bonusAmount": "0.500000", "description": null, "enabled": 1 },
{ "id": 2, "vipLevel": 3, "bonusAmount": "1.000000", "description": null, "enabled": 1 }
]
}POST /affiliate/admin/vip-milestones — [Admin] 新增/更新里程碑 🔒
新增或更新 VIP 里程碑獎勵(依 vipLevel 唯一索引做 upsert)。
// Request Body
{
"vipLevel": 5,
"bonusAmount": 5,
"description": "VIP 5 milestone bonus",
"enabled": 1
}
// Response result
{ "id": 5, "vipLevel": 5, "bonusAmount": "5.000000", "description": "VIP 5 milestone bonus", "enabled": 1 }錯誤碼:
2001vipLevel 須為 2~15
DELETE /affiliate/admin/vip-milestones/:id — [Admin] 刪除里程碑 🔒
// Response result
{ "success": true }錯誤碼:
2001里程碑設定不存在
GET /affiliate/admin/agent-tiers — [Admin] 代理階層列表 🔒
取得所有代理階層設定。
// Response result
{
"items": [
{
"id": 1,
"tierCode": "bronze",
"tierName": "青銅",
"minTotalEarned": "0.000000",
"minActiveMembers": 0,
"sortOrder": 1
}
]
}POST /affiliate/admin/agent-tiers — [Admin] 新增/更新階層 🔒
新增或更新代理階層設定(依 tierCode 唯一索引做 upsert)。
// Request Body
{
"tierCode": "silver",
"tierName": "白銀",
"minTotalEarned": 1000,
"minActiveMembers": 10,
"sortOrder": 2
}
// Response result
{ "id": 2, "tierCode": "silver", "tierName": "白銀", "minTotalEarned": "1000.000000", "minActiveMembers": 10, "sortOrder": 2 }DELETE /affiliate/admin/agent-tiers/:id — [Admin] 刪除階層 🔒
// Response result
{ "success": true }錯誤碼:
2001階層設定不存在 /2002無法刪除有代理使用的階層
POST /affiliate/admin/set-agent-tier — [Admin] 設定代理階層 🔒
手動設定指定代理的階層(admin 強制指定,不走自動升級邏輯)。
// Request Body
{
"agentId": 5,
"tierCode": "gold"
}
// Response result
{ "agentId": 5, "tierCode": "gold" }錯誤碼:
2001代理不存在 /2002階層代碼不存在
POST /affiliate/admin/trigger-daily-settlement — [Admin] 手動觸發日結算 🔒
手動觸發前一日的佣金日結算流程,用於測試或補結算。
// Request Body (optional)
{
"date": "2026-02-23"
}
// Response result
{
"settlementsCreated": 12,
"totalCommission": "850.000000",
"riskFlagged": 0,
"periodType": "daily"
}代理推廣前端整合流程
┌─────────────────────────────────────────────────────────────┐
│ 代理推廣流程 │
│ │
│ 1. 推廣連結點擊追蹤 │
│ 用戶進入 /?ref=AGENT001 → 前端呼叫 │
│ POST /affiliate/track-click { refCode: "AGENT001" } │
│ └→ 記錄 IP、device、refCode │
│ │
│ 2. 註冊綁定代理 │
│ 用戶註冊時帶入 refCode │
│ POST /auth/register { ..., refCode: "AGENT001" } │
│ └→ 自動建立 3 層代理關係 │
│ └→ 失敗不阻擋註冊 (best-effort) │
│ │
│ 3. 代理後台 │
│ GET /affiliate/dashboard → 儀表板總覽 │
│ GET /affiliate/promo-link → 取得推廣連結 │
│ GET /affiliate/downline → 查看下線列表 (分 1/2/3 層) │
│ GET /affiliate/click-stats → 推廣點擊統計 │
│ GET /affiliate/commissions → 佣金明細 │
│ GET /affiliate/settlements → 結算紀錄 │
│ GET /affiliate/balance → 佣金餘額 │
│ │
│ 4. 佣金結算 (自動排程:每週一 03:00) │
│ └→ 計算上週下線淨虧損 × 佣金比例 │
│ └→ Level 1: 30%, Level 2: 10%, Level 3: 5% │
│ └→ 風控檢測 → 無風控: pending / 有風控: pendingReview │
│ └→ Admin 審核 approved → 佣金入帳 available │
│ │
│ 5. 代理提款 │
│ POST /affiliate/withdrawals/request → 發起提款 │
│ └→ 凍結金額 (available → frozen) │
│ └→ Admin 審核 → 出款確認 → frozen → totalWithdrawn │
└─────────────────────────────────────────────────────────────┘Inbox — 站內信
支援「個人通知」與「全站通知」兩種類型,內容為多語系 HTML,分類為 system / promo。 全站通知不為每個用戶複製一份,使用獨立 notification-read 表追蹤已讀狀態。
GET /inbox 🔒 取得站內信列表
| Query | 型態 | 說明 |
|---|---|---|
| page | number (opt) | 頁碼 (預設 1) |
| pageSize | number (opt) | 每頁筆數 (預設 10,最大 50) |
| category | string (opt) | 分類篩選:system / promo |
Response
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"title": "系統維護通知",
"content": "<p>系統將於今晚維護</p>",
"category": "system",
"isRead": false,
"createdAt": "2026-02-24T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}
}title / content 已經 resolveText 轉為當前語系的字串
GET /inbox/unread-count 🔒 取得未讀通知數量
Response
{ "code": 200, "message": "ok", "result": { "unreadCount": 5 } }POST /inbox/:id/read 🔒 標記單則通知為已讀
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此通知 |
POST /inbox/read-all 🔒 全部標記為已讀
POST /inbox/admin/send 🔒 [Admin] 發送通知
| Body | 型態 | 說明 |
|---|---|---|
| userId | number (opt) | 目標用戶 ID (不傳=全站通知) |
| title | object | 多語系標題 {"zh-TW":"...","en-US":"...","zh-CN":"..."} |
| content | object | 多語系 HTML 內容 |
| category | string | system / promo |
Response
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"userId": null,
"title": { "zh-TW": "系統維護通知", "en-US": "System Maintenance Notice", "zh-CN": "系统维护通知" },
"content": { "zh-TW": "<p>系統將於今晚維護</p>", "en-US": "<p>System maintenance tonight</p>", "zh-CN": "<p>系统将于今晚维护</p>" },
"category": "system",
"createdAt": "2026-02-24T00:00:00.000Z"
}
}GET /inbox/admin/list 🔒 [Admin] 通知列表
| Query | 型態 | 說明 |
|---|---|---|
| page | number (opt) | 頁碼 |
| pageSize | number (opt) | 每頁筆數 |
| category | string (opt) | 分類篩選 |
Admin 列表回傳原始多語系 JSON(不做 resolveText)
DELETE /inbox/admin/:id 🔒 [Admin] 刪除通知
刪除時會一併清除該通知的所有已讀紀錄。
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此通知 |
SiteConfig — 站點設定
站點設定模組,支援多站點架構,透過
SITE_CODE環境變數區分站點。 每個站點可設定多組主題色,透過activeThemeId指定當前使用的主題。
GET /site-config — 取得當前站點設定
公開 API,不需登入。根據環境變數 SITE_CODE(預設 C9)回傳當前站點設定,包含當前選中主題的完整色號。
Response:
{
"code": 200,
"message": "ok",
"result": {
"siteCode": "C9",
"siteName": "C9",
"siteDescription": "最佳線上娛樂平台",
"supportedLocales": ["zh-TW", "en-US", "zh-CN"],
"activeTheme": {
"themeId": "default-emerald",
"themeName": "翡翠綠",
"primary": { "base": "#34d399", "dark": "#059669", "light": "#6ee7b7", "glow": "16, 185, 129" },
"accent": { "gold": "#fbbf24", "info": "#38bdf8", "violet": "#a78bfa", "cyan": "#22d3ee", "error": "#fb7185" },
"surface": { "page": "#0f172a", "navbar": "#1e293b", "card": "#131f30", "modal": "#0c1a2e", "sidebar": ["#0d1b2a", "#0a1628", "#071020"] },
"text": { "primary": "rgba(255,255,255,1)", "secondary": "rgba(255,255,255,0.6)", "muted": "rgba(255,255,255,0.4)", "hint": "rgba(255,255,255,0.25)" },
"border": { "subtle": "rgba(255,255,255,0.06)", "default": "rgba(255,255,255,0.1)", "strong": "rgba(255,255,255,0.15)" }
},
"availableThemes": [
{ "themeId": "default-emerald", "themeName": "翡翠綠" }
]
}
}
siteName/siteDescription/themeName已經過 resolveText,依請求語系回傳對應文字
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此站點設定 |
GET /site-config/admin/list 🔒 [Admin] 取得所有站點設定
回傳所有站點設定(含主題列表、原始多語系 JSON)。
PATCH /site-config/admin/:id 🔒 [Admin] 更新站點設定
| 欄位 | 型態 | 必填 | 說明 |
|---|---|---|---|
| siteName | object | 否 | {"zh-TW":"...","en-US":"...","zh-CN":"..."} |
| siteDescription | object | 否 | {"zh-TW":"...","en-US":"...","zh-CN":"..."} |
| supportedLocales | string[] | 否 | ["zh-TW","en-US","zh-CN"] |
| activeThemeId | number | 否 | 當前使用的主題 ID (site-theme PK) |
| enabled | number | 否 | 0=關 1=開 |
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此站點設定 |
GET /site-config/admin/:siteConfigId/themes 🔒 [Admin] 主題列表
回傳指定站點的所有主題(原始 JSON)。
POST /site-config/admin/:siteConfigId/themes 🔒 [Admin] 新增主題
| 欄位 | 型態 | 必填 | 說明 |
|---|---|---|---|
| themeId | string | 是 | 主題識別碼 (e.g. default-emerald) |
| themeName | object | 是 | {"zh-TW":"翡翠綠","en-US":"Emerald","zh-CN":"翡翠绿"} |
| primary | object | 是 | {base, dark, light, glow} |
| accent | object | 是 | {gold, info, violet, cyan, error} |
| surface | object | 是 | {page, navbar, card, modal, sidebar} |
| text | object | 是 | {primary, secondary, muted, hint} |
| border | object | 是 | {subtle, default, strong} |
| enabled | number | 否 | 0=關 1=開 (預設 1) |
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此站點設定 |
PATCH /site-config/admin/themes/:id 🔒 [Admin] 更新主題
所有欄位皆為 optional,僅傳入要更新的欄位。
| 錯誤碼 | 說明 |
|---|---|
| 2002 | 查無此主題 |
DELETE /site-config/admin/themes/:id 🔒 [Admin] 刪除主題
若刪除的是當前 activeTheme,會自動清除 activeThemeId。
| 錯誤碼 | 說明 |
|---|---|
| 2002 | 查無此主題 |
PATCH /site-config/admin/:siteConfigId/mascots 🔒 [Admin] 更新吉祥物列表
全量替換該站點的吉祥物頭像列表。
// Request Body
{
"mascots": [
{ "id": "c9-dragon", "label": "翡翠龍", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-dragon.png" },
{ "id": "c9-phoenix", "label": "鳳凰", "url": "https://pub-xxx.r2.dev/avatars/mascots/c9-phoenix.png" }
]
}
// Response result — 更新後的完整 mascots 陣列Withdrawal — 提領
USDT 提領模組,用戶需已綁定信箱、手機,並擁有已審核通過的加密錢包。 提領前需發送郵箱驗證碼,提交後進入人工審核流程(pending → approved → completed / rejected)。 提交提領時餘額原子凍結(balance → frozenBalance),拒絕時退回,完成時出帳。
POST /withdrawal/send-code 🔒 發送提領驗證碼
發送 6 碼驗證碼到用戶已綁定的信箱。
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 用戶信箱未驗證 |
| 2002 | 用戶手機未驗證 |
POST /withdrawal/request 🔒 提交提領申請
Request Body:
| 欄位 | 型態 | 必填 | 說明 |
|---|---|---|---|
| amount | number | 是 | 提領金額 (USD) |
| cryptoAddressId | number | 是 | 加密錢包 ID (status=1) |
| verifyCode | string | 是 | 郵箱驗證碼 (6 碼) |
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 驗證碼未發送 |
| 2002 | 驗證碼錯誤 |
| 2003 | 用戶信箱未驗證 |
| 2004 | 用戶手機未驗證 |
| 2005 | 該錢包不存在或未通過審核 |
| 2006 | 提領金額需大於 0 |
| 2007 | 餘額不足 |
| 2008 | 優惠打碼量未完成 |
| 2009 | 存款打碼量不足 |
打碼量檢查規則:
提領前系統自動檢查兩項打碼量條件,任一未通過即拒絕提領:
- 優惠打碼量:所有已領取優惠的打碼量需全部完成(turnoverCompleted = 1)才能提領
- 存款打碼量:累計有效投注(totalEffectiveBet)>= 累計入帳存款(sum of paid deposits usdAmount)× 1x 倍數
打碼量計算依遊戲類型有不同權重:CRYPTO / FISH = 50%,其他遊戲 = 100%(詳見「遊戲類型與流水權重」章節)
GET /withdrawal/list 🔒 用戶提領紀錄
Query Parameters:
| 參數 | 型態 | 必填 | 說明 |
|---|---|---|---|
| page | number | 否 | 頁碼 (預設 1) |
| pageSize | number | 否 | 每頁筆數 (預設 10,最大 50) |
| status | string | 否 | 篩選狀態:pending / approved / rejected / completed |
| startDate | string | 否 | 起始日期 (YYYY-MM-DD) |
| endDate | string | 否 | 結束日期 (YYYY-MM-DD) |
GET /withdrawal/turnover-status 🔒 查詢打碼量狀態
查詢當前用戶的存款打碼量與優惠打碼量完成進度。前端可依據 canWithdraw 控制提領按鈕狀態。
Response:
{
"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"
}
]
}
}GET /withdrawal/admin/list 🔒 [Admin] 提領列表
Query Parameters:
| 參數 | 型態 | 必填 | 說明 |
|---|---|---|---|
| page | number | 否 | 頁碼 (預設 1) |
| pageSize | number | 否 | 每頁筆數 (預設 10,最大 50) |
| status | string | 否 | 篩選狀態:pending / approved / rejected / completed |
| startDate | string | 否 | 起始日期 (YYYY-MM-DD) |
| endDate | string | 否 | 結束日期 (YYYY-MM-DD) |
POST /withdrawal/admin/:id/review 🔒 [Admin] 審核提領
Request Body:
| 欄位 | 型態 | 必填 | 說明 |
|---|---|---|---|
| action | string | 是 | approve / reject |
| rejectReason | string | 否 | 拒絕原因 (action=reject 時) |
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此提領單 |
| 2002 | 該提領單狀態不允許審核 |
POST /withdrawal/admin/:id/complete 🔒 [Admin] 確認出款完成
標記已匯款完成,凍結金額出帳。
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此提領單 |
| 2002 | 該提領單狀態不允許完成 |
LiveSports — 即時體育
公開端點,不需登入
GET /live-sports — 取得即時體育賽事 Banner
// Response result
[
{
"fixtureId": 1234567,
"kickoffAt": "2026-02-24T20:00:00+00:00",
"status": { "short": "1H", "long": "First Half", "elapsed": 34 },
"league": { "id": 2, "name": "UEFA Champions League", "country": "World", "logo": "https://...", "round": "Quarter-finals" },
"home": { "id": 530, "name": "Atletico Madrid", "logo": "https://...", "score": 1 },
"away": { "id": 569, "name": "Club Brugge", "logo": "https://...", "score": 0 },
"odds": { "home": "1.43", "draw": "5.20", "away": "6.80", "extraCount": 5 }
}
]資料每 30 分鐘自動更新(Cron + Redis 快取) 進行中賽事(status.short ∈ [1H, HT, 2H, ET, P])前端可顯示「現場」Badge 最多回傳 20 場賽事,進行中優先,其後依開賽時間排序
Mission — 任務
每日/每週/每月任務,分為「存款」與「投注」兩大類,各類 5 個階級(tier 1-5)。 進度在存款確認 / 投注結算時自動更新。每期(日/週/月)重置。
GET /mission — 取得任務列表(含進度與領取狀態)
OptionalAuth:未登入時
currentProgress為 null,isClaimed/isClaimable為 false。
// Response result
[
{
"id": 1,
"category": "deposit",
"periodType": "daily",
"tier": 1,
"threshold": "10.000000",
"rewardAmount": "0.300000",
"vipRequired": 0,
"turnoverMultiplier": "3.00",
"currentProgress": "50.000000",
"isClaimed": false,
"isClaimable": true,
"meetsVip": true
}
]
category:deposit(存款) /bet(投注)periodType:daily(每日) /weekly(每週) /monthly(每月)threshold: 達成門檻金額meetsVip: 是否滿足 VIP 等級要求(vipRequired <= 0或userVipLevel >= vipRequired)isClaimable:!isClaimed && currentProgress >= threshold && meetsVip
GET /mission/claims 🔒 — 取得任務領取紀錄
Query Params (optional):
page: number — 頁碼(預設 1)
pageSize: number — 每頁筆數(預設 10,最大 50)
tab: string — pending=未完成打碼 / completed=已完成
startDate: string — 起始日期 (YYYY-MM-DD)
endDate: string — 結束日期 (YYYY-MM-DD)// Response result
{
"items": [
{
"id": 1,
"missionId": 1,
"category": "deposit",
"periodType": "daily",
"tier": 1,
"periodKey": "2026-02-24",
"rewardAmount": "0.300000",
"requiredTurnover": "0.900000",
"completedTurnover": "0.000000",
"turnoverCompleted": 0,
"claimedAt": "2026-02-24T10:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}POST /mission/:id/claim 🔒 — 領取任務獎勵
// Response result
{
"rewardAmount": "0.300000",
"requiredTurnover": "0.900000",
"newBalance": "100.300000"
}| 錯誤碼 | 說明 |
|---|---|
| 3001 | 查無此任務 |
| 3002 | 此任務本期已領取過 |
| 3003 | VIP 等級不足 |
| 3004 | 尚未達成任務目標 |
Admin — 後台管理
管理員帳號獨立於前台用戶,使用
admin-jwt策略驗證。 AdminJWT = 需要管理員 JWT Token (Authorization: Bearer <admin-token>)
POST /admin/login — 管理員登入
Body:
{
"account": "admin001",
"password": "admin1234"
}Response (200):
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"admin": {
"id": 1,
"account": "admin001",
"name": "超級管理員",
"groupId": 1,
"status": 1,
"lastLoginIp": "127.0.0.1",
"lastLoginAt": "2026-02-26T10:00:00.000Z",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-02-26T10:00:00.000Z"
}
}| 錯誤碼 | 說明 |
|---|---|
| 2001 | 帳號或密碼錯誤 |
| 2002 | 帳號已停用 |
GET /admin/profile — 取得管理員個人資料 🔒 AdminJWT
Response (200):
{
"id": 1,
"account": "admin001",
"name": "超級管理員",
"groupId": 1,
"group": {
"id": 1,
"name": "超級管理員群組",
"permissions": ["admin:read", "admin:write"],
"description": "擁有所有管理權限",
"status": 1
},
"status": 1,
"lastLoginIp": "127.0.0.1",
"lastLoginAt": "2026-02-26T10:00:00.000Z",
"createdAt": "2026-01-01T00:00:00.000Z"
}GET /admin/list — 管理員列表 🔒 AdminJWT
Response (200):
[
{
"id": 1,
"account": "admin001",
"name": "超級管理員",
"groupId": 1,
"group": { "id": 1, "name": "超級管理員群組" },
"status": 1,
"lastLoginIp": "127.0.0.1",
"lastLoginAt": "2026-02-26T10:00:00.000Z",
"createdAt": "2026-01-01T00:00:00.000Z"
}
]GET /admin/:id — 取得單一管理員 🔒 AdminJWT
Params: id — 管理員 ID
Response (200): 同 profile 格式
POST /admin/create — 建立管理員 🔒 AdminJWT
Body:
{
"account": "admin002",
"password": "pass1234",
"name": "客服管理員",
"groupId": 2
}Response (200):
{
"id": 2,
"account": "admin002",
"name": "客服管理員",
"groupId": 2,
"status": 1,
"createdAt": "2026-02-26T10:00:00.000Z"
}| 錯誤碼 | 說明 |
|---|---|
| 2001 | 帳號已存在 |
| 2002 | 群組不存在 |
PATCH /admin/:id — 更新管理員 🔒 AdminJWT
Params: id — 管理員 ID
Body (皆為選填):
{
"name": "新名稱",
"password": "newpass123",
"groupId": 2,
"status": 0
}| 錯誤碼 | 說明 |
|---|---|
| 2001 | 管理員不存在 |
DELETE /admin/:id — 刪除管理員 🔒 AdminJWT
Params: id — 管理員 ID
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 管理員不存在 |
| 2002 | 不可刪除自己 |
GET /admin/groups/list — 管理員群組列表 🔒 AdminJWT
Response (200):
[
{
"id": 1,
"name": "超級管理員",
"permissions": ["admin:read", "admin:write", "deposit:read"],
"description": "擁有所有管理權限",
"status": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]GET /admin/groups/:id — 取得單一群組 🔒 AdminJWT
Params: id — 群組 ID
Response (200): 群組資料 + admins 陣列 (群組下的管理員列表)
POST /admin/groups/create — 建立群組 🔒 AdminJWT
Body:
{
"name": "客服群組",
"permissions": ["user:read", "inbox:read", "inbox:write"],
"description": "客服人員專用"
}PATCH /admin/groups/:id — 更新群組 🔒 AdminJWT
Params: id — 群組 ID
Body (皆為選填):
{
"name": "新群組名稱",
"permissions": ["admin:read", "deposit:read"],
"description": "更新描述",
"status": 0
}| 錯誤碼 | 說明 |
|---|---|
| 2001 | 群組不存在 |
DELETE /admin/groups/:id — 刪除群組 🔒 AdminJWT
Params: id — 群組 ID
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 群組不存在 |
| 2002 | 群組下仍有管理員,無法刪除 |
GET /admin/logs/list — 管理員操作紀錄列表 🔒 AdminJWT
Query 參數 (皆為選填):
| 參數 | 類型 | 說明 |
|---|---|---|
| adminId | number | 篩選管理員 |
| module | string | 篩選模組 (admin / admin-group / deposit / ...) |
| action | string | 篩選動作 (login / create / update / delete) |
| startDate | string | 開始日期 (YYYY-MM-DD) |
| endDate | string | 結束日期 (YYYY-MM-DD) |
| page | number | 頁碼 (預設 1) |
| pageSize | number | 每頁筆數 (預設 20) |
Response (200):
{
"items": [
{
"id": 1,
"adminId": 1,
"admin": { "id": 1, "account": "admin001", "name": "超級管理員" },
"module": "admin",
"action": "login",
"targetId": null,
"ip": "127.0.0.1",
"userAgent": "Mozilla/5.0 ...",
"method": "POST",
"path": "/api/admin/login",
"detail": null,
"summary": "管理員 admin001 登入",
"createdAt": "2026-02-26T10:00:00.000Z"
}
],
"total": 100,
"page": 1,
"pageSize": 20,
"totalPages": 5
}遊戲類型與流水權重
| gameType | 名稱 | 標籤 | 流水權重 | 說明 |
|---|---|---|---|---|
| 1 | 體育 | sports | 1.0 (100%) | betEffective = betAmount × 1.0 |
| 2 | 電子 | slot | 1.0 (100%) | betEffective = betAmount × 1.0 |
| 3 | 真人 | live | 1.0 (100%) | betEffective = betAmount × 1.0 |
| 4 | 彩票 | lottery | 1.0 (100%) | betEffective = betAmount × 1.0 |
| 5 | 棋牌 | chess | 1.0 (100%) | betEffective = betAmount × 1.0 |
| 8 | 電競 | esports | 1.0 (100%) | betEffective = betAmount × 1.0 |
| 9 | 加密貨幣 | crypto | 0.5 (50%) | betEffective = betAmount × 0.5 |
| 10 | 捕魚 | fish | 0.5 (50%) | betEffective = betAmount × 0.5 |
VIP 升級依據 = 累計
betEffective(有效投注流水),非存款金額 反水結算以betEffective為基準,按 VIP 等級 × 遊戲類型對應的 rebateRate 計算
VIP 保級機制
| 規則 | 說明 |
|---|---|
| 升級 | 累計有效投注 ≥ 等級 minChip → 自動升級(只升不降) |
| 保級檢查 | 每月 1 號 01:00 自動檢查 VIP 2+ 用戶 |
| 保級條件 | 上月有效投注 ≥ 當前等級 relegationChip |
| 第 1 月未達 | 發出警告 (relegationMissCount = 1) |
| 連續 2 月未達 | 降 1 級 (relegationMissCount 歸零) |
| 達標重置 | 達到保級條件後 relegationMissCount 歸零 |
| VIP Hold | VIP 5+ 可由管理員設定 vipHold=1,跳過保級檢查 |
每日反水結算
| 規則 | 說明 |
|---|---|
| 排程 | 每日 00:05 自動結算 |
| 計算 | 昨日各用戶各 gameType 的 SUM(betEffective) × rebateRate% |
| 發放 | 直接加到用戶 balance,同時寫入 vip-rebate-log |
| 手動觸發 | POST /vip/settlement/daily-rebate |
存款訂單生命週期
[萬通 wantong]
前端 POST /deposit (paymentMethod: fiat|credit)
→ 建立本地訂單 (status: pending) → 呼叫萬通建單 API → 回前端
↓
萬通 POST /vendor/wantong/callback/add
→ 訂單 status: created
↓
萬通 POST /vendor/wantong/callback/pay
→ 匯率轉換 → 上分到 user.balance
→ 訂單 status: paid
[USDT crypto]
前端 POST /deposit (paymentMethod: crypto)
→ 建立本地訂單 (status: pending) → 回傳繳費地址 → 前端顯示地址讓用戶轉帳
↓
POST /vendor/usdt/callback/pay
→ 上分到 user.balance
→ 訂單 status: paid投注後自動觸發機制
每次投注結算完成後(不論來源),系統自動執行以下兩項操作,前端無需額外呼叫:
觸發來源
| 來源 | 觸發時機 | 說明 |
|---|---|---|
POST /game/simulate | 模擬遊戲結算後 | 前端按鈕觸發的模擬遊戲 |
POST /game/betsolutions/bet | BS 下注回調 | 遊戲商 Server-to-Server |
POST /game/rsg/BetResult | RSG 結算回調 | 遊戲商 Server-to-Server |
自動執行項目
投注結算完成
│
├─ 1. VIP 等級重算 (recalculateUserVip)
│ └→ 計算用戶累計有效投注 (SUM betEffective WHERE status='valid')
│ └→ 同步更新 auth-user.totalEffectiveBet
│ └→ 若達到下一等級 minChip → 自動升級(只升不降)
│
└─ 2. 優惠打碼量累計 (updatePromoTurnover)
└→ 查找用戶所有 turnoverCompleted=0 的活動領取紀錄
└→ 將本次 betEffective 累加到 completedTurnover
└→ 若 completedTurnover ≥ requiredTurnover → 標記完成gameType 映射
投注紀錄 (bet-order.gameType) 存數字,反水規則 (vip-rebate.gameType) 存標籤,系統自動轉換:
| bet-order.gameType | vip-rebate.gameType | 說明 |
|---|---|---|
| "1" | "sports" | 體育 |
| "2" | "slot" | 電子 |
| "3" | "live" | 真人 |
| "4" | "lottery" | 彩票 |
| "5" | "chess" | 棋牌 |
| "8" | "esports" | 電競 |
| "9" | "crypto" | 加密貨幣 |
| "10" | "fish" | 捕魚 |
前端不需處理此映射,僅供理解後端邏輯。
系統自動排程
| 排程 | Cron | 說明 |
|---|---|---|
| 每日反水結算 | 00:05 每日 | 結算昨日各用戶各遊戲類型的有效投注反水,發放到餘額 |
| 月度保級檢查 | 01:00 每月 1 號 | 檢查 VIP 2+ 用戶上月有效投注是否達保級門檻 |
| 代理佣金週結 | 03:00 每週一 | 結算上週各代理的佣金(下線淨虧損 × 佣金比例),含風控檢測 |
| 代理佣金日結算 | 03:30 每日 | AffiliateSettlementService.handleDailySettlementCron 代理佣金日結算 |
| 即時賽事快取更新 | 每 30 分鐘 | 抓取 API-Football 即時/今日賽事 + 賠率,快取至 Redis |
手動觸發端點:
POST /vip/settlement/daily-rebate— 手動觸發每日反水結算POST /vip/settlement/monthly-relegation— 手動觸發月度保級檢查POST /affiliate/admin/trigger-settlement— 手動觸發代理佣金週結算POST /affiliate/admin/trigger-daily-settlement— 手動觸發代理佣金日結算
錯誤碼對照表
| 模組 | 端點 | Code | 說明 |
|---|---|---|---|
| Auth | register | 2001 | 帳號已存在 |
| Auth | register | 2002 | 推廣碼不存在 |
| Auth | login | 2001 | 帳號或密碼錯誤 |
| Auth | checkVerifyEmail | 2001 | 尚未發送驗證碼 |
| Auth | checkVerifyEmail | 2002 | 驗證碼錯誤 |
| Auth | enableGoogleAuth | 2001 | 尚未產生 Google Auth Secret |
| Auth | enableGoogleAuth | 2002 | TOTP 驗證碼錯誤 |
| Auth | editPassword | 2002 | 原密碼錯誤 / 新密碼不一致 |
| Auth | updateLocale | 2001 | 不支援的語系 |
| Auth | getLoginConfig | 2001 | Google OAuth 未設定 |
| Auth | loginGoogle | 2001~2009 | Google 登入各階段錯誤 |
| Vendor | getChannels | 2001 | 用戶未分配金流群組 |
| Vendor | getChannels | 2002 | 金流群組不存在或已停用 |
| Vendor | getChannels | 2003 | 金流通道不存在或不屬於此群組 |
| Wantong | getChannel | 2001 | 查無可用的萬通金流通道 |
| Wantong | addAtm | 2002 | 建立 ATM 訂單失敗 |
| Wantong | addAtm | 2003 | ATM 訂單請求異常 |
| Wantong | addCard | 2002 | 建立信用卡訂單失敗 |
| Wantong | addCard | 2003 | 信用卡訂單請求異常 |
| USDT | deposit | 2001 | 查無可用的 USDT 金流通道 |
| USDT | deposit | 2002 | 該通道尚未設定繳費地址 |
| Deposit | getExchangeRate | 2001 | 幣別格式錯誤 |
| Deposit | getExchangeRate | 2002 | 匯率解析失敗 |
| Deposit | deposit | 2004 | 不支援的支付方式 |
| Deposit | deposit | 2005 | 匯率轉換失敗 (上分時) |
| Deposit | deposit | 2006 | 金流通道尚未建置完成 |
| BankCard | addBankCard | 2001 | 缺少身分證正面照片 |
| BankCard | addBankCard | 2002 | 缺少身分證背面照片 |
| BankCard | addBankCard | 2003 | 缺少存摺封面照片 |
| BankCard | addBankCard | 2004 | 此銀行卡已綁定 |
| BankCard | deleteBankCard | 2001 | 銀行卡不存在 |
| CreditCard | addCreditCard | 2001 | 此信用卡已綁定 |
| CreditCard | deleteCreditCard | 2001 | 信用卡不存在 |
| CryptoAddress | addCryptoAddress | 2001 | 此錢包地址已綁定 |
| CryptoAddress | deleteCryptoAddress | 2001 | 查無此錢包地址 |
| Promo | getPromoById | 2001 | 查無此活動 |
| Promo | updatePromo | 2001 | 查無此活動 |
| Promo | deletePromo | 2001 | 查無此活動 |
| Promo | claimPromo | 2001 | 此活動已領取過 |
| Promo | claimPromo | 2002 | 此活動領取名額已滿 |
| Promo | claimPromo | 2003 | 尚未滿足領取條件 |
| VIP | createLevel | 2003 | 此 VIP 等級已存在 |
| VIP | updateLevel | 2001 | 查無此 VIP 等級 |
| VIP | deleteLevel | 2001 | 查無此 VIP 等級 |
| VIP | createRebate | 2004 | 此等級的遊戲類型返水規則已存在 |
| VIP | updateRebate | 2002 | 查無此返水規則 |
| VIP | deleteRebate | 2002 | 查無此返水規則 |
| VIP | setVipHold | 2001 | 用戶不存在 |
| VIP | setVipHold | 2002 | VIP 等級未達 5 |
| Game | launch | 5001 | 查無此遊戲商 |
| Game | launch | 5002 | 遊戲商已停用 |
| Game | launch | 5003 | 遊戲商維護中 |
| Game | launch | 5004 | 遊戲商 API 回傳錯誤 |
| Game | launch | 5005 | 未設定遊戲商 API |
| Game | demo | 5001 | 查無此遊戲商 |
| Game | demo | 5006 | 該遊戲商不支援試玩模式 |
| Game | simulate | 5001 | 查無此遊戲商 |
| Game | simulate | 5010 | 投注金額需在 0.01 ~ 10000 之間 |
| Game | simulate | 5011 | 餘額不足 |
| Game | simulate | 2001 | 用戶不存在 |
| Game | list | 5001 | 查無此遊戲商 |
| BetRecord | getOrderDetails | 2001 | 訂單不存在或不屬於該用戶 |
| Affiliate | trackClick | 2001 | 推廣碼不存在 |
| Affiliate | getDashboard | 2001 | 您不是代理身份 |
| Affiliate | getDownline | 2001 | 您不是代理身份 |
| Affiliate | getSettlementDetail | 2001 | 結算紀錄不存在 |
| Affiliate | requestWithdrawal | 2001 | 提款金額須大於零 |
| Affiliate | requestWithdrawal | 2002 | 提款方式無效 |
| Affiliate | requestWithdrawal | 2003 | 冷卻中 |
| Affiliate | requestWithdrawal | 2004 | 餘額不足 |
| Affiliate | requestWithdrawal | 2005 | 非代理 |
| Affiliate | createAgent | 2001 | 用戶不存在 |
| Affiliate | createAgent | 2002 | 推廣碼已被使用 |
| Affiliate | createAgent | 2003 | 已是代理 |
| Affiliate | reviewSettlement | 2001 | 紀錄不存在 |
| Affiliate | reviewSettlement | 2002 | 已審核 |
| Affiliate | reviewWithdrawal | 2001 | 紀錄不存在 |
| Affiliate | reviewWithdrawal | 2002 | 已審核 |
| Affiliate | completeWithdrawal | 2001 | 紀錄不存在 |
| Affiliate | completeWithdrawal | 2002 | 狀態須為 approved |
| Affiliate | adminBind | 2001 | 會員不存在 |
| Affiliate | adminBind | 2002 | 代理不存在 |
| Inbox | markAsRead | 2001 | 查無此通知 |
| Inbox | adminDelete | 2001 | 查無此通知 |
| SiteConfig | getPublicConfig | 2001 | 查無此站點設定 |
| SiteConfig | adminUpdate | 2001 | 查無此站點設定 |
| SiteConfig | adminCreateTheme | 2001 | 查無此站點設定 |
| SiteConfig | adminUpdateTheme | 2002 | 查無此主題 |
| SiteConfig | adminDeleteTheme | 2002 | 查無此主題 |
| Withdrawal | sendCode | 2001 | 用戶信箱未驗證 |
| Withdrawal | sendCode | 2002 | 用戶手機未驗證 |
| Withdrawal | request | 2001 | 驗證碼未發送 |
| Withdrawal | request | 2002 | 驗證碼錯誤 |
| Withdrawal | request | 2003 | 用戶信箱未驗證 |
| Withdrawal | request | 2004 | 用戶手機未驗證 |
| Withdrawal | request | 2005 | 該錢包不存在或未通過審核 |
| Withdrawal | request | 2006 | 提領金額需大於 0 |
| Withdrawal | request | 2007 | 餘額不足 |
| Withdrawal | request | 2008 | 優惠打碼量未完成 |
| Withdrawal | request | 2009 | 存款打碼量不足 |
| Withdrawal | review | 2001 | 查無此提領單 |
| Withdrawal | review | 2002 | 該提領單狀態不允許審核 |
| Withdrawal | complete | 2001 | 查無此提領單 |
| Withdrawal | complete | 2002 | 該提領單狀態不允許完成 |
| Auth | loginTelegram | 2001 | 尚未設置 TELEGRAM_BOT_TOKEN |
| Auth | loginTelegram | 2002 | Telegram 驗證失敗 |
| Auth | loginTelegram | 2003 | 本次操作時效已過期, 請重新登入 |
| Auth | updateAvatar | 2001 | 無效的頭像 ID |
| Mission | claimMission | 3001 | 查無此任務 |
| Mission | claimMission | 3002 | 此任務本期已領取過 |
| Mission | claimMission | 3003 | VIP 等級不足 |
| Mission | claimMission | 3004 | 尚未達成任務目標 |
🔒 標記說明
帶有 🔒 的端點需要在 Header 傳入 Authorization: Bearer <token>,token 由 /auth/login、/auth/register、/auth/login-google 或 /auth/login-telegram 取得。
端點速查表
| Method | Path | Auth | 說明 |
|---|---|---|---|
| GET | / | - | Health check |
| POST | /auth/register | - | 註冊 |
| POST | /auth/login | - | 登入 |
| GET | /auth/user-detail | 🔒 | 取得用戶資料 |
| GET | /auth/country-codes | 🔒 | 取得國碼列表 |
| POST | /auth/send-verify-email | 🔒 | 發送驗證信 |
| POST | /auth/check-verify-email | 🔒 | 驗證 Email |
| POST | /auth/generate-google-auth | 🔒 | 產生 Google Auth |
| POST | /auth/enable-google-auth | 🔒 | 啟用 Google Auth |
| POST | /auth/edit-password | 🔒 | 修改密碼 |
| POST | /auth/logout | 🔒 | 登出 (更新語系 + 重匹配金流群組 + 使 token 失效) |
| PATCH | /auth/locale | 🔒 | 更新語系偏好 |
| GET | /auth/login-config | - | 取得 Google OAuth URL + Telegram Bot 資訊 |
| POST | /auth/login-google | - | Google 登入 |
| POST | /auth/login-telegram | - | Telegram 登入 |
| GET | /auth/mascots | - | 取得吉祥物頭像列表 |
| PATCH | /auth/avatar | 🔒 | 切換用戶頭像 |
| GET | /game/provider | - | 取得遊戲商列表 |
| POST | /game/launch | 🔒 | 啟動遊戲 (取得遊戲 URL) |
| POST | /game/demo | - | 試玩遊戲 (免登入 Demo) |
| POST | /game/simulate | 🔒 | 模擬遊戲一輪 (按鈕觸發) |
| GET | /game/list | 🔒 | 取得遊戲商的遊戲列表 |
| 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 | 🔒 | 新增銀行卡 |
| GET | /wallet/bank-card/list | 🔒 | 取得銀行卡列表 |
| DELETE | /wallet/bank-card/:id | 🔒 | 刪除銀行卡 |
| POST | /wallet/credit-card/add | 🔒 | 新增信用卡 |
| GET | /wallet/credit-card/list | 🔒 | 取得信用卡列表 |
| DELETE | /wallet/credit-card/:id | 🔒 | 刪除信用卡 |
| POST | /wallet/crypto-address/add | 🔒 | 新增加密貨幣錢包 |
| GET | /wallet/crypto-address/list | 🔒 | 取得加密貨幣錢包列表 |
| DELETE | /wallet/crypto-address/:id | 🔒 | 刪除加密貨幣錢包 |
| GET | /vendor/channels | 🔒 | 取得用戶金流通道列表 |
| POST | /vendor/wantong/add-atm | 🔒 | 萬通 ATM 建單 |
| POST | /vendor/wantong/add-card | 🔒 | 萬通信用卡建單 |
| 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 | 🔒 | 取得金流通道 (含匯率) |
| GET | /deposit/orders | 🔒 | 取得存款訂單列表 |
| POST | /deposit | 🔒 | 建立存款訂單 |
| GET | /promo | - | 取得活動列表 |
| GET | /promo/claims | 🔒 | 取得領取紀錄 |
| GET | /promo/:id | - | 取得活動詳情 |
| POST | /promo | 🔒 | 建立活動 |
| PATCH | /promo/:id | 🔒 | 更新活動 |
| DELETE | /promo/:id | 🔒 | 刪除活動 |
| POST | /promo/:id/claim | 🔒 | 領取活動獎勵 |
| GET | /vip/levels | - | 取得 VIP 等級列表 |
| GET | /vip/rebates | - | 取得返水規則 |
| GET | /vip/status | 🔒 | 取得用戶 VIP 狀態 |
| POST | /vip/levels | 🔒 | 建立 VIP 等級 |
| PATCH | /vip/levels/:id | 🔒 | 更新 VIP 等級 |
| DELETE | /vip/levels/:id | 🔒 | 刪除 VIP 等級 |
| POST | /vip/rebates | 🔒 | 建立返水規則 |
| PATCH | /vip/rebates/:id | 🔒 | 更新返水規則 |
| DELETE | /vip/rebates/:id | 🔒 | 刪除返水規則 |
| POST | /vip/settlement/daily-rebate | 🔒 | 手動觸發每日反水結算 |
| POST | /vip/settlement/monthly-relegation | 🔒 | 手動觸發月度保級檢查 |
| PATCH | /vip/users/:userId/hold | 🔒 | 設定 VIP 保級鎖定 |
| GET | /ranking | - | 取得排行榜 |
| GET | /bet-record | 🔒 | 投注紀錄列表(分頁) |
| GET | /bet-record/:orderId/details | 🔒 | 訂單小注單明細 |
| POST | /affiliate/track-click | - | 記錄推廣連結點擊 |
| GET | /affiliate/dashboard | 🔒 | 代理儀表板 |
| GET | /affiliate/promo-link | 🔒 | 取得推廣連結 |
| GET | /affiliate/downline | 🔒 | 下線列表 |
| GET | /affiliate/click-stats | 🔒 | 點擊統計 |
| GET | /affiliate/commissions | 🔒 | 佣金明細 |
| GET | /affiliate/settlements | 🔒 | 結算紀錄 |
| GET | /affiliate/settlements/:id | 🔒 | 結算詳情 |
| GET | /affiliate/balance | 🔒 | 佣金餘額 |
| GET | /affiliate/withdrawals | 🔒 | 提款紀錄 |
| POST | /affiliate/withdrawals/request | 🔒 | 發起提款 |
| POST | /affiliate/admin/create-agent | 🔒 | [Admin] 建立代理 |
| GET | /affiliate/admin/settlements | 🔒 | [Admin] 全部結算列表 |
| POST | /affiliate/admin/settlements/:id/review | 🔒 | [Admin] 審核結算 |
| GET | /affiliate/admin/settlements/:id/risk-logs | 🔒 | [Admin] 結算風控紀錄 |
| GET | /affiliate/admin/withdrawals | 🔒 | [Admin] 全部提款列表 |
| POST | /affiliate/admin/withdrawals/:id/review | 🔒 | [Admin] 審核提款 |
| POST | /affiliate/admin/withdrawals/:id/complete | 🔒 | [Admin] 確認出款完成 |
| POST | /affiliate/admin/bind | 🔒 | [Admin] 人工綁定/解綁/轉移 |
| GET | /affiliate/admin/bind-logs | 🔒 | [Admin] 綁定審計紀錄 |
| POST | /affiliate/admin/trigger-settlement | 🔒 | [Admin] 手動觸發週結算 |
| GET | /inbox | 🔒 | 取得站內信列表 |
| GET | /inbox/unread-count | 🔒 | 取得未讀通知數量 |
| POST | /inbox/:id/read | 🔒 | 標記單則通知為已讀 |
| POST | /inbox/read-all | 🔒 | 全部標記為已讀 |
| POST | /inbox/admin/send | 🔒 | [Admin] 發送通知 |
| GET | /inbox/admin/list | 🔒 | [Admin] 通知列表 |
| DELETE | /inbox/admin/:id | 🔒 | [Admin] 刪除通知 |
| GET | /site-config | - | 取得當前站點設定 |
| GET | /site-config/admin/list | 🔒 | [Admin] 取得所有站點設定 (含主題) |
| PATCH | /site-config/admin/:id | 🔒 | [Admin] 更新站點設定 |
| GET | /site-config/admin/:siteConfigId/themes | 🔒 | [Admin] 主題列表 |
| POST | /site-config/admin/:siteConfigId/themes | 🔒 | [Admin] 新增主題 |
| PATCH | /site-config/admin/themes/:id | 🔒 | [Admin] 更新主題 |
| DELETE | /site-config/admin/themes/:id | 🔒 | [Admin] 刪除主題 |
| PATCH | /site-config/admin/:siteConfigId/mascots | 🔒 | [Admin] 更新吉祥物列表 |
| POST | /withdrawal/send-code | 🔒 | 發送提領驗證碼 |
| POST | /withdrawal/request | 🔒 | 提交提領申請 |
| GET | /withdrawal/list | 🔒 | 用戶提領紀錄 |
| GET | /withdrawal/turnover-status | 🔒 | 查詢打碼量狀態 |
| GET | /withdrawal/admin/list | 🔒 | [Admin] 提領列表 |
| POST | /withdrawal/admin/:id/review | 🔒 | [Admin] 審核提領 |
| POST | /withdrawal/admin/:id/complete | 🔒 | [Admin] 確認出款完成 |
| GET | /live-sports | - | 取得即時體育賽事 Banner |
| GET | /mission | - | 取得任務列表(含進度) |
| GET | /mission/claims | 🔒 | 取得任務領取紀錄 |
| POST | /mission/:id/claim | 🔒 | 領取任務獎勵 |
| POST | /admin/login | - | 管理員登入 |
| GET | /admin/profile | 🔑 | 取得管理員個人資料 |
| GET | /admin/list | 🔑 | 管理員列表 |
| GET | /admin/:id | 🔑 | 取得單一管理員 |
| POST | /admin/create | 🔑 | 建立管理員 |
| PATCH | /admin/:id | 🔑 | 更新管理員 |
| DELETE | /admin/:id | 🔑 | 刪除管理員 |
| GET | /admin/groups/list | 🔑 | 管理員群組列表 |
| GET | /admin/groups/:id | 🔑 | 取得單一群組 |
| POST | /admin/groups/create | 🔑 | 建立群組 |
| PATCH | /admin/groups/:id | 🔑 | 更新群組 |
| DELETE | /admin/groups/:id | 🔑 | 刪除群組 |
| GET | /admin/logs/list | 🔑 | 管理員操作紀錄列表 |
🔒 = 需要 JWT Token / 🔑 = 需要管理員 AdminJWT Token / S2S = Server-to-Server 回調 (不需 JWT)