Skip to content

C9 Backend API Reference

此文件供前端專案 Claude Code 讀取後直接串接使用。 Swagger UI: {BASE_URL}/api/docs 端點總數:142(GET 50 / POST 57 / PATCH 9 / DELETE 8 + 新增 18),含 11 遊戲商 S2S 回調


目錄


基本設定

項目
Base URLhttp://localhost:8080/api (開發環境)
Content-Typeapplication/json(銀行卡新增為 multipart/form-data
認證方式JWT Bearer Token,Header: Authorization: Bearer <token>
多語系Header: locales: zh-TW / en-US / zh-CN

統一回應格式

成功回應 (HTTP 200)

json
{
  "code": 200,
  "message": "ok",
  "result": { ... },
  "timestamp": 1708588800000,
  "path": "/api/auth/login"
}

錯誤回應

後端錯誤分為兩種 HTTP Status:

HTTP 401 — 未授權(Token 過期 / 無效 / 未帶)

json
{
  "code": 401,
  "message": "Unauthorized",
  "data": null,
  "timestamp": 1771899908020,
  "path": "/api/vendor/channels"
}

401 固定回 "Unauthorized",前端收到後直接跳轉登入頁,不需查 ERROR_CODES。

HTTP 200 — 業務錯誤(code ≠ 200)

json
{
  "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 建議設定

typescript
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

json
// Response result
"Hello World!"

Auth — 認證

POST /auth/register — 註冊

json
// 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 推廣碼不存在

自動分配金流群組:locales header 語系對應幣別(zh-TW→TWD, en-US→USD, zh-CN→CNY),自動指派第一個啟用中且含該幣別通道的金流群組。vendorGroupId 為 null 代表無符合群組,前端可據此提示用戶。

POST /auth/login — 登入

json
// 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 筆登入紀錄
json
// 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 — 取得國碼列表 🔒

json
// Response result
[
  { "country": "TW", "callingCode": "886", "name": "台灣" },
  { "country": "US", "callingCode": "1", "name": "美國" }
]

POST /auth/send-verify-email — 發送驗證信 🔒

json
// Request Body
{
  "email": "user@example.com",
  "subject": "C9邀請您驗證信箱"   // optional
}

// Response result (Resend API 回應)
{ "id": "email_id_xxx" }

錯誤碼:2001 此信箱已被其他帳號使用

POST /auth/check-verify-email — 驗證 Email 🔒

json
// Request Body
{
  "code": "123456",
  "email": "user@example.com"
}

// Response result
null

錯誤碼:2001 查無驗證資訊 / 2002 驗證碼錯誤

POST /auth/generate-google-auth — 產生 Google Authenticator 🔒

json
// Request Body: 無 (僅需 JWT)

// Response result
{
  "secret": "JBSWY3DPEHPK3PXP",
  "qrCode": "data:image/png;base64,..."
}

POST /auth/enable-google-auth — 啟用 Google Authenticator 🔒

json
// Request Body
{ "code": "123456" }

// Response result
{ "affected": 1 }

錯誤碼:2001 查無驗證資訊 / 2002 6 位數密碼輸入錯誤

POST /auth/edit-password — 修改密碼 🔒

json
// Request Body
{
  "password": "oldPass123",
  "newPassword": "newPass456",
  "confirmPassword": "newPass456"
}

// Response result
{ "affected": 1 }

錯誤碼:2001 當前密碼錯誤 / 2002 舊密碼與新密碼不符合

POST /auth/logout — 登出 🔒

json
// Response result
{ "locale": "en-US", "vendorGroupId": 4 }

locales header 取語系覆寫 auth-user.locale,並根據語系對應幣別重新匹配金流群組。同時 tokenVersion + 1 使現有 token 失效。

PATCH /auth/locale — 更新語系偏好 🔒

json
// Request Body
{ "locale": "zh-TW" }

// Response result
{ "locale": "zh-TW" }

錯誤碼:2001 不支援的語系

GET /auth/login-config — 取得登入設定 (Google OAuth + Telegram)

json
// 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 登入

json
// 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 / 2002 GOOGLE_CLIENT_ID 驗證失敗 / 2003 Google Payload 加密失敗 / 2004 Google Payload 解析失敗 / 2005 本次操作時效已過期, 請重新登入 / 2006 Google Code 驗證錯誤 / 2007 Google Api 響應過程錯誤 / 2008 Google Ticket 解析失敗 / 2009 Google Ticket Payload 解析失敗

POST /auth/login-telegram — Telegram 登入

透過 Telegram Login Widget 資料驗證並登入。首次登入自動建立帳號(account: tg_{telegramId})。

json
// 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 / 2002 Telegram 驗證失敗 / 2003 本次操作時效已過期, 請重新登入

GET /auth/mascots — 取得吉祥物頭像列表

回傳 10 張可選的吉祥物頭像,供用戶選擇更換頭像。

json
// 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 🔒 — 切換用戶頭像

json
// 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=捕魚)
json
// 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 (必填)
json
// 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 版本
  • 語系自動從 locales header 取得,不需額外傳參
  • 回傳的 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 (必填)
json
// 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 (必填)
json
// 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倍率機率說明
lose0x60%全輸
small0.5x22%小獎
medium2x10%中獎
good4x5%好獎
big8x2%大獎
huge20x0.8%巨獎
mega70x0.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 (必填)
json
// 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 各自格式)
fallbackfalse=正式遊戲商資料,true=測試遊戲(遊戲商 API 不可用時降級)

BetSolutions 遊戲欄位:

欄位說明
GameId遊戲 ID — 即為 /game/launch/game/simulateproductId
ProductId產品分類:2=Slots, 3=ProvablyFair
GameName遊戲名稱(測試遊戲帶 [測試] 前綴)
Category分類名稱
HasDemo是否支援試玩
RTP回報率

前端使用重點:

  • 統一從 result.games 取遊戲列表,不需判斷遊戲商格式差異
  • fallback=true 時可在 UI 顯示「測試模式」提示
  • BetSolutions 的 GameId 即為 /game/launch/game/simulateproductId
  • 測試遊戲名稱帶 [測試] 前綴,方便辨識

錯誤碼:5001 查無此遊戲商


遊戲商回調端點 (Callback — 遊戲商 Server-to-Server)

以下端點由遊戲商主動呼叫,前端不需要觸發,僅供後端對接用。 每次回調會同步寫入:game-transaction(錢包帳本)+ bet-order(投注訂單)+ bet-detail(注單明細)。 投注結算後自動觸發 VIP 等級重算 + 優惠打碼量累計。

端點說明
POST /game/rsg/GetBalanceRSG 查詢玩家餘額
POST /game/rsg/BetRSG 下注扣款 → 建立 bet-order + bet-detail
POST /game/rsg/BetResultRSG 結算派彩 → 更新 bet-order winLose → 觸發 VIP + 打碼量
POST /game/rsg/CancelBetRSG 取消注單退款
POST /game/rsg/JackpotResultRSG 彩金派獎
POST /game/betsolutions/authBS Token 交換
POST /game/betsolutions/getBalanceBS 查詢餘額
POST /game/betsolutions/betBS 下注扣款 → 建立 bet-order + bet-detail → 觸發 VIP + 打碼量
POST /game/betsolutions/winBS 派彩 → 更新 bet-order winLose
POST /game/betsolutions/cancelBetBS 取消注單
POST /game/betsolutions/getPlayerInfoBS 查詢玩家資料

遊戲模組前端整合流程

┌─────────────────────────────────────────────────────────────┐
│                    遊戲大廳頁面                               │
│                                                             │
│  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              │
└──────────────────────────────────────────┘

前端點擊「開始遊戲」按鈕:

  1. 呼叫 POST /game/simulate
  2. result 播放對應動畫/音效
  3. balanceAfter 更新 header 餘額
  4. 將結果 push 到最近紀錄列表

Common — 共用

GET /common/enums — 取得共用列舉 (依 locales header 回傳對應語系)

json
// 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 的文字會依 locales header 動態從 i18n 取得,切換語系即回傳對應語言。 key 格式與錯誤回應的 path 欄位完全一致(含 /api 前綴)。

前端錯誤碼使用方式:

typescript
// 錯誤回應中有 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)
json
// 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 — 取得銀行卡列表

json
// 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 — 新增信用卡

json
// 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 — 取得信用卡列表

json
// 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 — 新增加密貨幣錢包地址

json
// 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 — 取得加密貨幣錢包列表

json
// 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 — 刪除加密貨幣錢包地址

json
// Response result
null

錯誤碼:2001 查無此錢包地址


Vendor — 金流通道 🔒

GET /vendor/channels — 取得用戶金流通道列表

json
// 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 }
  ]
}

groupNamename 為多語系欄位,DB 儲存 JSON {"zh-TW":"...","en-US":"...","zh-CN":"..."},API 依 locales header 自動解析回傳對應語言文字。 crypto 通道會額外回傳 network 欄位(區塊鏈網路,e.g. TRC20, ERC20)。 錯誤碼:2001 用戶未分配金流群組 / 2002 金流群組不存在或已停用 / 2003 金流通道不存在或不屬於此群組


Vendor - Wantong — 萬通金流

POST /vendor/wantong/add-atm — 萬通 ATM 建單 🔒

json
// 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 訂單失敗 / 2003 ATM 訂單請求異常

POST /vendor/wantong/add-card — 萬通信用卡建單 🔒

json
// 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)

json
// 由萬通 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)

json
// 由萬通 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)

json
// 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")
json
// 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 — 取得金流通道 (含匯率) 🔒

json
// 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 — 建立存款訂單 🔒

json
// 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: 不需額外欄位
json
// 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
json
// 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 titlecontent 為多語系欄位,DB 儲存 JSON {"zh-TW":"...","en-US":"...","zh-CN":"..."},API 讀取時依 Accept-Language / locales header 自動回傳對應語系文字

GET /promo — 取得活動列表

Query Params (all optional):
  page: number       — 頁碼 (預設 1)
  pageSize: number   — 每頁筆數 (預設 10,最大 50)
  tag: string        — 活動標籤篩選 (e.g. "新手")
  activeOnly: string — 僅顯示進行中 ("1"=是)
json
// 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
json
// 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
json
// Response result (同時刪除 R2 上的活動圖片)
null

錯誤碼:2001 查無此活動

POST /promo/:id/claim — 領取活動獎勵 🔒

Path Param: id (number) — 活動 ID
json
// 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
json
// 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 等級列表

json
// 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 為多語系欄位,依 locales header 自動解析回傳對應語言文字。 minChip = 升級所需累計有效投注流水 (USD) relegationChip = 每月保級所需有效投注 (USD)

GET /vip/rebates — 取得返水規則

json
// 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 狀態 🔒

json
// 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 等級 🔒

json
// 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
json
// Response result
null

錯誤碼:2001 查無此 VIP 等級

POST /vip/rebates — 建立返水規則 🔒

json
// 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
json
// Response result
null

錯誤碼:2002 查無此返水規則

POST /vip/settlement/daily-rebate — 手動觸發每日反水結算 🔒

json
// Response result
{
  "usersProcessed": 25,
  "totalRebate": "3.120000"
}

結算昨日各用戶的反水金額並發放到餘額。自動排程:每日 00:05。

POST /vip/settlement/monthly-relegation — 手動觸發月度保級檢查 🔒

json
// Response result
{
  "checked": 10,
  "warned": 2,
  "demoted": 1
}

檢查 VIP 2+ 用戶上月有效投注是否達保級門檻。連續 1 月未達→警告,連續 2 月→降 1 級。 自動排程:每月 1 號 01:00。

PATCH /vip/users/:userId/hold — 設定 VIP 保級鎖定 🔒

json
// Request Body
{ "hold": 1 }
// Response result
{ "userId": 1, "vipHold": 1 }

hold: 1=鎖定(跳過保級檢查), 0=解鎖 錯誤碼:2001 用戶不存在 / 2002 VIP 等級未達 5


Ranking — 排行榜

公開端點,不需登入

GET /ranking — 取得排行榜

Query Params (all optional):
  type: string   — realtime | daily | weekly | monthly | total(預設 realtime)
  limit: number  — 筆數(預設 20,最大 50)
json
// 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
json
// 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 — 訂單小注單明細

json
// 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 — 記錄推廣連結點擊

前端用戶透過推廣連結進入時呼叫,記錄點擊追蹤資料。

json
// Request Body
{
  "refCode": "AGENT001"
}

// Response result
{ "tracked": true }

錯誤碼:2001 推廣碼不存在


GET /affiliate/dashboard — 代理儀表板 🔒

取得代理的總覽資料:下線人數、佣金統計、餘額等。

json
// 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 您不是代理身份


json
// 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)
json
// 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)
json
// 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)
json
// 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)
json
// 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 — 結算詳情 (含佣金明細) 🔒

json
// Response result (結算資訊 + 該週所有佣金明細)
{
  "settlement": { /* 同上 */ },
  "commissions": [
    {
      "memberId": 10,
      "memberAccount": "user010",
      "agentLevel": 1,
      "netLoss": "100.000000",
      "commissionRate": "30.00",
      "commissionAmount": "30.000000"
    }
  ]
}

錯誤碼:2001 結算紀錄不存在


GET /affiliate/balance — 佣金餘額 🔒

json
// 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 — 每頁筆數
json
// 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 — 發起提款 🔒

json
// 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] 建立代理 🔒

json
// 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] 審核結算 🔒

json
// Request Body
{
  "action": "approved",
  "remark": "已確認無異常"
}

action: "approved" | "rejected" approved 後佣金自動入帳到代理的 AffiliateBalance.available 錯誤碼:2001 結算紀錄不存在 / 2002 該結算已審核完成


GET /affiliate/admin/settlements/:id/risk-logs — [Admin] 結算風控紀錄 🔒

json
// 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] 審核提款 🔒

json
// Request Body
{
  "action": "approved",
  "remark": "確認出款"
}

action: "approved" | "rejected" rejected → frozen 退回 available 錯誤碼:2001 提款紀錄不存在 / 2002 該提款已審核完成


POST /affiliate/admin/withdrawals/:id/complete — [Admin] 確認出款完成 🔒

json
// Response result
{ "id": 1, "status": "completed" }

確認實際出款完成後呼叫,frozen 轉為 totalWithdrawn 錯誤碼:2001 提款紀錄不存在 / 2002 狀態須為已審核通過


POST /affiliate/admin/bind — [Admin] 人工綁定/解綁/轉移 🔒

json
// 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 篩選
json
// 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] 手動觸發週結算 (測試用) 🔒

手動觸發上週的佣金結算流程,用於測試或補結算。

json
// Response result (結算結果摘要)
{
  "settlementsCreated": 5,
  "totalCommission": "3500.000000",
  "riskFlagged": 1
}


GET /affiliate/alliance-info — 聯盟計劃公開資訊

取得聯盟計劃的公開說明資訊(代理階層制度、佣金比例、VIP 里程碑獎勵等),無需登入。

json
// 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 個)。

json
// 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 英數字)。

json
// 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 — 刪除推廣碼 🔒

刪除指定推廣碼。

json
// Response result
{ "success": true }

錯誤碼:2001 推廣碼不存在或無權限


GET /affiliate/vip-milestones — VIP 里程碑進度 🔒

取得代理的 VIP 里程碑獎勵發放紀錄(哪些下線會員已達哪個 VIP 等級並已發放獎金)。

json
// 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 — 代理階層資訊 🔒

取得當前代理的階層資訊及下一階層升級進度。

json
// 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] 佣金比例列表 🔒

取得所有佣金比例設定(依代理階層、層級、遊戲類型)。

json
// 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)。

json
// 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 }

錯誤碼:2001 agentTier 不存在 / 2002 agentLevel 須為 1~3


DELETE /affiliate/admin/commission-rates/:id — [Admin] 刪除佣金比例 🔒

json
// Response result
{ "success": true }

錯誤碼:2001 佣金比例設定不存在


GET /affiliate/admin/vip-milestones — [Admin] VIP 里程碑列表 🔒

取得所有 VIP 里程碑獎勵設定。

json
// 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)。

json
// 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 }

錯誤碼:2001 vipLevel 須為 2~15


DELETE /affiliate/admin/vip-milestones/:id — [Admin] 刪除里程碑 🔒

json
// Response result
{ "success": true }

錯誤碼:2001 里程碑設定不存在


GET /affiliate/admin/agent-tiers — [Admin] 代理階層列表 🔒

取得所有代理階層設定。

json
// Response result
{
  "items": [
    {
      "id": 1,
      "tierCode": "bronze",
      "tierName": "青銅",
      "minTotalEarned": "0.000000",
      "minActiveMembers": 0,
      "sortOrder": 1
    }
  ]
}

POST /affiliate/admin/agent-tiers — [Admin] 新增/更新階層 🔒

新增或更新代理階層設定(依 tierCode 唯一索引做 upsert)。

json
// 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] 刪除階層 🔒

json
// Response result
{ "success": true }

錯誤碼:2001 階層設定不存在 / 2002 無法刪除有代理使用的階層


POST /affiliate/admin/set-agent-tier — [Admin] 設定代理階層 🔒

手動設定指定代理的階層(admin 強制指定,不走自動升級邏輯)。

json
// Request Body
{
  "agentId": 5,
  "tierCode": "gold"
}

// Response result
{ "agentId": 5, "tierCode": "gold" }

錯誤碼:2001 代理不存在 / 2002 階層代碼不存在


POST /affiliate/admin/trigger-daily-settlement — [Admin] 手動觸發日結算 🔒

手動觸發前一日的佣金日結算流程,用於測試或補結算。

json
// 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型態說明
pagenumber (opt)頁碼 (預設 1)
pageSizenumber (opt)每頁筆數 (預設 10,最大 50)
categorystring (opt)分類篩選:system / promo

Response

json
{
  "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

json
{ "code": 200, "message": "ok", "result": { "unreadCount": 5 } }

POST /inbox/:id/read 🔒 標記單則通知為已讀

錯誤碼說明
2001查無此通知

POST /inbox/read-all 🔒 全部標記為已讀


POST /inbox/admin/send 🔒 [Admin] 發送通知

Body型態說明
userIdnumber (opt)目標用戶 ID (不傳=全站通知)
titleobject多語系標題 {"zh-TW":"...","en-US":"...","zh-CN":"..."}
contentobject多語系 HTML 內容
categorystringsystem / promo

Response

json
{
  "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型態說明
pagenumber (opt)頁碼
pageSizenumber (opt)每頁筆數
categorystring (opt)分類篩選

Admin 列表回傳原始多語系 JSON(不做 resolveText)


DELETE /inbox/admin/:id 🔒 [Admin] 刪除通知

刪除時會一併清除該通知的所有已讀紀錄。

錯誤碼說明
2001查無此通知

SiteConfig — 站點設定

站點設定模組,支援多站點架構,透過 SITE_CODE 環境變數區分站點。 每個站點可設定多組主題色,透過 activeThemeId 指定當前使用的主題。


GET /site-config — 取得當前站點設定

公開 API,不需登入。根據環境變數 SITE_CODE(預設 C9)回傳當前站點設定,包含當前選中主題的完整色號。

Response:

json
{
  "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] 更新站點設定

欄位型態必填說明
siteNameobject{"zh-TW":"...","en-US":"...","zh-CN":"..."}
siteDescriptionobject{"zh-TW":"...","en-US":"...","zh-CN":"..."}
supportedLocalesstring[]["zh-TW","en-US","zh-CN"]
activeThemeIdnumber當前使用的主題 ID (site-theme PK)
enablednumber0=關 1=開
錯誤碼說明
2001查無此站點設定

GET /site-config/admin/:siteConfigId/themes 🔒 [Admin] 主題列表

回傳指定站點的所有主題(原始 JSON)。


POST /site-config/admin/:siteConfigId/themes 🔒 [Admin] 新增主題

欄位型態必填說明
themeIdstring主題識別碼 (e.g. default-emerald)
themeNameobject{"zh-TW":"翡翠綠","en-US":"Emerald","zh-CN":"翡翠绿"}
primaryobject{base, dark, light, glow}
accentobject{gold, info, violet, cyan, error}
surfaceobject{page, navbar, card, modal, sidebar}
textobject{primary, secondary, muted, hint}
borderobject{subtle, default, strong}
enablednumber0=關 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] 更新吉祥物列表

全量替換該站點的吉祥物頭像列表。

json
// 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:

欄位型態必填說明
amountnumber提領金額 (USD)
cryptoAddressIdnumber加密錢包 ID (status=1)
verifyCodestring郵箱驗證碼 (6 碼)
錯誤碼說明
2001驗證碼未發送
2002驗證碼錯誤
2003用戶信箱未驗證
2004用戶手機未驗證
2005該錢包不存在或未通過審核
2006提領金額需大於 0
2007餘額不足
2008優惠打碼量未完成
2009存款打碼量不足

打碼量檢查規則:

提領前系統自動檢查兩項打碼量條件,任一未通過即拒絕提領:

  1. 優惠打碼量:所有已領取優惠的打碼量需全部完成(turnoverCompleted = 1)才能提領
  2. 存款打碼量:累計有效投注(totalEffectiveBet)>= 累計入帳存款(sum of paid deposits usdAmount)× 1x 倍數

打碼量計算依遊戲類型有不同權重:CRYPTO / FISH = 50%,其他遊戲 = 100%(詳見「遊戲類型與流水權重」章節)


GET /withdrawal/list 🔒 用戶提領紀錄

Query Parameters:

參數型態必填說明
pagenumber頁碼 (預設 1)
pageSizenumber每頁筆數 (預設 10,最大 50)
statusstring篩選狀態:pending / approved / rejected / completed
startDatestring起始日期 (YYYY-MM-DD)
endDatestring結束日期 (YYYY-MM-DD)

GET /withdrawal/turnover-status 🔒 查詢打碼量狀態

查詢當前用戶的存款打碼量與優惠打碼量完成進度。前端可依據 canWithdraw 控制提領按鈕狀態。

Response:

json
{
  "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:

參數型態必填說明
pagenumber頁碼 (預設 1)
pageSizenumber每頁筆數 (預設 10,最大 50)
statusstring篩選狀態:pending / approved / rejected / completed
startDatestring起始日期 (YYYY-MM-DD)
endDatestring結束日期 (YYYY-MM-DD)

POST /withdrawal/admin/:id/review 🔒 [Admin] 審核提領

Request Body:

欄位型態必填說明
actionstringapprove / reject
rejectReasonstring拒絕原因 (action=reject 時)
錯誤碼說明
2001查無此提領單
2002該提領單狀態不允許審核

POST /withdrawal/admin/:id/complete 🔒 [Admin] 確認出款完成

標記已匯款完成,凍結金額出帳。

錯誤碼說明
2001查無此提領單
2002該提領單狀態不允許完成

LiveSports — 即時體育

公開端點,不需登入

GET /live-sports — 取得即時體育賽事 Banner

json
// 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。

json
// 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 <= 0userVipLevel >= vipRequiredisClaimable: !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)
json
// 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 🔒 — 領取任務獎勵

json
// Response result
{
  "rewardAmount": "0.300000",
  "requiredTurnover": "0.900000",
  "newBalance": "100.300000"
}
錯誤碼說明
3001查無此任務
3002此任務本期已領取過
3003VIP 等級不足
3004尚未達成任務目標

Admin — 後台管理

管理員帳號獨立於前台用戶,使用 admin-jwt 策略驗證。 AdminJWT = 需要管理員 JWT Token (Authorization: Bearer <admin-token>)

POST /admin/login — 管理員登入

Body:

json
{
  "account": "admin001",
  "password": "admin1234"
}

Response (200):

json
{
  "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):

json
{
  "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):

json
[
  {
    "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:

json
{
  "account": "admin002",
  "password": "pass1234",
  "name": "客服管理員",
  "groupId": 2
}

Response (200):

json
{
  "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 (皆為選填):

json
{
  "name": "新名稱",
  "password": "newpass123",
  "groupId": 2,
  "status": 0
}
錯誤碼說明
2001管理員不存在

DELETE /admin/:id — 刪除管理員 🔒 AdminJWT

Params: id — 管理員 ID

錯誤碼說明
2001管理員不存在
2002不可刪除自己

GET /admin/groups/list — 管理員群組列表 🔒 AdminJWT

Response (200):

json
[
  {
    "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:

json
{
  "name": "客服群組",
  "permissions": ["user:read", "inbox:read", "inbox:write"],
  "description": "客服人員專用"
}

PATCH /admin/groups/:id — 更新群組 🔒 AdminJWT

Params: id — 群組 ID

Body (皆為選填):

json
{
  "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 參數 (皆為選填):

參數類型說明
adminIdnumber篩選管理員
modulestring篩選模組 (admin / admin-group / deposit / ...)
actionstring篩選動作 (login / create / update / delete)
startDatestring開始日期 (YYYY-MM-DD)
endDatestring結束日期 (YYYY-MM-DD)
pagenumber頁碼 (預設 1)
pageSizenumber每頁筆數 (預設 20)

Response (200):

json
{
  "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體育sports1.0 (100%)betEffective = betAmount × 1.0
2電子slot1.0 (100%)betEffective = betAmount × 1.0
3真人live1.0 (100%)betEffective = betAmount × 1.0
4彩票lottery1.0 (100%)betEffective = betAmount × 1.0
5棋牌chess1.0 (100%)betEffective = betAmount × 1.0
8電競esports1.0 (100%)betEffective = betAmount × 1.0
9加密貨幣crypto0.5 (50%)betEffective = betAmount × 0.5
10捕魚fish0.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 HoldVIP 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/betBS 下注回調遊戲商 Server-to-Server
POST /game/rsg/BetResultRSG 結算回調遊戲商 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.gameTypevip-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說明
Authregister2001帳號已存在
Authregister2002推廣碼不存在
Authlogin2001帳號或密碼錯誤
AuthcheckVerifyEmail2001尚未發送驗證碼
AuthcheckVerifyEmail2002驗證碼錯誤
AuthenableGoogleAuth2001尚未產生 Google Auth Secret
AuthenableGoogleAuth2002TOTP 驗證碼錯誤
AutheditPassword2002原密碼錯誤 / 新密碼不一致
AuthupdateLocale2001不支援的語系
AuthgetLoginConfig2001Google OAuth 未設定
AuthloginGoogle2001~2009Google 登入各階段錯誤
VendorgetChannels2001用戶未分配金流群組
VendorgetChannels2002金流群組不存在或已停用
VendorgetChannels2003金流通道不存在或不屬於此群組
WantonggetChannel2001查無可用的萬通金流通道
WantongaddAtm2002建立 ATM 訂單失敗
WantongaddAtm2003ATM 訂單請求異常
WantongaddCard2002建立信用卡訂單失敗
WantongaddCard2003信用卡訂單請求異常
USDTdeposit2001查無可用的 USDT 金流通道
USDTdeposit2002該通道尚未設定繳費地址
DepositgetExchangeRate2001幣別格式錯誤
DepositgetExchangeRate2002匯率解析失敗
Depositdeposit2004不支援的支付方式
Depositdeposit2005匯率轉換失敗 (上分時)
Depositdeposit2006金流通道尚未建置完成
BankCardaddBankCard2001缺少身分證正面照片
BankCardaddBankCard2002缺少身分證背面照片
BankCardaddBankCard2003缺少存摺封面照片
BankCardaddBankCard2004此銀行卡已綁定
BankCarddeleteBankCard2001銀行卡不存在
CreditCardaddCreditCard2001此信用卡已綁定
CreditCarddeleteCreditCard2001信用卡不存在
CryptoAddressaddCryptoAddress2001此錢包地址已綁定
CryptoAddressdeleteCryptoAddress2001查無此錢包地址
PromogetPromoById2001查無此活動
PromoupdatePromo2001查無此活動
PromodeletePromo2001查無此活動
PromoclaimPromo2001此活動已領取過
PromoclaimPromo2002此活動領取名額已滿
PromoclaimPromo2003尚未滿足領取條件
VIPcreateLevel2003此 VIP 等級已存在
VIPupdateLevel2001查無此 VIP 等級
VIPdeleteLevel2001查無此 VIP 等級
VIPcreateRebate2004此等級的遊戲類型返水規則已存在
VIPupdateRebate2002查無此返水規則
VIPdeleteRebate2002查無此返水規則
VIPsetVipHold2001用戶不存在
VIPsetVipHold2002VIP 等級未達 5
Gamelaunch5001查無此遊戲商
Gamelaunch5002遊戲商已停用
Gamelaunch5003遊戲商維護中
Gamelaunch5004遊戲商 API 回傳錯誤
Gamelaunch5005未設定遊戲商 API
Gamedemo5001查無此遊戲商
Gamedemo5006該遊戲商不支援試玩模式
Gamesimulate5001查無此遊戲商
Gamesimulate5010投注金額需在 0.01 ~ 10000 之間
Gamesimulate5011餘額不足
Gamesimulate2001用戶不存在
Gamelist5001查無此遊戲商
BetRecordgetOrderDetails2001訂單不存在或不屬於該用戶
AffiliatetrackClick2001推廣碼不存在
AffiliategetDashboard2001您不是代理身份
AffiliategetDownline2001您不是代理身份
AffiliategetSettlementDetail2001結算紀錄不存在
AffiliaterequestWithdrawal2001提款金額須大於零
AffiliaterequestWithdrawal2002提款方式無效
AffiliaterequestWithdrawal2003冷卻中
AffiliaterequestWithdrawal2004餘額不足
AffiliaterequestWithdrawal2005非代理
AffiliatecreateAgent2001用戶不存在
AffiliatecreateAgent2002推廣碼已被使用
AffiliatecreateAgent2003已是代理
AffiliatereviewSettlement2001紀錄不存在
AffiliatereviewSettlement2002已審核
AffiliatereviewWithdrawal2001紀錄不存在
AffiliatereviewWithdrawal2002已審核
AffiliatecompleteWithdrawal2001紀錄不存在
AffiliatecompleteWithdrawal2002狀態須為 approved
AffiliateadminBind2001會員不存在
AffiliateadminBind2002代理不存在
InboxmarkAsRead2001查無此通知
InboxadminDelete2001查無此通知
SiteConfiggetPublicConfig2001查無此站點設定
SiteConfigadminUpdate2001查無此站點設定
SiteConfigadminCreateTheme2001查無此站點設定
SiteConfigadminUpdateTheme2002查無此主題
SiteConfigadminDeleteTheme2002查無此主題
WithdrawalsendCode2001用戶信箱未驗證
WithdrawalsendCode2002用戶手機未驗證
Withdrawalrequest2001驗證碼未發送
Withdrawalrequest2002驗證碼錯誤
Withdrawalrequest2003用戶信箱未驗證
Withdrawalrequest2004用戶手機未驗證
Withdrawalrequest2005該錢包不存在或未通過審核
Withdrawalrequest2006提領金額需大於 0
Withdrawalrequest2007餘額不足
Withdrawalrequest2008優惠打碼量未完成
Withdrawalrequest2009存款打碼量不足
Withdrawalreview2001查無此提領單
Withdrawalreview2002該提領單狀態不允許審核
Withdrawalcomplete2001查無此提領單
Withdrawalcomplete2002該提領單狀態不允許完成
AuthloginTelegram2001尚未設置 TELEGRAM_BOT_TOKEN
AuthloginTelegram2002Telegram 驗證失敗
AuthloginTelegram2003本次操作時效已過期, 請重新登入
AuthupdateAvatar2001無效的頭像 ID
MissionclaimMission3001查無此任務
MissionclaimMission3002此任務本期已領取過
MissionclaimMission3003VIP 等級不足
MissionclaimMission3004尚未達成任務目標

🔒 標記說明

帶有 🔒 的端點需要在 Header 傳入 Authorization: Bearer <token>,token 由 /auth/login/auth/register/auth/login-google/auth/login-telegram 取得。


端點速查表

MethodPathAuth說明
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/GetBalanceS2S[RSG] 查詢餘額
POST/game/rsg/BetS2S[RSG] 下注扣款
POST/game/rsg/BetResultS2S[RSG] 結算派彩
POST/game/rsg/CancelBetS2S[RSG] 取消注單
POST/game/rsg/JackpotResultS2S[RSG] Jackpot 派彩
POST/game/betsolutions/authS2S[BetSolutions] 驗證 token
POST/game/betsolutions/getBalanceS2S[BetSolutions] 查詢餘額
POST/game/betsolutions/betS2S[BetSolutions] 下注扣款
POST/game/betsolutions/winS2S[BetSolutions] 派彩
POST/game/betsolutions/cancelBetS2S[BetSolutions] 取消注單
POST/game/betsolutions/getPlayerInfoS2S[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/addS2S萬通建單回調
POST/vendor/wantong/callback/payS2S萬通銷案回調
POST/vendor/usdt/callback/payS2SUSDT 付款確認回調
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)