C9 Platform — API 完整文件
本文件涵蓋 C9 娛樂城後端 (c9-be) 所有 API 端點的完整說明,供前端工程師、產品經理、UI 設計師參考。
Base URL:
http://localhost:8080/apiSwagger UI:http://localhost:8080/api/docs最後更新: 2026-03-01
目錄
- 系統概述
- 統一規範
- Auth 認證模組
- Common 公用模組
- App Health Check
- Game 遊戲模組
- VIP 會員模組
- Affiliate 代理推廣模組
- Deposit 存款模組
- Withdrawal 提領模組
- Wallet 錢包模組
- Vendor 金流商模組
- Promo 活動模組
- Inbox 站內信模組
- SiteConfig 站點配置模組
- BetRecord 投注紀錄模組
- Ranking 排行榜模組
- Mission 任務模組
- LiveSports 即時賽事模組
- Admin 後台管理模組
- S2S 伺服器回調
系統概述
技術棧
- 框架: NestJS v11 (TypeScript 5.7 strict)
- 資料庫: MySQL (utf8mb4, 時區 +08:00)
- ORM: TypeORM 0.3.28
- 快取: Redis (@keyv/redis)
- 認證: JWT + Passport(前台)/ AdminJWT(後台)
- API 文件: Swagger UI(
/api/docs)
多站點 (Multi-Tenant) 架構
C9 採用白牌架構,單一後端服務支援多個站點(品牌)。每個站點透過 siteCode 區分(例如 C9、A1)。
- 前台請求: 透過
site-nameHTTP header 識別站點 - 後台請求: 透過
x-site-codeHTTP header 或 query parameter 識別站點 - 資料隔離: 大部分資料表都有
siteCode欄位,確保各站點資料獨立
統一規範
回應格式
所有 API 回應統一使用以下 JSON 格式:
成功回應
{
"code": 200,
"message": "ok",
"result": { ... },
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/login"
}業務錯誤回應
HTTP 狀態碼仍為 200,但 code 不等於 200:
{
"code": 1001,
"message": "帳號或密碼錯誤",
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/login"
}未授權回應
HTTP 狀態碼 401:
{
"code": 401,
"message": "Unauthorized"
}認證方式
| 角色 | 方式 | Header | 有效期 |
|---|---|---|---|
| 前台用戶 | JWT Bearer Token | Authorization: Bearer <token> | 7 天 |
| 後台管理員 | AdminJWT | Authorization: Bearer <admin-token> | 依設定 |
通用 Headers
| Header | 必填 | 說明 |
|---|---|---|
Authorization | 需認證的端點 | Bearer <JWT Token> |
locales | 否 | 語系(zh-TW / en-US / zh-CN / th-TH / vi-VN),預設 zh-TW |
site-name | 前台請求 | 站點名稱,用於識別白牌站點 |
x-site-code | 後台請求 | 站點代碼,用於多站點篩選 |
金額精度
| 用途 | 型別 | 精度 | 說明 |
|---|---|---|---|
| 金額 | decimal(18,6) | 小數 6 位 | 所有金額統一使用 USD |
| 匯率 | decimal(18,10) | 小數 10 位 | 台幣/各幣種 -> USD 轉換用 |
| 百分比 | decimal(5,2) | 小數 2 位 | 返水比例等 |
| 倍率 | decimal(10,2) | 小數 2 位 | 遊戲倍率 |
語系與幣別對照
| 語系代碼 | 語系名稱 | 對應幣別 |
|---|---|---|
zh-TW | 繁體中文 | TWD (新台幣) |
en-US | English | USD (美元) |
zh-CN | 简体中文 | CNY (人民幣) |
th-TH | ภาษาไทย | THB (泰銖) |
vi-VN | Tiếng Việt | VND (越南盾) |
錯誤碼查表
前端不硬寫錯誤訊息,統一透過 GET /api/common/enums 取得 ERROR_CODES 物件,再依 path 和 code 查表顯示對應語系的錯誤文字。
// 前端錯誤處理範例
const errorCodes = enumStore.ERROR_CODES;
const path = '/api/auth/login';
const code = 2001;
const message = errorCodes[path]?.[code]; // "帳號或密碼錯誤"Auth 認證模組
路由前綴:
/api/authSwagger 標籤:Auth端點數量: 21 個 功能概述: 用戶註冊、登入(帳密/Google/Telegram)、登出、個人資料管理、Email/手機驗證、Google Authenticator 2FA、密碼管理、頭像切換、三方帳號綁定/解綁
認證模式說明
Auth 模組支援三種登入方式,每種方式都會回傳 JWT Token:
- 帳號密碼登入 — 傳統帳號 + 密碼登入
- Google OAuth 2.0 登入 — 透過 Authorization Code + PKCE 流程
- Telegram Login Widget 登入 — 透過 Telegram 官方 Widget 取得資料 + HMAC 驗簽
三方登入(Google / Telegram)的特殊行為:
- 新用戶: 自動建立帳號(account 格式為
google_{sub}或tg_{telegramId})、分配預設頭像、分配金流群組、發送歡迎站內信 - 已存在用戶: 直接登入,若 Google/Telegram 欄位為空則自動補綁
- 密碼欄位: 三方登入用戶的
password為空字串,可透過POST /auth/set-password首次設定
Token 機制
- JWT payload 包含
{ sub: userId, account, tokenVersion } tokenVersion每次登入遞增 +1,用於使舊 Token 失效- 登出時
tokenVersion也會 +1,確保 Token 即時失效 - Redis 快取 tokenVersion(cache key:
cache:auth:tv:{userId}),加速驗證
裝置指紋
- 登入和註冊 DTO 支援選填的
device欄位 - 前端應使用 FingerprintJS 產生唯一裝置指紋
- 後端優先使用客戶端傳入的
device,若無則 fallback 至User-Agent - 裝置指紋記錄於
auth-user-login-log表,供後台風控查詢
3.1 POST /api/auth/register — 用戶註冊
功能說明
用戶註冊帳號。註冊成功後,系統自動執行以下操作:
- 帳號唯一性檢查: 同一站點 (siteCode) 內帳號不可重複
- 推廣碼驗證 (選填): 若提供
refCode,驗證對應代理是否存在 - 密碼加密: 使用 bcryptjs 加密(saltRounds = 10)
- 站點歸屬: 根據
site-nameheader 判定用戶歸屬的站點 - 語系設定: 根據
localesheader 設定用戶預設語系 - 預設頭像指派: 自動指派吉祥物列表中的第一個頭像
- 金流群組分配: 根據語系對應的幣別,自動分配啟用中的金流群組
- 代理綁定 (選填): 若提供推廣碼,執行代理上下線綁定(best-effort,不阻擋註冊流程)
- 歡迎站內信: 發送多語系歡迎站內信(受信件設定控制,best-effort)
- JWT Token 簽發: 回傳 Token 供前端即時登入
認證需求
無需認證(公開端點)
Request
Headers:
| Header | 必填 | 說明 |
|---|---|---|
locales | 否 | 語系代碼,影響用戶預設語系與金流群組分配。預設 zh-TW |
site-name | 否 | 站點名稱,決定用戶所屬站點。未傳則使用環境變數 SITE_CODE(預設 C9) |
Body (JSON):
| 欄位 | 型別 | 必填 | 驗證規則 | 說明 |
|---|---|---|---|---|
account | string | 是 | @IsString() | 帳號,同站點內必須唯一 |
password | string | 是 | @IsString(), @MinLength(6) | 密碼,至少 6 字元 |
name | string | 是 | @IsString() | 暱稱/顯示名稱 |
refCode | string | 否 | @IsString() | 推廣碼(代理推廣碼),填入後綁定上線代理 |
device | string | 否 | @IsString() | 裝置指紋,由前端 FingerprintJS 產生 |
Body 範例:
{
"account": "user001",
"password": "pass1234",
"name": "John",
"refCode": "AGT123",
"device": "fp_abc123xyz"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsImFjY291bnQiOiJ1c2VyMDAxIiwidG9rZW5WZXJzaW9uIjowLCJpYXQiOjE3MDk5MDg4MDB9...",
"user": {
"id": 1,
"name": "John"
},
"vendorGroupId": 1
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/register"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result.token | string | JWT Token(7 天有效),前端儲存後作為後續請求的 Bearer Token |
result.user.id | number | 用戶 ID |
result.user.name | string | 用戶暱稱 |
result.vendorGroupId | number | null | 自動分配的金流群組 ID,null 表示無匹配的金流群組 |
錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 帳號已存在 | 該帳號在同站點內已被註冊 |
2002 | 推廣碼不存在 | 提供的 refCode 找不到對應的代理帳號 |
業務邏輯說明
- 帳號唯一性是基於
(siteCode, account)的複合唯一索引,不同站點可以有相同帳號 - 金流群組分配邏輯:語系 -> 幣別 -> 尋找啟用中且幣別匹配的金流群組(取最小 ID)
- 推廣碼綁定為 best-effort,即使綁定失敗也不影響註冊流程
- 歡迎站內信受後台「信件設定」中的
welcomeRegistration開關控制 - tokenVersion 初始值為 0
前端注意事項
- 註冊成功後直接取得 Token,無需再呼叫登入 API
- 應將
token存入 localStorage 或 cookie,並設定到 HTTP client 的Authorizationheader vendorGroupId回傳null時,可提示用戶聯繫客服或先跳過金流設定- 密碼長度限制前端應同步驗證(至少 6 字元)
UI 設計提示
- 註冊表單欄位:帳號、密碼、確認密碼、暱稱、推廣碼(選填,可預填 URL 參數)
- 推廣碼輸入框可設計為「有推薦人?輸入推廣碼」的可展開區域
- 密碼欄位建議加入顯示/隱藏切換按鈕
- 帳號欄位可加入即時查重(debounce 500ms,呼叫另外的接口或等待提交後顯示錯誤)
3.2 POST /api/auth/login — 用戶登入
功能說明
帳號密碼登入。登入成功後,系統在同一個資料庫交易 (Transaction) 內執行以下操作:
- 帳號查找: 查詢用戶是否存在
- 密碼比對: 使用 bcrypt 比較密碼雜湊
- Token 版本遞增:
tokenVersion + 1,使所有舊 Token 失效(實現「新登入踢舊裝置」效果) - 舊登入紀錄更新: 將上一筆 LOGIN 紀錄改為 LOGOUT
- 新登入紀錄寫入: 記錄 IP、裝置指紋、登入時間
- Redis 快取清除: 刪除
cache:auth:tv:{userId},確保新 tokenVersion 生效 - JWT Token 簽發: 回傳新 Token
登入失敗(帳號不存在或密碼錯誤)也會記錄到 auth-user-login-log 表,action 為 LOGIN_FAIL,方便後台風控分析。
認證需求
無需認證(公開端點)
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 驗證規則 | 說明 |
|---|---|---|---|---|
account | string | 是 | @IsString() | 帳號 |
password | string | 是 | @IsString(), @MinLength(6) | 密碼,至少 6 字元 |
device | string | 否 | @IsString() | 裝置指紋,由前端 FingerprintJS 產生 |
Body 範例:
{
"account": "user001",
"password": "pass1234",
"device": "fp_abc123xyz"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"account": "user001",
"name": "John",
"email": null,
"mobile": null,
"telegram": null,
"google": null,
"vipLevel": "1",
"vipProgress": "0",
"totalEffectiveBet": "0.000000",
"balance": "0.000000",
"frozenBalance": "0.000000",
"locale": "zh-TW",
"avatar": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-dragon.png",
"vendorGroupId": 1,
"agentCode": null,
"level1AgentId": null,
"googleAuthEnabled": 0,
"lastActivityAt": null,
"siteCode": "C9",
"createdAt": "2026-02-22T00:00:00.000Z"
}
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/login"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result.token | string | JWT Token(7 天有效) |
result.user.id | number | 用戶 ID |
result.user.account | string | 帳號 |
result.user.name | string | 暱稱 |
result.user.email | string | null | Email(已驗證才有值) |
result.user.mobile | string | null | 手機號碼 E.164 格式(已驗證才有值) |
result.user.telegram | string | null | Telegram ID(已綁定才有值) |
result.user.google | string | null | Google sub ID(已綁定才有值) |
result.user.vipLevel | string | VIP 等級("1" 起始) |
result.user.vipProgress | string | null | VIP 升級進度 |
result.user.totalEffectiveBet | string | 累計有效投注流水 USD(decimal 回傳為字串) |
result.user.balance | string | USD 餘額(decimal(18,6) 回傳為字串,如 "100.000000") |
result.user.frozenBalance | string | 凍結中金額(提領審核中) |
result.user.locale | string | 語系偏好 |
result.user.avatar | string | null | 頭像 URL |
result.user.vendorGroupId | number | null | 金流群組 ID |
result.user.agentCode | string | null | 代理推廣碼(null 表示非代理) |
result.user.level1AgentId | number | null | 直屬上線代理 ID |
result.user.googleAuthEnabled | number | Google Authenticator 是否啟用(0=停用, 1=啟用) |
result.user.lastActivityAt | string | null | 最後活動時間 |
result.user.siteCode | string | 所屬站點代碼 |
result.user.createdAt | string | 帳號建立時間 |
注意:
password欄位已在 Service 層移除,不會回傳
錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 帳號或密碼錯誤 | 帳號不存在,或密碼不正確。為安全考量不區分「帳號不存在」和「密碼錯誤」 |
業務邏輯說明
- 登入成功後,所有舊裝置的 Token 會因 tokenVersion 遞增而失效(單點登入效果)
- 登入失敗會記錄到
auth-user-login-log,供後台「登入失敗紀錄」頁面查詢 - 帳號不存在時的失敗紀錄
userId為null,remark記錄為ACCOUNT_NOT_FOUND:{account} - 密碼錯誤時的失敗紀錄
userId為實際用戶 ID,remark記錄為WRONG_PASSWORD - 整個登入流程(tokenVersion 遞增 + 登入紀錄寫入)在同一個 DB transaction 中執行,確保原子性
前端注意事項
- 登入成功後將 Token 存入 localStorage 或 cookie
- 建議在應用啟動時檢查 Token 是否過期(可透過 JWT decode 檢查 exp)
- 收到 401 回應時清除 Token 並導向登入頁
balance和frozenBalance為字串型別(decimal),顯示時需格式化googleAuthEnabled為數字 0 或 1,不是布林值
UI 設計提示
- 登入表單:帳號、密碼、登入按鈕
- 支援「記住帳號」功能(localStorage 存帳號,非密碼)
- 錯誤提示統一顯示「帳號或密碼錯誤」,不透露帳號是否存在
- 下方提供「Google 登入」、「Telegram 登入」按鈕
- 新用戶引導至註冊頁面的連結
3.3 GET /api/auth/user-detail — 取得用戶資料
功能說明
取得當前登入用戶的完整個人資料。可選擇是否同時載入最近 20 筆登入紀錄。
此端點回傳的用戶資料包含一個額外欄位 hasPassword(布林值),表示用戶是否已設定密碼。三方登入用戶(Google/Telegram)初始未設定密碼,hasPassword 為 false。
若用戶未設定頭像,系統會自動回傳第一隻吉祥物的 URL 作為預設頭像。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Headers:
| Header | 必填 | 說明 |
|---|---|---|
Authorization | 是 | Bearer <JWT Token> |
Query Parameters:
| 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|
related | string | 否 | 帶入 "loginLogs" 可一併取得最近 20 筆登入紀錄 |
Request 範例:
GET /api/auth/user-detail?related=loginLogs
Authorization: Bearer eyJhbGci...Response
成功回應 (code: 200) — 不含登入紀錄:
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"account": "user001",
"name": "John",
"email": "user@example.com",
"mobile": "+886912345678",
"telegram": null,
"google": "google_sub_id_123",
"vipLevel": "3",
"vipProgress": "45",
"totalEffectiveBet": "15000.500000",
"balance": "1250.000000",
"frozenBalance": "100.000000",
"locale": "zh-TW",
"avatar": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-dragon.png",
"vendorGroupId": 1,
"agentCode": "AGT001",
"level1AgentId": null,
"googleAuthEnabled": 1,
"lastActivityAt": "2026-02-28T15:30:00.000Z",
"siteCode": "C9",
"createdAt": "2026-01-15T08:00:00.000Z",
"hasPassword": true
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/user-detail"
}成功回應 (code: 200) — 含登入紀錄 (?related=loginLogs):
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"account": "user001",
"name": "John",
"email": "user@example.com",
"hasPassword": true,
"loginLogs": [
{
"id": 42,
"userId": 1,
"device": "fp_abc123xyz",
"ip": "203.69.123.45",
"lastUse": "2026-03-01T10:00:00.000Z",
"action": "LOGIN",
"remark": null
},
{
"id": 41,
"userId": 1,
"device": "Mozilla/5.0 ...",
"ip": "203.69.123.45",
"lastUse": "2026-02-28T08:00:00.000Z",
"action": "LOGOUT",
"remark": null
}
]
}
}| 欄位 | 型別 | 說明 |
|---|---|---|
result.hasPassword | boolean | 是否已設定密碼。三方登入用戶初始為 false |
result.loginLogs | Array | (僅 ?related=loginLogs 時回傳)最近 20 筆登入紀錄 |
result.loginLogs[].action | string | LOGIN / LOGOUT / LOGIN_FAIL / DEL / UNCAPTURED |
result.loginLogs[].device | string | 裝置指紋或 User-Agent |
result.loginLogs[].ip | string | 登入 IP 位址 |
result.loginLogs[].lastUse | string | 紀錄時間 |
前端注意事項
- 此端點是取得用戶完整資料的主要方式,建議在應用啟動後立即呼叫
hasPassword: false時,應在個人資料頁面提示用戶「設定密碼」(引導至POST /auth/set-password)hasPassword: true時,顯示「修改密碼」功能(引導至POST /auth/edit-password)- 登入紀錄可用於「安全中心」頁面顯示最近的登入裝置與 IP
googleAuthEnabled: 1時,個人資料頁應顯示「2FA 已啟用」狀態
UI 設計提示
- 個人資料頁面分區建議:
- 基本資訊(頭像、暱稱、帳號)
- 聯絡資訊(Email、手機 — 含驗證狀態指示器)
- 三方帳號(Google、Telegram — 含綁定/解綁按鈕)
- 安全設定(密碼、Google Authenticator)
- 登入紀錄(最近的登入裝置/IP/時間)
3.4 GET /api/auth/country-codes — 取得國碼列表
功能說明
取得全球所有國家/地區的電話國碼列表,用於手機驗證時選擇國碼。回傳結果依據當前語系排序(透過 Intl.DisplayNames API 取得國家名稱本地化)。
結果會快取在 Redis 中 24 小時(cache key: cache:auth:country-codes:{lang})。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Headers:
| Header | 必填 | 說明 |
|---|---|---|
Authorization | 是 | Bearer <JWT Token> |
Cookies:
| Cookie | 說明 |
|---|---|
i18n_redirected | 語系 cookie,決定國家名稱的顯示語言,預設 zh-TW |
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": [
{ "country": "TW", "callingCode": "886", "name": "台灣" },
{ "country": "US", "callingCode": "1", "name": "美國" },
{ "country": "JP", "callingCode": "81", "name": "日本" },
{ "country": "KR", "callingCode": "82", "name": "韓國" },
{ "country": "TH", "callingCode": "66", "name": "泰國" },
{ "country": "VN", "callingCode": "84", "name": "越南" }
],
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/country-codes"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result[].country | string | ISO 3166-1 alpha-2 國家代碼(如 TW、US) |
result[].callingCode | string | 電話國碼(如 886、1) |
result[].name | string | 國家名稱(依語系本地化) |
前端注意事項
- 列表較長(200+ 國家),建議使用可搜尋的下拉選單(Select with search)
- 可快取結果在前端,避免重複請求
- 預設顯示
886 - 台灣(或依用戶語系選擇對應國家)
UI 設計提示
- 國碼選擇器:國旗 icon + 國碼 + 國家名稱(如
TW +886 台灣) - 搜尋框支援用國碼數字或國家名稱搜尋
- 常用國碼(台灣、中國、美國)可置頂顯示
3.5 POST /api/auth/send-verify-email — 發送驗證信
功能說明
向指定的 Email 地址發送 6 位數驗證碼。用於用戶綁定 Email 前的驗證流程。
驗證碼產生規則:
- 6 位數字(000000 ~ 999999)
- 排除全相同數字(如 111111、222222)
- 排除連續遞增/遞減數字(如 123456、654321)
- 排除黑名單中的碼(如 000000)
驗證碼儲存在 auth-user.emailVerifyCode 欄位中,無過期時間限制(由前端控制重發間隔)。
郵件服務使用 Resend API(設定來源優先順序:環境變數 > 站點設定 serviceProviders.resend)。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 驗證規則 | 說明 |
|---|---|---|---|---|
email | string | 是 | @IsEmail() | 要驗證的 Email 地址 |
subject | string | 否 | @IsString() | 信件主旨,預設 "C9邀請您驗證信箱" |
Body 範例:
{
"email": "user@example.com",
"subject": "C9邀請您驗證信箱"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"id": "email_id_xxx_from_resend"
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/send-verify-email"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 此信箱已被其他帳號使用 | 該 Email 已經被其他帳號綁定 |
前端注意事項
- 發送成功後應啟動倒數計時器(建議 60 秒),避免用戶頻繁重發
- Email 格式驗證建議在前端先做一層
- 發送按鈕在倒數期間應 disabled 並顯示剩餘秒數
UI 設計提示
- Email 輸入框 + 「發送驗證碼」按鈕
- 發送後按鈕變為「重新發送 (59s)」灰色不可點擊狀態
- 驗證碼輸入框(6 格分離輸入,自動跳轉焦點)
3.6 POST /api/auth/check-verify-email — 驗證 Email
功能說明
驗證用戶輸入的 6 位 Email 驗證碼。驗證成功後,將 Email 綁定到當前用戶。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
code | string | 是 | 6 位驗證碼 |
email | string | 是 | 驗證的 Email 地址(必須與發送時一致) |
Body 範例:
{
"code": "123456",
"email": "user@example.com"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": null,
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/check-verify-email"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 查無驗證資訊 | 用戶未先呼叫 send-verify-email,或 emailVerifyCode 為空 |
2002 | 驗證碼錯誤 | 輸入的驗證碼與系統記錄不符 |
業務邏輯說明
- 驗證成功後,
email欄位會被寫入auth-user表 emailVerifyCode欄位不會被清除(重複驗證同一碼仍會通過)- Email 在同站點內有唯一索引,若 Email 已被佔用,會在
send-verify-email階段就攔截
前端注意事項
- 驗證成功後應重新呼叫
GET /auth/user-detail更新本地用戶資料 - 可加入重試上限(例如 5 次錯誤後鎖定一段時間)
3.7 POST /api/auth/send-verify-mobile — 發送手機驗證簡訊
功能說明
發送 SMS 驗證碼到指定手機號碼。支援兩種模式:
- 正式環境: 透過 Twilio Verify Service 發送簡訊
- 開發環境: 直接生成 6 位驗證碼並 console.log 輸出,不實際發送簡訊
手機號碼格式處理:
- 從
country.label提取國碼(格式為"886-台灣"->"886") - 去除手機號碼前導 0
- 組合為 E.164 格式(如
+886912345678)
Twilio 設定來源優先順序:環境變數 > 站點設定 serviceProviders.twilio。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 驗證規則 | 說明 |
|---|---|---|---|---|
mobile | string | 是 | @IsString(), @Length(3, 40) | 手機號碼(不含國碼) |
country | object | 否 | @ValidateNested() | 國碼物件 |
country.label | string | 否 | @IsString() | 格式為 "886-台灣" |
country.icon | string | 否 | @IsString() | 國旗 icon(如 "cif:tw") |
Body 範例:
{
"mobile": "912345678",
"country": {
"label": "886-台灣",
"icon": "cif:tw"
}
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"sent": true
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/send-verify-mobile"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 此手機號碼已被其他帳號使用 | 該 E.164 格式手機號碼已被其他帳號綁定 |
2002 | 簡訊發送失敗 | Twilio API 呼叫失敗 |
業務邏輯說明
- 開發環境下,驗證碼儲存格式為
"{e164}|{code}"(如"+886912345678|485723"),會在 console 輸出 - 正式環境下,
mobileVerifyCode只儲存 E.164 號碼,驗證碼由 Twilio 管理 - 同一手機號碼可以被同一用戶重複發送(只檢查其他用戶的佔用)
前端注意事項
- 手機號碼前導 0 會被後端自動去除,前端可保留也可不保留
- 國碼選擇建議使用
GET /auth/country-codes回傳的列表 - 發送成功後顯示倒數計時(建議 60 秒)
3.8 POST /api/auth/check-verify-mobile — 驗證手機 OTP
功能說明
驗證手機 SMS 驗證碼。驗證成功後將手機號碼(E.164 格式)綁定到當前用戶。
開發環境下,直接比對 DB 中儲存的驗證碼。正式環境下,透過 Twilio Verify Service 驗證。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
mobile | string | 是 | 手機號碼 |
code | string | 是 | 6 位驗證碼 |
Body 範例:
{
"mobile": "912345678",
"code": "485723"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": null,
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/check-verify-mobile"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 查無驗證資訊 | 用戶未先呼叫 send-verify-mobile,或 mobileVerifyCode 為空 |
2002 | 驗證碼錯誤或已過期 | 驗證碼不正確或已過期(Twilio 預設 10 分鐘過期) |
業務邏輯說明
- 驗證成功後,
mobile欄位更新為 E.164 格式號碼,mobileVerifyCode清空 - 手機號碼在同站點內有唯一索引
前端注意事項
- 驗證成功後重新呼叫
GET /auth/user-detail更新資料 - 可在 UI 上顯示「手機已驗證」狀態標籤
3.9 POST /api/auth/generate-google-auth — 產生 Google Authenticator 設定
功能說明
為當前用戶產生 Google Authenticator (TOTP) 的 secret 和 QR Code。此端點不會直接啟用 2FA,用戶需要在掃描 QR Code 後,使用 POST /auth/enable-google-auth 輸入驗證碼來啟用。
每次呼叫會產生新的 secret 並覆蓋舊的,同時將 googleAuthEnabled 重設為 0。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
無需 Body
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"secret": "JBSWY3DPEHPK3PXP",
"qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/generate-google-auth"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result.secret | string | Base32 編碼的 TOTP secret,用戶可手動輸入到 Authenticator App |
result.qrCode | string | QR Code 的 base64 PNG 圖片(data:image/png;base64,...) |
前端注意事項
- QR Code 可直接作為
<img>標籤的src屬性使用 - 同時提供 secret 文字,供無法掃碼的用戶手動複製輸入
- 此步驟只是「準備」,尚未啟用 2FA
UI 設計提示
- 啟用 2FA 對話框三步驟流程:
- 顯示 QR Code + secret 文字(提示用戶使用 Google Authenticator 掃描)
- 輸入 6 位驗證碼確認(呼叫
enable-google-auth) - 成功提示
3.10 POST /api/auth/enable-google-auth — 啟用 Google Authenticator
功能說明
輸入 Google Authenticator 產生的 6 位 TOTP 驗證碼,驗證成功後正式啟用 2FA。
TOTP 驗證使用 speakeasy 庫,允許 1 個時間視窗的偏移(window = 1,即前後 30 秒)。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
code | string | 是 | Google Authenticator 6 位 TOTP 驗證碼 |
Body 範例:
{
"code": "123456"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"affected": 1
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/enable-google-auth"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 查無驗證資訊 | 用戶未先呼叫 generate-google-auth,或 googleAuthSecret 為空 |
2002 | 6 位數密碼輸入錯誤 | TOTP 驗證碼不正確 |
前端注意事項
- 啟用成功後更新本地用戶狀態
googleAuthEnabled = 1 - 建議在成功後提示用戶「請妥善保管備份碼」
3.11 POST /api/auth/edit-password — 修改密碼
功能說明
修改已登入用戶的密碼。需提供原密碼進行身份驗證,以及新密碼和確認密碼。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
password | string | 是 | 當前密碼 |
newPassword | string | 是 | 新密碼 |
confirmPassword | string | 是 | 確認新密碼(必須與 newPassword 相同) |
Body 範例:
{
"password": "oldPass123",
"newPassword": "newPass456",
"confirmPassword": "newPass456"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"affected": 1
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/edit-password"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2002 | 密碼驗證失敗 | 當前密碼不正確,或新密碼與確認密碼不一致 |
注意: 錯誤碼
2002同時用於「原密碼錯誤」和「新密碼與確認密碼不一致」兩種情況。前端建議先在客戶端檢查兩次密碼是否一致,若一致但仍返回 2002 則為原密碼錯誤。
前端注意事項
- 前端應先驗證
newPassword === confirmPassword,不一致時本地提示 - 密碼修改成功後不會使 Token 失效,用戶可繼續使用
- 建議密碼欄位都加上顯示/隱藏切換按鈕
3.12 POST /api/auth/set-password — 首次設定密碼(三方登入用戶)
功能說明
僅限未設定密碼的三方登入用戶(Google / Telegram 註冊)使用。允許用戶首次設定帳號密碼。
判斷條件:用戶的 password 欄位為空字串 '' 時才能使用此端點。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 驗證規則 | 說明 |
|---|---|---|---|---|
newPassword | string | 是 | @IsString(), @MinLength(6) | 新密碼,至少 6 字元 |
confirmPassword | string | 是 | @IsString(), @MinLength(6) | 確認密碼 |
Body 範例:
{
"newPassword": "pass1234",
"confirmPassword": "pass1234"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": null,
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/set-password"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2002 | 密碼不一致 | newPassword 與 confirmPassword 不相同 |
2003 | 已設定過密碼 | 用戶已經有密碼(非三方登入用戶,或已設定過) |
前端注意事項
- 僅在
user-detail回傳hasPassword: false時顯示此功能入口 - 設定成功後,用戶可以使用帳號 + 密碼方式登入
- 建議在個人資料頁面以醒目提示「尚未設定密碼,建議設定以提升帳號安全性」
UI 設計提示
- 可在個人資料的「安全設定」區域放置黃色警告卡片
- 點擊後開啟彈窗:新密碼 + 確認密碼 + 確認按鈕
3.13 GET /api/auth/login-config — 取得登入設定
功能說明
取得三方登入的設定資訊,包含:
- Google OAuth 2.0: 回傳完整的授權 URL(含 PKCE code_challenge、state 防偽造)
- Telegram Login: 回傳 Bot 資訊(botUsername、botId)
Google OAuth 流程使用 PKCE(Proof Key for Code Exchange)安全機制:
- 產生隨機
codeVerifier(64 bytes) - 計算
codeChallenge= SHA256(codeVerifier) 的 base64url - 將
codeVerifier加密儲存在state參數中(HMAC-SHA256 簽名) - State 有效期為 10 分鐘
認證需求
無需認證(公開端點)
Request
無需參數
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"google": "https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fc9-ec.zeabur.app%2Fredirect%2Fgoogle&response_type=code&scope=openid+email+profile&state=eyJ2IjoxLCJuIjoiLi4uIn0.abc123&code_challenge=xxx&code_challenge_method=S256&access_type=offline&prompt=consent",
"telegram": {
"botUsername": "C9GameBot",
"botId": "1234567890"
}
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/login-config"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result.google | string | Google OAuth2 完整授權 URL,前端直接 window.location.href = url 即可跳轉 |
result.telegram | object | null | Telegram Bot 資訊。null 表示未設定 |
result.telegram.botUsername | string | Telegram Bot 使用者名稱,用於 Login Widget |
result.telegram.botId | string | Telegram Bot ID |
錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 尚未設置 GOOGLE_CLIENT_ID | Google OAuth 未設定,無法產生授權 URL |
業務邏輯說明
- Google OAuth redirect URI 格式:
{domain}/redirect/google(後端自動補上路徑) - State 參數結構:
{v: 1, n: nonce, cv: codeVerifier, ra: redirectAfter, iat: timestamp} - State 用 HMAC-SHA256 簽名防偽造,key 為 JWT_SECRET
- 若 Google 或 Telegram 未設定,對應欄位回傳
null,前端應隱藏對應的登入按鈕
前端注意事項
- Google 登入流程:
- 呼叫此端點取得 Google URL
window.location.href = result.google跳轉 Google 授權頁- 用戶授權後,Google 重導向至
/redirect/google?code=xxx&state=xxx - 前端從 URL 取得
code和state - 呼叫
POST /auth/login-google傳入code和state
- Telegram 登入:使用 Telegram Login Widget,設定
bot-username為result.telegram.botUsername
3.14 POST /api/auth/login-google — Google 登入
功能說明
使用 Google OAuth2 Authorization Code 完成登入或註冊。完整流程:
- State 驗簽: 驗證 state 的 HMAC 簽名,防止 CSRF 攻擊
- State 解析: 解碼 base64url,取出
codeVerifier和iat - 過期檢查: State 發出後超過 10 分鐘則拒絕
- Token 交換: 使用 code + codeVerifier 向 Google 交換 access_token 和 id_token
- ID Token 驗證: 使用 Google Auth Library 驗證 id_token 的真實性
- 用戶處理:
- 用 Google email 查找現有用戶
- 若存在:遞增 tokenVersion 登入,若未綁定 Google sub 則自動補綁
- 若不存在:建立新帳號(account:
google_{sub})、分配頭像/金流群組、處理推廣碼綁定
認證需求
無需認證(公開端點)
Request
Headers:
| Header | 必填 | 說明 |
|---|---|---|
locales | 否 | 語系(新用戶註冊時使用),預設 zh-TW |
site-name | 否 | 站點名稱(新用戶註冊時使用) |
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
code | string | 是 | Google OAuth2 authorization code(由 Google 回調帶回) |
state | string | 是 | OAuth2 state(由 login-config 產生,含 PKCE code_verifier) |
refCode | string | 否 | 推廣碼(僅新用戶註冊時使用) |
Body 範例:
{
"code": "4/0AQSTgQHhB7...",
"state": "eyJ2IjoxLCJuIjoiLi4uIn0.abc123def456",
"refCode": "AGT123"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"account": "google_123456789",
"name": "John Doe",
"email": "john@gmail.com",
"google": "123456789",
"vipLevel": "1",
"balance": "0.000000"
},
"google": {
"sub": "123456789",
"email": "john@gmail.com",
"name": "John Doe",
"picture": "https://lh3.googleusercontent.com/a/...",
"email_verified": true,
"iss": "https://accounts.google.com",
"aud": "xxx.apps.googleusercontent.com"
},
"isNewUser": true
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/login-google"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result.token | string | JWT Token |
result.user | object | 用戶完整資料 |
result.google | object | Google Token 資訊 + ID Token Payload |
result.isNewUser | boolean | 是否為新註冊用戶(前端可據此顯示歡迎引導) |
錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 尚未設置 GOOGLE_CLIENT_ID | Google OAuth 未設定 |
2002 | GOOGLE_CLIENT_ID 驗證失敗 | State 參數格式不正確 |
2003 | Google Payload 加密失敗 | State 簽名驗證不通過(可能被竄改) |
2004 | Google Payload 解析失敗 | State 的 base64url 解碼失敗 |
2005 | 本次操作時效已過期, 請重新登入 | State 超過 10 分鐘有效期 |
2006 | Google Code 驗證錯誤 | State 中缺少 codeVerifier |
2007 | Google Api 響應過程錯誤 | Google Token 交換 API 回傳錯誤 |
2008 | Google Ticket 解析失敗 | id_token 驗證失敗(無效的 token) |
2009 | Google Ticket Payload 解析失敗 | id_token payload 中缺少 sub 欄位 |
前端注意事項
isNewUser: true時,可顯示歡迎引導頁面或提示設定密碼- Google 登入新用戶的
password為空字串,hasPassword為false - 登入成功後的 Token 處理方式與一般登入相同
3.15 POST /api/auth/login-telegram — Telegram 登入
功能說明
使用 Telegram Login Widget 回傳的資料完成登入或註冊。驗證流程:
- Bot Token 檢查: 確認已設定 Telegram Bot Token
- HMAC 驗簽: 使用
SHA256(botToken)作為 key,對 data-check-string 做 HMAC-SHA256,比對 hash 是否一致 - 時效驗證:
auth_date必須在 5 分鐘內 - 用戶處理:
- 用 Telegram ID 查找現有用戶
- 若存在:遞增 tokenVersion 登入
- 若不存在:建立新帳號(account:
tg_{telegramId})
認證需求
無需認證(公開端點)
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
id | number | 是 | Telegram user ID |
first_name | string | 是 | Telegram first name |
last_name | string | 否 | Telegram last name |
username | string | 否 | Telegram username |
photo_url | string | 否 | Telegram 頭像 URL |
auth_date | number | 是 | Unix timestamp(認證時間) |
hash | string | 是 | HMAC-SHA256 驗證雜湊 |
refCode | string | 否 | 推廣碼(僅新用戶註冊時使用) |
Body 範例:
{
"id": 123456789,
"first_name": "John",
"last_name": "Doe",
"username": "johndoe",
"photo_url": "https://t.me/i/userpic/320/...",
"auth_date": 1708862400,
"hash": "a1b2c3d4e5f6...",
"refCode": "AGT123"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"account": "tg_123456789",
"name": "John Doe",
"telegram": "123456789"
},
"isNewUser": true
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/login-telegram"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 尚未設置 TELEGRAM_BOT_TOKEN | Telegram Bot 未設定 |
2002 | Telegram 驗證失敗 | HMAC hash 驗證不通過 |
2003 | 本次操作時效已過期, 請重新登入 | auth_date 超過 5 分鐘 |
前端注意事項
- Telegram Login Widget 會自動帶回以上所有欄位,前端直接傳送即可
hash由 Telegram 產生,前端不需自行計算- 5 分鐘過期限制代表用戶在 Telegram 授權後,必須在 5 分鐘內完成後端驗證
3.16 POST /api/auth/logout — 登出
功能說明
登出當前用戶。執行以下操作:
- 語系更新: 根據當前
localesheader 更新用戶語系偏好 - 金流群組重新分配: 根據新語系對應的幣別,重新匹配啟用中的金流群組
- Token 失效:
tokenVersion + 1,使所有現有 Token 失效 - Redis 快取清除: 刪除
cache:auth:tv:{userId}
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Headers:
| Header | 必填 | 說明 |
|---|---|---|
Authorization | 是 | Bearer <JWT Token> |
locales | 否 | 語系代碼(決定登出後用戶的語系偏好與金流群組) |
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"locale": "zh-TW",
"vendorGroupId": 1
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/logout"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result.locale | string | 更新後的語系偏好 |
result.vendorGroupId | number | null | 重新分配的金流群組 ID |
業務邏輯說明
- 登出後 tokenVersion 遞增,即使 Token 尚未過期也無法再使用
- 語系更新和金流群組重新分配是為了確保下次登入時的正確性
前端注意事項
- 登出成功後清除本地儲存的 Token 和用戶資料
- 重導向至登入頁面
- 即使 API 呼叫失敗(如網路問題),前端也應清除本地狀態並導向登入頁
3.17 PATCH /api/auth/locale — 更新語系偏好
功能說明
更新當前用戶的語系偏好。僅接受系統支援的 5 種語系。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
locale | string | 是 | 語系代碼:zh-TW / en-US / zh-CN / th-TH / vi-VN |
Body 範例:
{
"locale": "en-US"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"locale": "en-US"
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/locale"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 不支援的語系 | 傳入的 locale 不在支援列表中 |
前端注意事項
- 前端切換語系時同步呼叫此 API 更新後端紀錄
- 僅更新偏好,不影響 Token 或金流群組
3.18 GET /api/auth/mascots — 取得吉祥物頭像列表
功能說明
取得所有可選的吉祥物頭像列表。目前共 10 隻吉祥物,頭像圖片儲存在 Cloudflare R2 上。
吉祥物列表由 SiteConfigService.getMascots() 提供,可透過後台管理設定。
認證需求
無需認證(公開端點)
Request
無需參數
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": [
{ "id": "c9-dragon", "label": "翡翠龍", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-dragon.png" },
{ "id": "c9-phoenix", "label": "鳳凰", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-phoenix.png" },
{ "id": "c9-angel", "label": "天使", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-angel.png" },
{ "id": "c9-wizard", "label": "法師", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-wizard.png" },
{ "id": "c9-knight", "label": "騎士", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-knight.png" },
{ "id": "c9-mermaid", "label": "人魚", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-mermaid.png" },
{ "id": "c9-tiger", "label": "猛虎", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-tiger.png" },
{ "id": "c9-fox", "label": "靈狐", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-fox.png" },
{ "id": "c9-owl", "label": "貓頭鷹", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-owl.png" },
{ "id": "c9-wolf", "label": "銀狼", "url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-wolf.png" }
],
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/mascots"
}| 欄位 | 型別 | 說明 |
|---|---|---|
result[].id | string | 吉祥物 ID(用於 PATCH /auth/avatar) |
result[].label | string | 吉祥物名稱 |
result[].url | string | 頭像圖片 URL(R2 CDN) |
前端注意事項
- 可在註冊完成後或個人資料頁提供頭像選擇功能
- 圖片 URL 指向 R2 CDN,可直接用於
<img>標籤
UI 設計提示
- 頭像選擇器:3x4 或 5x2 的網格排列
- 當前選中的頭像加上邊框高亮
- 點擊後呼叫
PATCH /auth/avatar即時更換
3.19 PATCH /api/auth/avatar — 切換用戶頭像
功能說明
切換當前用戶的頭像為指定的吉祥物。系統會驗證 mascotId 是否存在於吉祥物列表中。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
mascotId | string | 是 | 吉祥物 ID(從 GET /auth/mascots 取得) |
可用的 mascotId 列表:
| mascotId | 名稱 |
|---|---|
c9-dragon | 翡翠龍 |
c9-phoenix | 鳳凰 |
c9-angel | 天使 |
c9-wizard | 法師 |
c9-knight | 騎士 |
c9-mermaid | 人魚 |
c9-tiger | 猛虎 |
c9-fox | 靈狐 |
c9-owl | 貓頭鷹 |
c9-wolf | 銀狼 |
Body 範例:
{
"mascotId": "c9-dragon"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"avatar": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-dragon.png"
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/avatar"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 無效的頭像 ID | 傳入的 mascotId 不存在於吉祥物列表中 |
前端注意事項
- 更換成功後,前端即時更新顯示的頭像(使用回傳的
avatarURL) - 不需要重新呼叫
user-detailAPI
3.20 POST /api/auth/bind-google — 綁定 Google 帳號
功能說明
將 Google 帳號綁定到已登入用戶。流程與 login-google 相同的 OAuth 驗證流程,但不建立新用戶,而是更新現有用戶的 google 欄位。
額外功能:若用戶尚未設定 Email,且 Google 帳號有 Email,會一併寫入。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
code | string | 是 | Google OAuth2 authorization code |
state | string | 是 | OAuth2 state(含 PKCE code_verifier) |
Body 範例:
{
"code": "4/0AQSTgQ...",
"state": "eyJ2IjoxLCJuIjoiLi4uIn0.abc123"
}Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"google": "google_sub_id_123",
"email": "john@gmail.com",
"name": "John Doe"
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/bind-google"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 ~ 2009 | (同 login-google) | Google OAuth 驗證過程中的各種錯誤 |
2010 | 此 Google 帳號已被其他用戶綁定 | 該 Google sub 已經綁定在其他帳號上 |
2011 | 此 Email 已被其他用戶使用 | Google 帳號的 Email 已被其他帳號佔用 |
前端注意事項
- 綁定流程和登入流程共用同一個 Google OAuth 授權 URL(由
login-config取得) - 差別在於前端收到 Google callback 後,呼叫
bind-google而非login-google - 綁定成功後刷新用戶資料,顯示「Google 已綁定」狀態
3.21 POST /api/auth/bind-telegram — 綁定 Telegram 帳號
功能說明
將 Telegram 帳號綁定到已登入用戶。驗證邏輯與 login-telegram 相同(HMAC 驗簽 + 5 分鐘過期檢查)。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
Body (JSON): (與 login-telegram 相同)
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
id | number | 是 | Telegram user ID |
first_name | string | 是 | Telegram first name |
last_name | string | 否 | Telegram last name |
username | string | 否 | Telegram username |
photo_url | string | 否 | Telegram 頭像 URL |
auth_date | number | 是 | Unix timestamp |
hash | string | 是 | HMAC-SHA256 驗證雜湊 |
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"telegram": "123456789"
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/bind-telegram"
}錯誤碼
| code | 訊息 | 說明 |
|---|---|---|
2001 | 尚未設置 TELEGRAM_BOT_TOKEN | Telegram Bot 未設定 |
2002 | Telegram 驗證失敗 | HMAC hash 驗證不通過 |
2003 | 本次操作時效已過期, 請重新登入 | auth_date 超過 5 分鐘 |
2010 | 此 Telegram 帳號已被其他用戶綁定 | 該 Telegram ID 已綁定在其他帳號上 |
3.22 POST /api/auth/unbind-google — 解除綁定 Google
功能說明
解除當前用戶的 Google 帳號綁定。將 google 欄位設為 null。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
無需 Body
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"affected": 1
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/unbind-google"
}前端注意事項
- 解綁前建議彈出確認對話框
- 若用戶僅透過 Google 登入(
hasPassword: false),解綁後將無法登入。建議先引導設定密碼
3.23 POST /api/auth/unbind-telegram — 解除綁定 Telegram
功能說明
解除當前用戶的 Telegram 帳號綁定。將 telegram 欄位設為 null。
認證需求
需要 JWT Token (@UseGuards(JwtAuthGuard))
Request
無需 Body
Response
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"affected": 1
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/auth/unbind-telegram"
}前端注意事項
- 解綁前建議彈出確認對話框
- 同理,若用戶僅透過 Telegram 登入且未設定密碼,解綁後將無法登入
Auth 模組端點摘要表
| # | 方法 | 路徑 | 認證 | 說明 |
|---|---|---|---|---|
| 1 | POST | /api/auth/register | -- | 用戶註冊 |
| 2 | POST | /api/auth/login | -- | 帳號密碼登入 |
| 3 | GET | /api/auth/user-detail | JWT | 取得用戶資料(可含登入紀錄) |
| 4 | GET | /api/auth/country-codes | JWT | 取得國碼列表 |
| 5 | POST | /api/auth/send-verify-email | JWT | 發送 Email 驗證碼 |
| 6 | POST | /api/auth/check-verify-email | JWT | 驗證 Email 驗證碼 |
| 7 | POST | /api/auth/send-verify-mobile | JWT | 發送手機 SMS 驗證碼 |
| 8 | POST | /api/auth/check-verify-mobile | JWT | 驗證手機 OTP |
| 9 | POST | /api/auth/generate-google-auth | JWT | 產生 Google Authenticator QR Code |
| 10 | POST | /api/auth/enable-google-auth | JWT | 啟用 Google Authenticator 2FA |
| 11 | POST | /api/auth/edit-password | JWT | 修改密碼 |
| 12 | POST | /api/auth/set-password | JWT | 首次設定密碼(三方登入用戶) |
| 13 | GET | /api/auth/login-config | -- | 取得三方登入設定(Google URL + Telegram Bot) |
| 14 | POST | /api/auth/login-google | -- | Google OAuth 登入/註冊 |
| 15 | POST | /api/auth/login-telegram | -- | Telegram 登入/註冊 |
| 16 | POST | /api/auth/logout | JWT | 登出(Token 失效 + 語系更新) |
| 17 | PATCH | /api/auth/locale | JWT | 更新語系偏好 |
| 18 | GET | /api/auth/mascots | -- | 取得吉祥物頭像列表 |
| 19 | PATCH | /api/auth/avatar | JWT | 切換用戶頭像 |
| 20 | POST | /api/auth/bind-google | JWT | 綁定 Google 帳號 |
| 21 | POST | /api/auth/bind-telegram | JWT | 綁定 Telegram 帳號 |
| 22 | POST | /api/auth/unbind-google | JWT | 解除綁定 Google |
| 23 | POST | /api/auth/unbind-telegram | JWT | 解除綁定 Telegram |
Common 公用模組
路由前綴:
/api/commonSwagger 標籤:Common端點數量: 1 個 功能概述: 提供全系統共用的列舉值和錯誤碼對照表,是前端錯誤處理機制的核心依賴
模組初始化機制
CommonService 實作了 OnModuleInit 介面,在模組啟動時執行以下初始化流程:
- 掃描 i18n 目錄: 遞迴讀取
i18n/zh-TW/下所有 JSON 檔案 - 收集數字 key: 找出所有數字格式的 key(這些就是錯誤碼)
- 建構 CodeMap: 產生
{ "authError.register": [2001, 2002], "authError.login": [2001], ... }的結構 - API 路徑映射: 透過
I18N_PATH_MAP將 i18n prefix 轉換為 API path
這個機制讓新增錯誤碼只需要在 i18n JSON 中加入即可,不需要手動維護錯誤碼清單。
4.1 GET /api/common/enums — 取得共用列舉與錯誤碼
功能說明
回傳系統所有共用列舉值和依語系翻譯的錯誤碼對照表。這是前端錯誤處理機制的核心 API。
錯誤碼查表機制:
前端收到業務錯誤回應時(code !== 200),不顯示後端回傳的 message(可能因快取或其他原因不一致),而是透過 ERROR_CODES[path][code] 查表取得當前語系的錯誤文字。
結果會快取在 Redis 中 1 小時(cache key: cache:common:enums:{lang})。
認證需求
無需認證(公開端點)
Request
Headers:
| Header | 必填 | 說明 |
|---|---|---|
locales | 否 | 語系代碼,決定 ERROR_CODES 的語言。預設 zh-TW |
Request 範例:
GET /api/common/enums
locales: zh-TWResponse
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": {
"AUTH_ENUM": {
"RELATED": {
"LOGIN_LOG": "LOGIN_LOG"
},
"LOGIN_LOG": {
"ACTION": {
"LOGIN": "登入",
"LOGOUT": "登出",
"LOGIN_FAIL": "登入失敗",
"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": {
"2002": "密碼驗證失敗"
},
"/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/locale": {
"2001": "不支援的語系"
},
"/api/wallet/bank-card/add": {
"2001": "銀行卡已存在"
},
"/api/deposit": {
"2003": "不支援的金流通道類型"
},
"/api/game/launch": {
"5010": "您已被限制進入此遊戲"
}
}
},
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api/common/enums"
}回傳結構詳解
AUTH_ENUM — 認證相關列舉值
| 路徑 | 值 | 說明 |
|---|---|---|
AUTH_ENUM.RELATED.LOGIN_LOG | "LOGIN_LOG" | 用於 GET /auth/user-detail?related=loginLogs 的常數 |
AUTH_ENUM.LOGIN_LOG.ACTION.LOGIN | "登入" | 登入紀錄的 action 顯示名稱 |
AUTH_ENUM.LOGIN_LOG.ACTION.LOGOUT | "登出" | 登出紀錄 |
AUTH_ENUM.LOGIN_LOG.ACTION.LOGIN_FAIL | "登入失敗" | 登入失敗紀錄 |
AUTH_ENUM.LOGIN_LOG.ACTION.DEL | "移除" | 手動移除紀錄 |
AUTH_ENUM.LOGIN_LOG.ACTION.UNCAPTURED | "未補獲" | 未捕獲的異常紀錄 |
ERROR_CODES — 錯誤碼對照表
結構為 Record<apiPath, Record<errorCode, translatedMessage>>:
- Key (apiPath): API 端點路徑(含
/api前綴),如/api/auth/login - Value: 該端點所有可能的錯誤碼和對應的翻譯文字
路徑支援 :id 萬用匹配,例如 /api/wallet/bank-card/:id 可匹配 /api/wallet/bank-card/123。
業務邏輯說明
- 錯誤碼的翻譯由
nestjs-i18n庫處理,根據localesheader 回傳對應語系 - 快取 1 小時,語系切換時會使用不同的 cache key,不會影響
- I18N_PATH_MAP 支援一對多映射:同一組錯誤碼可以映射到多個 API 路徑
I18N_PATH_MAP 完整映射表
| i18n Prefix | API Path |
|---|---|
authError.register | /api/auth/register |
authError.login | /api/auth/login |
authError.sendVerifyEmail | /api/auth/send-verify-email |
authError.checkVerifyEmail | /api/auth/check-verify-email |
authError.enableGoogleAuth | /api/auth/enable-google-auth |
authError.editPassword | /api/auth/edit-password |
authError.loginConfig | /api/auth/login-config |
authError.loginGoogle | /api/auth/login-google |
authError.updateLocale | /api/auth/locale |
walletError.bankCard.add | /api/wallet/bank-card/add |
walletError.bankCard.delete | /api/wallet/bank-card/:id |
walletError.creditCard.add | /api/wallet/credit-card/add |
walletError.creditCard.delete | /api/wallet/credit-card/:id |
walletError.cryptoAddress.add | /api/wallet/crypto-address/add |
walletError.cryptoAddress.delete | /api/wallet/crypto-address/:id |
vendorError.channels | /api/vendor/channels |
vendorError.wantong | /api/vendor/wantong/add-atm, /api/vendor/wantong/add-card |
vendorError.usdt | /api/deposit |
depositError | /api/deposit |
depositError.exchangeRate | /api/deposit/exchange-rate |
promoError | /api/promo/:id, /api/admin/promos/create, /api/admin/promos/:id |
promoError.claim | /api/promo/:id/claim |
vipError.levels | /api/vip/levels, /api/vip/levels/:id |
vipError.rebates | /api/vip/rebates, /api/vip/rebates/:id |
vipError.users.hold | /api/vip/users/:userId/hold |
gameError.launch | /api/game/launch |
gameError.demo | /api/game/demo |
gameError.simulate | /api/game/simulate |
gameError.list | /api/game/list |
betRecordError.details | /api/bet-record/:orderId/details |
affiliateError.* | (代理相關多個路徑) |
inboxError | /api/inbox/:id/read, /api/inbox/admin/:id |
siteConfigError | /api/site-config, /api/site-config/admin/* |
withdrawalError.* | (提領相關多個路徑) |
adminError.* | (後台管理相關多個路徑) |
前端注意事項
- 應用啟動時呼叫一次此 API,將
ERROR_CODES存入全域狀態管理(ZustandenumStore) - 語系切換時重新呼叫,因為錯誤碼文字會隨語系變化
- 錯誤處理流程:
- API 回傳
code !== 200 - 從
enumStore.ERROR_CODES查找[apiPath][code] - 找到則顯示該翻譯文字
- 找不到則 fallback 使用 API 回傳的
message
- API 回傳
:id路徑匹配:前端需將實際路徑(如/api/wallet/bank-card/123)轉換為模板路徑(如/api/wallet/bank-card/:id)再查表
前端錯誤處理程式碼範例
// IMS 前端 (c9-ims) useHttp 三層錯誤碼映射
function resolveErrorMessage(path: string, code: number, override?: string | Record<number, string>) {
// 第一層:Store 查表
const errorCodes = enumStore.getState().errorCodes;
const storeMessage = errorCodes?.[path]?.[code];
// 第二層:呼叫端覆寫
if (typeof override === 'string') return override;
if (typeof override === 'object' && override[code]) return override[code];
// 第三層:Store 查表結果
if (storeMessage) return storeMessage;
// Fallback
return '操作失敗';
}App Health Check
路由前綴:
/apiSwagger 標籤:App端點數量: 1 個 功能概述: 系統健康檢查端點,用於確認後端服務是否正常運行
5.1 GET /api — 健康檢查
功能說明
最基本的健康檢查端點,用於確認後端服務是否正常啟動和運行。回傳固定字串 "Hello World!"。
此端點通常用於:
- 負載均衡器的健康檢查探針
- 部署後的存活性驗證
- 前端啟動時的後端連通性檢查
- 監控系統的心跳偵測
認證需求
無需認證(公開端點)
Request
無需參數
Request 範例:
GET /apiResponse
成功回應 (code: 200):
{
"code": 200,
"message": "ok",
"result": "Hello World!",
"timestamp": "2026-03-01T12:00:00.000Z",
"path": "/api"
}注意:
result為純字串"Hello World!",不是物件。此回應經過SuccessResponseInterceptor包裝為統一格式。
前端注意事項
- 可用於應用啟動時的「後端連線檢查」
- 若呼叫失敗,表示後端服務未啟動或網路不通,應顯示「服務維護中」頁面
- 回應時間可用於粗略估計網路延遲
適用場景
| 場景 | 說明 |
|---|---|
| 前端啟動檢查 | 確認 API Server 可達,再進入主應用 |
| Kubernetes Liveness Probe | GET /api 回傳 200 表示容器健康 |
| CI/CD 部署驗證 | 部署後呼叫此端點確認服務正常 |
| 監控告警 | 定期 ping 此端點,異常時觸發告警 |
附錄
Auth 用戶資料表結構 (auth-user)
| 欄位 | 型別 | Nullable | 預設值 | 索引 | 說明 |
|---|---|---|---|---|---|
id | int (PK) | 否 | auto | PK | 用戶 ID |
account | varchar(50) | 否 | - | UNIQUE(siteCode, account) | 帳號 |
password | varchar(255) | 否 | - | - | bcrypt 密碼雜湊(三方登入為空字串) |
name | varchar(50) | 否 | - | - | 暱稱 |
email | varchar(50) | 是 | null | UNIQUE(siteCode, email) | 已驗證的 Email |
emailVerifyCode | varchar(6) | 是 | null | - | Email 驗證碼暫存 |
mobile | varchar(30) | 是 | null | UNIQUE(siteCode, mobile) | E.164 格式手機號碼 |
mobileVerifyCode | varchar(40) | 是 | null | - | 手機驗證中的 E.164 號碼 |
telegram | varchar(50) | 是 | null | UNIQUE(siteCode, telegram) | Telegram user ID |
google | varchar(50) | 是 | null | UNIQUE(siteCode, google) | Google sub ID |
vipLevel | varchar(4) | 否 | '1' | - | VIP 等級 |
vipProgress | varchar(3) | 是 | '0' | - | VIP 升級進度 |
totalEffectiveBet | decimal(18,6) | 否 | 0 | - | 累計有效投注流水 USD |
relegationMissCount | tinyint(1) | 否 | 0 | - | 連續未達保級月數 |
vipHold | tinyint(1) | 否 | 0 | - | VIP 保級鎖定(1=鎖定) |
googleAuthSecret | varchar(32) | 是 | null | - | TOTP secret (base32) |
googleAuthEnabled | tinyint(1) | 否 | 0 | - | Google Authenticator 啟用(0/1) |
tokenVersion | int | 否 | 0 | - | Token 版本號(遞增使舊 Token 失效) |
balance | decimal(18,6) | 否 | 0 | - | USD 餘額 |
frozenBalance | decimal(18,6) | 否 | 0 | - | 凍結中金額(提領審核中) |
withdrawalVerifyCode | varchar(6) | 是 | null | - | 提領驗證碼 |
locale | varchar(10) | 否 | 'zh-TW' | - | 語系偏好 |
avatar | varchar(255) | 是 | null | - | 頭像 URL |
vendorGroupId | int | 是 | null | INDEX | 金流群組 ID |
agentCode | varchar(20) | 是 | null | UNIQUE(siteCode, agentCode) | 代理推廣碼 |
level1AgentId | int | 是 | null | INDEX | 一級代理 ID |
level2AgentId | int | 是 | null | - | 二級代理 ID |
level3AgentId | int | 是 | null | - | 三級代理 ID |
agentTourCompletedAt | datetime | 是 | null | - | 代理導覽完成時間 |
agentTourDismissedAt | datetime | 是 | null | - | 代理導覽跳過時間 |
lastActivityAt | datetime | 是 | null | INDEX | 最後活動時間 |
siteCode | varchar(30) | 否 | 'C9' | INDEX | 所屬站點代碼 |
createdAt | datetime | 否 | CURRENT_TIMESTAMP | - | 建立時間 |
登入紀錄表結構 (auth-user-login-log)
| 欄位 | 型別 | Nullable | 說明 |
|---|---|---|---|
id | int (PK) | 否 | 紀錄 ID |
userId | int | 是 | 用戶 ID(登入失敗且帳號不存在時為 null) |
device | varchar | 否 | 裝置指紋或 User-Agent |
ip | varchar | 否 | 登入 IP 位址 |
lastUse | datetime | 否 | 紀錄時間 |
action | varchar | 否 | LOGIN / LOGOUT / LOGIN_FAIL / DEL / UNCAPTURED |
remark | varchar | 是 | 備註(如 ACCOUNT_NOT_FOUND:user001、WRONG_PASSWORD) |
全域 Middleware Pipeline
後端請求處理流水線順序:
HTTP Request
→ cookieParser() # 解析 Cookie
→ compression() # Gzip 壓縮
→ CORS (origin: true, credentials) # 跨域設定
→ Global Prefix: /api # 路由前綴
→ ValidationPipe (whitelist, transform) # DTO 驗證(去除未知欄位、自動轉型)
→ Controller # 路由匹配
→ Guards (JWT / Admin / Permissions) # 認證與授權檢查
→ Interceptors # 攔截器
→ Service # 業務邏輯
→ SuccessResponseInterceptor # 成功回應包裝 { code, message, result }
→ AllExceptionsFilter # 錯誤回應處理
→ HTTP ResponseC9 Platform API 文件 — Part 2
涵蓋 Game 遊戲模組、VIP 會員模組、BetRecord 投注紀錄模組、Ranking 排行榜模組、Mission 任務模組、LiveSports 即時賽事模組
遊戲類型對照表(全模組通用)
| 數字代碼 | typeKey | 中文名稱 |
|---|---|---|
| 1 | sports | 體育 |
| 2 | slot | 電子(老虎機) |
| 3 | live | 真人 |
| 4 | lottery | 彩票 |
| 5 | chess | 棋牌 |
| 8 | esports | 電競 |
| 9 | crypto | 加密貨幣 |
| 10 | fish | 捕魚 |
Game 遊戲模組
路由前綴:
/api/game遊戲模組負責遊戲商管理、遊戲啟動、試玩、遊戲分類設定,以及後台的遊戲供應商 CRUD、模板帶入、跨站複製等功能。遊戲採「轉帳錢包」模式,玩家進入遊戲後由遊戲商透過 S2S 回調進行扣款/派彩。每次投注結算後會自動觸發 VIP 等級重算、活動打碼量累計、任務投注進度更新。
相關資料表
| 資料表 | 說明 |
|---|---|
game-provider | 遊戲供應商(含 siteCode,每站獨立配置) |
game-type-config | 遊戲分類設定(含 siteCode,每站獨立配置) |
game-transaction | 遊戲交易紀錄(扣款/派彩) |
game-play-log | 玩家遊玩紀錄(UPSERT,記錄最近遊玩的遊戲) |
bet-order | 投注訂單 |
bet-detail | 投注明細(小注單) |
GET /api/game/provider — 取得遊戲商列表
功能說明
取得目前啟用的遊戲供應商列表。此端點為公開端點,不需要登入即可呼叫。前台首頁、遊戲大廳頁面載入時應呼叫此 API 取得可用的遊戲商清單,根據 gameType 參數可篩選特定類型的遊戲商。回傳結果會經 Redis 快取(1 小時 TTL),減少資料庫查詢壓力。
認證需求:無(公開端點)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Query | gameType | string | 否 | 遊戲類型篩選 | "2" |
gameType 可選值:1(體育)、2(電子)、3(真人)、4(彩票)、5(棋牌)、8(電競)、9(加密貨幣)、10(捕魚)
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"gameCode": "slot-rsg",
"providerCode": "rsg",
"gameType": 2,
"gameTypeLabel": "slot",
"areaBlock": false,
"maintain": false,
"enable": true,
"createdAt": "2026-02-22T00:00:00.000Z"
}
],
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/game/provider"
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
id | number | 遊戲供應商 ID |
gameCode | string | 遊戲代碼(全站唯一,用於啟動遊戲) |
providerCode | string | 對接的遊戲商 API 代碼(rsg / betsolutions) |
gameType | number | 遊戲類型代碼 |
gameTypeLabel | string | 遊戲類型標籤(sports/slot/live 等) |
areaBlock | boolean | 是否地區封鎖 |
maintain | boolean | 是否維護中 |
enable | boolean | 是否啟用 |
createdAt | string | 建立時間 (ISO 8601) |
業務邏輯說明
- 僅回傳
enable = true的遊戲商(前台端點自動過濾) - 快取 key 格式:
cache:game:providers或cache:game:providers:{gameType},TTL 1 小時 - 遊戲商以
providerCode區分實際對接的遊戲商 API(目前支援rsg和betsolutions)
前端注意事項 / UI 設計提示
- 前台遊戲大廳應依
gameType分類顯示遊戲商 maintain = true的遊戲商應顯示維護中提示,禁止進入areaBlock = true的遊戲商根據用戶地區判斷是否顯示
POST /api/game/launch — 啟動遊戲(取得遊戲 URL)
功能說明
已登入用戶透過此端點啟動遊戲,後端會根據 gameCode 查詢對應的遊戲供應商設定,自動轉換成遊戲商 API 所需的參數格式,產生含有認證 token 的遊戲 URL 回傳給前端。前端收到 URL 後應以 iframe 或新視窗開啟。啟動前會檢查風控黑名單,若用戶被封鎖則回傳錯誤碼 5010。啟動成功後會非同步記錄玩家遊玩紀錄(UPSERT game-play-log)。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| Header | locales | string | 否 | 語系(預設 zh-TW) | "zh-TW" / "en-US" |
| Body | device | string | 是 | 裝置類型 | "mobile" / "desktop" |
| Body | gameCode | string | 是 | 遊戲商代碼(對應 game-provider.gameCode) | "slot-betsolutions" |
| Body | productId | number | 是 | 遊戲商內的遊戲 ID | 1 |
Body 範例
{
"device": "mobile",
"gameCode": "slot-betsolutions",
"productId": 1
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"url": "https://game-server.example.com/play?token=xxx"
},
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/game/launch"
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 5010 | 用戶已被列入遊戲黑名單,禁止進入遊戲 |
| 401 | 未授權(未登入或 Token 過期) |
業務邏輯說明
- 啟動前呼叫
AdminRiskService.isUserBlockedFromGame()檢查風控黑名單 - 黑名單支援三層封鎖:全封鎖(
gameType=null, productId=null)、類型封鎖(gameType='slot')、特定遊戲封鎖(gameType='slot', productId=123) - 語系由
localesheader 決定(zh-TW/en-US),用於遊戲內介面語言 - 啟動成功後非同步 UPSERT
game-play-log,記錄玩家最近遊玩的遊戲(.catch(() => {})不阻塞主流程) - BetSolutions 遊戲商的
gameType會透過BS_PRODUCT_MAP映射:2(slot) → ProductId 2,9(crypto) → ProductId 3
前端注意事項 / UI 設計提示
- 收到 URL 後建議以 iframe 或新視窗開啟,避免離開當前頁面
- 手機版建議全螢幕 iframe;桌機版可用彈窗或新分頁
- 若收到 5010 錯誤碼,應顯示「您已被限制進入此遊戲」提示
device參數影響遊戲商回傳的 URL(手機版/桌機版介面不同)
POST /api/game/simulate — 模擬遊戲一輪(模擬遊戲商回調)
功能說明
前端按下按鈕觸發一輪模擬遊戲。此端點內部模擬遊戲商 S2S callback 的完整流程:先扣款(debit)、再隨機開獎(RTP 97%)、最後派彩(credit)。玩家餘額會即時變動,交易紀錄會寫入 game-transaction 和 bet-order / bet-detail。此端點主要用於開發測試和 Demo 展示,讓前端無需真正連接遊戲商即可模擬完整的投注流程。投注結算後會自動觸發 VIP 重算、活動打碼量、任務進度更新。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| Body | gameCode | string | 是 | 遊戲商代碼 | "slot-betsolutions" |
| Body | productId | number | 是 | 遊戲 ID | 1 |
| Body | betAmount | number | 是 | 投注金額 (USD,範圍 0.01 ~ 10000) | 10 |
Body 範例
{
"gameCode": "slot-betsolutions",
"productId": 1,
"betAmount": 10
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"roundId": "SIM_1708888888888_abc123",
"betAmount": 10,
"multiplier": 2,
"winAmount": 20,
"profit": 10,
"result": "medium",
"balanceBefore": 200,
"balanceAfter": 210
},
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/game/simulate"
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
roundId | string | 模擬回合 ID(格式:SIM_{timestamp}_{random}) |
betAmount | number | 投注金額 (USD) |
multiplier | number | 開獎倍率 |
winAmount | number | 贏得金額 (USD) |
profit | number | 淨損益 (USD,可為負數) |
result | string | 結果標籤(lose/small/medium/good/big/huge/mega) |
balanceBefore | number | 投注前餘額 |
balanceAfter | number | 投注後餘額 |
業務邏輯說明
- RTP(Return to Player)設定為 97%,賠率表如下:
| 機率 | 倍率 | 標籤 |
|---|---|---|
| 60% | 0x | lose(全輸) |
| 22% | 0.5x | small |
| 10% | 2x | medium |
| 5% | 4x | good |
| 2% | 8x | big |
| 0.8% | 20x | huge |
| 0.2% | 70x | mega(大獎) |
- 模擬成功後自動觸發以下連鎖操作:
VipService.recalculateUserVip()— VIP 等級重算(只升不降)PromoService.updatePromoTurnover()— 優惠打碼量累計MissionService.updateBetProgress()— 任務投注進度
- 模擬成功後非同步記錄
game-play-log
前端注意事項 / UI 設計提示
- 此端點主要用於 Demo 展示,正式環境遊戲由遊戲商回調處理
- 前端可根據
result標籤播放不同的動畫效果 - 投注後應刷新用戶餘額顯示(可用
balanceAfter即時更新) - 建議加入投注金額輸入驗證(0.01 ~ 10000)
POST /api/game/demo — 試玩遊戲(免登入 Demo)
功能說明
不需要登入即可取得遊戲 Demo URL,讓訪客可以體驗遊戲。目前僅支援 BetSolutions 的 slot 類遊戲。前端可在遊戲列表中提供「試玩」按鈕,讓未註冊的用戶也能體驗遊戲樂趣,提升轉化率。
認證需求:無(公開端點)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | locales | string | 否 | 語系(預設 zh-TW) | "en-US" |
| Body | device | string | 是 | 裝置類型 | "mobile" / "desktop" |
| Body | gameCode | string | 是 | 遊戲商代碼 | "slot-betsolutions" |
| Body | productId | number | 是 | 遊戲 ID | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"url": "https://auth-staging.betsolutions.com/auth/auth?..."
},
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/game/demo"
}業務邏輯說明
- 僅支援 BetSolutions slot 類遊戲的 Demo 模式
- Demo 遊戲使用虛擬籌碼,不影響真實餘額
- 不需要風控黑名單檢查(無登入用戶)
- 不記錄遊玩紀錄
前端注意事項 / UI 設計提示
- 在遊戲列表每個遊戲卡片上提供「試玩」和「正式遊戲」兩個按鈕
- 試玩按鈕不需要判斷登入狀態
- 開啟方式同
/game/launch(iframe 或新視窗)
GET /api/game/recent — 取得玩家最近遊玩的遊戲
功能說明
查詢已登入玩家最近遊玩過的遊戲列表。資料來源為 game-play-log 表,每次遊戲啟動或模擬成功後會自動 UPSERT 該表。前端可在遊戲大廳顯示「最近遊玩」區塊,讓玩家快速回到之前玩過的遊戲。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| Query | limit | string | 否 | 回傳筆數(預設 10,最多 20) | "10" |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"gameCode": "slot-rsg",
"productId": "112",
"lastPlayedAt": "2026-02-28T10:30:00.000Z"
},
{
"gameCode": "slot-betsolutions",
"productId": "5",
"lastPlayedAt": "2026-02-27T15:20:00.000Z"
}
],
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/game/recent"
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
gameCode | string | 遊戲商代碼 |
productId | string | 遊戲商內的遊戲 ID |
lastPlayedAt | string | 最後遊玩時間 (ISO 8601) |
業務邏輯說明
limit參數範圍限制:最小 1,最大 20,預設 10- 資料來源為
game-play-log表,按lastPlayedAt降序排列 - 每次遊戲啟動/模擬成功時自動 UPSERT(以
userId + gameCode + productId為唯一鍵)
前端注意事項 / UI 設計提示
- 在遊戲大廳顯示「最近遊玩」橫向滾動列表
- 需搭配
/game/provider的資料來顯示遊戲名稱、圖片等詳細資訊 - 未登入狀態不應顯示此區塊
GET /api/game/type-configs — 取得遊戲分類設定(公開)
功能說明
取得遊戲分類設定列表,供前台側邊欄或遊戲大廳的分類導航使用。此端點為公開端點,不需要登入。回傳的分類設定包含多語系名稱、圖標、排序等資訊,前端可據此渲染遊戲分類選單。
認證需求:無(公開端點)
Request:無參數
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"gameType": 2,
"siteCode": "C9",
"typeKey": "slot",
"label": { "zh-TW": "電子遊戲", "en-US": "Slots" },
"icon": "mdi:slot-machine",
"sortOrder": 1,
"enabled": true,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
],
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/game/type-configs"
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
id | number | 分類設定 ID |
gameType | number | 遊戲類型代碼(1/2/3/4/5/8/9/10) |
siteCode | string | 所屬站點代碼 |
typeKey | string | 類型 key(sports/slot/live/lottery/chess/esports/crypto/fish) |
label | object | 多語系顯示名稱 |
icon | string | 圖標名稱(Iconify 格式) |
sortOrder | number | 排序權重(越小越前) |
enabled | boolean | 是否啟用 |
前端注意事項 / UI 設計提示
- 前台側邊欄遊戲分類應根據
enabled過濾,按sortOrder排序 - 使用
label物件中對應語系的值作為顯示文字 icon為 Iconify 格式,可使用 Iconify 元件渲染
GET /api/game/admin/providers — [Admin] 取得遊戲供應商列表
功能說明
後台管理員取得遊戲供應商列表。與前台 /game/provider 不同,此端點回傳完整欄位(含 name 多語系、siteCode、sortOrder),且支援依站點篩選。搭配 @AdminSiteCode() 裝飾器,自動讀取 x-site-code header 或 siteCode query param。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| Header | x-site-code | string | 否 | 站點代碼(由 API Client 自動注入) | "C9" |
| Query | gameType | string | 否 | 遊戲類型篩選 | "2" |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"gameCode": "slot-rsg",
"providerCode": "rsg",
"gameType": 2,
"name": { "zh-TW": "RSG電子", "en-US": "RSG Slot" },
"areaBlock": false,
"maintain": false,
"enable": true,
"siteCode": "C9",
"sortOrder": 1
}
],
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/game/admin/providers"
}業務邏輯說明
siteCode篩選邏輯:Header 選「全部站點」時不帶x-site-code,前端手動傳siteCodequery param;選獨立站點時 API Client 自動帶x-site-codeheadername欄位為 JSON 格式的多語系名稱,後台編輯時應支援多語系輸入
前端注意事項 / UI 設計提示
- 後台遊戲供應商管理頁面應使用
SiteTabs多站點切換 - 列表支援新增、編輯、刪除操作
- 支援「同預設站點」一鍵複製和「帶入模板」功能
POST /api/game/admin/providers — [Admin] 新增遊戲供應商
功能說明
後台管理員新增遊戲供應商。每個站點的 gameCode 必須唯一(game-provider 表有 siteCode + gameCode 的唯一約束)。新增時需指定遊戲代碼、供應商代碼、遊戲類型等基本資訊。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| Body | gameCode | string | 是 | 遊戲代碼(站內唯一) | "slot-rsg" |
| Body | providerCode | string | 是 | 供應商代碼 | "rsg" |
| Body | gameType | number | 是 | 遊戲類型 | 2 |
| Body | name | object | 否 | 多語系名稱 | { "zh-TW": "RSG電子", "en-US": "RSG Slot" } |
| Body | siteCode | string | 否 | 站點代碼 | "C9" |
| Body | enable | boolean | 否 | 啟用狀態 | true |
| Body | sortOrder | number | 否 | 排序 | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 5,
"gameCode": "slot-rsg",
"providerCode": "rsg",
"gameType": 2,
"name": { "zh-TW": "RSG電子", "en-US": "RSG Slot" },
"siteCode": "C9",
"enable": true,
"sortOrder": 1,
"createdAt": "2026-03-01T10:00:00.000Z"
}
}PATCH /api/game/admin/providers/:id — [Admin] 更新遊戲供應商
功能說明
更新指定 ID 的遊戲供應商資訊。可部分更新(僅傳需要修改的欄位)。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| URL Param | id | number | 是 | 遊戲供應商 ID | 1 |
| Body | (同新增,所有欄位皆為選填) |
DELETE /api/game/admin/providers/:id — [Admin] 刪除遊戲供應商
功能說明
刪除指定 ID 的遊戲供應商。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| URL Param | id | number | 是 | 遊戲供應商 ID | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": null
}GET /api/game/admin/type-configs — [Admin] 取得遊戲分類設定列表
功能說明
後台管理員取得遊戲分類設定列表,支援依站點篩選。搭配 @AdminSiteCode() 裝飾器自動讀取站點代碼。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| Header | x-site-code | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"gameType": 2,
"siteCode": "C9",
"typeKey": "slot",
"label": { "zh-TW": "電子遊戲", "en-US": "Slots" },
"icon": "mdi:slot-machine",
"sortOrder": 1,
"enabled": true,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
]
}POST /api/game/admin/type-configs — [Admin] 新增遊戲分類設定
功能說明
新增遊戲分類設定。每個站點的 gameType 必須唯一(game-type-config 表有 siteCode + gameType 的唯一約束)。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| Body | gameType | number | 是 | 遊戲類型 | 2 |
| Body | name | object | 否 | 多語系名稱 | { "zh-TW": "電子遊戲", "en-US": "Slots" } |
| Body | siteCode | string | 否 | 站點代碼 | "C9" |
| Body | enable | boolean | 否 | 啟用狀態 | true |
| Body | sortOrder | number | 否 | 排序 | 1 |
PATCH /api/game/admin/type-configs/:id — [Admin] 更新遊戲分類設定
功能說明
更新指定 ID 的遊戲分類設定。可部分更新。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| URL Param | id | number | 是 | 遊戲分類設定 ID | 1 |
DELETE /api/game/admin/type-configs/:id — [Admin] 刪除遊戲分類設定
功能說明
刪除指定 ID 的遊戲分類設定。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
GET /api/game/admin/preview-template — [Admin] 預覽遊戲模板資料
功能說明
回傳預設遊戲模板的 typeConfigs 和 providers 資料,僅供預覽,不寫入資料庫。後台管理員可在「帶入模板」功能中先預覽模板內容,確認無誤後再執行帶入。前端可提供模板預覽對話框,讓管理員在確認前檢查並編輯模板資料。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request:無參數(僅需 Admin Bearer Token)
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"typeConfigs": [
{ "gameType": 1, "typeKey": "sports", "label": { "zh-TW": "體育", "en-US": "Sports" }, "icon": "mdi:basketball", "sortOrder": 1 }
],
"providers": [
{ "gameCode": "slot-rsg", "providerCode": "rsg", "gameType": 2, "name": { "zh-TW": "RSG電子", "en-US": "RSG Slot" }, "sortOrder": 1 }
]
}
}POST /api/game/admin/load-template — [Admin] 帶入遊戲預設模板
功能說明
將遊戲模板資料帶入指定站點。可傳入自訂資料(經管理員在預覽對話框中編輯後),不傳則使用預設模板。寫入時依 x-site-code header 決定目標站點,null 則寫入全站(預設站點代碼)。操作在 transaction 內執行,先刪除目標站點現有資料再插入模板資料,確保原子性。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| Header | x-site-code | string | 否 | 目標站點代碼 | "C9" |
| Body | typeConfigs | array | 否 | 自訂分類設定(不傳用預設) | [...] |
| Body | providers | array | 否 | 自訂供應商設定(不傳用預設) | [...] |
Response 範例
{
"code": 200,
"message": "ok",
"result": { "typeConfigs": 8, "providers": 12 }
}業務邏輯說明
- Transaction 內先刪後插,確保原子性
- 僅影響指定站點的資料,不會影響其他站點
- 帶入後自動清除相關 Redis 快取
POST /api/game/admin/copy-site-data — [Admin] 同預設站點(跨站複製)
功能說明
將來源站點的遊戲資料複製到目標站點。支援複製遊戲供應商(providers)或遊戲分類設定(typeConfigs)。操作在 transaction 內執行,先刪除目標站點現有資料再從來源站點複製。這是後台「同預設站點」按鈕的核心 API。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| Body | sourceSiteCode | string | 是 | 來源站點代碼 | "C9" |
| Body | targetSiteCode | string | 是 | 目標站點代碼 | "SITE_A" |
| Body | type | string | 是 | 複製類型 | "providers" 或 "typeConfigs" |
Body 範例
{
"sourceSiteCode": "C9",
"targetSiteCode": "SITE_A",
"type": "providers"
}Response 範例
{
"code": 200,
"message": "ok",
"result": { "copied": 12 }
}業務邏輯說明
- Transaction 內先刪後插,確保原子性
- 複製時會將
siteCode替換為目標站點代碼 - 複製完成後自動清除目標站點的 Redis 快取
前端注意事項 / UI 設計提示
- 後台
SiteTabs的非預設站 tab 會出現「同預設站點」按鈕 - 點擊後應彈出確認對話框,顯示來源和目標站點代碼
- 複製成功後自動刷新當前列表
VIP 會員模組
路由前綴:
/api/vipVIP 模組負責會員等級管理、返水(Rebate)規則配置、反水結算、月度保級檢查等功能。系統採每站獨立配置,每個站點可自行設定 VIP 等級數量和返水規則。投注累積自動升級(只升不降),每日 00:05 自動結算反水,每月 1 號 01:00 進行保級檢查。
相關資料表
| 資料表 | 說明 |
|---|---|
vip-level | VIP 等級配置(含 siteCode,每站獨立) |
vip-rebate | 返水規則(unique key: siteCode + level + gameType) |
vip-rebate-log | 反水結算紀錄 |
自動排程
| 排程 | 時間 | 說明 |
|---|---|---|
| 每日反水結算 | 每日 00:05 | 結算昨日各用戶的反水金額並發放到餘額 |
| 月度保級檢查 | 每月 1 號 01:00 | 檢查 VIP 2+ 用戶是否達到保級門檻 |
GET /api/vip/levels — 取得 VIP 等級列表
功能說明
取得指定站點的 VIP 等級列表。此端點為公開端點,前台 VIP 頁面、個人中心、以及任何需要顯示 VIP 等級資訊的頁面都應呼叫此 API。回傳結果會經 Redis 快取(1 小時 TTL)。站點代碼來源優先順序:siteCode query param > site-name header > 環境變數 SITE_CODE。多語系名稱會依當前語系自動解析。
認證需求:無(公開端點)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | site-name | string | 否 | 站點名稱(前台自動帶入) | "C9" |
| Query | siteCode | string | 否 | 站點代碼(優先於 header) | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"level": 1,
"name": "Bronze I",
"tier": "bronze",
"minChip": "0.000000",
"relegationChip": "0.000000",
"sortOrder": 1,
"enabled": 1,
"siteCode": "C9",
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
],
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/vip/levels"
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
id | number | VIP 等級 ID |
level | number | VIP 等級編號 |
name | string | 等級名稱(已依語系解析,非原始 JSON) |
tier | string | 階級(bronze/gold/platinum/diamond) |
minChip | string | 升級所需最低累計籌碼 (USD, decimal(18,6)) |
relegationChip | string | 保級所需月度投注額 (USD, decimal(18,6)) |
sortOrder | number | 排序權重 |
enabled | number | 是否啟用(0/1) |
siteCode | string | 所屬站點代碼 |
業務邏輯說明
- 僅回傳
enabled = 1的等級 - 按
sortOrder升序排列 - 快取 key:
cache:vip:levels:{siteCode},TTL 1 小時 name欄位原始值為 JSON({"zh-TW":"青銅 I","en-US":"Bronze I"}),由resolveText()自動解析為當前語系文字
前端注意事項 / UI 設計提示
- VIP 等級數量為動態的(不限 15 級),前端不應硬編碼等級數量
- 前台 VIP 相關下拉選項(如篩選 VIP 等級)均應使用此 API 動態取得
- 可用
tier欄位決定對應的色彩主題(bronze=銅色、gold=金色、platinum=白金、diamond=鑽石)
GET /api/vip/rebates — 取得返水規則
功能說明
取得指定站點的 VIP 返水規則列表。每筆規則定義了特定 VIP 等級在特定遊戲類型下的返水比例。前台 VIP 頁面可用此資料展示各等級的返水比例表格。
認證需求:無(公開端點)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | site-name | string | 否 | 站點名稱 | "C9" |
| Query | siteCode | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"level": 1,
"gameType": "live",
"rebateRate": "0.50",
"siteCode": "C9",
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
},
{
"id": 2,
"level": 1,
"gameType": "slot",
"rebateRate": "0.50",
"siteCode": "C9"
}
]
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
level | number | VIP 等級 |
gameType | string | 遊戲類型標籤(sports/slot/live/lottery/chess/esports/crypto/fish) |
rebateRate | string | 返水比例 (%, decimal(5,2)),例如 "0.50" 代表 0.5% |
業務邏輯說明
- 快取 key:
cache:vip:rebates:{siteCode},TTL 1 小時 - 返水比例為百分比,0.50 = 0.5%
- 預設模板為 15 級 x 8 遊戲類型 = 120 筆規則
前端注意事項 / UI 設計提示
- 前台 VIP 頁面可用矩陣表格展示:行=VIP 等級、列=遊戲類型、格子=返水比例
- 返水比例單位為
%,顯示時加上百分號
GET /api/vip/status — 取得用戶 VIP 狀態
功能說明
取得當前登入用戶的完整 VIP 狀態資訊,包含當前等級、累積有效投注、升級進度、本月有效投注、返水比例、保級資訊、以及全部等級列表。此端點是前台個人中心 VIP 資訊的核心 API,前端可據此渲染 VIP 等級卡片、進度條、返水表格等 UI 元件。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| Header | site-name | string | 否 | 站點名稱 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"level": 1,
"name": "Bronze I",
"tier": "bronze",
"totalEffectiveBet": "1200.000000",
"currentChip": "0.000000",
"nextLevelMinChip": "3600.000000",
"progress": "0.333333",
"relegationChip": "0.000000",
"monthlyEffective": "450.000000",
"relegationMissCount": 0,
"vipHold": 0,
"rebates": [
{ "gameType": "sports", "rebateRate": "0.50" },
{ "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": "Bronze I",
"tier": "bronze",
"minChip": "0.000000",
"relegationChip": "0.000000"
},
{
"level": 2,
"name": "Bronze II",
"tier": "bronze",
"minChip": "3600.000000",
"relegationChip": "200.000000"
}
]
}
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
level | number | 當前 VIP 等級 |
name | string | 等級名稱(已依語系解析) |
tier | string | 階級(bronze/gold/platinum/diamond) |
totalEffectiveBet | string | 歷史累計有效投注 (USD) |
currentChip | string | 當前等級所需最低籌碼 |
nextLevelMinChip | string | null | 下一等級所需最低籌碼(已滿級時為 null) |
progress | string | 升級進度(0~1 之間的小數,如 0.333333 = 33.33%) |
relegationChip | string | 當前等級保級所需月度投注額 |
monthlyEffective | string | 本月已累計的有效投注(即時查詢) |
relegationMissCount | number | 連續未達保級門檻的月數(0=正常、1=警告中) |
vipHold | number | 保級鎖定狀態(0=未鎖定、1=已鎖定) |
rebates | array | 當前等級的返水比例列表 |
allLevels | array | 全部 VIP 等級列表(用於進度條渲染) |
業務邏輯說明
monthlyEffective為即時查詢(非快取),確保資料即時性progress計算方式:totalEffectiveBet / nextLevelMinChip,最大為 1- 已滿級時
nextLevelMinChip = null,progress = 1 relegationMissCount每月保級檢查時更新:達標重置為 0,未達標 +1,連續 2 月未達則降級
前端注意事項 / UI 設計提示
- 使用
progress渲染升級進度條 relegationMissCount = 1時應顯示保級警告提示vipHold = 1時顯示「保級鎖定」標章(VIP 5+ 才能鎖定)- 用
allLevels渲染完整等級路線圖,高亮當前等級 rebates陣列渲染為返水比例表格
POST /api/vip/settlement/daily-rebate — [Admin] 手動觸發每日反水結算
功能說明
手動觸發每日反水結算作業(通常由 Cron 排程於每日 00:05 自動執行)。結算昨日各用戶在各遊戲類型的有效投注,依 VIP 等級對應的返水比例計算反水金額,直接發放到用戶餘額,同時寫入 vip-rebate-log 結算紀錄。此端點供後台管理員在特殊情況下(如結算異常、補結算)手動觸發。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"usersProcessed": 25,
"totalRebate": "3.120000"
}
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
usersProcessed | number | 處理的用戶數量 |
totalRebate | string | 發放的反水總額 (USD) |
業務邏輯說明
- 結算昨日(UTC+8 00:00~23:59:59)的投注資料
- 計算公式:
反水金額 = 有效投注 x (返水比例 / 100) - 反水金額使用
truncateUsd()無條件捨去至 6 位小數 - 結算流程:
- 查詢昨日各用戶各遊戲類型的有效投注合計
- 批次載入所有相關用戶的 VIP 等級
- 查詢所有返水規則建立 map(key:
siteCode-level-gameType) - 逐用戶計算反水金額,發放到餘額
- 寫入
vip-rebate-log紀錄
POST /api/vip/settlement/monthly-relegation — [Admin] 手動觸發月度保級檢查
功能說明
手動觸發月度保級檢查(通常由 Cron 排程於每月 1 號 01:00 自動執行)。檢查所有 VIP 2 級以上用戶上月的有效投注是否達到保級門檻。連續第 1 月未達標僅發出警告(relegationMissCount +1),連續第 2 月未達標則降 1 級。VIP 5 級以上且已鎖定保級(vipHold = 1)的用戶會跳過檢查。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"checked": 10,
"warned": 2,
"demoted": 1
}
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
checked | number | 檢查的用戶數量(VIP 2+ 用戶) |
warned | number | 發出警告的用戶數量(第 1 月未達標) |
demoted | number | 降級的用戶數量(連續 2 月未達標) |
業務邏輯說明
- 僅檢查 VIP 等級 >= 2 的用戶(VIP 1 級為最低等級,不需保級)
vipHold = 1且 VIP >= 5 的用戶跳過保級檢查- 保級規則:
- 上月有效投注 >=
relegationChip→ 達標,relegationMissCount重置為 0 - 第 1 月未達標 →
relegationMissCount= 1(警告) - 連續第 2 月未達標 → 降 1 級,
relegationMissCount重置為 0
- 上月有效投注 >=
- 降級後最低只能降到 VIP 1 級
PATCH /api/vip/users/:userId/hold — [Admin] 設定 VIP 保級鎖定
功能說明
手動鎖定或解鎖指定用戶的 VIP 保級狀態。僅 VIP 5 級以上的用戶可以設定保級鎖定。鎖定後月度保級檢查會跳過該用戶,不會因未達投注門檻而降級。此功能供後台管理員對 VIP 高等級用戶進行特殊維護。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| URL Param | userId | number | 是 | 用戶 ID | 1 |
| Body | hold | number | 是 | 鎖定狀態(0=解鎖、1=鎖定) | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"userId": 1,
"vipHold": 1
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 用戶不存在 |
| 2002 | VIP 等級未達 5,不可設定保級鎖定 |
POST /api/vip/levels — [Admin] 建立 VIP 等級
功能說明
建立新的 VIP 等級。每個站點的 VIP 等級編號必須唯一。搭配 @AdminSiteCode() 裝飾器,自動將新等級歸屬到指定站點。建立成功後自動清除該站點的 VIP 等級快取。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Admin Bearer Token | Bearer eyJhbG... |
| Header | x-site-code | string | 否 | 站點代碼 | "C9" |
| Body | level | number | 是 | VIP 等級編號(>= 1) | 1 |
| Body | name | object | 是 | 多語系名稱 | { "zh-TW": "青銅 I", "en-US": "Bronze I" } |
| Body | tier | string | 是 | 階級 | "bronze" / "gold" / "platinum" / "diamond" |
| Body | minChip | number | 是 | 升級所需最低累計籌碼 (USD) | 3600 |
| Body | relegationChip | number | 是 | 保級所需月度投注額 (USD) | 200 |
| Body | sortOrder | number | 否 | 排序權重(預設 = level) | 1 |
| Body | enabled | number | 否 | 是否啟用(0/1,預設 1) | 1 |
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2003 | 此 VIP 等級已存在(同站點同 level 重複) |
PATCH /api/vip/levels/:id — [Admin] 更新 VIP 等級
功能說明
更新指定 ID 的 VIP 等級。可部分更新(使用 PartialType)。更新成功後自動清除該站點的 VIP 等級快取。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| URL Param | id | number | 是 | VIP 等級 ID | 1 |
| Body | (同建立,所有欄位皆為選填) |
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此 VIP 等級 |
DELETE /api/vip/levels/:id — [Admin] 刪除 VIP 等級
功能說明:刪除指定 ID 的 VIP 等級。刪除成功後自動清除該站點的 VIP 等級快取。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 查無此 VIP 等級 |
POST /api/vip/rebates — [Admin] 建立返水規則
功能說明
建立新的返水規則。每個站點的 level + gameType 組合必須唯一。搭配 @AdminSiteCode() 裝飾器自動歸屬站點。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | x-site-code | string | 否 | 站點代碼 | "C9" |
| Body | level | number | 是 | VIP 等級 | 1 |
| Body | gameType | string | 是 | 遊戲類型 | "slot" |
| Body | rebateRate | number | 是 | 返水比例 (%) | 0.5 |
gameType 可選值:sports、slot、live、lottery、chess、esports、crypto、fish
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2004 | 此等級的遊戲類型返水規則已存在 |
PATCH /api/vip/rebates/:id — [Admin] 更新返水規則
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2002 | 查無此返水規則 |
DELETE /api/vip/rebates/:id — [Admin] 刪除返水規則
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2002 | 查無此返水規則 |
POST /api/vip/rebates/bulk — [Admin] 批次更新返水規則
功能說明
批次新增或更新(upsert)返水規則。依 siteCode + level + gameType 判斷是新增還是更新。此端點適用於後台返水設定頁面的「批次儲存」操作,一次送出所有等級 x 遊戲類型的返水規則。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | x-site-code | string | 否 | 站點代碼 | "C9" |
| Body | items | array | 是 | 返水規則陣列 | 見下方 |
Body 範例
{
"items": [
{ "level": 1, "gameType": "sports", "rebateRate": 0.2 },
{ "level": 1, "gameType": "slot", "rebateRate": 0.5 },
{ "level": 1, "gameType": "live", "rebateRate": 0.5 },
{ "level": 2, "gameType": "sports", "rebateRate": 0.25 },
{ "level": 2, "gameType": "slot", "rebateRate": 0.55 }
]
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"affected": 5
}
}業務邏輯說明
- 使用 TypeORM
upsert()方法,以['siteCode', 'level', 'gameType']為衝突鍵 - 已存在的規則會更新
rebateRate,不存在的會新增 - 操作完成後自動清除該站點的返水規則快取
GET /api/vip/preview-template — [Admin] 預覽 VIP 模板資料
功能說明
回傳預設 VIP 模板資料,包含 15 個等級和 120 筆返水規則(15 級 x 8 遊戲類型),僅供預覽,不寫入資料庫。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"levels": [
{ "level": 1, "name": { "zh-TW": "青銅 I", "en-US": "Bronze I" }, "tier": "bronze", "minChip": "0.000000", "relegationChip": "0.000000", "sortOrder": 1 },
{ "level": 2, "name": { "zh-TW": "青銅 II", "en-US": "Bronze II" }, "tier": "bronze", "minChip": "3600.000000", "relegationChip": "200.000000", "sortOrder": 2 }
],
"rebates": [
{ "level": 1, "gameType": "sports", "rebateRate": "0.20" },
{ "level": 1, "gameType": "slot", "rebateRate": "0.50" }
]
}
}業務邏輯說明
- 預設模板包含 4 個階級:bronze(1~6)、gold(7~9)、platinum(10~12)、diamond(13~15)
- 返水比例隨等級遞增,從 VIP 1 的 0.2%~0.5% 到 VIP 15 的 0.8%~1.5%
POST /api/vip/load-template — [Admin] 帶入 VIP 模板
功能說明
將 VIP 模板資料帶入指定站點。在 transaction 內先刪除目標站點的現有 VIP 等級和返水規則,再插入模板資料。可傳入管理員編輯後的自訂資料,不傳則使用預設模板。
認證需求:需要後台管理員登入(AdminJwtAuthGuard)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | x-site-code | string | 否 | 目標站點代碼 |
| Body | levels | array | 否 | 自訂等級資料(不傳用預設) |
| Body | rebates | array | 否 | 自訂返水規則(不傳用預設) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"levels": 15,
"rebates": 120
}
}BetRecord 投注紀錄模組
路由前綴:
/api/bet-record投注紀錄模組提供前台用戶查詢自己的投注歷史,包含訂單列表(含匯總統計)和小注單明細。投注資料由遊戲模組的 S2S 回調或模擬投注自動寫入。
相關資料表
| 資料表 | 說明 |
|---|---|
bet-order | 投注訂單(主單) |
bet-detail | 投注明細(小注單,關聯到 bet-order) |
GET /api/bet-record — 投注紀錄列表
功能說明
查詢當前登入用戶的投注紀錄列表,支援分頁、狀態篩選、遊戲類型篩選、日期範圍篩選。除了分頁的訂單列表外,還會同時回傳匯總統計(總投注次數、投注金額、有效投注、輸贏),匯總統計僅計算有效注單(status = 'valid')。前台投注紀錄頁面的核心 API。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| Query | page | string | 否 | 頁碼(預設 1) | "1" |
| Query | pageSize | string | 否 | 每頁筆數(預設 10,最大 50) | "10" |
| Query | status | string | 否 | 注單狀態篩選 | "valid" / "invalid" / "cancelled" |
| Query | gameType | string | 否 | 遊戲類型篩選 | "2" |
| Query | startDate | string | 否 | 開始日期 (YYYY-MM-DD) | "2026-01-01" |
| Query | endDate | string | 否 | 結束日期 (YYYY-MM-DD) | "2026-02-28" |
Response 範例
{
"code": 200,
"message": "ok",
"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",
"betDatetime": "2026-02-11 20:39:00",
"gameName": "RSG電子"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 6,
"totalPages": 1
}
},
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/bet-record"
}回傳欄位說明(頂層匯總)
| 欄位 | 型別 | 說明 |
|---|---|---|
totalBetCount | number | 有效注單的總投注次數合計 |
betAmount | string | 有效注單的投注金額合計 (USD) |
betEffective | string | 有效注單的有效投注合計 (USD) |
winLose | string | 有效注單的淨輸贏合計 (USD,負數=虧損) |
回傳欄位說明(items 陣列)
| 欄位 | 型別 | 說明 |
|---|---|---|
id | number | 訂單 ID |
gameType | string | 遊戲類型代碼(數字字串) |
gamePlatform | string | 遊戲平台代碼 |
gameNumber | string | 遊戲商方注單編號 |
totalBetCount | number | 該訂單內的投注次數 |
betAmount | string | 投注金額 (USD, decimal(18,6)) |
betEffective | string | 有效投注 (USD) |
winLose | string | 淨輸贏 (USD) |
betDatetime | string | 投注時間 |
gameName | string | 遊戲名稱 |
業務邏輯說明
- 匯總統計(頂層 4 個欄位)僅計算
status = 'valid'的注單,且受gameType和日期範圍篩選影響 - 列表查詢支援所有狀態篩選(
valid/invalid/cancelled) - 分頁使用
parsePagination()工具函數,pageSize最大限制 50 - 日期範圍使用
applyDateRange()工具函數 - 結果按
betDatetime降序排列(最新的在前)
前端注意事項 / UI 設計提示
- 頁面頂部顯示匯總統計卡片(總投注次數、投注金額、有效投注、輸贏)
winLose為負數時以紅色顯示,正數以綠色顯示- 提供
status、gameType、日期範圍三個篩選條件 - 每筆訂單可點擊展開查看小注單明細(呼叫
GET /:orderId/details) - 金額顯示保留 2~6 位小數(依設計需求截斷)
GET /api/bet-record/:orderId/details — 訂單小注單明細
功能說明
根據訂單 ID 取得該訂單內的所有小注單(bet-detail)明細。前端在投注紀錄列表中點擊某筆訂單後呼叫此 API 取得詳細的每輪投注資訊。系統會驗證該訂單是否屬於當前登入用戶,不屬於則回傳錯誤碼 2001。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| URL Param | orderId | number | 是 | 訂單 ID | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"roundNo": 1,
"betAmount": "0.020000",
"winLose": "0.500000",
"createdAt": "2026-02-11T12:39:01.000Z"
},
{
"id": 2,
"roundNo": 2,
"betAmount": "0.040000",
"winLose": "-0.040000",
"createdAt": "2026-02-11T12:39:05.000Z"
}
],
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/bet-record/1/details"
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
id | number | 小注單 ID |
roundNo | number | 回合序號 |
betAmount | string | 該回合投注金額 (USD) |
winLose | string | 該回合淨輸贏 (USD) |
createdAt | string | 回合時間 (ISO 8601) |
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 2001 | 訂單不存在或不屬於該用戶 |
業務邏輯說明
- 安全檢查:同時查詢
orderId和userId,確保用戶只能查看自己的訂單 - 小注單按
roundNo升序排列 - 僅回傳
id、roundNo、betAmount、winLose、createdAt五個欄位
前端注意事項 / UI 設計提示
- 在投注紀錄列表中,點擊訂單行可展開 accordion 或彈出 dialog 顯示小注單
- 每筆小注單顯示回合序號、投注金額、輸贏
winLose以顏色區分正負值
Ranking 排行榜模組
路由前綴:
/api/ranking排行榜模組提供前台的投注排行和累積提領排行功能。支援即時、每日、每週、每月、累積五種類型。排行榜資料同時包含真實數據和假資料填充(當真實數據不足時)。匿名用戶的名稱會以翻譯後的「隱身」文字替代。
相關資料表
| 資料表 | 說明 |
|---|---|
rank-list | 排行榜資料(含投注金額、倍率、支付金額、匿名狀態) |
GET /api/ranking — 取得排行榜
功能說明
根據排行榜類型回傳對應的排行資料。支援 5 種類型:realtime(即時投注)、daily(每日)、weekly(每週)、monthly(每月)、total(累積提領)。前 4 種類型回傳投注明細格式(含遊戲名稱、投注金額、倍率、支付金額),total 類型回傳累積提領格式(含玩家帳號、累積支付總額)。此端點為公開端點,供前台首頁或排行榜頁面使用。資料不足時會用假資料填充至指定筆數。
認證需求:無(公開端點)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Query | type | string | 否 | 排行榜類型(預設 realtime) | "realtime" |
| Query | limit | string | 否 | 筆數(預設 20,最大 50) | "20" |
type 可選值:
| 值 | 說明 | 排序方式 | 時間範圍 |
|---|---|---|---|
realtime | 即時投注 | 時間降序(最新在前) | 不限(最近 30 分鐘的假資料填充) |
daily | 每日排行 | 支付金額降序 | 今天 00:00 ~ 現在 |
weekly | 每週排行 | 支付金額降序 | 最近 7 天 |
monthly | 每月排行 | 支付金額降序 | 最近 30 天 |
total | 累積提領 | 累積支付降序 | 全部時間 |
Response 範例(realtime / daily / weekly / monthly)
{
"code": 200,
"message": "ok",
"result": [
{
"rank": 1,
"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
},
{
"rank": 2,
"id": 3,
"gameName": "Sweet Bonanza",
"playerName": "ja***s88",
"time": "2026-02-22T14:25:00.000Z",
"betAmount": "500.000000",
"multiplier": "3.50",
"payout": "1750.000000",
"isAnonymous": false
}
]
}Response 範例(total 累積提領)
{
"code": 200,
"message": "ok",
"result": [
{
"rank": 1,
"playerAccount": "ja***s88",
"totalPayout": "125000.000000"
},
{
"rank": 2,
"playerAccount": "lu***y07",
"totalPayout": "98000.000000"
}
]
}回傳欄位說明(投注類型:realtime / daily / weekly / monthly)
| 欄位 | 型別 | 說明 |
|---|---|---|
rank | number | 排名(從 1 開始) |
id | number | 排行紀錄 ID(假資料為負數) |
gameName | string | 遊戲名稱 |
playerName | string | 玩家名稱(匿名時顯示翻譯後的「隱身」) |
time | string | 投注時間 (ISO 8601) |
betAmount | string | 投注金額 (USD) |
multiplier | string | 倍率 |
payout | string | 支付金額 (USD) |
isAnonymous | boolean | 是否匿名 |
回傳欄位說明(累積類型:total)
| 欄位 | 型別 | 說明 |
|---|---|---|
rank | number | 排名 |
playerAccount | string | 玩家帳號 |
totalPayout | string | 累積支付總額 (USD) |
業務邏輯說明
realtime類型不做快取,每次請求都即時查詢- 其他類型快取 30 秒(key 格式:
cache:ranking:{type}:{limit}:{lang}) - 真實資料不足時用假資料填充至
limit筆,假資料特徵:id為負數(-(index + 1))- 使用 SeededRandom(確定性偽隨機數)產生,同一 cache 週期內結果穩定
- 假資料的投注金額分布:60% 在 $50~$500、25% 在 $500~$2000、10% 在 $2000~$5000、5% 在 $5000~$10000
- 假資料的倍率分布:15% 為 0(全輸)、50% 在 0.3x~2x、20% 在 2x~5x、10% 在 5x~10x、5% 在 10x~50x(大獎)
- 匿名用戶(
isAnonymous = true)的playerName使用 i18n 翻譯的「隱身」文字 total類型按userId分組,加總payout後排序
前端注意事項 / UI 設計提示
- 排行榜頁面提供 Tab 切換 5 種類型
realtime類型可加入自動刷新機制(每 10~30 秒)- 匿名用戶可用特殊圖示或遮罩效果表示
- 排名前 3 名可使用特殊樣式(金/銀/銅牌)
id < 0的紀錄為假資料填充,前端不需要特別處理(對用戶透明)total類型的 UI 格式與投注類型不同(只有帳號和累積支付)
Mission 任務模組
路由前綴:
/api/mission任務模組提供每日/每週/每月的存款任務和投注任務。用戶達成任務門檻後可領取獎勵,獎勵會直接發放到餘額。部分任務要求完成打碼量(turnover),未完成打碼量的獎勵處於「pending」狀態。投注結算後和存款確認後會自動更新對應的任務進度。
相關資料表
| 資料表 | 說明 |
|---|---|
mission | 任務定義(category/periodType/tier/門檻/獎勵) |
mission-progress | 任務進度(userId/periodType/periodKey/累計存款/累計投注) |
mission-claim | 任務領取紀錄(含打碼量追蹤) |
任務類別與週期
| 類別 (category) | 說明 |
|---|---|
deposit | 存款任務 — 累計存款金額達門檻 |
bet | 投注任務 — 累計有效投注達門檻 |
| 週期 (periodType) | 說明 | periodKey 格式 |
|---|---|---|
daily | 每日任務 | 2026-03-01 |
weekly | 每週任務 | 2026-W09 (ISO week) |
monthly | 每月任務 | 2026-03 |
GET /api/mission — 取得任務列表(含進度與領取狀態)
功能說明
取得所有啟用中的任務列表。若用戶已登入,會同時回傳每個任務的當前進度、是否已領取、是否可領取、VIP 等級是否符合。若未登入(使用 OptionalJwtAuthGuard),仍會回傳任務列表但進度相關欄位為預設值。前台任務頁面的核心 API,載入頁面時即應呼叫。任務列表會經 Redis 快取(1 小時 TTL),但進度資料為即時查詢。
認證需求:可選登入(OptionalJwtAuthGuard,未登入也能呼叫但無進度資料)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 否 | Bearer Token(可選) | Bearer eyJhbG... |
Response 範例(已登入)
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"category": "deposit",
"periodType": "daily",
"tier": 1,
"threshold": "10.000000",
"rewardAmount": "0.300000",
"vipRequired": 0,
"turnoverMultiplier": "3.00",
"enabled": 1,
"siteCode": "C9",
"currentProgress": "50.000000",
"isClaimed": false,
"isClaimable": true,
"meetsVip": true
},
{
"id": 2,
"category": "bet",
"periodType": "daily",
"tier": 1,
"threshold": "100.000000",
"rewardAmount": "1.000000",
"vipRequired": 3,
"turnoverMultiplier": "5.00",
"enabled": 1,
"siteCode": "C9",
"currentProgress": "75.000000",
"isClaimed": false,
"isClaimable": false,
"meetsVip": false
}
]
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
id | number | 任務 ID |
category | string | 任務類別(deposit / bet) |
periodType | string | 週期類型(daily / weekly / monthly) |
tier | number | 階級(1~5,同類同週期的不同門檻等級) |
threshold | string | 門檻金額 (USD) |
rewardAmount | string | 獎勵金額 (USD) |
vipRequired | number | VIP 最低等級要求(0=無要求) |
turnoverMultiplier | string | 打碼量倍數(領取後需完成 獎勵 x 倍數 的投注) |
currentProgress | string | null | 當前累計進度 (USD)。未登入時為 null |
isClaimed | boolean | 本期是否已領取 |
isClaimable | boolean | 是否可領取(!isClaimed && 進度 >= 門檻 && VIP 等級達標) |
meetsVip | boolean | VIP 等級是否符合要求 |
業務邏輯說明
- 任務列表(
mission表)使用 Redis 快取,key:cache:mission:list,TTL 1 小時 - 進度查詢為即時資料(不快取),批次查詢
mission-progress和mission-claim isClaimable的判斷條件:!isClaimed && currentProgress >= threshold && meetsVip- 未登入時:
currentProgress = null、isClaimed = false、isClaimable = false、meetsVip = true - 任務按
category ASC, periodType ASC, tier ASC排序
前端注意事項 / UI 設計提示
- 任務頁面可分 Tab 展示:存款任務 / 投注任務
- 每個任務卡片顯示:門檻、獎勵、進度條(
currentProgress / threshold) isClaimable = true時高亮顯示「領取」按鈕isClaimed = true時顯示「已領取」標記meetsVip = false時顯示「需 VIP x 級」提示- 未登入時顯示「登入後查看進度」提示
GET /api/mission/claims — 取得任務領取紀錄
功能說明
查詢當前登入用戶的任務領取紀錄,支援分頁、打碼量狀態篩選(pending=未完成打碼 / completed=已完成)、日期範圍篩選。回傳結果包含任務類別、週期、獎勵金額、打碼量進度等資訊。前台用於「我的任務獎勵紀錄」頁面。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| Query | page | string | 否 | 頁碼(預設 1) | "1" |
| Query | pageSize | string | 否 | 每頁筆數(預設 10,最大 50) | "10" |
| Query | tab | string | 否 | 篩選:pending=未完成打碼、completed=已完成 | "pending" |
| Query | startDate | string | 否 | 開始日期 (YYYY-MM-DD) | "2026-01-01" |
| Query | endDate | string | 否 | 結束日期 (YYYY-MM-DD) | "2026-12-31" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"missionId": 3,
"category": "deposit",
"periodType": "daily",
"tier": 1,
"periodKey": "2026-02-28",
"rewardAmount": "0.300000",
"requiredTurnover": "0.900000",
"completedTurnover": "0.500000",
"turnoverCompleted": 0,
"claimedAt": "2026-02-28T08:30:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 5,
"totalPages": 1
}
}
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
id | number | 領取紀錄 ID |
missionId | number | 任務定義 ID |
category | string | 任務類別 |
periodType | string | 週期類型 |
tier | number | 階級 |
periodKey | string | 領取時的週期 key |
rewardAmount | string | 實際發放金額 (USD) |
requiredTurnover | string | 需完成的打碼量 (USD) |
completedTurnover | string | 已完成的打碼量 (USD) |
turnoverCompleted | number | 打碼量是否已完成(0/1) |
claimedAt | string | 領取時間 (ISO 8601) |
業務邏輯說明
tab篩選:pending→turnoverCompleted = 0;completed→turnoverCompleted = 1- 打碼量進度:領取獎勵後需完成
rewardAmount x turnoverMultiplier的投注額 - 投注結算後會自動呼叫
MissionService.updateMissionTurnover()更新打碼量進度 - 結果按
claimedAt降序排列
前端注意事項 / UI 設計提示
- 提供 Tab 切換:全部 / 未完成打碼 / 已完成
- 每筆紀錄顯示打碼量進度條(
completedTurnover / requiredTurnover) turnoverCompleted = 0且有requiredTurnover > 0時顯示打碼量進度
POST /api/mission/:id/claim — 領取任務獎勵
功能說明
領取指定任務的獎勵。系統會依序檢查:任務是否存在且啟用、本期是否已領取、VIP 等級是否達標、進度是否達到門檻。所有檢查通過後,獎勵金額直接加到用戶餘額,同時寫入領取紀錄(含打碼量要求)。回傳新餘額讓前端即時更新顯示。
認證需求:需要登入(JwtAuthGuard,前台用戶 JWT Bearer Token)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer Token | Bearer eyJhbG... |
| URL Param | id | number | 是 | 任務 ID | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"rewardAmount": "0.300000",
"requiredTurnover": "0.900000",
"newBalance": "150.300000"
},
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/mission/1/claim"
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
rewardAmount | string | 發放的獎勵金額 (USD) |
requiredTurnover | string | 需完成的打碼量 (USD)。若 turnoverMultiplier = 0 則為 "0.000000" |
newBalance | string | 領取後的最新餘額 (USD) |
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
| 3001 | 任務不存在或未啟用 |
| 3002 | 本期已領取過此任務獎勵 |
| 3003 | VIP 等級未達任務要求 |
| 3004 | 累計進度未達任務門檻 |
業務邏輯說明
- 領取流程:
- 查詢任務定義(
enabled = 1) - 檢查本期是否已領取(
mission-claim表,unique:siteCode + missionId + userId + periodKey) - 檢查 VIP 等級(
vipRequired > 0時才檢查) - 檢查進度(
deposit類看depositTotal,bet類看betTotal) - 發放獎勵:
balance += rewardAmount - 計算打碼量:
requiredTurnover = rewardAmount x turnoverMultiplier - 寫入
mission-claim紀錄
- 查詢任務定義(
- 若
turnoverMultiplier = 0,則無打碼量要求,turnoverCompleted直接設為 1 periodKey依periodType決定:daily→2026-03-01、weekly→2026-W09、monthly→2026-03
前端注意事項 / UI 設計提示
- 領取成功後:
- 更新頁面上的餘額顯示(使用
newBalance) - 刷新任務列表(將該任務標記為已領取)
- 若
requiredTurnover > 0,顯示打碼量提示
- 更新頁面上的餘額顯示(使用
- 領取失敗時根據錯誤碼顯示對應的錯誤訊息
- 「領取」按鈕應在點擊後立即禁用,避免重複提交
LiveSports 即時賽事模組
路由前綴:
/api/live-sports即時賽事模組透過 API-Football 第三方服務取得進行中或即將開始的足球賽事資訊,供前台首頁的賽事 Banner 使用。資料每 30 分鐘由 Cron 排程自動更新至 Redis 快取。支援多語系的狀態標籤和賽事類型名稱。
資料來源
- API-Football (v3.football.api-sports.io):提供即時足球賽事、聯賽、球隊、賠率等資料
- 快取 TTL:35 分鐘(略長於 30 分鐘的更新週期,避免快取提前過期)
- API Key 配置:環境變數
LIVE_SPORTS_API_KEY或站點設定gameProviders.liveSports.apiKey
自動排程
| 排程 | 時間 | 說明 |
|---|---|---|
| 賽事快取更新 | 每 30 分鐘 | 拉取 live 賽事 + 今日未開始賽事,快取至 Redis |
GET /api/live-sports — 取得即時體育賽事 Banner
功能說明
取得進行中或即將開始的足球賽事列表,供前台首頁橫向滾動 Banner 使用。資料每 30 分鐘自動更新。回傳內容包含賽事基本資訊、聯賽資訊、主客隊資訊(含比分和隊徽 URL)、賠率,以及依據語系翻譯的狀態標籤。此端點為公開端點,不需要登入。
認證需求:無(公開端點)
Request
| 類型 | 名稱 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | locales | string | 否 | 語系(影響狀態標籤翻譯) | "zh-TW" / "en-US" |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"fixtureId": 1234567,
"kickoffAt": "2026-02-24T20:00:00+00:00",
"sportLabel": "足球",
"isLive": false,
"status": {
"short": "NS",
"long": "Not Started",
"label": "尚未開始",
"elapsed": null
},
"league": {
"id": 2,
"name": "UEFA Champions League",
"country": "World",
"logo": "https://media.api-sports.io/football/leagues/2.png",
"round": "Quarter-finals"
},
"home": {
"id": 530,
"name": "Atletico Madrid",
"logo": "https://media.api-sports.io/football/teams/530.png",
"score": null
},
"away": {
"id": 569,
"name": "Club Brugge",
"logo": "https://media.api-sports.io/football/teams/569.png",
"score": null
},
"odds": {
"home": "1.43",
"draw": "5.20",
"away": "6.80",
"extraCount": 5
}
},
{
"fixtureId": 1234568,
"kickoffAt": "2026-02-24T18:00:00+00:00",
"sportLabel": "足球",
"isLive": true,
"status": {
"short": "1H",
"long": "First Half",
"label": "上半場",
"elapsed": 35
},
"league": {
"id": 39,
"name": "Premier League",
"country": "England",
"logo": "https://media.api-sports.io/football/leagues/39.png",
"round": "Regular Season - 25"
},
"home": {
"id": 33,
"name": "Manchester United",
"logo": "https://media.api-sports.io/football/teams/33.png",
"score": 1
},
"away": {
"id": 40,
"name": "Liverpool",
"logo": "https://media.api-sports.io/football/teams/40.png",
"score": 2
},
"odds": {
"home": "2.10",
"draw": "3.40",
"away": "3.20",
"extraCount": 5
}
}
]
}回傳欄位說明
| 欄位 | 型別 | 說明 |
|---|---|---|
fixtureId | number | 賽事 ID(API-Football) |
kickoffAt | string | 開球時間 (ISO 8601) |
sportLabel | string | 運動類型標籤(依語系翻譯,如「足球」/「Football」) |
isLive | boolean | 是否為進行中的賽事 |
status.short | string | 狀態簡碼(NS/1H/2H/HT/ET/BT/P/FT 等) |
status.long | string | 狀態全名(英文) |
status.label | string | 狀態標籤(依語系翻譯) |
status.elapsed | number | null | 已進行分鐘數(未開始或已結束時為 null) |
league.id | number | 聯賽 ID |
league.name | string | 聯賽名稱 |
league.country | string | 國家 |
league.logo | string | 聯賽 Logo URL |
league.round | string | null | 輪次資訊 |
home.id | number | 主隊 ID |
home.name | string | 主隊名稱 |
home.logo | string | 主隊隊徽 URL |
home.score | number | null | 主隊比分(未開始時為 null) |
away.id | number | 客隊 ID |
away.name | string | 客隊名稱 |
away.logo | string | 客隊隊徽 URL |
away.score | number | null | 客隊比分(未開始時為 null) |
odds.home | string | null | 主勝賠率 |
odds.draw | string | null | 平手賠率 |
odds.away | string | null | 客勝賠率 |
odds.extraCount | number | 額外投注選項數量 |
「進行中」狀態碼
| 狀態碼 | 說明 |
|---|---|
1H | 上半場 (First Half) |
2H | 下半場 (Second Half) |
HT | 中場休息 (Half Time) |
ET | 加時賽 (Extra Time) |
BT | 加時賽中場 (Break Time) |
P | 點球大戰 (Penalty) |
業務邏輯說明
- 資料來源為 Redis 快取,由 Cron 排程每 30 分鐘更新
- 更新流程:
- 拉取 live 賽事(
/fixtures?live=all) - 拉取今日未開始賽事(
/fixtures?date={today}&status=NS) - 去重合併,最多取 20 場
- 為前 5 場賽事拉取賠率(
/odds?fixture={id}&bookmaker=1&bet=1) - 建構語言無關的快取資料存入 Redis
- 拉取 live 賽事(
- 排序規則:進行中的賽事排在前面,然後按開球時間升序
- API 配額管理:剩餘配額 < 10 時跳過賠率查詢
- Cold start:快取為空時會同步觸發一次更新
- 狀態標籤(
status.label)使用 i18n 翻譯(i18n key:liveSports.status.{short})
前端注意事項 / UI 設計提示
- 首頁設計為橫向滾動 Banner 卡片
- 每張卡片顯示:聯賽 Logo + 名稱、主客隊 Logo + 名稱 + 比分、狀態標籤、賠率
isLive = true的賽事加上閃爍的「LIVE」標章score = null時顯示-或0odds = null時隱藏賠率區塊elapsed不為 null 時顯示進行分鐘數(如35')- 建議每 30 秒 ~ 1 分鐘自動刷新一次(配合後端快取週期)
模組間關聯圖
投注結算(Game S2S 回調 / 模擬投注)
│
├──→ VipService.recalculateUserVip() → VIP 等級重算(只升不降)
│ └──→ Alliance VIP 里程碑獎勵檢查(若升級且有代理)
│
├──→ PromoService.updatePromoTurnover() → 優惠打碼量累計
│
├──→ MissionService.updateBetProgress() → 任務投注進度更新
│
└──→ MissionService.updateMissionTurnover() → 任務領取紀錄打碼量更新
存款確認
└──→ MissionService.updateDepositProgress() → 任務存款進度更新
遊戲啟動/模擬成功
└──→ GameService.recordGamePlay() → 遊玩紀錄 UPSERT (game-play-log)附錄:統一回應格式
所有端點回應均遵循以下格式:
成功
{
"code": 200,
"message": "ok",
"result": "...",
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/..."
}業務錯誤
{
"code": 2001,
"message": "帳號或密碼錯誤",
"timestamp": "2026-03-01T10:00:00.000Z",
"path": "/api/..."
}未授權
{
"code": 401,
"message": "Unauthorized"
}Affiliate 代理推廣模組
代理推廣系統採三層代理結構(上線 → 下線,最多 3 層),佣金依據下線投注淨損失 (net loss) 按比例結算。佣金結算分為週結(每週一 03:00)與日結(每日 03:30),結算後進入待審核狀態,需後台人工審核後才入帳到代理餘額。代理提款支援三階段流程:pending → approved → completed。
所有金額使用 USD decimal(18,6) 精度,截斷規則為無條件捨去 (Math.floor(value * 1e6) / 1e6)。
下線帳號在代理端點中會透過 maskAccount() 遮蔽中間字元以保護隱私(例如 us***01)。
公開端點 (Public — 無需認證)
1. POST /api/affiliate/track-click
功能說明
記錄推廣連結的點擊事件。當用戶透過帶有 refCode 的推廣連結進入前台時,前端呼叫此端點記錄點擊資訊,用於後續追蹤轉換率。支援聯盟推廣碼(alliance-referral-code 表)與主代理碼(auth-user.agentCode)兩種查找方式。
認證需求:無
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
refCode | body | string | 是 | 推廣碼(如 AGT123 或自訂推廣碼) |
referrer | body | string | 否 | 來源頁面 URL |
{
"refCode": "AGT123",
"referrer": "https://google.com"
}Response
{
"code": 200,
"message": "ok",
"result": {
"tracked": true
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 推廣碼不存在(查詢 alliance-referral-code 與 auth-user.agentCode 均無結果) |
業務邏輯
- 透過
AllianceService.resolveRefCode()解析推廣碼,先查alliance-referral-code表,再查auth-user.agentCode欄位 - 記錄到
affiliate-click表,含 IP、User-Agent、referrer - 點擊紀錄用於代理儀表板的點擊統計及轉換率追蹤
- 用戶註冊時會透過
bindAgentOnRegister()將點擊標記為已轉換(converted = 1)
2. GET /api/affiliate/alliance-info
功能說明
取得聯盟計劃的公開資訊,包含佣金比例表、代理階層列表、VIP 里程碑獎勵。此端點供前台推廣展示頁使用,讓潛在代理了解佣金制度。回傳結果會快取 1 小時(Redis)。
認證需求:無
Request
無參數。
Response
{
"code": 200,
"message": "ok",
"result": {
"commissionRates": [
{
"agentTier": "bronze",
"agentLevel": 1,
"gameType": "slot",
"commissionRate": "0.28"
},
{
"agentTier": "bronze",
"agentLevel": 1,
"gameType": null,
"commissionRate": "0.25"
}
],
"agentTiers": [
{
"tierCode": "bronze",
"tierName": "Bronze Agent",
"minTotalEarned": "0",
"minActiveMembers": 0
},
{
"tierCode": "silver",
"tierName": "Silver Agent",
"minTotalEarned": "500",
"minActiveMembers": 5
},
{
"tierCode": "gold",
"tierName": "Gold Agent",
"minTotalEarned": "5000",
"minActiveMembers": 20
},
{
"tierCode": "platinum",
"tierName": "Platinum Agent",
"minTotalEarned": "50000",
"minActiveMembers": 50
}
],
"vipMilestones": [
{
"vipLevel": 3,
"bonusAmount": "5",
"description": "下線達 VIP 3 獎勵"
},
{
"vipLevel": 5,
"bonusAmount": "20",
"description": "下線達 VIP 5 獎勵"
}
]
}
}業務邏輯
- 快取 key:
cache:alliance:public-info,TTL 1 小時 gameType為null時代表該階層的預設佣金比例(萬用 wildcard)commissionRate為百分比值(如0.25表示 0.25%)- 佣金比例按
agentTier → agentLevel → gameType排序 - 任何後台 CRUD 操作(新增/更新/刪除佣金比例、階層、里程碑)都會清除此快取
代理端點 (Agent — 需前台用戶登入)
以下端點皆使用 JwtAuthGuard,需在 Request Header 中帶入 Authorization: Bearer <token>。
3. POST /api/affiliate/apply
功能說明
前台用戶申請成為代理。可自訂推廣碼或由系統自動產生。申請成功後會初始化佣金餘額記錄(affiliate-balance 表),並取得專屬推廣碼。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
agentCode | body | string | 否 | 自訂推廣碼(3-20 字元),不填則自動產生 |
{
"agentCode": "MYCHANNEL"
}Response
{
"code": 200,
"message": "ok",
"result": {
"agentCode": "MYCHANNEL"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 您已是代理(agentCode 欄位已有值) |
2002 | 推廣碼已被使用(自訂的推廣碼重複) |
業務邏輯
- 自動產生的推廣碼格式為
AGT+ 6 位隨機英數字(排除易混淆字元0OIL1) - 若自動產生的推廣碼碰撞,會重新產生一次
- 成功後自動建立
affiliate-balance記錄(初始餘額為 0)
4. GET /api/affiliate/tour-status
功能說明
取得代理導覽的顯示狀態。用於判斷前台是否需要顯示代理推廣導覽彈窗,引導用戶成為代理。已是代理的用戶不會顯示導覽。跳過導覽後,根據站點配置的間隔時間(預設 7 天)後再次顯示。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
Response
{
"code": 200,
"message": "ok",
"result": {
"shouldShow": true
}
}業務邏輯
- 已是代理(
agentCode不為空) →shouldShow: false - 從未跳過導覽(
agentTourDismissedAt為空) →shouldShow: true - 站點
agentTourEnabled設為 0 →shouldShow: false - 已跳過但超過間隔時間(
agentTourIntervalSec,預設 604800 秒 = 7 天) →shouldShow: true
5. POST /api/affiliate/apply-from-tour
功能說明
從導覽頁面一鍵成為代理並領取 5 USDT 獎勵金。此端點僅限導覽流程觸發,成功後自動產生推廣碼並將 5 USDT 加入用戶主帳戶餘額。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
無 body 參數。
Response
{
"code": 200,
"message": "ok",
"result": {
"agentCode": "AGTXK5N7P",
"bonusAmount": "5.000000",
"newBalance": "105.000000"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 您已是代理 |
業務邏輯
- 自動產生推廣碼(格式同
apply端點) - 記錄
agentTourCompletedAt時間戳 - 初始化
affiliate-balance記錄 - 發放 5 USDT 至用戶主帳戶
balance(原子 SQL 操作balance + 5.000000) - 回傳獎勵金額與更新後的帳戶餘額
6. POST /api/affiliate/tour-dismiss
功能說明
跳過代理導覽。記錄跳過時間,在站點設定的間隔時間(預設 7 天)後再次顯示導覽。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
無 body 參數。
Response
{
"code": 200,
"message": "ok",
"result": {
"dismissed": true
}
}業務邏輯
- 更新
agentTourDismissedAt為當前時間 - 下次呼叫
GET /tour-status時會根據間隔時間判斷是否再次顯示
7. GET /api/affiliate/dashboard
功能說明
取得代理儀表板數據總覽。包含代理推廣碼、佣金餘額、三層下線人數統計、本月佣金累計、最近 7 天的點擊/轉換統計。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
Response
{
"code": 200,
"message": "ok",
"result": {
"agentCode": "AGTXK5N7P",
"balance": {
"available": "120.000000",
"frozen": "30.000000",
"totalEarned": "500.000000",
"totalWithdrawn": "350.000000"
},
"downline": {
"level1": 15,
"level2": 8,
"level3": 2,
"total": 25
},
"monthCommission": "45.230000",
"clickStats": [
{
"date": "2026-02-28",
"clicks": "12",
"conversions": "3"
},
{
"date": "2026-02-27",
"clicks": "8",
"conversions": "1"
}
]
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 您不是代理身份(agentCode 為空) |
業務邏輯
balance.available:可提領佣金餘額balance.frozen:已申請提款但尚未完成的凍結金額balance.totalEarned:歷史累計佣金收入balance.totalWithdrawn:歷史累計已提領金額downline.level1:直屬下線人數downline.level2:二層下線人數(下線的下線)downline.level3:三層下線人數monthCommission:當月累計佣金(從月初到當前)clickStats:最近 7 天每日點擊數與轉換數,按日期倒序排列- 下線人數使用單一查詢 + CASE WHEN 統計三層,避免 N+1
8. GET /api/affiliate/promo-link
功能說明
取得代理的專屬推廣連結。包含推廣碼與完整的註冊連結 URL,用於代理分享推廣使用。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
Response
{
"code": 200,
"message": "ok",
"result": {
"refCode": "AGTXK5N7P",
"link": "https://example.com/register?refCode=AGTXK5N7P"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 您不是代理身份 |
業務邏輯
- 前端 URL 優先取自環境變數
FRONTEND_URL - 若未設定,fallback 從
site-config.domains取第一個 hostname 並拼接https:// - 推廣連結格式:
{frontendUrl}/register?refCode={agentCode}
9. GET /api/affiliate/downline
功能說明
取得代理的下線會員列表,支援依層級(1/2/3)篩選與分頁。下線帳號會以遮蔽方式顯示以保護隱私。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
page | query | number | 否 | 頁碼(預設 1) |
pageSize | query | number | 否 | 每頁筆數(預設 10,最大 50) |
level | query | number | 否 | 層級篩選:1(直屬)、2(二層)、3(三層),預設 1 |
Response
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 42,
"account": "us***01",
"name": "王小明",
"vipLevel": 5,
"createdAt": "2026-01-15T08:30:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 25,
"totalPages": 3
}
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 您不是代理身份 |
業務邏輯
level=1查詢level1AgentId = agentId(直屬下線)level=2查詢level2AgentId = agentId(二層下線)level=3查詢level3AgentId = agentId(三層下線)- 帳號遮蔽規則:長度 <=4 時顯示首字 +
***;否則顯示前 2 字 +***+ 後 2 字 - 結果按
createdAt倒序排列
10. GET /api/affiliate/click-stats
功能說明
取得推廣連結的每日點擊與轉換統計。可透過日期範圍篩選特定期間的數據。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
startDate | query | string | 否 | 開始日期 YYYY-MM-DD(如 2026-02-17) |
endDate | query | string | 否 | 結束日期 YYYY-MM-DD(如 2026-02-23) |
Response
{
"code": 200,
"message": "ok",
"result": [
{
"date": "2026-02-23",
"clicks": "15",
"conversions": "4"
},
{
"date": "2026-02-22",
"clicks": "10",
"conversions": "2"
}
]
}業務邏輯
- 按日期分組統計點擊數(
clicks)與轉換數(conversions) - 轉換數 = 透過該推廣碼註冊的用戶數
- 結果按日期倒序排列
- 若不帶日期篩選,回傳所有歷史統計
11. GET /api/affiliate/commissions
功能說明
查詢代理的佣金明細列表。每筆佣金記錄對應一筆有效投注的佣金分成,包含投注層級、遊戲類型、佣金比例與金額。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
page | query | number | 否 | 頁碼(預設 1) |
pageSize | query | number | 否 | 每頁筆數(預設 10,最大 50) |
weekStart | query | string | 否 | 篩選週開始日 YYYY-MM-DD |
Response
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"agentId": 5,
"memberId": 42,
"betOrderId": 1001,
"agentLevel": 1,
"gameType": "slot",
"netLoss": "50.000000",
"commissionRate": "0.28",
"commissionAmount": "0.140000",
"weekStart": "2026-02-17",
"weekEnd": "2026-02-23",
"settlementId": 10,
"createdAt": "2026-02-24T03:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 150,
"totalPages": 15
}
}
}業務邏輯
agentLevel:佣金來源層級(1=直屬下線、2=二層下線、3=三層下線)netLoss:該筆投注的淨損失金額(USD),佣金僅從淨損失中提取commissionRate:適用的佣金比例(百分比)commissionAmount:實際佣金金額 =netLoss * commissionRate / 100(USD 截斷)weekStart/weekEnd:佣金所屬結算週期- 結果按
createdAt倒序排列
12. GET /api/affiliate/settlements
功能說明
查詢代理的結算紀錄列表。每筆結算記錄代表一個週期(週/日)的佣金彙總,包含三層佣金分項、活躍下線數、總淨損失。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
page | query | number | 否 | 頁碼(預設 1) |
pageSize | query | number | 否 | 每頁筆數(預設 10,最大 50) |
status | query | string | 否 | 篩選狀態:pending / approved / rejected |
Response
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 10,
"agentId": 5,
"weekStart": "2026-02-17",
"weekEnd": "2026-02-23",
"activeMemberCount": 8,
"totalNetLoss": "1200.000000",
"level1Commission": "3.360000",
"level2Commission": "1.200000",
"level3Commission": "0.500000",
"totalCommission": "5.060000",
"gameTypeBreakdown": {
"slot": "2.100000",
"live": "1.500000",
"sports": "1.460000"
},
"periodType": "weekly",
"status": "pending",
"reviewedBy": null,
"reviewedAt": null,
"createdAt": "2026-02-24T03:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 12,
"totalPages": 2
}
}
}業務邏輯
status狀態流轉:pending→approved(佣金入帳到 available) 或rejectedperiodType:weekly(週結)或daily(日結)gameTypeBreakdown:按遊戲類型拆分的佣金明細level1Commission/level2Commission/level3Commission:三層佣金分項- 結算具備冪等性,同一期間不會重複結算
- 結果按
weekStart倒序排列
13. GET /api/affiliate/settlements/:id
功能說明
取得單筆結算的詳情,包含結算摘要與該期間的所有佣金明細列表。用於代理查看特定結算週期的完整資訊。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
id | path | number | 是 | 結算紀錄 ID |
Response
{
"code": 200,
"message": "ok",
"result": {
"settlement": {
"id": 10,
"agentId": 5,
"weekStart": "2026-02-17",
"weekEnd": "2026-02-23",
"activeMemberCount": 8,
"totalNetLoss": "1200.000000",
"level1Commission": "3.360000",
"level2Commission": "1.200000",
"level3Commission": "0.500000",
"totalCommission": "5.060000",
"gameTypeBreakdown": {
"slot": "2.100000",
"live": "1.500000",
"sports": "1.460000"
},
"periodType": "weekly",
"status": "approved",
"reviewedBy": "admin@c9.com",
"reviewedAt": "2026-02-24T10:30:00.000Z",
"createdAt": "2026-02-24T03:00:00.000Z"
},
"commissions": [
{
"id": 1,
"agentId": 5,
"memberId": 42,
"betOrderId": 1001,
"agentLevel": 1,
"gameType": "slot",
"netLoss": "50.000000",
"commissionRate": "0.28",
"commissionAmount": "0.140000",
"weekStart": "2026-02-17",
"weekEnd": "2026-02-23",
"settlementId": 10,
"createdAt": "2026-02-24T03:00:00.000Z"
}
]
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 結算紀錄不存在(或不屬於當前代理) |
業務邏輯
- 查詢條件同時比對
settlementId與agentId,確保代理只能查看自己的結算 commissions按createdAt倒序排列
14. GET /api/affiliate/balance
功能說明
取得代理的佣金餘額資訊。包含可用餘額、凍結餘額、歷史累計收入與歷史累計提領。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
Response
{
"code": 200,
"message": "ok",
"result": {
"available": "120.000000",
"frozen": "30.000000",
"totalEarned": "500.000000",
"totalWithdrawn": "350.000000"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 您不是代理身份 |
業務邏輯
available:可提領佣金餘額(結算審核通過後入帳)frozen:已申請提款但尚未完成的凍結金額totalEarned:歷史累計佣金收入(含 VIP 里程碑獎勵)totalWithdrawn:歷史累計已完成提領的金額- 餘額關係:
available+frozen+totalWithdrawn=totalEarned+ VIP 里程碑獎勵
15. GET /api/affiliate/withdrawals
功能說明
查詢代理的提款申請紀錄列表,支援分頁。顯示每筆提款的金額、方式、狀態及審核資訊。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
page | query | number | 否 | 頁碼(預設 1) |
pageSize | query | number | 否 | 每頁筆數(預設 10,最大 50) |
Response
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"agentId": 5,
"amount": "100.000000",
"method": "bank",
"bankCardId": 3,
"cryptoAddressId": null,
"status": "pending",
"reviewedBy": null,
"reviewedAt": null,
"rejectReason": null,
"completedAt": null,
"createdAt": "2026-02-28T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 5,
"totalPages": 1
}
}
}業務邏輯
- 狀態流轉:
pending→approved→completed,或pending→rejected - 結果按
createdAt倒序排列
16. POST /api/affiliate/withdrawals/request
功能說明
代理發起佣金提款申請。提款金額會從可用餘額 (available) 凍結到凍結餘額 (frozen),等待後台審核。支援銀行轉帳與加密貨幣兩種提款方式。每 24 小時僅允許一筆進行中的提款申請。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
amount | body | string | 是 | 提款金額 (USD),如 "100.000000" |
method | body | string | 是 | 提款方式:bank 或 crypto |
bankCardId | body | number | 否 | 銀行卡 ID(method=bank 時必填) |
cryptoAddressId | body | number | 否 | 加密錢包 ID(method=crypto 時必填) |
{
"amount": "100.000000",
"method": "bank",
"bankCardId": 3
}Response
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"agentId": 5,
"amount": "100.000000",
"method": "bank",
"bankCardId": 3,
"cryptoAddressId": null,
"status": "pending",
"createdAt": "2026-02-28T10:00:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 提款金額須大於零 |
2002 | 提款方式無效(銀行卡/加密錢包不存在或未啟用) |
2003 | 冷卻中(24 小時內已有 pending 或 approved 提款申請) |
2004 | 餘額不足(可用餘額 available 小於提款金額) |
2005 | 非代理身份 |
業務邏輯
- 提款金額經過 USD 截斷處理(
truncateUsd) - 凍結操作為原子 SQL:
available - amount且frozen + amount,同時檢查available >= amount - 銀行卡/加密錢包必須屬於當前用戶且狀態為啟用(
status = 1) - 24 小時冷卻:查詢最近 24 小時內是否有
pending或approved狀態的提款 - 提款被拒時,凍結金額會退回到可用餘額
聯盟代理端點 (Alliance Agent — 需前台用戶登入)
17. GET /api/affiliate/referral-codes
功能說明
取得當前代理的所有推廣碼列表。每個代理最多可建立 10 個自訂推廣碼,用於不同推廣渠道的追蹤。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
Response
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"agentId": 5,
"code": "YOUTUBE2026",
"label": "YouTube 頻道",
"clickCount": 120,
"convertCount": 15,
"enabled": 1,
"createdAt": "2026-01-10T08:00:00.000Z"
},
{
"id": 2,
"agentId": 5,
"code": "TELEGRAM01",
"label": "Telegram 群組",
"clickCount": 80,
"convertCount": 8,
"enabled": 1,
"createdAt": "2026-01-15T10:00:00.000Z"
}
]
}業務邏輯
clickCount:透過此推廣碼的點擊次數convertCount:透過此推廣碼成功註冊的用戶數- 結果按
createdAt倒序排列 - 推廣碼與主代理碼(
agentCode)是獨立的,使用時皆可作為refCode追蹤
18. POST /api/affiliate/referral-codes
功能說明
建立新的自訂推廣碼。推廣碼僅允許英數字,長度 3-30 字元,會自動轉為大寫。每個代理最多 10 個推廣碼。推廣碼在全站唯一(包含其他代理的推廣碼與主代理碼)。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
code | body | string | 是 | 推廣碼(3-30 英數字) |
label | body | string | 否 | 渠道標籤(最長 50 字元) |
{
"code": "YOUTUBE2026",
"label": "YouTube 頻道"
}Response
{
"code": 200,
"message": "ok",
"result": {
"id": 3,
"agentId": 5,
"code": "YOUTUBE2026",
"label": "YouTube 頻道",
"clickCount": 0,
"convertCount": 0,
"enabled": 1,
"createdAt": "2026-02-28T10:00:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 非代理身份 |
2002 | 已達上限(推廣碼數量已達 10 個) |
2003 | 推廣碼已被使用(與其他推廣碼或主代理碼重複) |
業務邏輯
- 推廣碼自動轉大寫(
code.toUpperCase()) - 唯一性檢查同時查詢
alliance-referral-code表與auth-user.agentCode欄位 - 只允許英數字(正則:
/^[a-zA-Z0-9]+$/) - 建立時
clickCount與convertCount初始為 0
19. DELETE /api/affiliate/referral-codes/:id
功能說明
刪除指定的推廣碼。只能刪除自己擁有的推廣碼。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
id | path | number | 是 | 推廣碼 ID |
Response
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 推廣碼不存在(或不屬於當前代理) |
業務邏輯
- 查詢條件同時比對
id與agentId,確保只能刪除自己的推廣碼 - 刪除後已綁定的下線不受影響(綁定關係記錄在
auth-user表的level1/2/3AgentId)
20. GET /api/affiliate/vip-milestones
功能說明
查看代理的 VIP 里程碑獎勵進度。顯示所有啟用中的 VIP 里程碑配置,以及每位直屬下線的 VIP 等級與已領取的里程碑獎勵狀態。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
Response
{
"code": 200,
"message": "ok",
"result": {
"milestones": [
{
"vipLevel": 3,
"bonusAmount": "5",
"description": "下線達 VIP 3 獎勵"
},
{
"vipLevel": 5,
"bonusAmount": "20",
"description": "下線達 VIP 5 獎勵"
}
],
"members": [
{
"memberId": 42,
"account": "us***01",
"vipLevel": 5,
"claimed": [
{ "vipLevel": 3, "bonusAmount": "5", "claimed": true },
{ "vipLevel": 5, "bonusAmount": "20", "claimed": true }
]
},
{
"memberId": 43,
"account": "te***er",
"vipLevel": 2,
"claimed": []
}
],
"totalBonusEarned": "25.000000"
}
}業務邏輯
milestones:所有啟用中的 VIP 里程碑配置,按vipLevel升序members:直屬下線(level1AgentId = agentId)的 VIP 等級與領取狀態claimed:每位下線已觸發且達標的里程碑列表claimed.claimed:true表示已發放獎勵,false理論上不會出現(達標即發放)totalBonusEarned:歷史累計從 VIP 里程碑獲得的獎勵總額- VIP 里程碑獎勵在下線升級 VIP 時由
VipService自動觸發checkAndAwardMilestone() - 獎勵直接入帳到代理的
affiliate-balance.available - 使用唯一約束防止重複發放(
agentId + memberId + vipLevel)
21. GET /api/affiliate/tier-info
功能說明
查看代理的階層資訊。顯示當前階層、升級所需條件(下一階層的最低累計收入與最低活躍下線數)、所有階層配置。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
Authorization | header | string | 是 | Bearer <JWT token> |
Response
{
"code": 200,
"message": "ok",
"result": {
"currentTier": {
"tierCode": "silver",
"tierName": "Silver Agent"
},
"totalEarned": "800.000000",
"activeMembers": 12,
"nextTier": {
"tierCode": "gold",
"tierName": "Gold Agent",
"minTotalEarned": "5000",
"minActiveMembers": 20
},
"allTiers": [
{
"tierCode": "bronze",
"tierName": "Bronze Agent",
"minTotalEarned": "0",
"minActiveMembers": 0
},
{
"tierCode": "silver",
"tierName": "Silver Agent",
"minTotalEarned": "500",
"minActiveMembers": 5
},
{
"tierCode": "gold",
"tierName": "Gold Agent",
"minTotalEarned": "5000",
"minActiveMembers": 20
},
{
"tierCode": "platinum",
"tierName": "Platinum Agent",
"minTotalEarned": "50000",
"minActiveMembers": 50
}
]
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 非代理身份 |
業務邏輯
currentTier:當前代理階層(預設bronze)totalEarned:歷史累計收入(從affiliate-balance.totalEarned)activeMembers:直屬下線人數(level1AgentId = agentId的用戶數)nextTier:下一階層的升級條件,已是最高階層時為null- 階層由低到高:
bronze→silver→gold→platinum - 代理階層影響佣金比例(不同階層 x 不同層級 x 不同遊戲類型有不同的佣金比例)
allTiers按sortOrder升序排列
代理推廣系統 API 文件 — Admin 與 Alliance 管理端點
模組路徑:
/api/affiliate/admin/*Controller:src/modules/affiliate/affiliate.controller.ts認證:所有端點均需AdminJwtAuthGuard(後台管理員 JWT)
一、Admin 代理管理端點
1. GET /affiliate/admin/agents — 代理列表
取得所有代理帳號的列表,支援關鍵字搜尋、代理階層篩選、註冊日期範圍篩選、直屬下線人數範圍篩選。透過 @AdminSiteCode() 裝飾器自動讀取站點代碼進行多站點過濾。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Header | x-site-code | string | 否 | 站點代碼(由 @AdminSiteCode() 讀取) |
| Query | page | number | 否 | 頁碼(預設 1) |
| Query | pageSize | number | 否 | 每頁筆數(預設 20,最大 50) |
| Query | keyword | string | 否 | 搜尋關鍵字(比對 account / agentCode / email) |
| Query | agentTier | string | 否 | 代理階層篩選:bronze / silver / gold / platinum |
| Query | startDate | string | 否 | 註冊起始日 YYYY-MM-DD |
| Query | endDate | string | 否 | 註冊結束日 YYYY-MM-DD |
| Query | memberCountRange | string | 否 | 直屬下線人數範圍:0-10 / 10-50 / 50-100 / 100+ |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"account": "agent001",
"email": "agent@example.com",
"name": "張三",
"agentCode": "AGT7K9N2X",
"agentTier": "silver",
"createdAt": "2026-01-15T08:30:00.000Z",
"available": "120.500000",
"frozen": "30.000000",
"totalEarned": "500.000000",
"totalWithdrawn": "350.000000",
"directMemberCount": "25",
"siteCode": "C9"
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}業務邏輯
- 透過 LEFT JOIN
affiliate-balance表取得佣金餘額和代理階層 - 透過子查詢計算直屬下線人數(
level1AgentId聚合) siteCode篩選作用於auth-user.siteCode欄位- 排序:建立時間 DESC
2. POST /affiliate/admin/create-agent — 新增代理
手動將一個前台用戶設定為代理身份。可自訂推廣碼或由系統自動產生(格式:AGT + 6 位隨機英數字)。建立時會同時初始化佣金餘額記錄。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Body | userId | number | 是 | 前台用戶 ID |
| Body | agentCode | string | 否 | 自訂推廣碼(3~20 字元,不填則自動產生) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"agentCode": "AGT7K9N2X"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 用戶不存在 |
2002 | 推廣碼已被使用 |
2003 | 該用戶已是代理 |
業務邏輯
- 寫入
auth-user.agentCode欄位 - 自動在
affiliate-balance表建立餘額記錄(初始金額皆為 0) - 自動產生的推廣碼排除容易混淆的字元(O/0/I/1)
3. GET /affiliate/admin/settlements — 佣金結算列表
查詢全部代理的佣金結算紀錄(不限特定代理),支援按結算狀態和結算週期篩選。管理員可從此列表進入審核流程。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Query | page | number | 否 | 頁碼(預設 1) |
| Query | pageSize | number | 否 | 每頁筆數(預設 10,最大 50) |
| Query | status | string | 否 | 結算狀態:pending / pendingReview / approved / rejected |
| Query | weekStart | string | 否 | 結算週期起始日 YYYY-MM-DD |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"agentId": 5,
"weekStart": "2026-02-17",
"weekEnd": "2026-02-23",
"activeMemberCount": 12,
"totalNetLoss": "5000.000000",
"level1Commission": "150.000000",
"level2Commission": "30.000000",
"level3Commission": "10.000000",
"totalCommission": "190.000000",
"gameTypeBreakdown": { "slot": "100.000000", "live": "50.000000", "sports": "40.000000" },
"periodType": "weekly",
"status": "pending",
"riskFlagged": 0,
"riskReasons": null,
"reviewedBy": null,
"reviewedAt": null,
"siteCode": "C9",
"createdAt": "2026-02-24T03:00:15.000Z",
"updatedAt": "2026-02-24T03:00:15.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 25,
"totalPages": 3
}
}
}業務邏輯
periodType區分週結 (weekly) 和日結 (daily)- 結算狀態流程:
pending→ (pendingReview,若有風控標記) →approved/rejected riskFlagged: 1表示該結算紀錄觸發風控檢測(可查看對應風控紀錄)gameTypeBreakdown為 JSON 物件,key 為遊戲類型名稱,value 為該類型的佣金總額- 排序:結算週期起始日 DESC
4. POST /affiliate/admin/settlements/:id/review — 結算審核
對指定的佣金結算紀錄進行審核,可核准或拒絕。核准時佣金自動入帳到代理的可用餘額(available)和累計收益(totalEarned)。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Path | id | number | 是 | 結算紀錄 ID |
| Body | action | string | 是 | 審核動作:approved / rejected |
| Body | reason | string | 否 | 拒絕原因(僅 rejected 時使用) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"success": true
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 結算紀錄不存在 |
2002 | 該紀錄已審核完畢(不可重複審核) |
業務邏輯
- 核准 (approved):
- 更新結算狀態為
approved,記錄審核人帳號和審核時間 - 將
totalCommission金額加入代理的affiliate-balance.available和totalEarned - 金額使用
truncateUsd()無條件捨去至 6 位小數
- 更新結算狀態為
- 拒絕 (rejected):
- 更新結算狀態為
rejected,記錄審核人帳號和審核時間 - 佣金不入帳
- 更新結算狀態為
- 審核人帳號從
req.user.email取得
5. GET /affiliate/admin/settlements/:id/risk-logs — 結算風控紀錄
查詢指定結算紀錄的風控檢測結果。風控在每次結算時自動執行,檢測代理與下線之間是否存在可疑的 IP 或裝置關聯。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Path | id | number | 是 | 結算紀錄 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"settlementId": 10,
"agentId": 5,
"memberId": null,
"riskType": "same_ip",
"detail": "{\"ip\":\"192.168.1.100\",\"userIds\":[12,15,18]}",
"siteCode": "C9",
"createdAt": "2026-02-24T03:00:20.000Z"
},
{
"id": 2,
"settlementId": 10,
"agentId": 5,
"memberId": 12,
"riskType": "agent_member_ip",
"detail": "{\"ip\":\"192.168.1.100\",\"memberId\":12}",
"siteCode": "C9",
"createdAt": "2026-02-24T03:00:20.000Z"
}
]
}風控類型 (riskType)
| 類型 | 說明 |
|---|---|
same_ip | 多位下線會員共用相同 IP 登入(>=2 人) |
same_device | 多位下線會員共用相同裝置指紋(>=2 人) |
agent_member_ip | 代理本人與下線會員使用相同 IP 登入 |
agent_member_device | 代理本人與下線會員使用相同裝置指紋 |
業務邏輯
- 風控檢測範圍為結算週期(
weekStart~weekEnd)內的登入紀錄 detail為 JSON 字串,內含可疑的 IP/裝置和相關用戶 ID- 觸發風控時,結算狀態會自動改為
pendingReview,riskFlagged設為1 - 排序:建立時間 DESC
6. GET /affiliate/admin/withdrawals — 代理提款列表
查詢全部代理的提款申請紀錄,支援按狀態篩選。管理員可從此列表進入提款審核流程。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Query | page | number | 否 | 頁碼(預設 1) |
| Query | pageSize | number | 否 | 每頁筆數(預設 10,最大 50) |
| Query | status | string | 否 | 提款狀態:pending / approved / rejected / completed |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"agentId": 5,
"amount": "100.000000",
"method": "bank",
"bankCardId": 3,
"cryptoAddressId": null,
"status": "pending",
"rejectReason": null,
"reviewedBy": null,
"reviewedAt": null,
"completedAt": null,
"siteCode": "C9",
"createdAt": "2026-02-25T10:30:00.000Z",
"updatedAt": "2026-02-25T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 5,
"totalPages": 1
}
}
}業務邏輯
- 提款狀態流程:
pending→approved→completed(或pending→rejected) method為bank(銀行卡)或crypto(加密貨幣),對應bankCardId或cryptoAddressId- 排序:建立時間 DESC
7. POST /affiliate/admin/withdrawals/:id/review — 提款審核
對指定的代理提款申請進行審核。拒絕時凍結金額自動退回代理的可用餘額。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Path | id | number | 是 | 提款紀錄 ID |
| Body | action | string | 是 | 審核動作:approved / rejected |
| Body | rejectReason | string | 否 | 拒絕原因(僅 rejected 時使用) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"success": true
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 提款紀錄不存在 |
2002 | 該紀錄已審核完畢(狀態須為 pending) |
業務邏輯
- 核准 (approved):
- 更新提款狀態為
approved,記錄審核人帳號和時間 - 凍結金額保持不變(等待人工出款確認後才轉入
totalWithdrawn)
- 更新提款狀態為
- 拒絕 (rejected):
- 更新提款狀態為
rejected,記錄拒絕原因 - 凍結金額退回:
frozen -= amount,available += amount
- 更新提款狀態為
- 僅接受
status === 'pending'的提款紀錄 - 審核人帳號從
req.user.email取得
8. POST /affiliate/admin/withdrawals/:id/complete — 提款完成
標記已核准的提款為完成狀態(代表實際出款已完成)。凍結金額轉入累計提領金額。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Path | id | number | 是 | 提款紀錄 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"success": true
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 提款紀錄不存在 |
2002 | 狀態須為 approved(只有已核准的才可完成) |
業務邏輯
- 餘額變動:
frozen -= amount,totalWithdrawn += amount - 更新提款狀態為
completed,記錄完成時間 - 三階段提款流程完整:
pending→approved(審核通過)→completed(實際出款完成)
9. POST /affiliate/admin/bind — 手動綁定/解綁/轉移
管理員手動操作會員與代理之間的綁定關係。支援三種動作:綁定、解綁、轉移。所有操作都會寫入審計紀錄。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Body | memberId | number | 是 | 會員 user ID |
| Body | agentId | number | 是 | 代理 user ID |
| Body | action | string | 是 | 動作:admin_bind / admin_unbind / admin_transfer |
| Body | remark | string | 否 | 備註/原因 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"success": true
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 會員不存在 |
2002 | 代理不存在(或該用戶不是代理) |
業務邏輯
- admin_bind / admin_transfer:
- 設定會員的三層代理鏈:
level1AgentId = agent.id,level2AgentId = agent.level1AgentId,level3AgentId = agent.level2AgentId - 轉移等同於覆蓋原有綁定
- 設定會員的三層代理鏈:
- admin_unbind:
- 清除會員的所有代理綁定:
level1AgentId = null,level2AgentId = null,level3AgentId = null
- 清除會員的所有代理綁定:
- 操作者帳號(
operatorAccount)從req.user.email取得 - 寫入
affiliate-bind-log審計紀錄
10. GET /affiliate/admin/bind-logs — 綁定審計紀錄
查詢代理與會員之間的綁定操作歷史紀錄,包含註冊自動綁定和管理員手動操作。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Query | page | number | 否 | 頁碼(預設 1) |
| Query | pageSize | number | 否 | 每頁筆數(預設 20,最大 50) |
| Query | memberId | number | 否 | 篩選特定會員 |
| Query | agentId | number | 否 | 篩選特定代理 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"memberId": 10,
"agentId": 5,
"refCode": "AGT7K9N2X",
"action": "register_bind",
"ip": "203.0.113.50",
"device": "Mozilla/5.0...",
"operatorAccount": null,
"remark": null,
"siteCode": "C9",
"createdAt": "2026-01-20T14:30:00.000Z"
},
{
"id": 2,
"memberId": 10,
"agentId": 8,
"refCode": "AGTXYZ123",
"action": "admin_transfer",
"ip": null,
"device": null,
"operatorAccount": "admin@example.com",
"remark": "用戶要求轉移至新代理",
"siteCode": "C9",
"createdAt": "2026-02-10T09:15:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 2,
"totalPages": 1
}
}
}綁定動作類型 (action)
| 動作 | 說明 |
|---|---|
register_bind | 用戶註冊時透過推廣碼自動綁定 |
admin_bind | 管理員手動綁定 |
admin_unbind | 管理員手動解綁 |
admin_transfer | 管理員手動轉移至其他代理 |
11. POST /affiliate/admin/set-agent-tier — 設定代理等級
手動調整指定代理的階層等級。會直接更新 affiliate-balance.agentTier 欄位,影響後續佣金計算所使用的佣金費率。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Body | agentId | number | 是 | 代理 user ID |
| Body | tierCode | string | 是 | 階層碼:bronze / silver / gold / platinum |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"agentId": 5,
"agentTier": "silver"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 代理不存在(在 affiliate-balance 表中找不到) |
2002 | 無效的階層碼(在 alliance-agent-tier 表中找不到) |
業務邏輯
- 代理階層影響佣金費率:不同階層在
alliance-commission-rate表中有不同的commissionRate - 手動設定會覆蓋自動升級結果
- 此操作不需要符合自動升級門檻(
minTotalEarned/minActiveMembers)
12. POST /affiliate/admin/trigger-settlement — 手動觸發週結算
手動觸發一次佣金週結算(等同 Cron Job 每週一 03:00 執行的邏輯)。主要用於測試或補結算。結算範圍為上一個完整週(上週一至上週日)。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"agentsProcessed": 15,
"totalCommission": "2500.000000",
"flaggedCount": 2
}
}業務邏輯
- 冪等保證:同一個
weekStart+periodType不會重複結算 - 結算流程:
- 載入佣金比例 (
buildRateMap()) - 查詢該期間所有有效注單(
status = 'valid') - 依會員的三層代理鏈逐層計算佣金(基於下線淨輸
netLoss× 佣金比例) - 佣金比例查詢優先順序:
{tier}-{level}-{gameType}→{tier}-{level}-*(wildcard fallback) - 建立
affiliate-settlement和affiliate-commission記錄 - 執行風控檢測(若觸發則標記
riskFlagged,狀態改為pendingReview)
- 載入佣金比例 (
agentsProcessed:本次處理的代理數量totalCommission:本次產生的佣金總額(USD)flaggedCount:觸發風控標記的結算數量
13. POST /affiliate/admin/trigger-daily-settlement — 手動觸發日結算
手動觸發一次佣金日結算(等同 Cron Job 每日 03:30 執行的邏輯)。結算範圍為昨日整天。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"agentsProcessed": 10,
"totalCommission": "800.000000",
"flaggedCount": 1
}
}業務邏輯
- 邏輯同週結算,但
periodType = 'daily',範圍為前一天(00:00:00 ~ 23:59:59) - 冪等保證同週結算(同日不重複結算)
- 結算完畢後同樣執行風控檢測
二、Alliance 聯盟管理端點
14. GET /affiliate/admin/commission-rates — 佣金比例列表
取得所有佣金比例設定記錄。佣金比例決定不同代理階層、不同層級(直屬/二級/三級)、不同遊戲類型的佣金百分比。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"agentTier": "bronze",
"agentLevel": 1,
"gameType": null,
"commissionRate": "0.25",
"enabled": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
},
{
"id": 2,
"agentTier": "bronze",
"agentLevel": 1,
"gameType": "slot",
"commissionRate": "0.28",
"enabled": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]
}業務邏輯
- 排序:
agentTier ASC→agentLevel ASC→gameType ASC gameType = null表示「全部遊戲類型 fallback」(通配)commissionRate為百分比(例:"0.25"表示 25%)- Unique constraint:
(agentTier, agentLevel, gameType)組合唯一 - 結算時查詢優先順序:精確遊戲類型 → 通配 (
null)
15. POST /affiliate/admin/commission-rates — 新增/更新佣金比例
新增或更新一條佣金比例設定。當 body 包含 id 時為更新,不包含 id 時為新增。更新後自動清除公開資訊快取。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Body | id | number | 否 | 有值 = 更新,無值 = 新增 |
| Body | agentTier | string | 是 | 代理階層:bronze / silver / gold / platinum |
| Body | agentLevel | number | 是 | 代理層級:1(直屬) / 2(二級) / 3(三級) |
| Body | gameType | string | 否 | 遊戲類型(不填 = 全部 fallback):sports / slot / live / lottery / chess / esports / crypto / fish |
| Body | commissionRate | number | 是 | 佣金比例(%),範圍 0~100 |
| Body | enabled | number | 否 | 是否啟用:0 / 1(預設 1) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"agentTier": "silver",
"agentLevel": 1,
"gameType": "slot",
"commissionRate": "30",
"enabled": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-02-28T15:30:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 更新時指定的 id 不存在 |
業務邏輯
- 完整模板包含 4 階層 x 3 層級 x (1 通配 + 8 遊戲類型) = 108 條記錄
- 新增/更新後自動清除
cache:alliance:public-info快取 commissionRate存為decimal(5,2)型別
16. DELETE /affiliate/admin/commission-rates/:id — 刪除佣金比例
刪除指定的佣金比例設定。刪除後自動清除公開資訊快取。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Path | id | number | 是 | 佣金比例記錄 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 指定的記錄不存在 |
17. GET /affiliate/admin/vip-milestones — VIP 里程碑列表
取得所有 VIP 里程碑設定。VIP 里程碑定義當下線會員達到指定 VIP 等級時,代理可獲得的獎勵金額。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"vipLevel": 3,
"bonusAmount": "5.000000",
"description": "下線達 VIP 3 獎勵",
"enabled": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
},
{
"id": 2,
"vipLevel": 5,
"bonusAmount": "20.000000",
"description": "下線達 VIP 5 獎勵",
"enabled": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]
}業務邏輯
- 排序:
vipLevel ASC vipLevel為 unique constraint,每個 VIP 等級只能有一個里程碑- 里程碑獎勵由
VipService.recalculateUserVip()觸發,會員升級後自動呼叫AllianceService.checkAndAwardMilestone() - 獎勵直接入帳到直屬代理(L1)的
affiliate-balance.available和totalEarned - 獎勵冪等:每位代理對同一會員的同一 VIP 等級只發放一次(unique constraint on
alliance-vip-milestone-log)
18. POST /affiliate/admin/vip-milestones — 新增/更新 VIP 里程碑
新增或更新一條 VIP 里程碑設定。當 body 包含 id 時為更新,不包含 id 時為新增。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Body | id | number | 否 | 有值 = 更新,無值 = 新增 |
| Body | vipLevel | number | 是 | 觸發的 VIP 等級(1~15) |
| Body | bonusAmount | number | 是 | 獎勵金額(USD),最小 0 |
| Body | description | string | 否 | 里程碑說明(最長 100 字元) |
| Body | enabled | number | 否 | 是否啟用:0 / 1(預設 1) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 3,
"vipLevel": 7,
"bonusAmount": "50",
"description": "下線達 VIP 7 獎勵",
"enabled": 1,
"createdAt": "2026-02-28T15:30:00.000Z",
"updatedAt": "2026-02-28T15:30:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 更新時指定的 id 不存在 |
2002 | 新增時 vipLevel 已存在(unique constraint) |
業務邏輯
- 更新後自動清除
cache:alliance:public-info快取 bonusAmount存為decimal(18,6)型別
19. DELETE /affiliate/admin/vip-milestones/:id — 刪除 VIP 里程碑
刪除指定的 VIP 里程碑設定。刪除後自動清除公開資訊快取。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Path | id | number | 是 | VIP 里程碑記錄 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 指定的記錄不存在 |
20. GET /affiliate/admin/agent-tiers — 代理階層列表
取得所有代理階層設定。代理階層定義不同等級的門檻條件和佣金費率對應。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"tierCode": "bronze",
"tierName": "Bronze Agent",
"minTotalEarned": "0.000000",
"minActiveMembers": 0,
"sortOrder": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
},
{
"id": 2,
"tierCode": "silver",
"tierName": "Silver Agent",
"minTotalEarned": "500.000000",
"minActiveMembers": 5,
"sortOrder": 2,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
},
{
"id": 3,
"tierCode": "gold",
"tierName": "Gold Agent",
"minTotalEarned": "5000.000000",
"minActiveMembers": 20,
"sortOrder": 3,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
},
{
"id": 4,
"tierCode": "platinum",
"tierName": "Platinum Agent",
"minTotalEarned": "50000.000000",
"minActiveMembers": 50,
"sortOrder": 4,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]
}業務邏輯
- 排序:
sortOrder ASC tierCode為 unique constraintminTotalEarned:自動升級的累計佣金門檻(USD)minActiveMembers:自動升級所需的最低活躍直屬下線人數(0= 無要求)- 預設 4 個階層:bronze → silver → gold → platinum
21. POST /affiliate/admin/agent-tiers — 新增/更新代理階層
新增或更新一個代理階層設定。當 body 包含 id 時為更新,不包含 id 時為新增。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Body | id | number | 否 | 有值 = 更新,無值 = 新增 |
| Body | tierCode | string | 是 | 階層識別碼:bronze / silver / gold / platinum |
| Body | tierName | string | 是 | 顯示名稱(最長 50 字元) |
| Body | minTotalEarned | number | 是 | 自動升級門檻(USD 累計佣金),最小 0 |
| Body | minActiveMembers | number | 是 | 最低活躍直屬下線人數,最小 0 |
| Body | sortOrder | number | 否 | 排序順序(預設 1) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 2,
"tierCode": "silver",
"tierName": "Silver Agent",
"minTotalEarned": "500",
"minActiveMembers": 5,
"sortOrder": 2,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-02-28T16:00:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 更新時指定的 id 不存在 |
業務邏輯
- 更新後自動清除
cache:alliance:public-info快取 minTotalEarned存為decimal(18,6)型別
22. DELETE /affiliate/admin/agent-tiers/:id — 刪除代理階層
刪除指定的代理階層設定。刪除後自動清除公開資訊快取。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Path | id | number | 是 | 代理階層記錄 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 指定的記錄不存在 |
注意事項
- 刪除已被代理使用的階層(
affiliate-balance.agentTier引用中)不會阻止刪除,但會導致結算時查不到對應的佣金比例(fallback 為 0)
23. GET /affiliate/admin/preview-template — 預覽聯盟模板資料
回傳預設模板的完整資料(代理階層 + 佣金比例 + VIP 里程碑),僅供預覽,不寫入資料庫。管理員可在確認後呼叫 load-template 寫入。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"tiers": [
{ "tierCode": "bronze", "tierName": "Bronze Agent", "minTotalEarned": "0", "minActiveMembers": 0, "sortOrder": 1 },
{ "tierCode": "silver", "tierName": "Silver Agent", "minTotalEarned": "500", "minActiveMembers": 5, "sortOrder": 2 },
{ "tierCode": "gold", "tierName": "Gold Agent", "minTotalEarned": "5000", "minActiveMembers": 20, "sortOrder": 3 },
{ "tierCode": "platinum", "tierName": "Platinum Agent", "minTotalEarned": "50000", "minActiveMembers": 50, "sortOrder": 4 }
],
"commissionRates": [
{ "agentTier": "bronze", "agentLevel": 1, "gameType": null, "commissionRate": "0.25", "enabled": 1 },
{ "agentTier": "bronze", "agentLevel": 1, "gameType": "sports", "commissionRate": "0.25", "enabled": 1 },
{ "agentTier": "bronze", "agentLevel": 1, "gameType": "slot", "commissionRate": "0.28", "enabled": 1 }
],
"milestones": [
{ "vipLevel": 3, "bonusAmount": "5", "description": "下線達 VIP 3 獎勵", "enabled": 1 },
{ "vipLevel": 5, "bonusAmount": "20", "description": "下線達 VIP 5 獎勵", "enabled": 1 },
{ "vipLevel": 7, "bonusAmount": "50", "description": "下線達 VIP 7 獎勵", "enabled": 1 },
{ "vipLevel": 10, "bonusAmount": "200", "description": "下線達 VIP 10 獎勵", "enabled": 1 },
{ "vipLevel": 13, "bonusAmount": "1000", "description": "下線達 VIP 13 獎勵", "enabled": 1 }
]
}
}預設模板內容
| 類型 | 數量 | 說明 |
|---|---|---|
| 代理階層 | 4 個 | bronze / silver / gold / platinum |
| 佣金比例 | 108 條 | 4 階層 x 3 層級 x (1 通配 + 8 遊戲類型) |
| VIP 里程碑 | 5 個 | VIP 3/5/7/10/13 |
佣金比例基礎費率(通配):
| 階層 | 直屬 (L1) | 二級 (L2) | 三級 (L3) |
|---|---|---|---|
| bronze | 25% | 10% | 5% |
| silver | 30% | 12% | 6% |
| gold | 35% | 15% | 8% |
| platinum | 40% | 18% | 10% |
遊戲類型費率微調:slot +3%, live +2%, crypto -5%, fish -5%(其餘類型 +0%)
24. POST /affiliate/admin/load-template — 帶入聯盟模板
將模板資料寫入資料庫,在 transaction 內先刪除所有現有記錄再插入新記錄。可傳入自訂資料覆蓋預設模板。此操作會清空全部現有的佣金比例、代理階層和 VIP 里程碑設定。
認證需求:AdminJwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer <admin-jwt-token> |
| Body | tiers | array | 否 | 自訂代理階層陣列(不傳則使用預設模板) |
| Body | commissionRates | array | 否 | 自訂佣金比例陣列(不傳則使用預設模板) |
| Body | milestones | array | 否 | 自訂 VIP 里程碑陣列(不傳則使用預設模板) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"tiers": 4,
"commissionRates": 108,
"milestones": 5
}
}業務邏輯
- 原子操作 (Transaction):在 transaction 內先
DELETE三張表的全部記錄,再批量INSERT - 若 body 的
tiers/commissionRates/milestones為空陣列或未提供,則使用預設模板資料 - 操作完成後自動清除
cache:alliance:public-info快取 - 回傳值為各類型寫入的記錄數量
- 注意:此操作會覆蓋所有現有設定,屬於破壞性操作,前端應在呼叫前顯示確認對話框
三、相關資料表結構
affiliate-settlement
| 欄位 | 型別 | 說明 |
|---|---|---|
id | int (PK) | 自增主鍵 |
agentId | int | 代理 user ID |
weekStart | date | 結算開始日 |
weekEnd | date | 結算結束日 |
activeMemberCount | int | 本期活躍下線人數 |
totalNetLoss | decimal(18,6) | 下線總淨輸 (USD) |
level1Commission | decimal(18,6) | 一級佣金 |
level2Commission | decimal(18,6) | 二級佣金 |
level3Commission | decimal(18,6) | 三級佣金 |
totalCommission | decimal(18,6) | 佣金總額 |
gameTypeBreakdown | json | 各遊戲類型佣金分解 |
periodType | varchar(10) | 結算週期:weekly / daily |
status | varchar(20) | pending / pendingReview / approved / rejected |
riskFlagged | tinyint(1) | 是否有風控標記 |
riskReasons | text | 風控原因 (JSON array) |
reviewedBy | varchar(50) | 審核人帳號 |
reviewedAt | datetime | 審核時間 |
siteCode | varchar(30) | 站點代碼 |
Unique: (siteCode, agentId, weekStart)
affiliate-withdrawal
| 欄位 | 型別 | 說明 |
|---|---|---|
id | int (PK) | 自增主鍵 |
agentId | int | 代理 user ID |
amount | decimal(18,6) | 提款金額 (USD) |
method | varchar(10) | 提款方式:bank / crypto |
bankCardId | int? | 銀行卡 ID |
cryptoAddressId | int? | 加密錢包 ID |
status | varchar(20) | pending / approved / rejected / completed |
rejectReason | varchar(255) | 拒絕原因 |
reviewedBy | varchar(50) | 審核人帳號 |
reviewedAt | datetime | 審核時間 |
completedAt | datetime | 完成時間 |
siteCode | varchar(30) | 站點代碼 |
affiliate-risk-log
| 欄位 | 型別 | 說明 |
|---|---|---|
id | int (PK) | 自增主鍵 |
settlementId | int | 關聯結算 ID |
agentId | int | 代理 user ID |
memberId | int? | 相關會員 user ID |
riskType | varchar(50) | 風控類型 |
detail | text | 詳細資訊 (JSON) |
siteCode | varchar(30) | 站點代碼 |
alliance-commission-rate
| 欄位 | 型別 | 說明 |
|---|---|---|
id | int (PK) | 自增主鍵 |
agentTier | varchar(20) | 代理階層 |
agentLevel | tinyint | 代理層級 (1/2/3) |
gameType | varchar(20)? | 遊戲類型 (null=通配) |
commissionRate | decimal(5,2) | 佣金比例 (%) |
enabled | tinyint(1) | 是否啟用 |
Unique: (agentTier, agentLevel, gameType)
alliance-agent-tier
| 欄位 | 型別 | 說明 |
|---|---|---|
id | int (PK) | 自增主鍵 |
tierCode | varchar(20) | 階層識別碼 (unique) |
tierName | varchar(50) | 顯示名稱 |
minTotalEarned | decimal(18,6) | 升級門檻 (USD) |
minActiveMembers | int | 最低活躍下線人數 |
sortOrder | int | 排序 |
alliance-vip-milestone
| 欄位 | 型別 | 說明 |
|---|---|---|
id | int (PK) | 自增主鍵 |
vipLevel | int | 觸發 VIP 等級 (unique) |
bonusAmount | decimal(18,6) | 獎勵金額 (USD) |
description | varchar(100)? | 說明 |
enabled | tinyint(1) | 是否啟用 |
四、端點總覽
| # | 方法 | 路徑 | 說明 |
|---|---|---|---|
| 1 | GET | /affiliate/admin/agents | 代理列表(含多站篩選) |
| 2 | POST | /affiliate/admin/create-agent | 建立代理 |
| 3 | GET | /affiliate/admin/settlements | 結算列表 |
| 4 | POST | /affiliate/admin/settlements/:id/review | 審核結算 |
| 5 | GET | /affiliate/admin/settlements/:id/risk-logs | 風控紀錄 |
| 6 | GET | /affiliate/admin/withdrawals | 提款列表 |
| 7 | POST | /affiliate/admin/withdrawals/:id/review | 審核提款 |
| 8 | POST | /affiliate/admin/withdrawals/:id/complete | 提款完成 |
| 9 | POST | /affiliate/admin/bind | 手動綁定/解綁/轉移 |
| 10 | GET | /affiliate/admin/bind-logs | 綁定紀錄 |
| 11 | POST | /affiliate/admin/set-agent-tier | 設定代理等級 |
| 12 | POST | /affiliate/admin/trigger-settlement | 手動觸發週結 |
| 13 | POST | /affiliate/admin/trigger-daily-settlement | 手動觸發日結 |
| 14 | GET | /affiliate/admin/commission-rates | 佣金比例列表 |
| 15 | POST | /affiliate/admin/commission-rates | 新增/更新佣金比例 |
| 16 | DELETE | /affiliate/admin/commission-rates/:id | 刪除佣金比例 |
| 17 | GET | /affiliate/admin/vip-milestones | VIP 里程碑列表 |
| 18 | POST | /affiliate/admin/vip-milestones | 新增/更新 VIP 里程碑 |
| 19 | DELETE | /affiliate/admin/vip-milestones/:id | 刪除 VIP 里程碑 |
| 20 | GET | /affiliate/admin/agent-tiers | 代理階層列表 |
| 21 | POST | /affiliate/admin/agent-tiers | 新增/更新代理階層 |
| 22 | DELETE | /affiliate/admin/agent-tiers/:id | 刪除代理階層 |
| 23 | GET | /affiliate/admin/preview-template | 預覽聯盟模板 |
| 24 | POST | /affiliate/admin/load-template | 帶入聯盟模板 |
共 24 個端點,全部需要 AdminJwtAuthGuard 認證。
Admin 後台管理模組 — 完整 API 文件
本文件涵蓋
c9-be後端專案中AdminController的所有端點(76+ 個),路由前綴為/api/admin。 所有端點除認證相關(login / register / send-verify-code / verify-email)外,皆需攜帶 AdminJWT Bearer Token。
目錄
通用說明
認證機制
所有後台 API 端點皆使用 AdminJwtAuthGuard,需在 HTTP Header 中攜帶:
Authorization: Bearer <admin_jwt_token>JWT Payload 結構:
{
"sub": 1,
"email": "admin@example.com",
"tokenVersion": 5,
"role": "admin",
"groupId": 1,
"groupType": "root"
}RBAC 權限檢查
大部分端點同時使用 PermissionsGuard + @RequirePermissions('module:action') 進行權限檢查。
16 個權限模組:admin, admin-group, admin-log, user, deposit, withdrawal, promo, promo-tag, affiliate, vip, game, risk, report, vendor, finance, site-config
2 種動作:read, write
4 種群組類型:
| 群組類型 | 權限範圍 |
|---|---|
root | 全部權限,略過所有檢查 |
super_admin | 全部權限(除 site-config) |
general_admin | 僅讀取權限(除 site-config) |
custom | 依群組 permissions JSON 陣列自訂 |
多站點篩選
使用 @AdminSiteCode() 裝飾器的端點支援多站點篩選:
- 優先讀取
x-site-codeHTTP Header - 其次讀取
siteCodeQuery Parameter - 若均未提供則為
null(表示全站)
統一回應格式
{
"code": 200,
"message": "ok",
"result": "<回傳資料>",
"timestamp": "2026-03-01T00:00:00.000Z",
"path": "/api/admin/xxx"
}業務錯誤回應(HTTP 仍為 200):
{
"code": 2001,
"message": "錯誤訊息(依當前語系)",
"timestamp": "2026-03-01T00:00:00.000Z",
"path": "/api/admin/xxx"
}1. 認證管理
POST /api/admin/login — 管理員登入
功能說明
管理員透過 Email 與密碼登入後台管理系統。系統會驗證 Email 是否存在、密碼是否正確、帳號是否為啟用狀態。驗證通過後簽發 AdminJWT Token,Token 中包含管理員 ID、Email、tokenVersion、角色資訊及所屬群組。若管理員已啟用 Google Authenticator 2FA,登入時需額外驗證。
認證需求:無(公開端點)
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
email | string (Email) | 是 | 管理員 Email | "admin@example.com" |
password | string (min: 6) | 是 | 管理員密碼,至少 6 碼 | "admin1234" |
// Request Body
{
"email": "admin@example.com",
"password": "admin1234"
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"admin": {
"id": 1,
"email": "admin@example.com",
"name": "超級管理員",
"groupId": 1,
"status": 1
}
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | Email 或密碼錯誤 |
2002 | 帳號已停用 |
業務邏輯說明
- 系統先查詢
admin-user資料表確認 Email 是否存在 - 使用 bcrypt 比對密碼雜湊
- 確認帳號
status === 1(啟用中) - 簽發 JWT 時帶入
tokenVersion,供後續強制登出機制使用 - 登入成功後記錄操作紀錄
前端注意事項
- 登入成功後將
token存入 NextAuth session - 後續所有 API 請求需在
AuthorizationHeader 帶入Bearer <token> - 若收到 HTTP 401 回應,前端需清除 session 並導向登入頁
POST /api/admin/send-verify-code — 發送驗證碼
功能說明
向指定 Email 發送 6 位數驗證碼,用於管理員註冊流程的 Email 驗證步驟。驗證碼透過 Resend API 發送至指定信箱,具有過期時間限制,且短時間內不可重複發送。
認證需求:無(公開端點)
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
email | string (Email) | 是 | 接收驗證碼的 Email | "admin@example.com" |
{
"email": "admin@example.com"
}Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 請先提供 Email |
2002 | 驗證碼已發送,請稍後再試 |
2003 | Email 發送失敗 |
業務邏輯說明
- 驗證碼存入 Redis 快取,設有 TTL 過期時間
- 同一 Email 在短時間內重複請求會回傳
2002錯誤 - 驗證碼為 6 位純數字
前端注意事項
- 發送後應啟動倒數計時器(如 60 秒),防止用戶連續點擊
- 成功後引導用戶至驗證碼輸入介面
POST /api/admin/verify-email — 驗證 Email
功能說明
驗證用戶輸入的 6 位數驗證碼是否正確。此步驟為管理員註冊流程的前置條件,只有通過 Email 驗證後才能進行註冊。
認證需求:無(公開端點)
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
email | string (Email) | 是 | 管理員 Email | "admin@example.com" |
code | string (length: 6) | 是 | 6 位數驗證碼 | "123456" |
{
"email": "admin@example.com",
"code": "123456"
}Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 驗證碼不存在或已過期 |
2002 | 驗證碼錯誤 |
業務邏輯說明
- 從 Redis 中讀取該 Email 對應的驗證碼進行比對
- 驗證成功後在 Redis 標記該 Email 已驗證
- 驗證碼使用後即失效(一次性)
前端注意事項
- 驗證成功後才啟用「註冊」按鈕
- 驗證碼長度固定 6 位,前端需做格式驗證
POST /api/admin/register — 管理員註冊
功能說明
完成 Email 驗證後,管理員可透過此端點完成註冊。系統會檢查 Email 是否已被使用、是否已通過 Email 驗證、指定的群組是否存在。註冊成功後自動建立管理員帳號並分配至指定群組。
認證需求:無(公開端點)
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
email | string (Email) | 是 | 管理員 Email | "admin@example.com" |
password | string (min: 6) | 是 | 密碼,至少 6 碼 | "admin1234" |
name | string | 是 | 管理員顯示名稱 | "超級管理員" |
code | string | 是 | 6 位驗證碼 | "123456" |
groupId | number | 否 | 所屬群組 ID | 1 |
{
"email": "admin@example.com",
"password": "admin1234",
"name": "超級管理員",
"code": "123456",
"groupId": 1
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"email": "admin@example.com",
"name": "超級管理員",
"groupId": 1
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | Email 已存在 |
2002 | Email 尚未驗證 |
2003 | 群組不存在 |
業務邏輯說明
- 密碼以 bcrypt 雜湊後儲存
- 若未指定
groupId,系統會分配預設群組 - 註冊完成後 Redis 中的驗證標記會被清除
tokenVersion初始值為 0
前端注意事項
- 註冊流程:發送驗證碼 -> 驗證 Email -> 填寫資料 -> 註冊
- 註冊成功後自動導向登入頁
2. 個人資料
GET /api/admin/profile — 取得管理員個人資料
功能說明
取得當前已登入管理員的完整個人資料,包含基本資訊、所屬群組名稱、Google Authenticator 啟用狀態等。此端點用於後台個人資料頁面的資料載入。
認證需求:AdminJwtAuthGuard + PermissionsGuard(無特定權限要求,僅需登入)
Request
- Headers:
Authorization: Bearer <token> - 無其他參數
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"email": "admin@example.com",
"name": "超級管理員",
"groupId": 1,
"groupName": "root",
"status": 1,
"googleAuthEnabled": 0,
"createdAt": "2026-02-22T00:00:00.000Z"
}
}業務邏輯說明
- 從 JWT Token 中取得
adminId,查詢admin-user資料表 - JOIN
admin-group取得群組名稱 googleAuthEnabled為0(未啟用)或1(已啟用)
前端注意事項
- 用於渲染個人資料頁面
googleAuthEnabled用於決定是否顯示「啟用 2FA」或「停用 2FA」按鈕
POST /api/admin/google-auth/generate — 產生 Google Authenticator 密鑰與 QR Code
功能說明
為當前管理員產生 TOTP 密鑰與對應的 QR Code 圖片(Base64 編碼的 PNG)。管理員可使用 Google Authenticator App 掃描 QR Code 以綁定帳號。產生的密鑰會暫存於管理員記錄中,待驗證成功後才正式啟用。
認證需求:AdminJwtAuthGuard + PermissionsGuard(僅需登入)
Request
- Headers:
Authorization: Bearer <token> - 無其他參數
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"secret": "JBSWY3DPEHPK3PXP",
"qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
}
}業務邏輯說明
- 使用
speakeasy套件產生 TOTP Secret - QR Code 包含
otpauth://totp/C9:admin@example.com?secret=xxx&issuer=C9格式的 URI - Secret 暫存於
admin-user.googleAuthSecret欄位,但googleAuthEnabled維持為 0 - 需後續呼叫
/google-auth/enable端點才會正式啟用
前端注意事項
- 將
qrCode直接作為<img>的src顯示 - 同時顯示
secret供無法掃描的用戶手動輸入 - 前端元件:
google-auth-dialog.tsx(狀態流程:idle -> qr -> verify)
POST /api/admin/google-auth/enable — 啟用 Google Authenticator
功能說明
管理員掃描 QR Code 後,輸入 Google Authenticator App 顯示的 6 位數驗證碼,系統驗證成功後正式啟用 2FA。啟用後每次登入將需要額外提供 TOTP 驗證碼。
認證需求:AdminJwtAuthGuard + PermissionsGuard(僅需登入)
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
code | string (length: 6) | 是 | Google Authenticator 6 位數驗證碼 | "123456" |
{
"code": "123456"
}Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 請先產生密鑰(需先呼叫 /google-auth/generate) |
2002 | 驗證碼錯誤 |
業務邏輯說明
- 使用
speakeasy.totp.verify()驗證 TOTP 碼 - 驗證成功後將
googleAuthEnabled設為1 - Secret 保留在資料庫中供後續登入驗證使用
前端注意事項
- 啟用成功後關閉對話框並刷新 Profile 資料
- 提醒用戶備份 Secret,避免遺失手機後無法登入
POST /api/admin/google-auth/disable — 停用 Google Authenticator
功能說明
管理員輸入當前的 6 位數 TOTP 驗證碼以確認身份,驗證成功後停用 Google Authenticator 2FA。停用後登入將不再需要 TOTP 驗證碼。
認證需求:AdminJwtAuthGuard + PermissionsGuard(僅需登入)
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
code | string (length: 6) | 是 | Google Authenticator 6 位數驗證碼 | "654321" |
{
"code": "654321"
}Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 請先產生密鑰 |
2002 | 驗證碼錯誤 |
業務邏輯說明
- 必須提供正確的 TOTP 驗證碼才能停用,防止未授權操作
- 停用後
googleAuthEnabled設為0,googleAuthSecret清空
前端注意事項
- 停用操作需先彈出確認對話框
- 成功後刷新 Profile 資料,UI 切回「啟用」按鈕狀態
3. 權限管理
GET /api/admin/permissions/all — 取得所有可用權限模組與動作
功能說明
取得系統中所有可配置的權限模組名稱與可用動作列表。此端點用於群組管理頁面的權限勾選介面,讓管理員了解有哪些權限可以分配給群組。
認證需求:AdminJwtAuthGuard + PermissionsGuard(僅需登入)
Request
- Headers:
Authorization: Bearer <token> - 無其他參數
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"modules": [
"admin",
"admin-group",
"admin-log",
"user",
"deposit",
"withdrawal",
"promo",
"promo-tag",
"affiliate",
"vip",
"game",
"risk",
"report",
"vendor",
"finance",
"site-config"
],
"actions": ["read", "write"]
}
}業務邏輯說明
- 回傳來自
constants/permissions.ts中定義的靜態常數 - 權限格式為
module:action,如admin:read、deposit:write - 共 16 個模組 x 2 個動作 = 32 個可配置權限
前端注意事項
- 用於群組建立/編輯頁面的權限矩陣
- 前端使用
usePermissions()hook 進行 RBAC 檢查 root群組類型不受權限限制,super_admin除了site-config外擁有全部權限
4. 管理員 CRUD
GET /api/admin/list — 管理員列表
功能說明
取得後台管理員列表,支援分頁、關鍵字搜尋(Email / 名稱)、啟用狀態篩選、群組篩選及日期範圍篩選。此端點用於系統管理中的管理員列表頁面。注意此為全站共用設定,不區分站點。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼,預設 1 | 1 |
pageSize | Query | number | 否 | 每頁筆數,預設 20 | 20 |
keyword | Query | string | 否 | 搜尋 email / name | "admin" |
status | Query | number | 否 | 啟用狀態(1=啟用, 0=停用) | 1 |
groupId | Query | number | 否 | 群組 ID 篩選 | 1 |
startDate | Query | string | 否 | 起始日期 (YYYY-MM-DD) | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 (YYYY-MM-DD) | "2026-12-31" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"email": "admin@example.com",
"name": "超級管理員",
"groupId": 1,
"groupName": "root",
"status": 1,
"createdAt": "2026-02-22T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}
}業務邏輯說明
- 使用
parsePagination()工具函數正規化分頁參數 keyword同時模糊搜尋email和name欄位- JOIN
admin-group表取得groupName - 使用
applyDateRange()套用日期區間篩選 - 密碼欄位不回傳
前端注意事項
- 此頁面在 Header
SiteSelector中自動隱藏站點選擇(全站共用) - 使用
FilterBar元件管理篩選條件 - 使用
SimpleTable元件渲染列表,搭配pagination元件
GET /api/admin/:id — 取得單一管理員
功能說明
根據管理員 ID 取得單一管理員的完整資料,包含所屬群組名稱。用於管理員編輯頁面的資料載入。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | number | 是 | 管理員 ID | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"email": "admin@example.com",
"name": "超級管理員",
"groupId": 1,
"groupName": "root",
"status": 1,
"createdAt": "2026-02-22T00:00:00.000Z"
}
}前端注意事項
- 用於編輯對話框的資料初始化
- 注意此端點路由
/:id的位置可能與其他路由衝突,NestJS 會按宣告順序匹配
POST /api/admin/create — 建立管理員
功能說明
由具有 admin:write 權限的管理員建立新的管理員帳號。系統會檢查 Email 是否已被使用,以及指定的群組是否存在。建立成功後會記錄操作紀錄,包含操作者、被建立的管理員資訊。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
email | string (Email) | 是 | 管理員 Email | "new_admin@example.com" |
password | string (min: 6) | 是 | 密碼,至少 6 碼 | "pass1234" |
name | string | 是 | 管理員顯示名稱 | "新管理員" |
groupId | number | 否 | 所屬群組 ID | 2 |
{
"email": "new_admin@example.com",
"password": "pass1234",
"name": "新管理員",
"groupId": 2
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 2,
"email": "new_admin@example.com",
"name": "新管理員",
"groupId": 2
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | Email 已存在 |
2002 | 群組不存在 |
業務邏輯說明
- 密碼以 bcrypt 雜湊後儲存
- 自動記錄操作紀錄至
admin-operation-log表 - 操作紀錄包含
adminId(操作者)、action: 'create'、module: 'admin'、detail(被建立者資訊)、ip
前端注意事項
- 建立成功後需刷新管理員列表
- 群組下拉選單的選項來自群組列表 API
PATCH /api/admin/:id — 更新管理員
功能說明
更新指定管理員的資料,支援修改名稱、密碼、群組、啟用狀態。所有欄位皆為選填,僅更新有提供的欄位。停用管理員後該管理員將無法登入。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | number | 是 | 管理員 ID | 1 |
name | Body | string | 否 | 新名稱 | "新名稱" |
password | Body | string (min: 6) | 否 | 新密碼 | "newpass123" |
groupId | Body | number | 否 | 新群組 ID | 2 |
status | Body | number (0 or 1) | 否 | 啟用狀態 | 0 |
{
"name": "新名稱",
"status": 0
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"email": "admin@example.com",
"name": "新名稱",
"groupId": 1,
"status": 0
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 管理員不存在 |
業務邏輯說明
- 若更新密碼,新密碼同樣以 bcrypt 雜湊後儲存
- 停用管理員 (
status: 0) 後,該管理員的 JWT 驗證將失敗 - 操作紀錄會記錄變更前後的差異
前端注意事項
- 更新成功後需刷新列表
- 密碼欄位為選填,空白表示不更新密碼
DELETE /api/admin/:id — 刪除管理員
功能說明
刪除指定的管理員帳號。系統會檢查目標管理員是否存在,且不允許管理員刪除自己。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | number | 是 | 管理員 ID | 2 |
Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 管理員不存在 |
2002 | 不可刪除自己 |
業務邏輯說明
- 硬刪除(物理刪除),資料從資料庫中移除
- 刪除前檢查
req.user.id !== targetId - 記錄刪除操作至操作紀錄
前端注意事項
- 刪除前需使用
useConfirm()彈出確認對話框 - 刪除成功後刷新列表
5. 群組 CRUD
GET /api/admin/groups/list — 管理員群組列表
功能說明
取得後台管理員群組列表,支援分頁、關鍵字搜尋(群組名稱)、啟用狀態篩選及日期範圍篩選。群組為 RBAC 權限系統的核心,每個管理員必須隸屬於一個群組。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin-group:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
keyword | Query | string | 否 | 搜尋群組名稱 | "admin" |
status | Query | number | 否 | 啟用狀態(1/0) | 1 |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"name": "Root",
"groupType": "root",
"permissions": ["admin:read", "admin:write", "..."],
"description": "超級管理員群組",
"status": 1,
"memberCount": 2,
"createdAt": "2026-02-22T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 4,
"totalPages": 1
}
}
}業務邏輯說明
memberCount為該群組下的管理員數量(透過 COUNT 子查詢計算)permissions為 JSON 陣列,格式為["module:action", ...]- 群組權限快取於 Redis 中(key:
cache:admin-group:perms:{groupId},TTL: 60 秒)
前端注意事項
- 此頁面為全站共用設定,不顯示站點選擇器
- 顯示
memberCount有助於判斷群組是否可安全刪除
GET /api/admin/groups/:id — 取得單一群組
功能說明
根據群組 ID 取得單一群組的完整資料,包含權限列表。用於群組編輯頁面的資料載入。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin-group:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 群組 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"name": "Root",
"groupType": "root",
"permissions": ["admin:read", "admin:write"],
"description": "超級管理員群組",
"status": 1,
"createdAt": "2026-02-22T00:00:00.000Z"
}
}POST /api/admin/groups/create — 建立管理員群組
功能說明
建立新的管理員群組,可指定群組名稱、權限列表及描述。新建群組的 groupType 預設為 custom。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin-group:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
name | string | 是 | 群組名稱 | "客服組" |
permissions | string[] | 否 | 權限列表 | ["user:read", "deposit:read"] |
description | string | 否 | 群組描述 | "客服人員專用" |
{
"name": "客服組",
"permissions": ["user:read", "deposit:read", "withdrawal:read"],
"description": "客服人員專用群組"
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 5,
"name": "客服組",
"groupType": "custom",
"permissions": ["user:read", "deposit:read", "withdrawal:read"],
"description": "客服人員專用群組",
"status": 1
}
}業務邏輯說明
- 新建群組自動設為
groupType: 'custom' permissions欄位儲存為 JSON 陣列- 建立成功後清除相關群組權限快取
PATCH /api/admin/groups/:id — 更新管理員群組
功能說明
更新指定群組的名稱、權限列表、描述或啟用狀態。更新權限後會立即清除 Redis 中該群組的權限快取,確保下次權限檢查使用最新資料。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin-group:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | number | 是 | 群組 ID | 5 |
name | Body | string | 否 | 群組名稱 | "新群組名稱" |
permissions | Body | string[] | 否 | 權限列表 | ["admin:read", "deposit:read"] |
description | Body | string | 否 | 群組描述 | "更新描述" |
status | Body | number | 否 | 啟用狀態 | 0 |
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 群組不存在 |
業務邏輯說明
- 更新
permissions後會刪除cache:admin-group:perms:{groupId}快取 - 操作紀錄記錄變更內容
DELETE /api/admin/groups/:id — 刪除管理員群組
功能說明
刪除指定的管理員群組。若群組下仍有管理員,則無法刪除,需先將管理員移至其他群組。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin-group:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 群組 ID |
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 群組不存在 |
2002 | 群組下仍有管理員,不可刪除 |
前端注意事項
- 刪除前需使用
useConfirm()確認 - 如群組下有管理員,前端可提示用戶先移轉管理員
6. 操作紀錄
GET /api/admin/logs/list — 管理員操作紀錄列表
功能說明
查詢後台管理員的操作紀錄,支援按管理員 ID、操作模組、操作動作、日期範圍進行篩選。所有管理員的新增/修改/刪除操作都會被記錄,用於稽核追蹤。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 admin-log:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
adminId | Query | number | 否 | 管理員 ID | 1 |
module | Query | string | 否 | 操作模組 | "admin" |
action | Query | string | 否 | 操作動作 | "create" |
startDate | Query | string | 否 | 起始日期 (YYYY-MM-DD) | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 (YYYY-MM-DD) | "2026-12-31" |
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"adminId": 1,
"adminEmail": "admin@example.com",
"action": "create",
"module": "admin",
"detail": {
"targetId": 2,
"email": "new_admin@example.com",
"name": "新管理員"
},
"ip": "127.0.0.1",
"createdAt": "2026-02-22T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}
}業務邏輯說明
admin-operation-log資料表記錄所有管理操作detail為 JSON 欄位,記錄操作的具體內容(如變更前後差異)ip來源為req.ip- 此為全站共用紀錄,不區分站點
前端注意事項
- 此頁面在 Header
SiteSelector中自動隱藏站點選擇 detail欄位可使用 JSON 展開元件顯示- 可依
module和action建立下拉篩選器
7. 活動管理
7.1 優惠活動 (Promo)
GET /api/admin/promos/list — 活動列表
功能說明
取得後台活動管理列表,支援分頁、標籤篩選、啟用狀態篩選、日期範圍篩選、標題搜尋及條件類型篩選。此端點供後台管理員瀏覽所有活動(包含已關閉的),與前台 API 不同的是不會過濾未啟用的活動。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
tag | Query | string | 否 | 活動標籤 | "新手" |
enabled | Query | number | 否 | 啟用狀態(1=開啟, 0=關閉) | 1 |
startDate | Query | string | 否 | 起始日期 (YYYY-MM-DD) | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 (YYYY-MM-DD) | "2026-12-31" |
title | Query | string | 否 | 標題搜尋 | "首存" |
conditionType | Query | string | 否 | 條件類型 | "first_deposit" |
x-site-code | Header | string | 否 | 站點代碼(多站點篩選) | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"title": { "zh-TW": "新手首存禮", "en-US": "Welcome Bonus" },
"content": { "zh-TW": "<p>首次存款即送 10 USD</p>", "en-US": "<p>First deposit bonus 10 USD</p>" },
"tag": "新手",
"enabled": 1,
"conditionType": "first_deposit",
"conditionValue": "0",
"rewardAmount": "10.000000",
"maxClaims": 0,
"turnoverMultiplier": "3.00",
"startTime": "2026-03-01T00:00:00.000Z",
"endTime": "2026-04-01T00:00:00.000Z",
"imgPc": { "zh-TW": "https://cdn.example.com/promo/1_pc_zh-TW.jpg" },
"imgMobile": { "zh-TW": "https://cdn.example.com/promo/1_mobile_zh-TW.jpg" },
"siteCode": "C9",
"claimCount": 15,
"createdAt": "2026-02-20T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 5, "totalPages": 1 }
}
}業務邏輯說明
activeOnly: false— 後台列表不過濾已過期或未啟用的活動title使用CAST(promo.title AS CHAR) LIKE :keyword進行 JSON 欄位模糊搜尋conditionType支援:none,deposit_threshold,vip_level,first_depositrewardAmount以 USD decimal(18,6) 儲存turnoverMultiplier為打碼量倍數,decimal(10,2)maxClaims: 0表示無限制領取siteCode篩選透過@AdminSiteCode()裝飾器自動讀取
前端注意事項
- 已支援多站點 SiteTabs 切換
title和content為多語系 JSON,前端用resolveText()解析顯示- 活動圖片分為 PC 版和行動版,各語系獨立
GET /api/admin/promos/:id — 取得單一活動
功能說明
根據活動 ID 取得活動的完整原始資料(不做多語系解析),用於編輯頁面載入。支援多站點篩選確保只能存取所屬站點的活動。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 活動 ID |
x-site-code | Header | string | 否 | 站點代碼 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"title": { "zh-TW": "新手首存禮", "en-US": "Welcome Bonus" },
"content": { "zh-TW": "<p>首次存款即送 10 USD</p>", "en-US": "<p>First deposit bonus 10 USD</p>" },
"actionHtml": "<a href='/deposit'>立即存款</a>",
"tag": "新手",
"enabled": 1,
"conditionType": "first_deposit",
"conditionValue": "0",
"rewardAmount": "10.000000",
"maxClaims": 0,
"turnoverMultiplier": "3.00",
"startTime": "2026-03-01T00:00:00.000Z",
"endTime": "2026-04-01T00:00:00.000Z",
"imgPc": { "zh-TW": "https://cdn.example.com/promo/1_pc_zh-TW.jpg" },
"imgMobile": { "zh-TW": "https://cdn.example.com/promo/1_mobile_zh-TW.jpg" },
"siteCode": "C9",
"createdAt": "2026-02-20T00:00:00.000Z",
"updatedAt": "2026-02-25T00:00:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無此活動 |
POST /api/admin/promos/create — 建立活動
功能說明
建立新的優惠活動,支援多語系標題/內容、活動圖片上傳(PC/Mobile 各語系版本)、領取條件設定、獎勵金額設定等。活動圖片會上傳至 Cloudflare R2 儲存。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo:write 權限
Request
Content-Type: multipart/form-data
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
title | JSON string | 是 | 活動標題(多語系) | '{"zh-TW":"新手首存禮","en-US":"Welcome Bonus"}' |
content | JSON string | 是 | 活動內容 HTML(多語系) | '{"zh-TW":"<p>...</p>"}' |
actionHtml | string | 否 | 渲染連結/按鈕 HTML | "<a href='/deposit'>立即存款</a>" |
startTime | string (ISO 8601) | 是 | 活動開始時間 | "2026-03-01T00:00:00.000Z" |
endTime | string (ISO 8601) | 是 | 活動結束時間 | "2026-04-01T00:00:00.000Z" |
tag | string (max: 30) | 是 | 活動標籤 | "新手" |
enabled | number | 否 | 是否啟用(預設 1) | 1 |
conditionType | string | 是 | 領取條件類型 | "first_deposit" |
conditionValue | string | 否 | 門檻值(預設 "0") | "100" |
rewardAmount | number | 是 | 獎勵金額 (USD) | 10 |
maxClaims | number | 否 | 最大領取總數(0=無限) | 0 |
turnoverMultiplier | number | 否 | 打碼量倍數(0=無要求) | 3 |
imgPcZhTW | File | 否 | PC 版圖片(繁中) | - |
imgPcEnUS | File | 否 | PC 版圖片(英文) | - |
imgMobileZhTW | File | 否 | Mobile 版圖片(繁中) | - |
imgMobileEnUS | File | 否 | Mobile 版圖片(英文) | - |
conditionType 可用值
| 值 | 說明 |
|---|---|
none | 無條件 |
deposit_threshold | 存款門檻(conditionValue 為存款金額) |
vip_level | VIP 等級門檻(conditionValue 為 VIP 等級) |
first_deposit | 首次存款 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 6,
"title": { "zh-TW": "新手首存禮", "en-US": "Welcome Bonus" },
"siteCode": "C9",
"createdAt": "2026-03-01T00:00:00.000Z"
}
}業務邏輯說明
- 圖片檔案限制:僅接受 image/* MIME 類型,最大 5MB
- 圖片上傳至 R2 路徑:
{siteName}/promos/{promoId}_{platform}_{locale}.{ext} title和content以 JSON string 傳入(因為使用 multipart/form-data),後端自動JSON.parse()rewardAmount以 USD decimal(18,6) 儲存siteCode由@AdminSiteCode()決定寫入的站點
前端注意事項
- 使用 FormData 組裝請求
- JSON 欄位(
title,content)需先JSON.stringify()再塞入 FormData - 使用 Tiptap HTML 編輯器(
htmlEditor元件)編輯content
PATCH /api/admin/promos/:id — 更新活動
功能說明
更新指定活動的資料,所有欄位皆為選填。支援更新圖片(新上傳的圖片會覆蓋舊圖片)。使用 PartialType(CreatePromoDto) 繼承,所有建立時的欄位都可選填更新。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo:write 權限
Request
Content-Type: multipart/form-data
- URL Param:
id(活動 ID) - Body: 同
POST /promos/create,所有欄位皆為選填 - 圖片檔案為選填,僅上傳的語系版本會更新
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無此活動 |
DELETE /api/admin/promos/:id — 刪除活動
功能說明
刪除指定的優惠活動。關聯的活動領取紀錄(promo-claim)不會被刪除。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 活動 ID |
錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無此活動 |
7.2 活動標籤 (Promo Tags)
GET /api/admin/promo-tags/list — 活動標籤列表
功能說明
取得所有活動標籤列表,不分頁。標籤用於活動分類,每個標籤可設定多語系名稱、顏色、排序順序及啟用狀態。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo-tag:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
x-site-code | Header | string | 否 | 站點代碼 |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"name": "新手",
"label": { "zh-TW": "新手活動", "en-US": "Beginner" },
"color": "#FF6B6B",
"sortOrder": 1,
"enabled": 1,
"siteCode": "C9"
},
{
"id": 2,
"name": "VIP",
"label": { "zh-TW": "VIP 專屬", "en-US": "VIP Exclusive" },
"color": "#4ECDC4",
"sortOrder": 2,
"enabled": 1,
"siteCode": "C9"
}
]
}POST /api/admin/promo-tags/create — 建立活動標籤
功能說明
建立新的活動標籤,用於活動分類。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo-tag:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
name | string | 是 | 標籤內部名稱 | "新手" |
label | Record<string, string> | 否 | 多語系顯示名稱 | {"zh-TW":"新手活動","en-US":"Beginner"} |
color | string | 否 | 標籤顏色 (HEX) | "#FF6B6B" |
sortOrder | number | 否 | 排序順序 | 1 |
enabled | number | 否 | 啟用狀態 (0/1) | 1 |
業務邏輯說明
siteCode由@AdminSiteCode()自動帶入name為內部使用的標籤識別碼,label為前端顯示的多語系名稱
PATCH /api/admin/promo-tags/:id — 更新活動標籤
功能說明
更新指定活動標籤的資料,所有欄位皆為選填。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo-tag:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 標籤 ID |
name | Body | string | 否 | 標籤名稱 |
label | Body | Record<string, string> | 否 | 多語系顯示名稱 |
color | Body | string | 否 | 標籤顏色 |
sortOrder | Body | number | 否 | 排序順序 |
enabled | Body | number | 否 | 啟用狀態 |
DELETE /api/admin/promo-tags/:id — 刪除活動標籤
功能說明
刪除指定的活動標籤。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 promo-tag:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 標籤 ID |
8. 財務管理
8.1 存款審核
GET /api/admin/finance/deposit-review — 存款訂單審核列表
功能說明
取得所有存款訂單列表,支援按訂單狀態、支付方式、日期範圍篩選。管理員可在此頁面審核存款訂單。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard(無指定 @RequirePermissions,但受 PermissionsGuard 保護)
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
status | Query | string | 否 | 訂單狀態 | "pending" |
paymentMethod | Query | string | 否 | 支付方式 | "fiat" |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
訂單狀態 (status)
| 值 | 說明 |
|---|---|
pending | 待處理 |
created | 已建立 |
paid | 已支付 |
failed | 失敗 |
支付方式 (paymentMethod)
| 值 | 說明 |
|---|---|
fiat | 法幣(ATM 轉帳) |
credit | 信用卡 |
crypto | 加密貨幣 (USDT) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"orderId": "DEP20260301001",
"userId": 5,
"userAccount": "user001",
"amount": "100.000000",
"amountLocal": "3100.00",
"currency": "TWD",
"exchangeRate": "31.0000000000",
"paymentMethod": "fiat",
"status": "pending",
"channelName": { "zh-TW": "萬通金流 ATM" },
"siteCode": "C9",
"createdAt": "2026-03-01T10:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 10, "totalPages": 1 }
}
}業務邏輯說明
amount為系統內部金額 (USD decimal(18,6))amountLocal為用戶當地幣別金額exchangeRate為當時的即時匯率 (decimal(18,10))- 金額轉換公式:
amount = amountLocal / exchangeRate(以台灣銀行即時匯率換算)
PATCH /api/admin/finance/deposit-review/:id — 審核存款訂單
功能說明
審核指定的存款訂單,可通過 (approve) 或拒絕 (reject)。通過後系統會自動將金額加入用戶餘額,拒絕時需提供拒絕原因。
認證需求:AdminJwtAuthGuard + PermissionsGuard
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | string | 是 | 訂單 ID | "1" |
action | Body | string | 是 | 審核動作 | "approve" 或 "reject" |
rejectReason | Body | string | 否 | 拒絕原因(拒絕時必填) | "資料不符" |
{
"action": "approve"
}或
{
"action": "reject",
"rejectReason": "資料不符"
}業務邏輯說明
- 通過後自動更新用戶餘額
- 存款確認後觸發任務系統進度更新(
MissionService.updateDepositProgress()) - 審核者的 email 會被記錄
8.2 用戶管理
GET /api/admin/finance/users — 前台用戶列表
功能說明
取得前台用戶列表,包含用戶餘額資訊。支援分頁、關鍵字搜尋(帳號/名稱)及多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 user:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
keyword | Query | string | 否 | 搜尋帳號/名稱 | "user001" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"account": "user001",
"name": "王小明",
"email": "user@example.com",
"mobile": "0912345678",
"balance": "1000.500000",
"vipLevel": 3,
"status": 1,
"siteCode": "C9",
"createdAt": "2026-01-15T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 50, "totalPages": 3 }
}
}GET /api/admin/finance/users/:id — 取得單一用戶完整資訊
功能說明
取得指定前台用戶的完整資訊,包含餘額、VIP 等級、錢包資訊、登入紀錄等。用於用戶詳情頁面。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 user:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | string | 是 | 用戶 ID |
x-site-code | Header | string | 否 | 站點代碼 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"account": "user001",
"name": "王小明",
"email": "user@example.com",
"mobile": "0912345678",
"balance": "1000.500000",
"vipLevel": 3,
"totalEffectiveBet": "50000.000000",
"siteCode": "C9",
"bankCards": [],
"creditCards": [],
"cryptoAddresses": [],
"recentLogins": [],
"createdAt": "2026-01-15T00:00:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
1001 | 查無此用戶 |
PATCH /api/admin/finance/users/:id — 更新前台用戶基本資料
功能說明
由管理員更新前台用戶的基本資料(名稱、Email、手機號碼)。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 user:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | string | 是 | 用戶 ID | "1" |
name | Body | string | 否 | 用戶名稱 | "王大明" |
email | Body | string | null | 否 | Email(傳 null 清除) | "new@example.com" |
mobile | Body | string | null | 否 | 手機號碼(傳 null 清除) | "0987654321" |
8.3 用戶金流群組
PATCH /api/admin/users/:userId/vendor-group — 更新用戶所屬金流群組
功能說明
將指定用戶分配到指定金流群組,或傳 null 清除群組分配。金流群組決定用戶可使用的入金通道。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 user:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
userId | URL Param | string | 是 | 用戶 ID | "1" |
vendorGroupId | Body | number | null | 是 | 金流群組 ID(null 清除) | 2 |
{
"vendorGroupId": 2
}Response 範例
{
"code": 200,
"message": "ok",
"result": null
}8.4 人工調帳
POST /api/admin/finance/adjust-balance — 人工調節用戶餘額
功能說明
管理員手動調整前台用戶的餘額,支援加值和扣款兩種操作。此操作會記錄在操作紀錄中,包含操作者、操作類型、金額及原因。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
userId | number | 是 | 用戶 ID | 1 |
amount | number | 是 | 調整金額 (USD) | 100 |
type | string | 是 | 調整類型 | "add" 或 "deduct" |
reason | string | 是 | 調整原因 | "補償客訴" |
{
"userId": 1,
"amount": 100,
"type": "add",
"reason": "補償客訴"
}業務邏輯說明
type: "add"— 增加用戶餘額type: "deduct"— 扣減用戶餘額- 金額以 USD decimal(18,6) 精度處理
- 操作紀錄包含管理員 ID、操作類型、金額、原因
前端注意事項
- 已支援多站點 SiteTabs
- 此為高風險操作,建議使用
useConfirm()確認
8.5 銀行卡管理
GET /api/admin/finance/bank-cards — 銀行卡列表
功能說明
取得所有用戶的銀行卡列表,支援按用戶 ID、審核狀態、日期範圍及關鍵字篩選。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
userId | Query | number | 否 | 用戶 ID | 1 |
status | Query | number | 否 | 審核狀態 | 1 |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
keyword | Query | string | 否 | 搜尋帳號/持卡人 | "王" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"userId": 5,
"userAccount": "user001",
"bankCode": "004",
"bankAccount": "12345678901234",
"branch": "台北分行",
"holderName": "王小明",
"status": 1,
"siteCode": "C9",
"createdAt": "2026-02-01T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 5, "totalPages": 1 }
}
}PATCH /api/admin/finance/bank-cards/:id/review — 銀行卡審核
功能說明
審核用戶提交的銀行卡資料,更新審核狀態。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | number | 是 | 銀行卡 ID | 1 |
status | Body | number | 是 | 審核狀態 | 1 (通過) 或 2 (拒絕) |
PATCH /api/admin/finance/bank-cards/:id — 銀行卡欄位更新
功能說明
由管理員直接更新銀行卡的欄位資料。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 銀行卡 ID |
bankCode | Body | string | 否 | 銀行代碼 |
bankAccount | Body | string | 否 | 銀行帳號 |
branch | Body | string | 否 | 分行名稱 |
holderName | Body | string | 否 | 持卡人姓名 |
status | Body | number | 否 | 狀態 |
POST /api/admin/finance/bank-cards — 新增銀行卡
功能說明
由管理員為指定用戶新增銀行卡。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
userId | number | 是 | 用戶 ID | 1 |
bankCode | string | 是 | 銀行代碼 | "004" |
bankAccount | string | 是 | 銀行帳號 | "12345678901234" |
branch | string | 是 | 分行名稱 | "台北分行" |
holderName | string | 是 | 持卡人姓名 | "王小明" |
DELETE /api/admin/finance/bank-cards/:id — 刪除銀行卡
功能說明
由管理員刪除指定的銀行卡記錄。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 銀行卡 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": { "success": true }
}8.6 信用卡管理
GET /api/admin/finance/credit-cards — 信用卡列表
功能說明
取得所有用戶的信用卡列表,支援按用戶 ID、審核狀態、日期範圍及關鍵字篩選。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
userId | Query | number | 否 | 用戶 ID | 1 |
status | Query | number | 否 | 審核狀態 | 1 |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
keyword | Query | string | 否 | 搜尋關鍵字 | "王" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
PATCH /api/admin/finance/credit-cards/:id/review — 信用卡審核
功能說明
審核用戶提交的信用卡資料,更新審核狀態。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 信用卡 ID |
status | Body | number | 是 | 審核狀態 |
PATCH /api/admin/finance/credit-cards/:id — 信用卡欄位更新
功能說明
由管理員直接更新信用卡的欄位資料。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 信用卡 ID |
cardNumber | Body | string | 否 | 卡號 |
holderName | Body | string | 否 | 持卡人姓名 |
expiryDate | Body | string | 否 | 有效期限 |
status | Body | number | 否 | 狀態 |
POST /api/admin/finance/credit-cards — 新增信用卡
功能說明
由管理員為指定用戶新增信用卡。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
userId | number | 是 | 用戶 ID | 1 |
cardNumber | string | 是 | 卡號 | "4111111111111111" |
holderName | string | 是 | 持卡人姓名 | "王小明" |
cvv | string | 是 | CVV 安全碼 | "123" |
expiryDate | string | 是 | 有效期限 | "12/28" |
DELETE /api/admin/finance/credit-cards/:id — 刪除信用卡
功能說明
由管理員刪除指定的信用卡記錄。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Response 範例
{
"code": 200,
"message": "ok",
"result": { "success": true }
}8.7 虛擬錢包地址管理
GET /api/admin/finance/crypto-addresses — 虛擬錢包地址列表
功能說明
取得所有用戶的加密貨幣錢包地址列表,支援按用戶 ID、審核狀態、日期範圍及關鍵字篩選。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
userId | Query | number | 否 | 用戶 ID | 1 |
status | Query | number | 否 | 審核狀態 | 1 |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
keyword | Query | string | 否 | 搜尋地址/幣種 | "TRC" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
PATCH /api/admin/finance/crypto-addresses/:id/review — 虛擬錢包地址審核
功能說明
審核用戶提交的虛擬錢包地址,更新審核狀態。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 地址 ID |
status | Body | number | 是 | 審核狀態 |
PATCH /api/admin/finance/crypto-addresses/:id — 虛擬錢包地址欄位更新
功能說明
由管理員直接更新虛擬錢包地址的欄位資料。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 地址 ID |
walletName | Body | string | 否 | 錢包名稱 |
currency | Body | string | 否 | 幣種 |
network | Body | string | 否 | 網路 |
address | Body | string | 否 | 地址 |
status | Body | number | 否 | 狀態 |
POST /api/admin/finance/crypto-addresses — 新增虛擬錢包地址
功能說明
由管理員為指定用戶新增虛擬錢包地址。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
userId | number | 是 | 用戶 ID | 1 |
walletName | string | 是 | 錢包名稱 | "我的 USDT 錢包" |
currency | string | 否 | 幣種 | "USDT" |
network | string | 否 | 網路 | "TRC20" |
address | string | 是 | 錢包地址 | "TYDzsYUEr..." |
DELETE /api/admin/finance/crypto-addresses/:id — 刪除虛擬錢包地址
功能說明
由管理員刪除指定的虛擬錢包地址記錄。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 finance:write 權限
Response 範例
{
"code": 200,
"message": "ok",
"result": { "success": true }
}8.8 提領管理
GET /api/admin/finance/withdrawals — 提領列表
功能說明
取得所有用戶的提領訂單列表,支援按訂單狀態、用戶 ID、關鍵字(搜尋帳號/名稱/地址)、日期範圍篩選。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 withdrawal:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
status | Query | string | 否 | 訂單狀態 | "pending" |
userId | Query | number | 否 | 用戶 ID | 1 |
keyword | Query | string | 否 | 搜尋帳號/名稱/地址 | "user001" |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
提領狀態流程
pending (待審核) → approved (已核准) → completed (已完成)
→ rejected (已拒絕)POST /api/admin/finance/withdrawals/:id/review — 提領審核
功能說明
審核指定的提領訂單,可通過 (approve) 或拒絕 (reject)。通過後進入待出款狀態,拒絕時需提供原因。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 withdrawal:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | number | 是 | 提領單 ID | 1 |
action | Body | string | 是 | 審核動作 | "approve" 或 "reject" |
rejectReason | Body | string | 否 | 拒絕原因 | "餘額不足" |
業務邏輯說明
- 審核者 email 會記錄在提領單中
- 拒絕時金額自動退回用戶餘額
POST /api/admin/finance/withdrawals/:id/upload-proof — 上傳代付證明
功能說明
為已核准的提領單上傳代付(轉帳)證明檔案。支援圖片和 PDF 格式,最大 10MB。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 withdrawal:write 權限
Request
Content-Type: multipart/form-data
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 提領單 ID |
file | Body (File) | File | 是 | 代付證明檔案 |
檔案限制
- MIME 類型:
image/*或application/pdf - 最大檔案大小:10MB
業務邏輯說明
- 檔案上傳至 R2 儲存
- 檔案路徑記錄在提領單中
- 操作者 email 記錄於提領單
POST /api/admin/finance/withdrawals/:id/complete — 提領確認出款完成
功能說明
將提領單標記為已完成出款。此為提領流程的最終步驟。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 withdrawal:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 提領單 ID |
業務邏輯說明
- 提領單狀態更新為
completed - 操作者 email 記錄於提領單
- 完整提領流程:
pending → approved → (upload proof) → completed
8.9 金流群組管理
GET /api/admin/vendor-groups/list — 金流群組列表
功能說明
取得所有金流群組列表。金流群組用於管理不同的入金通道組合,用戶可被分配至不同群組以使用不同的入金方式。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:read 權限
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"name": { "zh-TW": "台灣金流組", "en-US": "TWD Group" },
"enabled": 1,
"channelCount": 3,
"createdAt": "2026-01-01T00:00:00.000Z"
}
]
}POST /api/admin/vendor-groups/create — 新增金流群組
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
name | Record<string, string> | 是 | 多語系群組名稱 | {"zh-TW":"台灣金流組","en-US":"TWD Group"} |
enabled | number | 否 | 啟用狀態 | 1 |
PATCH /api/admin/vendor-groups/:id — 更新金流群組
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 群組 ID |
name | Body | Record<string, string> | 否 | 多語系名稱 |
enabled | Body | number | 否 | 啟用狀態 |
DELETE /api/admin/vendor-groups/:id — 刪除金流群組
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:write 權限
GET /api/admin/vendor-groups/:id/channels — 取得群組的通道 ID 列表
功能說明
取得指定金流群組下所有關聯的通道 ID 列表。用於編輯群組時的通道勾選介面。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:read 權限
Response 範例
{
"code": 200,
"message": "ok",
"result": [1, 3, 5]
}PUT /api/admin/vendor-groups/:id/channels — 設定群組的通道關聯
功能說明
設定指定金流群組與通道的多對多關聯。此操作會完整取代現有關聯(先清除再新增)。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
id | URL Param | number | 是 | 群組 ID | 1 |
channelIds | Body | number[] | 是 | 通道 ID 陣列 | [1, 3, 5] |
{
"channelIds": [1, 3, 5]
}8.10 金流通道管理
GET /api/admin/vendor-channels/list — 金流通道列表
功能說明
取得所有金流通道列表,可選擇按群組 ID 篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
groupId | Query | number | 否 | 群組 ID 篩選 | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"name": { "zh-TW": "萬通金流 ATM", "en-US": "Wantong ATM" },
"storeCode": "WANTONG_001",
"currency": "TWD",
"paymentMethods": ["fiat"],
"enabled": 1,
"createdAt": "2026-01-01T00:00:00.000Z"
}
]
}POST /api/admin/vendor-channels/create — 新增金流通道
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
name | Record<string, string> | 是 | 多語系通道名稱 | {"zh-TW":"萬通 ATM"} |
storeCode | string | 是 | 金流商店代碼 | "WANTONG_001" |
secret1 | string | 是 | 密鑰 1 | "sk_..." |
secret2 | string | 否 | 密鑰 2 | - |
secret3 | string | 否 | 密鑰 3 | - |
secret4 | string | 否 | 密鑰 4 | - |
currency | string | 是 | 幣別 | "TWD" |
paymentMethods | string[] | 是 | 支付方式 | ["fiat", "credit"] |
paymentAddress | string | 否 | 收款地址 | - |
enabled | number | 否 | 啟用狀態 | 1 |
PATCH /api/admin/vendor-channels/:id — 更新金流通道
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 通道 ID |
| 其他 | Body | Record<string, any> | 否 | 任意可更新欄位 |
DELETE /api/admin/vendor-channels/:id — 刪除金流通道
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 vendor:write 權限
9. 報表管理
GET /api/admin/reports/players — 玩家報表
功能說明
取得玩家報表,包含投注統計、存款統計等彙總資料。支援多條件篩選,包括關鍵字搜尋、VIP 等級篩選(含比較運算子)、錢包資訊篩選(銀行卡/信用卡/加密地址模糊搜尋)、線上狀態篩選等。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
keyword | Query | string | 否 | 搜尋帳號/名稱 | "user001" |
vipLevel | Query | string | 否 | VIP 等級 | "5" |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
bankAccount | Query | string | 否 | 銀行卡帳號(模糊搜尋) | "1234" |
cardNumber | Query | string | 否 | 信用卡卡號(模糊搜尋) | "4111" |
cryptoAddress | Query | string | 否 | 加密錢包地址(模糊搜尋) | "TYDz" |
vipOp | Query | string | 否 | VIP 比較運算子 | "gt", "eq", "lt" |
vipFilterLevel | Query | string | 否 | VIP 比較等級 (1-15) | "5" |
online | Query | string | 否 | 僅顯示線上玩家 | "true" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
VIP 比較運算子 (vipOp)
| 值 | 說明 |
|---|---|
gt | 大於指定等級 |
eq | 等於指定等級 |
lt | 小於指定等級 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"account": "user001",
"name": "王小明",
"balance": "1000.500000",
"vipLevel": 3,
"totalDeposit": "5000.000000",
"totalWithdrawal": "2000.000000",
"totalEffectiveBet": "50000.000000",
"totalWinLoss": "-500.000000",
"lastLoginAt": "2026-03-01T08:00:00.000Z",
"siteCode": "C9",
"createdAt": "2026-01-15T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 100, "totalPages": 5 }
}
}業務邏輯說明
- 所有金額以 USD decimal(18,6) 表示
totalWinLoss為負值表示平台盈利(玩家虧損)vipOp+vipFilterLevel組合使用:如vipOp=gt&vipFilterLevel=5表示篩選 VIP > 5 的玩家online篩選使用登入紀錄的最近活動時間判斷- 銀行卡/信用卡/加密地址搜尋透過 JOIN 子查詢實現
前端注意事項
- 已支援多站點 SiteTabs(使用
useMultiSiteTabshook) - VIP 篩選使用 Select 下拉元件
- 支援 CSV 匯出(透過
/reports/export/players)
GET /api/admin/reports/vip-players — VIP 玩家查詢
功能說明
專門查詢 VIP 玩家的報表,提供比基礎玩家報表更豐富的 VIP 相關篩選條件,包含 VIP 等級範圍、層級、保級狀態、VIP 鎖定、累積投注範圍等。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
keyword | Query | string | 否 | 搜尋帳號/名稱 | "user001" |
vipLevelMin | Query | string | 否 | VIP 等級下限 (1-15) | "3" |
vipLevelMax | Query | string | 否 | VIP 等級上限 (1-15) | "10" |
tier | Query | string | 否 | 層級 | "gold" |
relegationStatus | Query | string | 否 | 保級狀態 | "0" |
vipHold | Query | string | 否 | VIP 鎖定 | "1" |
minBet | Query | string | 否 | 最低累積投注 (USD) | "10000" |
maxBet | Query | string | 否 | 最高累積投注 (USD) | "100000" |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
層級 (tier)
| 值 | 說明 |
|---|---|
bronze | 青銅 |
gold | 黃金 |
platinum | 白金 |
diamond | 鑽石 |
保級狀態 (relegationStatus)
| 值 | 說明 |
|---|---|
0 | 正常 |
1 | 警告 |
2 | 降級風險 |
VIP 鎖定 (vipHold)
| 值 | 說明 |
|---|---|
0 | 未鎖定 |
1 | 已鎖定(VIP 5+ 支援) |
業務邏輯說明
- 後端使用
@AdminSiteCode()進行站點篩選 minBet/maxBet用於篩選累積有效投注額範圍- VIP 鎖定的玩家在月度保級檢查時不會被降級
GET /api/admin/reports/bet-records — 投注紀錄報表
功能說明
取得全站投注紀錄報表,支援按用戶 ID、遊戲類型、投注狀態、日期範圍篩選。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
userId | Query | number | 否 | 用戶 ID | 1 |
keyword | Query | string | 否 | 搜尋關鍵字 | "user001" |
gameType | Query | string | 否 | 遊戲類型 | "slot" |
status | Query | string | 否 | 投注狀態 | "valid" |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
遊戲類型 (gameType)
| 代碼 | 數值 | 說明 |
|---|---|---|
SPORTS | 1 | 體育 |
SLOT | 2 | 老虎機 |
LIVE | 3 | 真人 |
LOTTERY | 4 | 彩票 |
CHESS | 5 | 棋牌 |
ESPORTS | 8 | 電競 |
CRYPTO | 9 | 加密遊戲 |
FISH | 10 | 捕魚 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"userId": 5,
"userAccount": "user001",
"gameType": "slot",
"gameName": "Fortune Tiger",
"providerCode": "rsg",
"betAmount": "10.000000",
"betEffective": "10.000000",
"payout": "25.000000",
"winLoss": "15.000000",
"status": "valid",
"siteCode": "C9",
"betTime": "2026-03-01T10:30:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 500, "totalPages": 25 }
}
}GET /api/admin/reports/overview — 總體報表
功能說明
取得全站摘要統計資料,包含總存款、總提款、總投注、總盈虧、活躍用戶數等。可按日期範圍篩選。支援每日摘要表格。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"summary": {
"totalDeposit": "500000.000000",
"totalWithdrawal": "300000.000000",
"totalBet": "2000000.000000",
"totalPayout": "1900000.000000",
"totalWinLoss": "-100000.000000",
"activeUsers": 150,
"newUsers": 30,
"depositCount": 500,
"withdrawalCount": 200
},
"dailySummary": [
{
"date": "2026-03-01",
"deposit": "10000.000000",
"withdrawal": "5000.000000",
"bet": "50000.000000",
"payout": "48000.000000",
"winLoss": "-2000.000000",
"activeUsers": 45,
"newUsers": 5
}
]
}
}業務邏輯說明
totalWinLoss為負值表示平台盈利(玩家總虧損)dailySummary按日期分組提供每日統計- 所有金額以 USD decimal(18,6) 表示
GET /api/admin/reports/profit-loss — 損益報表
功能說明
取得損益報表,支援按日、週、月分組統計。包含投注額、派彩額、盈虧等數據。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
groupBy | Query | string | 否 | 分組方式 | "day", "week", "month" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
分組方式 (groupBy)
| 值 | 說明 |
|---|---|
day | 按日分組(預設) |
week | 按週分組 |
month | 按月分組 |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"period": "2026-03-01",
"totalBet": "50000.000000",
"totalPayout": "48000.000000",
"profitLoss": "2000.000000",
"betCount": 1200,
"uniqueUsers": 45
}
]
}GET /api/admin/reports/games — 遊戲報表
功能說明
取得遊戲統計報表,按遊戲類型和遊戲平台(供應商)分組統計投注額、派彩額、盈虧等。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
gamePlatform | Query | string | 否 | 遊戲平台(供應商代碼) | "rsg" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"gameType": "slot",
"gameTypeName": "老虎機",
"gamePlatform": "rsg",
"totalBet": "100000.000000",
"totalPayout": "95000.000000",
"profitLoss": "5000.000000",
"betCount": 5000,
"uniqueUsers": 80
}
]
}GET /api/admin/reports/promos — 優惠報表
功能說明
取得活動領取統計報表,顯示每個活動的領取次數、發放總額等。支援分頁和日期範圍篩選。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"promoId": 1,
"promoTitle": "新手首存禮",
"claimCount": 150,
"totalReward": "1500.000000",
"uniqueClaimers": 120,
"tag": "新手",
"enabled": 1
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 5, "totalPages": 1 }
}
}GET /api/admin/reports/player-summary — 玩家簡表
功能說明
取得精簡的用戶列表,包含餘額、累積有效投注、VIP 等級等關鍵指標。支援排序(按餘額、投注、建立時間、VIP 等級)。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
keyword | Query | string | 否 | 搜尋關鍵字 | "user001" |
sortBy | Query | string | 否 | 排序欄位 | "balance" |
sortOrder | Query | string | 否 | 排序方向 | "ASC" 或 "DESC" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
排序欄位 (sortBy)
| 值 | 說明 |
|---|---|
balance | 餘額 |
totalEffectiveBet | 累積有效投注 |
createdAt | 建立時間 |
vipLevel | VIP 等級 |
GET /api/admin/reports/r2-logs — R2 操作紀錄
功能說明
查詢 Cloudflare R2 雲端儲存的操作紀錄,包含檔案上傳、刪除等操作。支援按模組、動作、管理員 ID、關鍵字(檔案路徑/檔名)及日期範圍篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
module | Query | string | 否 | 模組 | "site-config", "promo", "storage" |
action | Query | string | 否 | 動作 | "upload", "delete" |
adminId | Query | number | 否 | 管理員 ID | 1 |
keyword | Query | string | 否 | 搜尋檔案路徑/檔名 | "logo" |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"adminId": 1,
"adminEmail": "admin@example.com",
"action": "upload",
"module": "storage",
"objectKey": "C9/images/logo.png",
"originalName": "logo.png",
"fileSize": 102400,
"mimeType": "image/png",
"ip": "127.0.0.1",
"userAgent": "Mozilla/5.0...",
"detail": null,
"createdAt": "2026-03-01T10:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 50, "totalPages": 3 }
}
}前端注意事項
- 前端已從報表模組移至
/system/cloud-storage-logs路由 - 支援 OS/Browser 解析(從
userAgent欄位) - 支援 Detail Dialog 展開完整資訊
- 顯示
mimeType欄位
GET /api/admin/reports/export/:type — 匯出報表 (CSV)
功能說明
將指定類型的報表匯出為 CSV 格式。回傳 CSV 字串內容及建議的檔案名稱。支援所有報表類型的匯出。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 report:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
type | URL Param | string | 是 | 報表類型 | "players" |
| 其他 | Query | Record<string, string> | 否 | 各報表的篩選參數 | - |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
支援的報表類型 (type)
| 值 | 說明 |
|---|---|
players | 玩家報表 |
bet-records | 投注紀錄 |
deposits | 存款紀錄 |
overview | 總覽 |
profit-loss | 損益報表 |
games | 遊戲報表 |
promos | 活動報表 |
player-summary | 玩家簡表 |
history | 歷史紀錄 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"csv": "帳號,名稱,餘額,VIP等級\nuser001,王小明,1000.50,3\n...",
"filename": "players-2026-03-01.csv"
}
}前端注意事項
- 使用
ExportButton元件觸發匯出 - 前端接收 CSV 字串後建立 Blob 下載
- 檔案名稱格式:
{type}-{date}.csv
10. 風控管理
10.1 IP 黑白名單
GET /api/admin/risk/ip-rules — IP 黑白名單列表
功能說明
取得 IP 黑白名單列表,支援按類型(黑名單/白名單)、IP 關鍵字篩選。黑名單中的 IP 將被阻止存取前台;白名單中的 IP 將跳過部分安全檢查。支援多站點篩選,每個站點可獨立配置 IP 規則。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
type | Query | string | 否 | 規則類型 | "blacklist" 或 "whitelist" |
keyword | Query | string | 否 | IP 關鍵字搜尋 | "192.168" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"ip": "192.168.1.100",
"type": "blacklist",
"remark": "異常登入行為",
"siteCode": "C9",
"createdAt": "2026-02-28T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 5, "totalPages": 1 }
}
}前端注意事項
- 已支援多站點 SiteTabs + FilterBar 統一風格
- 類型篩選使用 Select 下拉元件
POST /api/admin/risk/ip-rules — 新增 IP 規則
功能說明
新增一筆 IP 黑名單或白名單規則。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
ip | string | 是 | IP 地址 | "192.168.1.100" |
type | string | 是 | 規則類型 | "blacklist" 或 "whitelist" |
remark | string | 否 | 備註 | "異常登入行為" |
業務邏輯說明
siteCode由@AdminSiteCode()自動帶入,若未提供則預設為"C9"- IP 支援單一地址格式
PATCH /api/admin/risk/ip-rules/:id — 更新 IP 規則
功能說明
更新指定 IP 規則的內容。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 規則 ID |
ip | Body | string | 是 | IP 地址 |
type | Body | string | 是 | 規則類型 |
remark | Body | string | 否 | 備註 |
DELETE /api/admin/risk/ip-rules/:id — 刪除 IP 規則
功能說明
刪除指定的 IP 黑白名單規則。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 規則 ID |
10.2 登入失敗紀錄
GET /api/admin/risk/login-failures — 登入失敗列表
功能說明
查詢前台用戶的登入失敗紀錄,支援按關鍵字(帳號/IP/裝置等)和日期範圍篩選。用於監控異常登入行為和潛在的暴力破解攻擊。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
keyword | Query | string | 否 | 搜尋關鍵字 | "user001" |
startDate | Query | string | 否 | 起始日期 | "2026-01-01" |
endDate | Query | string | 否 | 結束日期 | "2026-12-31" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"account": "user001",
"ip": "203.0.113.50",
"device": "Mozilla/5.0 (Windows NT 10.0)...",
"reason": "password_mismatch",
"siteCode": "C9",
"createdAt": "2026-03-01T08:30:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 10, "totalPages": 1 }
}
}業務邏輯說明
- 資料來源為
auth-user-login-log表中action為失敗的記錄 keyword搜尋範圍包含帳號、IP、裝置資訊
10.3 IP/FP 查詢
GET /api/admin/risk/lookup — 風控關鍵字查詢
功能說明
透過 IP 地址、裝置指紋 (Fingerprint)、帳號、姓名、Email 或手機號碼反查關聯的用戶資訊。此功能用於風控調查,可找出使用相同 IP 或裝置的多個帳號(潛在多開/作弊行為)。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
keyword | Query | string | 是 | 查詢關鍵字 | "192.168.1.100" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
keyword 支援的搜尋範圍
- IP 地址(精確或模糊匹配)
- 裝置指紋(FingerprintJS 產生的 hash)
- 用戶帳號
- 用戶姓名
- Email 地址
- 手機號碼
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"userId": 1,
"account": "user001",
"name": "王小明",
"email": "user@example.com",
"mobile": "0912345678",
"ip": "192.168.1.100",
"device": "abc123fingerprint",
"lastLogin": "2026-03-01T08:00:00.000Z",
"siteCode": "C9"
},
{
"userId": 5,
"account": "user005",
"name": "李大華",
"ip": "192.168.1.100",
"device": "abc123fingerprint",
"lastLogin": "2026-03-01T09:00:00.000Z",
"siteCode": "C9"
}
]
}業務邏輯說明
- 查詢
auth-user-login-log表及auth-user表 - 相同 IP 或裝置指紋出現在多個帳號表示可能的異常行為
- 裝置指紋由前端 FingerprintJS 產生,登入/註冊時傳入後端記錄
前端注意事項
- 已支援多站點 SiteTabs + FilterBar 統一風格
- 結果以表格形式展示,同一 IP/FP 出現多筆記錄時可高亮顯示
10.4 遊戲黑名單
GET /api/admin/risk/game-blacklist — 遊戲黑名單列表
功能說明
取得遊戲黑名單列表。可將特定用戶封鎖於全部遊戲、特定類型遊戲、或特定遊戲。支援按用戶 ID、遊戲類型篩選。支援多站點篩選。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:read 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
page | Query | number | 否 | 頁碼 | 1 |
pageSize | Query | number | 否 | 每頁筆數 | 20 |
userId | Query | number | 否 | 用戶 ID | 1 |
gameType | Query | string | 否 | 遊戲類型 | "slot" |
x-site-code | Header | string | 否 | 站點代碼 | "C9" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"userId": 5,
"userAccount": "user005",
"gameType": null,
"productId": null,
"remark": "作弊行為,封鎖所有遊戲",
"siteCode": "C9",
"createdAt": "2026-02-28T00:00:00.000Z"
},
{
"id": 2,
"userId": 10,
"userAccount": "user010",
"gameType": "slot",
"productId": null,
"remark": "封鎖老虎機類型",
"siteCode": "C9",
"createdAt": "2026-02-28T00:00:00.000Z"
},
{
"id": 3,
"userId": 15,
"userAccount": "user015",
"gameType": "slot",
"productId": 123,
"remark": "封鎖特定遊戲",
"siteCode": "C9",
"createdAt": "2026-02-28T00:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 3, "totalPages": 1 }
}
}封鎖層級說明
| gameType | productId | 效果 |
|---|---|---|
null | null | 封鎖用戶所有遊戲 |
"slot" | null | 封鎖用戶該類型所有遊戲 |
"slot" | 123 | 封鎖用戶特定遊戲 |
POST /api/admin/risk/game-blacklist — 新增遊戲黑名單
功能說明
將指定用戶加入遊戲黑名單。可設定封鎖範圍:全部遊戲、特定類型遊戲、或特定遊戲。封鎖後該用戶啟動遊戲時會收到錯誤碼 5010。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:write 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
userId | number | 是 | 用戶 ID | 5 |
gameType | string | 否 | 遊戲類型(null=全部) | "slot" |
productId | number | 否 | 遊戲產品 ID(null=該類型全部) | 123 |
remark | string | 否 | 備註 | "作弊行為" |
{
"userId": 5,
"gameType": null,
"productId": null,
"remark": "作弊行為,封鎖所有遊戲"
}業務邏輯說明
siteCode由@AdminSiteCode()自動帶入,若未提供則預設為"C9"- 遊戲啟動時
GameService會呼叫AdminRiskService.isUserBlockedFromGame()檢查封鎖狀態 - 封鎖檢查順序:全部封鎖 > 類型封鎖 > 特定遊戲封鎖
- 被封鎖的用戶啟動遊戲時收到錯誤碼
5010
DELETE /api/admin/risk/game-blacklist/:id — 刪除遊戲黑名單
功能說明
移除指定的遊戲黑名單記錄,解除該用戶的遊戲封鎖。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 risk:write 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
id | URL Param | number | 是 | 黑名單 ID |
11. R2 雲端儲存
所有 R2 儲存管理端點使用
storage:manage權限,操作 Cloudflare R2(S3 相容)物件儲存。 每個操作都會自動記錄至r2-operation-log資料表,用於稽核追蹤。
GET /api/admin/r2/list — R2 檔案列表
功能說明
列出指定路徑前綴下的所有檔案和資料夾。支援分頁式瀏覽(使用 continuation token),可指定站點 ID 使用該站點的 R2 設定(不同站點可能使用不同的 R2 bucket)。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 storage:manage 權限
Request
| 參數 | 位置 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
prefix | Query | string | 否 | 路徑前綴(資料夾路徑) | "C9/images/" |
continuationToken | Query | string | 否 | 分頁 token(上一次回傳的) | "abc123..." |
maxKeys | Query | number | 否 | 每頁最大筆數(預設 100) | 100 |
siteConfigId | Query | number | 否 | 指定站點 ID | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"folders": [
{ "name": "images/", "prefix": "C9/images/" },
{ "name": "promos/", "prefix": "C9/promos/" }
],
"files": [
{
"key": "C9/logo.png",
"size": 102400,
"lastModified": "2026-02-28T10:00:00.000Z",
"etag": "\"abc123\"",
"url": "https://cdn.example.com/C9/logo.png"
}
],
"isTruncated": false,
"nextContinuationToken": null,
"keyCount": 3
}
}業務邏輯說明
- 使用 S3
ListObjectsV2Command實現 prefix必須以/結尾才能瀏覽資料夾內容folders和files分開回傳,方便前端分別渲染資料夾和檔案url為公開存取 URL(使用R2_PUBLIC_URL環境變數拼接)siteConfigId可指定不同站點的 R2 配置
前端注意事項
- 前端實作檔案管理器介面,以樹狀結構展示
- 支援點擊資料夾進入子目錄
- 使用
continuationToken實現「載入更多」功能
POST /api/admin/r2/upload — R2 上傳檔案
功能說明
上傳一或多個檔案至 R2 儲存。支援批次上傳(最多同時 20 個檔案),每個檔案最大 50MB。上傳後自動記錄操作紀錄,包含管理員 ID、檔案路徑、原始檔名、檔案大小、MIME 類型、操作者 IP 及 User-Agent。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 storage:manage 權限
Request
Content-Type: multipart/form-data
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
files | File[] | 是 | 檔案陣列(最多 20 個) | - |
prefix | string | 是 | 上傳路徑前綴(資料夾路徑) | "C9/images/" |
siteConfigId | string | 否 | 指定站點 ID | "1" |
檔案限制
- 最多同時上傳 20 個檔案
- 單一檔案最大:50MB
- 不限制 MIME 類型
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"uploaded": [
{
"key": "C9/images/banner.jpg",
"url": "https://cdn.example.com/C9/images/banner.jpg",
"size": 204800
},
{
"key": "C9/images/icon.png",
"url": "https://cdn.example.com/C9/images/icon.png",
"size": 51200
}
]
}
}業務邏輯說明
- 檔案儲存路徑:
{prefix}{originalFilename} - 使用
memoryStorage()暫存上傳檔案於記憶體 - 每個檔案上傳完成後立即記錄操作紀錄
- 操作紀錄包含:
adminId、action: 'upload'、module: 'storage'、objectKey、originalName、fileSize、mimeType、ip、userAgent
前端注意事項
- 使用 FormData 組裝多檔案上傳
- 顯示上傳進度條
- 上傳完成後刷新檔案列表
POST /api/admin/r2/delete — R2 批次刪除檔案
功能說明
批次刪除指定的 R2 檔案。傳入檔案 key 陣列,系統會逐一刪除並記錄操作紀錄。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 storage:manage 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
keys | string[] | 是 | 檔案 key 陣列 | ["C9/images/old.jpg", "C9/images/temp.png"] |
siteConfigId | number | 否 | 指定站點 ID | 1 |
{
"keys": ["C9/images/old.jpg", "C9/images/temp.png"],
"siteConfigId": 1
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"deleted": 2
}
}業務邏輯說明
- 使用 S3
DeleteObjectsCommand批次刪除 - 每個被刪除的 key 都會獨立記錄操作紀錄
- 不存在的 key 不會報錯(靜默跳過)
POST /api/admin/r2/move — R2 移動檔案/資料夾
功能說明
移動(重新命名)檔案或資料夾。實際操作為複製到新位置後刪除原始檔案。若為資料夾移動,會遞迴處理資料夾下所有檔案。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 storage:manage 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
sourceKey | string | 是 | 原始路徑 | "C9/images/old.jpg" |
destinationKey | string | 是 | 目標路徑 | "C9/images/new.jpg" |
isFolder | boolean | 是 | 是否為資料夾移動 | false |
siteConfigId | number | 否 | 指定站點 ID | 1 |
{
"sourceKey": "C9/images/old-folder/",
"destinationKey": "C9/images/new-folder/",
"isFolder": true,
"siteConfigId": 1
}業務邏輯說明
- 檔案移動:
CopyObject+DeleteObject - 資料夾移動:列出資料夾下所有物件,逐一複製到新前綴,再批次刪除原始物件
- 操作紀錄記錄
sourceKey和destinationKey
前端注意事項
- 支援拖放式移動檔案/資料夾
- 資料夾移動可能需要較長時間,建議顯示處理中狀態
POST /api/admin/r2/create-folder — R2 建立資料夾
功能說明
在 R2 儲存中建立新的資料夾(實際上是建立一個以 / 結尾的空物件)。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 storage:manage 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
prefix | string | 否 | 父資料夾路徑 | "C9/images/" |
name | string | 是 | 新資料夾名稱 | "banners" |
siteConfigId | string | 否 | 指定站點 ID | "1" |
{
"prefix": "C9/images/",
"name": "banners",
"siteConfigId": "1"
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"key": "C9/images/banners/"
}
}業務邏輯說明
- 實際操作為
PutObject,建立一個 key 為{prefix}{name}/的空物件 - S3 本身無資料夾概念,以
/結尾的空物件模擬資料夾 - 操作紀錄記錄建立的資料夾路徑
POST /api/admin/r2/delete-folder — R2 刪除資料夾(遞迴)
功能說明
遞迴刪除指定的資料夾及其下所有檔案和子資料夾。此為破壞性操作,不可復原。
認證需求:AdminJwtAuthGuard + PermissionsGuard,需 storage:manage 權限
Request
| 欄位 | 類型 | 必填 | 說明 | 範例 |
|---|---|---|---|---|
prefix | string | 是 | 資料夾路徑(必須以 / 結尾) | "C9/images/temp/" |
siteConfigId | string | 否 | 指定站點 ID | "1" |
{
"prefix": "C9/images/temp/",
"siteConfigId": "1"
}Response 範例
{
"code": 200,
"message": "ok",
"result": {
"deleted": 15
}
}業務邏輯說明
prefix必須以/結尾,否則回傳400 Bad Request- 先列出 prefix 下所有物件,再批次刪除
deleted回傳實際刪除的物件數量- 操作紀錄記錄
deletedCount
前端注意事項
- 此為高風險操作,前端必須使用
useConfirm()彈出確認對話框 - 建議在確認文字中顯示資料夾名稱和預估的檔案數量
- 刪除完成後刷新檔案列表
附錄
端點總覽表
| # | 方法 | 路徑 | 權限 | 說明 |
|---|---|---|---|---|
| 1 | POST | /admin/login | 無 | 管理員登入 |
| 2 | POST | /admin/send-verify-code | 無 | 發送驗證碼 |
| 3 | POST | /admin/verify-email | 無 | 驗證 Email |
| 4 | POST | /admin/register | 無 | 管理員註冊 |
| 5 | GET | /admin/profile | 登入 | 取得個人資料 |
| 6 | POST | /admin/google-auth/generate | 登入 | 產生 2FA QR Code |
| 7 | POST | /admin/google-auth/enable | 登入 | 啟用 2FA |
| 8 | POST | /admin/google-auth/disable | 登入 | 停用 2FA |
| 9 | GET | /admin/permissions/all | 登入 | 取得權限列表 |
| 10 | GET | /admin/list | admin:read | 管理員列表 |
| 11 | GET | /admin/:id | admin:read | 取得單一管理員 |
| 12 | POST | /admin/create | admin:write | 建立管理員 |
| 13 | PATCH | /admin/:id | admin:write | 更新管理員 |
| 14 | DELETE | /admin/:id | admin:write | 刪除管理員 |
| 15 | GET | /admin/groups/list | admin-group:read | 群組列表 |
| 16 | GET | /admin/groups/:id | admin-group:read | 取得單一群組 |
| 17 | POST | /admin/groups/create | admin-group:write | 建立群組 |
| 18 | PATCH | /admin/groups/:id | admin-group:write | 更新群組 |
| 19 | DELETE | /admin/groups/:id | admin-group:write | 刪除群組 |
| 20 | GET | /admin/logs/list | admin-log:read | 操作紀錄列表 |
| 21 | GET | /admin/promos/list | promo:read | 活動列表 |
| 22 | GET | /admin/promos/:id | promo:read | 取得單一活動 |
| 23 | POST | /admin/promos/create | promo:write | 建立活動 |
| 24 | PATCH | /admin/promos/:id | promo:write | 更新活動 |
| 25 | DELETE | /admin/promos/:id | promo:write | 刪除活動 |
| 26 | GET | /admin/promo-tags/list | promo-tag:read | 標籤列表 |
| 27 | POST | /admin/promo-tags/create | promo-tag:write | 建立標籤 |
| 28 | PATCH | /admin/promo-tags/:id | promo-tag:write | 更新標籤 |
| 29 | DELETE | /admin/promo-tags/:id | promo-tag:write | 刪除標籤 |
| 30 | GET | /admin/finance/deposit-review | - | 存款訂單列表 |
| 31 | PATCH | /admin/finance/deposit-review/:id | - | 審核存款 |
| 32 | GET | /admin/finance/users | user:read | 用戶列表 |
| 33 | GET | /admin/finance/users/:id | user:read | 用戶詳情 |
| 34 | PATCH | /admin/finance/users/:id | user:write | 更新用戶 |
| 35 | PATCH | /admin/users/:userId/vendor-group | user:write | 更新金流群組 |
| 36 | POST | /admin/finance/adjust-balance | finance:write | 人工調帳 |
| 37 | GET | /admin/finance/bank-cards | finance:read | 銀行卡列表 |
| 38 | PATCH | /admin/finance/bank-cards/:id/review | finance:write | 銀行卡審核 |
| 39 | PATCH | /admin/finance/bank-cards/:id | finance:write | 更新銀行卡 |
| 40 | POST | /admin/finance/bank-cards | finance:write | 新增銀行卡 |
| 41 | DELETE | /admin/finance/bank-cards/:id | finance:write | 刪除銀行卡 |
| 42 | GET | /admin/finance/credit-cards | finance:read | 信用卡列表 |
| 43 | PATCH | /admin/finance/credit-cards/:id/review | finance:write | 信用卡審核 |
| 44 | PATCH | /admin/finance/credit-cards/:id | finance:write | 更新信用卡 |
| 45 | POST | /admin/finance/credit-cards | finance:write | 新增信用卡 |
| 46 | DELETE | /admin/finance/credit-cards/:id | finance:write | 刪除信用卡 |
| 47 | GET | /admin/finance/crypto-addresses | finance:read | 虛擬錢包列表 |
| 48 | PATCH | /admin/finance/crypto-addresses/:id/review | finance:write | 虛擬錢包審核 |
| 49 | PATCH | /admin/finance/crypto-addresses/:id | finance:write | 更新虛擬錢包 |
| 50 | POST | /admin/finance/crypto-addresses | finance:write | 新增虛擬錢包 |
| 51 | DELETE | /admin/finance/crypto-addresses/:id | finance:write | 刪除虛擬錢包 |
| 52 | GET | /admin/finance/withdrawals | withdrawal:read | 提領列表 |
| 53 | POST | /admin/finance/withdrawals/:id/review | withdrawal:write | 提領審核 |
| 54 | POST | /admin/finance/withdrawals/:id/upload-proof | withdrawal:write | 上傳代付證明 |
| 55 | POST | /admin/finance/withdrawals/:id/complete | withdrawal:write | 確認出款完成 |
| 56 | GET | /admin/vendor-groups/list | vendor:read | 金流群組列表 |
| 57 | POST | /admin/vendor-groups/create | vendor:write | 新增金流群組 |
| 58 | PATCH | /admin/vendor-groups/:id | vendor:write | 更新金流群組 |
| 59 | DELETE | /admin/vendor-groups/:id | vendor:write | 刪除金流群組 |
| 60 | GET | /admin/vendor-groups/:id/channels | vendor:read | 群組通道列表 |
| 61 | PUT | /admin/vendor-groups/:id/channels | vendor:write | 設定群組通道 |
| 62 | GET | /admin/vendor-channels/list | vendor:read | 金流通道列表 |
| 63 | POST | /admin/vendor-channels/create | vendor:write | 新增金流通道 |
| 64 | PATCH | /admin/vendor-channels/:id | vendor:write | 更新金流通道 |
| 65 | DELETE | /admin/vendor-channels/:id | vendor:write | 刪除金流通道 |
| 66 | GET | /admin/reports/players | report:read | 玩家報表 |
| 67 | GET | /admin/reports/vip-players | report:read | VIP 玩家查詢 |
| 68 | GET | /admin/reports/bet-records | report:read | 投注紀錄 |
| 69 | GET | /admin/reports/overview | report:read | 總體報表 |
| 70 | GET | /admin/reports/profit-loss | report:read | 損益報表 |
| 71 | GET | /admin/reports/games | report:read | 遊戲報表 |
| 72 | GET | /admin/reports/promos | report:read | 優惠報表 |
| 73 | GET | /admin/reports/player-summary | report:read | 玩家簡表 |
| 74 | GET | /admin/reports/r2-logs | report:read | R2 操作紀錄 |
| 75 | GET | /admin/reports/export/:type | report:read | 匯出報表 CSV |
| 76 | GET | /admin/risk/ip-rules | risk:read | IP 規則列表 |
| 77 | POST | /admin/risk/ip-rules | risk:write | 新增 IP 規則 |
| 78 | PATCH | /admin/risk/ip-rules/:id | risk:write | 更新 IP 規則 |
| 79 | DELETE | /admin/risk/ip-rules/:id | risk:write | 刪除 IP 規則 |
| 80 | GET | /admin/risk/login-failures | risk:read | 登入失敗列表 |
| 81 | GET | /admin/risk/lookup | risk:read | 風控關鍵字查詢 |
| 82 | GET | /admin/risk/game-blacklist | risk:read | 遊戲黑名單列表 |
| 83 | POST | /admin/risk/game-blacklist | risk:write | 新增遊戲黑名單 |
| 84 | DELETE | /admin/risk/game-blacklist/:id | risk:write | 刪除遊戲黑名單 |
| 85 | GET | /admin/r2/list | storage:manage | R2 檔案列表 |
| 86 | POST | /admin/r2/upload | storage:manage | R2 上傳檔案 |
| 87 | POST | /admin/r2/delete | storage:manage | R2 批次刪除 |
| 88 | POST | /admin/r2/move | storage:manage | R2 移動檔案 |
| 89 | POST | /admin/r2/create-folder | storage:manage | R2 建立資料夾 |
| 90 | POST | /admin/r2/delete-folder | storage:manage | R2 刪除資料夾 |
本文件由 AI 自動產生,對應原始碼位置:
/Users/chenshoupei/c9/c9-be/src/modules/admin/admin.controller.ts
C9 Platform API 文件 — Part 5:存款、提領、錢包、金流商、活動、站內信、站點配置
本文件涵蓋 7 大模組共 50+ 個端點,適用於 PM、前端開發、UI 設計師參考。 所有金額均使用 USD decimal(18,6),截斷規則:
Math.floor(value * 1e6) / 1e6。 多站點架構:所有端點均支援site-nameheader 白牌路由。
Deposit 存款模組
路由前綴:
/api/deposit存款是平台核心金流入口。前端統一透過POST /deposit建單,後端根據channelId+paymentMethod自動路由至對應金流商(萬通 ATM / 萬通信用卡 / USDT 加密貨幣)。匯率由台灣銀行即時匯率 API 及 Binance Public API 提供。
GET /api/deposit/exchange-rate — 取得法幣匯率
功能說明 取得以 USD 為基底的即時匯率資訊。資料來源為台灣銀行即時匯率 API (tw-exchange)。前端在存款頁面載入時應呼叫此端點,讓用戶看到當前匯率,計算存入金額對應的 USD 數量。若不帶 currency 參數則回傳所有可用幣別的匯率。
認證需求:無(公開端點)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Query | currency | string | 否 | 幣別代碼,不帶則回傳全部 | TWD |
| Query | bankCode | string | 否 | 銀行代碼(預設 twbk) | twbk |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"time": "2026/02/22 14:30",
"base": "USD",
"TWD": { "buy": 31.82, "sell": 32.15 }
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 銀行代碼格式錯誤 |
2002 | 匯率解析失敗(台銀 API 不可用時) |
業務邏輯說明
buy為銀行買入匯率(用戶賣出外幣換台幣);sell為銀行賣出匯率(用戶買入外幣)- 存款時使用
sell匯率(用戶用台幣換 USD) - 匯率資料會依銀行開盤時間更新,非營業時間可能返回最後一次報價
time為匯率更新時間,格式YYYY/MM/DD HH:mm
前端注意事項 / UI 設計提示
- 存款頁面載入時呼叫取得匯率,顯示「1 USD ≈ XX TWD」
- 建議每 5 分鐘輪詢更新匯率(匯率會隨市場波動)
- 匯率僅供參考,實際入帳以建單時鎖定的匯率為準
GET /api/deposit/crypto-rate — 取得加密貨幣匯率
功能說明 取得以 USDT 為基底的加密貨幣即時匯率。資料來源為 Binance Public API。前端在用戶選擇加密貨幣入金時呼叫此端點,顯示各幣種對 USDT 的當前價格。若不帶 symbol 參數則回傳所有支援幣種(BTC、ETH、BNB、SOL、TRX)。
認證需求:無(公開端點)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Query | symbol | string | 否 | 幣種代碼,不帶則回傳全部 | BTC |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"base": "USDT",
"time": "2026-02-27T08:00:00.000Z",
"BTC": { "price": 95432.5 },
"ETH": { "price": 2654.3 },
"BNB": { "price": 652.1 },
"SOL": { "price": 178.5 },
"TRX": { "price": 0.245 }
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2007 | 加密貨幣匯率取得失敗(Binance API 不可用) |
業務邏輯說明
- 價格代表「1 個該幣種 = 多少 USDT」
- USDT 入金時不需匯率轉換(1 USDT = 1 USD 在系統內等值處理)
- Binance API 為公開端點,無需認證
前端注意事項 / UI 設計提示
- 加密貨幣存款頁面顯示即時匯率供用戶參考
- 建議搭配幣種圖示(BTC/ETH/BNB/SOL/TRX)顯示
- 匯率波動大,建議每 1-2 分鐘更新
GET /api/deposit/channels — 取得金流通道(含匯率)
功能說明 取得當前用戶可用的金流通道列表,同時附帶各通道的即時匯率。後端會根據用戶所屬的金流群組(vendor-group),回傳該群組下所有啟用的金流通道。此端點整合了通道資訊與匯率,前端一次呼叫即可取得存款所需的全部資訊。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"groupId": 1,
"groupName": "default",
"channels": [
{
"id": 1,
"name": "wantong",
"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": "TRC-20",
"enabled": 1,
"exchangeRate": null
}
]
}
}業務邏輯說明
- 用戶註冊時會依語系自動分配金流群組(zh-TW → TWD 群組)
paymentMethods陣列表示該通道支援的支付方式:fiat(ATM 轉帳)、credit(信用卡)、crypto(加密貨幣)- USDT 通道的
paymentAddress為平台收款錢包地址,network為鏈路(如 TRC-20) exchangeRate為即時匯率,USDT 通道為null(1:1 等值)enabled= 0 表示通道暫停使用
前端注意事項 / UI 設計提示
- 根據
paymentMethods顯示對應的支付方式選項卡(ATM / 信用卡 / 加密貨幣) - USDT 通道需顯示
paymentAddress讓用戶複製,並標註network提醒使用正確鏈路 - 隱藏
enabled = 0的通道或顯示為「維護中」 - 匯率動態顯示在金額輸入框旁(如「≈ 31.82 TWD/USD」)
GET /api/deposit/orders — 取得存款訂單列表
功能說明 取得當前登入用戶的存款訂單歷史紀錄,支援分頁與狀態篩選。回傳的訂單不含 callbackData(金流商回調原始資料,僅後台管理員可見)。前端在「存款紀錄」頁面呼叫此端點。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} | |
| Query | page | number | 否 | 頁碼(預設 1) | 1 |
| Query | pageSize | number | 否 | 每頁筆數(預設 10,最大 50) | 10 |
| Query | status | string | 否 | 訂單狀態篩選 | pending / created / paid / failed |
| Query | startDate | string | 否 | 開始日期 (YYYY-MM-DD) | 2026-01-01 |
| Query | endDate | string | 否 | 結束日期 (YYYY-MM-DD) | 2026-02-28 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"channelName": "wantong",
"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
}
}
}業務邏輯說明
- 訂單狀態流程:
pending(建單中)→created(金流商已受理)→paid(已付款,上分完成)/failed(失敗或逾期) orderAmount為原幣別金額(如 TWD 1000);usdAmount為轉換後的 USD 金額exchangeRate為建單時鎖定的匯率,精度 decimal(18,10)- 僅回傳當前用戶的訂單(後端以 JWT userId 過濾)
前端注意事項 / UI 設計提示
- 訂單狀態使用不同顏色標籤:
pending=灰色、created=藍色、paid=綠色、failed=紅色 - 顯示原幣別金額 + USD 金額(如「TWD 1,000 ≈ USD 31.25」)
- 支援日期範圍篩選 + 狀態下拉篩選
payTime為實際付款時間,pending/created狀態下為 null
POST /api/deposit — 建立存款訂單
功能說明 統一存款入口端點。前端帶入 channelId(金流通道 ID)和 paymentMethod(支付方式),後端自動路由至對應的金流商(萬通 ATM / 萬通信用卡 / USDT)建立訂單。建單成功後,ATM 方式會取得代收帳號,信用卡方式會取得付款頁面連結,USDT 方式則直接記錄訂單等待鏈上確認。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} | |
| Body | channelId | number | 是 | 金流通道 ID(從 GET /deposit/channels 取得) | 1 |
| Body | paymentMethod | string | 是 | 支付方式:fiat / credit / crypto | "fiat" |
| Body | subOrder | string | 是 | 商家訂單編號(8-24 碼) | "ORD20260222001" |
| Body | orderAmount | number | 是 | 訂單金額(USDT),最小 0.000001 | 20.5 |
| Body | expectedCode | string | 否 | ATM 預期收款銀行代碼(fiat 時使用) | "004" |
| Body | expectedAccount | string | 否 | ATM 預期收款帳號(fiat 時使用) | "12345678901234" |
| Body | userCardLastValue | string | 否 | 信用卡末五碼(credit 時使用,用於後續驗證) | "12345" |
| Body | productDes | string | 否 | 商品描述 | "儲值" |
| Body | msg | string | 否 | 備註訊息 | "備註" |
| Body | payerName | string | 否 | 付款人姓名 | "王小明" |
| Body | payerMobile | string | 否 | 付款人手機 | "0912345678" |
| Body | payerEmail | string | 否 | 付款人 Email | "user@example.com" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"status": true,
"sub_order": "ORD20260222001"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2003 | 不支援的金流通道類型 |
2004 | 不支援的支付方式 |
2005 | 匯率轉換失敗,無法取得有效匯率 |
2006 | 金流通道尚未建置完成 |
業務邏輯說明
- 後端根據
channelId查找通道,再依paymentMethod路由:fiat→ 萬通 ATM 建單(WantongService.addAtm)credit→ 萬通信用卡建單(WantongService.addCard)crypto→ USDT 訂單記錄
- 建單時會鎖定當前匯率,存入
deposit-order表 orderAmount為 USDT 金額;ATM/信用卡方式後端會自動轉換為對應法幣金額subOrder由前端產生,建議使用時間戳 + 隨機碼確保唯一性- 建單成功不代表付款完成,須等待金流商 S2S 回調(
callback/pay)才會上分
前端注意事項 / UI 設計提示
subOrder前端自行產生(格式建議:ORD+ 時間戳 + 4碼隨機數)- ATM 方式:建單後顯示代收帳號(從回調取得),提示用戶至 ATM 轉帳
- 信用卡方式:建單後可能取得跳轉 URL,引導用戶至金流商頁面完成付款
- USDT 方式:顯示平台收款地址 + 鏈路 + 金額,提示用戶手動轉帳
- 建單後導向訂單詳情頁,輪詢或 WebSocket 監聽狀態變更
PATCH /api/deposit/:id/confirm — 確認已轉帳 + 上傳繳費證明
功能說明 用戶完成 ATM 轉帳或 USDT 轉帳後,呼叫此端點上傳繳費證明圖片,表示「我已經付款了」。後端會將圖片上傳至 Cloudflare R2 儲存,並更新訂單狀態。此端點主要用於手動確認場景(如 USDT 鏈上確認較慢時,讓用戶主動回報)。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} | |
| Header | site-name | string | 是 | 站點名稱(R2 路徑用) | a1 |
| URL Param | id | number | 是 | 存款訂單 ID | 1 |
| Body (FormData) | file | File | 是 | 繳費證明圖片(image/*,最大 5MB) |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"proofImage": "deposit-proof/a1/1_1234567890.jpg",
"status": "created"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2010 | 請上傳繳費證明圖片 |
業務邏輯說明
- 僅接受圖片檔案(
image/*),最大 5MB - 圖片上傳至 R2 路徑:
deposit-proof/{siteName}/{orderId}_{timestamp}.{ext} - 上傳後訂單不會立即變為
paid,仍需等待金流商回調或後台管理員審核 - 只有訂單所有者(JWT userId 與訂單 userId 一致)才能操作
前端注意事項 / UI 設計提示
- 使用
multipart/form-data格式上傳 - 支援拍照上傳或從相簿選擇
- 圖片預覽 + 壓縮(建議前端壓縮至 1MB 以下再上傳)
- 上傳成功後顯示「已提交,等待確認」提示
Withdrawal 提領模組
路由前綴:
/api/withdrawal提領採三階段流程:用戶申請(pending)→ 後台審核(approved/rejected)→ 確認出款完成(completed)。提領前須通過 Email 驗證碼 + 打碼量檢查。
POST /api/withdrawal/send-code — 發送提領驗證碼
功能說明 發送 6 碼數字驗證碼到用戶已綁定的信箱。此為提領流程的第一步,用戶必須先取得驗證碼才能提交提領申請。後端會檢查用戶是否已完成信箱與手機驗證,若未完成則拒絕發送。驗證碼透過 Resend API 發送 Email。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} |
無 Body 參數。
Response 範例
{
"code": 200,
"message": "ok",
"result": null
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 用戶信箱未驗證(須先完成 Email 綁定) |
2002 | 用戶手機未驗證(須先完成手機綁定) |
業務邏輯說明
- 驗證碼有效期約 5 分鐘,存入 Redis
- 同一用戶短時間內重複發送會覆蓋上一組驗證碼
- 信箱與手機必須都已驗證才允許提領(風控要求)
前端注意事項 / UI 設計提示
- 提領頁面先檢查用戶是否已綁定信箱 + 手機,若未綁定引導至個人設定頁
- 「發送驗證碼」按鈕點擊後倒數 60 秒防重複點擊
- 驗證碼輸入框 6 格分離式設計(提升用戶體驗)
POST /api/withdrawal/request — 提交提領申請
功能說明 提交 USDT 提領申請。用戶需指定提領金額、目標加密錢包 ID 與 Email 驗證碼。後端會檢查驗證碼正確性、錢包審核狀態、餘額充足性、以及打碼量是否達標。通過所有檢查後建立提領單,扣除用戶餘額(凍結),等待後台審核。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} | |
| Body | amount | number | 是 | 提領金額(USD) | 100 |
| Body | cryptoAddressId | number | 是 | 加密錢包 ID(須 status=1 已審核通過) | 1 |
| Body | verifyCode | string | 是 | 郵箱驗證碼(6 碼) | "123456" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"userId": 123,
"amount": "100.000000",
"cryptoAddressId": 1,
"status": "pending",
"createdAt": "2026-02-22T10:00:00.000Z"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 驗證碼未發送(須先呼叫 send-code) |
2002 | 驗證碼錯誤 |
2003 | 用戶信箱未驗證 |
2004 | 用戶手機未驗證 |
2005 | 該錢包不存在或未通過審核 |
2006 | 提領金額需大於 0 |
2007 | 餘額不足 |
2008 | 優惠打碼量未完成(有領取的活動獎勵尚未完成打碼要求) |
2009 | 存款打碼量不足(存款後需完成一定投注量才可提領) |
業務邏輯說明
- 提領目前僅支援 USDT(加密貨幣),需先綁定並通過審核的加密錢包
- 提交時立即從用戶餘額扣除(凍結),防止超額提領
- 若審核被拒絕(
rejected),凍結金額退回用戶餘額 - 打碼量檢查:每次存款後需達到 1 倍存款金額的有效投注量;活動獎勵有各自的打碼倍數要求
- 驗證碼驗證後即失效(一次性使用)
前端注意事項 / UI 設計提示
- 提領頁面流程:選擇錢包 → 輸入金額 → 發送驗證碼 → 輸入驗證碼 → 確認提領
- 顯示用戶可用餘額(扣除凍結金額)
- 金額輸入框限制最小值與最大值(最大值 = 可用餘額)
- 錢包下拉選單只顯示
status=1(已審核通過)的錢包 - 提交前呼叫
GET /withdrawal/turnover-status檢查打碼量,未達標則禁用提領按鈕
GET /api/withdrawal/list — 用戶提領紀錄
功能說明 取得當前登入用戶的提領歷史紀錄,支援分頁、狀態篩選與日期範圍篩選。前端在「提領紀錄」頁面呼叫此端點。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} | |
| Query | page | number | 否 | 頁碼(預設 1) | 1 |
| Query | pageSize | number | 否 | 每頁筆數(預設 10) | 10 |
| Query | status | string | 否 | 狀態篩選 | pending / approved / rejected / completed |
| Query | startDate | string | 否 | 起始日期 (YYYY-MM-DD) | 2026-01-01 |
| Query | endDate | string | 否 | 結束日期 (YYYY-MM-DD) | 2026-12-31 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"amount": "100.000000",
"status": "pending",
"cryptoAddress": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo",
"network": "TRC-20",
"createdAt": "2026-02-22T10:00:00.000Z",
"updatedAt": "2026-02-22T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 1,
"totalPages": 1
}
}
}業務邏輯說明
- 提領狀態流程:
pending(待審核)→approved(已核准)→completed(已完成);或pending→rejected(已拒絕,退回餘額) - 僅回傳當前用戶的提領紀錄
前端注意事項 / UI 設計提示
- 狀態標籤顏色:
pending=橙色、approved=藍色、completed=綠色、rejected=紅色 - 被拒絕的訂單顯示拒絕原因
- 支援日期範圍 + 狀態下拉篩選
GET /api/withdrawal/turnover-status — 查詢打碼量狀態
功能說明 查詢當前用戶的存款打碼量與優惠打碼量完成進度。前端可根據回傳的 canWithdraw 欄位控制提領按鈕的啟用/禁用狀態。此端點整合了存款打碼量(每次存款需完成 1 倍有效投注)和優惠打碼量(活動獎勵的打碼倍數要求)兩項檢查。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"canWithdraw": false,
"deposit": {
"totalDeposits": "1000.000000",
"multiplier": 1,
"requiredTurnover": "1000.000000",
"completedTurnover": "750.000000",
"remaining": "250.000000",
"completed": false
},
"promo": {
"pendingCount": 1,
"completed": false,
"items": [
{
"promoId": 1,
"promoTitle": "新手首存禮",
"rewardAmount": "10.000000",
"requiredTurnover": "50.000000",
"completedTurnover": "30.000000",
"remaining": "20.000000"
}
]
}
}
}業務邏輯說明
canWithdraw = true僅當deposit.completed與promo.completed皆為true- 存款打碼量:
requiredTurnover = totalDeposits * multiplier(預設 1 倍) - 優惠打碼量:每個領取的活動獎勵有各自的
turnoverMultiplier,requiredTurnover = rewardAmount * turnoverMultiplier - 打碼量透過有效投注累計(
bet-order表的betEffective)
前端注意事項 / UI 設計提示
- 提領頁面顯示打碼量進度條(存款 + 優惠分開顯示)
canWithdraw = false時禁用提領按鈕,顯示「尚未達到提領門檻」- 顯示各項打碼量的完成百分比(
completedTurnover / requiredTurnover * 100) - 優惠打碼量若有多項,逐一列出每個活動的進度
GET /api/withdrawal/admin/list — [Admin] 提領列表
功能說明 後台管理員取得全站提領訂單列表,支援分頁與狀態篩選。用於後台提領審核頁面。
認證需求:JwtAuthGuard(注意:此處使用前台 Guard,實際應搭配 AdminJwtAuthGuard)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {Admin JWT Token} | |
| Query | page | number | 否 | 頁碼(預設 1) | 1 |
| Query | pageSize | number | 否 | 每頁筆數(預設 10) | 10 |
| Query | status | string | 否 | 狀態篩選 | pending / approved / rejected / completed |
| Query | startDate | string | 否 | 起始日期 | 2026-01-01 |
| Query | endDate | string | 否 | 結束日期 | 2026-12-31 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"userId": 123,
"userAccount": "user001",
"amount": "100.000000",
"status": "pending",
"cryptoAddress": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo",
"network": "TRC-20",
"createdAt": "2026-02-22T10:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}
}前端注意事項 / UI 設計提示
- 後台提領管理頁面,按狀態分頁籤顯示(待審核 / 已核准 / 已完成 / 已拒絕)
- 待審核訂單行提供「審核」按鈕
POST /api/withdrawal/admin/:id/review — [Admin] 審核提領
功能說明 後台管理員審核提領單。approve 通過審核(進入等待出款狀態),reject 拒絕並退回凍結金額至用戶餘額。
認證需求:JwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {Admin JWT Token} | |
| URL Param | id | number | 是 | 提領單 ID | 1 |
| Body | action | string | 是 | 審核動作:approve / reject | "approve" |
| Body | rejectReason | string | 否 | 拒絕原因(action=reject 時必填) | "資料異常" |
Response 範例
{
"code": 200,
"message": "ok",
"result": { "success": true }
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無此提領單 |
2002 | 該提領單狀態不允許審核(非 pending 狀態) |
業務邏輯說明
approve:狀態從pending→approved,凍結金額維持reject:狀態從pending→rejected,凍結金額退回用戶可用餘額- 審核人帳號會記錄在提領單中
POST /api/withdrawal/admin/:id/complete — [Admin] 確認出款完成
功能說明 管理員確認已完成鏈上匯款,標記提領單為已完成。此為提領流程的最後一步,凍結金額正式出帳。
認證需求:JwtAuthGuard
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {Admin JWT Token} | |
| URL Param | id | number | 是 | 提領單 ID | 1 |
Response 範例
{
"code": 200,
"message": "ok",
"result": { "success": true }
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無此提領單 |
2002 | 該提領單狀態不允許完成(非 approved 狀態) |
業務邏輯說明
- 僅
approved狀態的訂單可完成 - 完成後凍結金額正式扣除,不可逆
Wallet 錢包模組
錢包模組管理用戶的三種支付/收款工具:銀行卡、信用卡、加密貨幣錢包地址。 所有錢包工具新增後預設
status=0(待審核),需後台管理員審核通過(status=1)後才可用於存款/提領。
銀行卡 (Bank Card)
路由前綴:
/api/wallet/bank-card整個 Controller 使用@UseGuards(JwtAuthGuard),所有端點皆需前台用戶 JWT。
POST /api/wallet/bank-card/add — 新增銀行卡
功能說明 用戶新增銀行卡,須上傳身分證正面、背面及銀行存摺封面三張圖片作為審核資料。圖片上傳至 Cloudflare R2 儲存。新增後狀態為 status=0(待審核),需後台管理員審核通過後才可用於存款。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | Authorization | string | 是 | Bearer {JWT Token} | |
| Header | site-name | string | 是 | 站點名稱(R2 路徑用) | a1 |
| Body (FormData) | bankCode | string | 是 | 銀行代碼 | "004" |
| Body (FormData) | bankAccount | string | 是 | 銀行帳號(純數字,至少 8 碼) | "12345678901234" |
| Body (FormData) | branch | string | 是 | 分行名稱 | "台北分行" |
| Body (FormData) | holderName | string | 是 | 持卡人姓名(至少 2 字) | "王小明" |
| Body (FormData) | idCardFront | File | 是 | 身分證正面(image/*,最大 5MB) | |
| Body (FormData) | idCardBack | File | 是 | 身分證背面(image/*,最大 5MB) | |
| Body (FormData) | passbookCover | File | 是 | 存摺封面(image/*,最大 5MB) |
Response 範例
{
"code": 200,
"message": "ok",
"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 | 此銀行卡已存在(同一銀行帳號不可重複綁定) |
業務邏輯說明
- 使用
FileFieldsInterceptor處理多檔案上傳(三個欄位各一張圖) - 圖片驗證:僅接受
image/*MIME type,單檔最大 5MB bankAccount驗證:只允許純數字,至少 8 碼- 新增後
status=0(待審核),需後台PATCH /admin/finance/bank-cards/:id審核
前端注意事項 / UI 設計提示
- 表單使用
multipart/form-data格式 - 三個圖片上傳區域,建議加入圖片預覽功能
- 銀行代碼可用下拉選單(搭配台灣銀行代碼清單)
- 銀行帳號輸入限制為純數字
- 提交後提示「審核中,請等待 1-3 個工作天」
GET /api/wallet/bank-card/list — 取得銀行卡列表
功能說明 取得當前用戶已綁定的銀行卡列表。圖片欄位會回傳完整的 R2 URL(可直接顯示)。
認證需求:JwtAuthGuard(前台用戶 JWT)
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"userId": 1,
"bankCode": "004",
"bankAccount": "12345678901234",
"branch": "台北分行",
"holderName": "王小明",
"idCardFront": "https://r2.example.com/bank-card/xxx.jpg",
"idCardBack": "https://r2.example.com/bank-card/xxx.jpg",
"passbookCover": "https://r2.example.com/bank-card/xxx.jpg",
"status": 0,
"createdAt": "2026-02-22T00:00:00.000Z",
"updatedAt": "2026-02-22T00:00:00.000Z"
}
]
}前端注意事項 / UI 設計提示
- 卡片式列表顯示,每張銀行卡顯示銀行名稱 + 帳號後四碼
- 狀態標籤:
0=審核中(橙色)、1=已通過(綠色)、2=已拒絕(紅色) - 已通過的銀行卡在存款時可選擇
DELETE /api/wallet/bank-card/:id — 刪除銀行卡
功能說明 刪除指定的銀行卡。僅能刪除自己的銀行卡。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| URL Param | id | number | 是 | 銀行卡 ID |
Response 範例
{ "code": 200, "message": "ok", "result": null }錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無此銀行卡(ID 不存在或不屬於當前用戶) |
信用卡 (Credit Card)
路由前綴:
/api/wallet/credit-card
POST /api/wallet/credit-card/add — 新增信用卡
功能說明 用戶新增信用卡。不需上傳圖片,直接填寫卡號、持卡人姓名、CVV、到期日即可。新增後預設 status=0(待審核)。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Body | cardNumber | string | 是 | 信用卡卡號(13-19 位純數字) | "4111111111111111" |
| Body | holderName | string | 是 | 持卡人姓名(至少 2 字) | "王小明" |
| Body | cvv | string | 是 | 安全碼 CVV/CVC(3-4 位數字) | "123" |
| Body | expiryDate | string | 是 | 到期日(MM/YY 格式) | "12/27" |
Response 範例
{
"code": 200,
"message": "ok",
"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"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 此信用卡已綁定(同一卡號不可重複) |
前端注意事項 / UI 設計提示
- 卡號輸入:每 4 碼自動加空格顯示(提交時去除空格)
- CVV 輸入:密碼模式(
type="password") - 到期日:MM/YY 格式,可用兩個下拉選單(月/年)
- 卡號格式驗證:Luhn 演算法(前端可做基本檢查)
GET /api/wallet/credit-card/list — 取得信用卡列表
功能說明 取得當前用戶已綁定的信用卡列表。
認證需求:JwtAuthGuard
Response 範例
{
"code": 200,
"message": "ok",
"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"
}
]
}前端注意事項 / UI 設計提示
- 卡號顯示時遮罩中間數字(如
4111 **** **** 1111) - CVV 不顯示(安全考量)
DELETE /api/wallet/credit-card/:id — 刪除信用卡
認證需求:JwtAuthGuard
錯誤碼:2001 查無此信用卡
Response 範例
{ "code": 200, "message": "ok", "result": null }加密貨幣錢包地址 (Crypto Address)
路由前綴:
/api/wallet/crypto-address
POST /api/wallet/crypto-address/add — 新增加密貨幣錢包地址
功能說明 用戶新增加密貨幣錢包地址,用於提領時的收款地址。需指定錢包名稱、幣別、鏈路與地址。新增後預設 status=0(待審核),須後台審核通過(status=1)後才可用於提領。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Body | walletName | string | 是 | 錢包名稱 | "otis的USDT錢包" |
| Body | currency | string | 否 | 幣別(預設 USDT) | "USDT" |
| Body | network | string | 否 | 鏈路(預設 TRC-20) | "TRC-20" |
| Body | address | string | 是 | 錢包地址(至少 10 碼) | "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo" |
Response 範例
{
"code": 200,
"message": "ok",
"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 | 此錢包地址已綁定(同一地址不可重複) |
業務邏輯說明
- 提領時只能選擇
status=1(已審核通過)的錢包地址 - 目前主要支援 USDT / TRC-20 組合
- 錢包地址至少 10 碼,防止誤輸入
前端注意事項 / UI 設計提示
- 鏈路下拉選單:TRC-20 / ERC-20 / BEP-20 等
- 幣別下拉選單:USDT(目前僅支援)
- 地址輸入支援 QR Code 掃描(行動版)
- 提醒用戶確認鏈路與地址正確,轉錯鏈路會導致資金丟失
GET /api/wallet/crypto-address/list — 取得加密貨幣錢包列表
認證需求:JwtAuthGuard
Response 範例
{
"code": 200,
"message": "ok",
"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 /api/wallet/crypto-address/:id — 刪除加密貨幣錢包地址
認證需求:JwtAuthGuard
錯誤碼:2001 查無此錢包地址
Response 範例
{ "code": 200, "message": "ok", "result": null }Vendor 金流商模組
路由前綴:
/api/vendor金流商模組提供金流通道查詢,以及萬通金流(Wantong)和 USDT 兩個子模組的建單與回調處理。 前端通常不直接呼叫子模組端點,而是透過POST /deposit統一入口建單。
GET /api/vendor/channels — 取得用戶金流通道列表
功能說明 取得當前用戶所屬金流群組的通道列表。此端點與 GET /deposit/channels 類似,但不附帶匯率資訊。前端建議使用 GET /deposit/channels(含匯率)而非此端點。
認證需求:JwtAuthGuard(前台用戶 JWT)
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"groupId": 1,
"groupName": "default",
"channels": [
{
"id": 1,
"name": "wantong",
"currency": "TWD",
"paymentMethods": ["fiat", "credit"],
"paymentAddress": null,
"enabled": 1
},
{
"id": 2,
"name": "usdt",
"currency": "USDT",
"paymentMethods": ["crypto"],
"paymentAddress": "TXqH4j5xGZz8vKJm9bN2rL7pWcYfDsAeUo",
"network": "TRC-20",
"enabled": 1
}
]
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 用戶未分配金流群組(註冊異常) |
2002 | 金流群組不存在或已停用 |
2003 | 金流通道不存在或不屬於此群組 |
萬通金流子模組 (Wantong)
路由前綴:
/api/vendor/wantong萬通金流支援 ATM 虛擬帳號轉帳與信用卡線上刷卡兩種方式。
POST /api/vendor/wantong/add-atm — 萬通 ATM 建單
功能說明 向萬通金流發送 ATM 虛擬帳號建單請求。建單成功後萬通會回傳代收銀行帳號與繳費期限。前端通常不直接呼叫此端點,而是透過 POST /deposit 統一入口(paymentMethod: 'fiat')自動路由至此。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Body | subOrder | string | 是 | 商家訂單編號(8-24 碼) | "ORD20260222001" |
| Body | orderAmount | number | 是 | 訂單金額(整數,TWD) | 1000 |
| Body | expectedCode | string | 是 | 預期收款銀行代碼 | "004" |
| Body | expectedAccount | string | 是 | 預期收款帳號 | "12345678901234" |
| Body | productDes | string | 否 | 商品描述 | "儲值" |
| Body | msg | string | 否 | 備註訊息 | "備註" |
| Body | payerName | string | 否 | 付款人姓名 | "王小明" |
| Body | payerMobile | string | 否 | 付款人手機 | "0912345678" |
| Body | payerEmail | string | 否 | 付款人 Email | "user@example.com" |
Response 範例
{
"code": 200,
"message": "ok",
"result": { "status": true, "sub_order": "ORD20260222001" }
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無可用的萬通金流通道 |
2002 | 建立 ATM 訂單失敗(萬通 API 回傳錯誤) |
2003 | ATM 訂單請求異常(網路錯誤等) |
業務邏輯說明
- 建單成功後,萬通會透過
POST /vendor/wantong/callback/add回調推送代收帳號資訊 - 用戶完成 ATM 轉帳後,萬通會透過
POST /vendor/wantong/callback/pay回調通知付款成功 - 付款成功後後端自動上分(增加用戶 USD 餘額)
POST /api/vendor/wantong/add-card — 萬通信用卡建單
功能說明 向萬通金流發送信用卡建單請求。建單成功後用戶會被導向至金流商的信用卡付款頁面。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Body | subOrder | string | 是 | 商家訂單編號(8-24 碼) | "ORD20260222001" |
| Body | orderAmount | number | 是 | 訂單金額(整數,TWD) | 1000 |
| Body | userCardLastValue | string | 否 | 信用卡末五碼(用於後續驗證) | "12345" |
| Body | productDes | string | 否 | 商品描述 | "儲值" |
| Body | msg | string | 否 | 備註訊息 | |
| Body | payerName | string | 否 | 付款人姓名 | "王小明" |
| Body | payerMobile | string | 否 | 付款人手機 | "0912345678" |
| Body | payerEmail | string | 否 | 付款人 Email | "user@example.com" |
Response 範例
{
"code": 200,
"message": "ok",
"result": { "status": true, "sub_order": "ORD20260222001" }
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 查無可用的萬通金流通道 |
2002 | 建立信用卡訂單失敗 |
2003 | 信用卡訂單請求異常 |
POST /api/vendor/wantong/callback/add — 萬通建單回調
功能說明 萬通金流伺服器呼叫的 S2S 回調端點(不需 JWT)。萬通在受理建單請求後,會推送代收帳號資訊到此端點。後端會更新訂單的繳費資訊(代收銀行帳號、繳費期限等)。
認證需求:無(公開端點,萬通 S2S 呼叫)
Request(由萬通推送)
| 參數 | 型別 | 說明 |
|---|---|---|
sub_order | string | 商家訂單編號 |
check_sum | string | MD5 校驗碼 |
expire_date | string | 繳費期限 |
order_amount | number | 應繳金額 |
receiving_code | string | 代收銀行代碼 |
receiving_account | string | 代收銀行帳號 |
Response 範例
{ "received": true }POST /api/vendor/wantong/callback/pay — 萬通付款成功回調
功能說明 萬通金流伺服器呼叫的 S2S 回調端點(不需 JWT)。用戶完成付款後,萬通推送付款成功通知。後端會自動上分(增加用戶 USD 餘額),並更新訂單狀態為 paid。
認證需求:無(公開端點,萬通 S2S 呼叫)
Request(由萬通推送)
| 參數 | 型別 | 說明 |
|---|---|---|
sub_order | string | 商家訂單編號 |
check_sum | string | MD5 校驗碼 |
pay_amount | number | 實際繳款金額 |
pay_time | string | 實際繳款時間 |
payment_account | string | 實際繳款帳號(ATM 專用) |
expected_code | string | 預約銀行代碼(ATM 專用) |
expected_account | string | 預約銀行帳戶(ATM 專用) |
vertify_pay_account | number | 帳號比對狀態:0=無法比對 / 1=不一致 / 2=一致(ATM 專用) |
vertify_pay_amount | number | 金額比對狀態:0=不符 / 1=一致 |
user_pay_card | string | 實際刷卡卡號(信用卡專用) |
vertify_card_last_value | number | 卡號末五碼驗證:0=未使用 / 1=不一致 / 2=一致(信用卡專用) |
Response 範例
{ "received": true }業務邏輯說明
- 回調會驗證
check_sumMD5 校驗碼確保請求來自萬通 - 付款成功後自動上分:
usdAmount = payAmount / exchangeRate - 上分後訂單狀態從
created→paid - 上分後自動觸發任務系統的存款進度更新
USDT 子模組
路由前綴:
/api/vendor/usdt
POST /api/vendor/usdt/callback/pay — USDT 付款確認回調
功能說明 USDT 付款確認的 S2S 回調端點(不需 JWT)。當系統偵測到鏈上轉帳完成(或人工確認)時呼叫此端點。後端會自動上分並更新訂單狀態為 paid。
認證需求:無(S2S 端點)
Request
| 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|
subOrder | string | 是 | 商家訂單編號 |
payAmount | number | 是 | 實際繳款金額(USDT) |
txHash | string | 否 | 鏈上交易 hash |
payTime | string | 否 | 繳款時間 |
Response 範例
{ "received": true }業務邏輯說明
- USDT 1:1 等值 USD,不需匯率轉換
txHash可用於後台核對鏈上交易- 上分後自動觸發任務系統的存款進度更新
Promo 活動模組
路由前綴:
/api/promo活動模組管理優惠活動的建立、查詢、領取。支援多語系標題/內容、多裝置圖片(PC/Mobile)、領取條件檢查(首存/存款門檻/VIP 等級)、打碼量追蹤。
GET /api/promo — 取得活動列表
功能說明 取得活動列表,支援分頁、標籤篩選、僅顯示進行中活動。此端點使用 OptionalJwtAuthGuard——登入用戶可看到 isClaimed(是否已領取)和 isClaimable(是否可領取)的個人化狀態;未登入用戶這兩個欄位皆為 false。圖片欄位回傳完整 R2 URL,依用戶當前語系解析。
認證需求:OptionalJwtAuthGuard(可選認證,未登入也可存取)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Query | page | number | 否 | 頁碼(預設 1) | 1 |
| Query | pageSize | number | 否 | 每頁筆數(預設 10,最大 50) | 10 |
| Query | tag | string | 否 | 活動標籤篩選 | "新手" |
| Query | activeOnly | string | 否 | 僅顯示進行中(1=是) | "1" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"title": "新手首存禮",
"imgPc": "https://r2.example.com/promo/pc-xxx.jpg",
"imgMobile": "https://r2.example.com/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",
"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:當前時間在startTime~endTime之間且enabled=1isClaimed:當前用戶已領取過此活動(僅登入時計算)isClaimable:活動進行中 + 未領取 + 名額未滿 + 滿足條件(僅登入時計算)title、content為多語系 JSON,後端依用戶語系解析後回傳單一字串imgPc、imgMobile為完整 R2 URL,依語系選擇對應版本
前端注意事項 / UI 設計提示
- 活動列表頁面:頂部標籤篩選列(從
GET /promo/tags取得標籤列表) - PC 版顯示
imgPc,行動版顯示imgMobile - 已領取的活動顯示「已領取」徽章
- 可領取的活動顯示「立即領取」按鈕
actionHtml為 HTML 片段,可安全渲染(如導向存款頁的按鈕)
GET /api/promo/claims — 取得領取紀錄
功能說明 取得當前用戶的活動獎勵領取紀錄,支援分頁與狀態篩選。包含每個已領取活動的打碼量進度。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Query | page | number | 否 | 頁碼(預設 1) | 1 |
| Query | pageSize | number | 否 | 每頁筆數(預設 10,最大 50) | 10 |
| Query | tab | string | 否 | 篩選:all / pending(打碼中)/ completed(已完成) | "all" |
| Query | startDate | string | 否 | 起始日期 (YYYY-MM-DD) | 2026-01-01 |
| Query | endDate | string | 否 | 結束日期 (YYYY-MM-DD) | 2026-12-31 |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"items": [
{
"id": 1,
"promoId": 1,
"promoTitle": "新手首存禮",
"promoTag": "新手",
"rewardAmount": "10.000000",
"requiredTurnover": "50.000000",
"completedTurnover": "30.000000",
"turnoverCompleted": 0,
"claimedAt": "2026-02-22T10:00:00.000Z"
}
],
"pagination": { "page": 1, "pageSize": 10, "total": 1, "totalPages": 1 }
}
}業務邏輯說明
turnoverCompleted:0=打碼中、1=已完成requiredTurnover = rewardAmount * turnoverMultiplier- 打碼量透過有效投注自動累計
前端注意事項 / UI 設計提示
- 分頁籤:全部 / 打碼中 / 已完成
- 每項紀錄顯示打碼量進度條
- 打碼中的項目標記為橙色,已完成標記為綠色
GET /api/promo/tags — 取得已啟用的活動標籤
功能說明 取得所有已啟用(enabled=1)的活動標籤,按 sortOrder 排序。前台活動列表頁面用於顯示標籤篩選列。
認證需求:無(公開端點)
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{ "id": 1, "name": "新手", "sortOrder": 1, "enabled": 1 },
{ "id": 2, "name": "每週", "sortOrder": 2, "enabled": 1 }
]
}GET /api/promo/:id — 取得活動詳情
功能說明 取得指定活動的完整詳情,包含 isActive、isClaimed、isClaimable 計算欄位。使用 OptionalJwtAuthGuard,未登入用戶 isClaimed 和 isClaimable 皆為 false。
認證需求:OptionalJwtAuthGuard(可選認證)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| URL Param | id | number | 是 | 活動 ID |
錯誤碼:2001 查無此活動
POST /api/promo — 建立活動
功能說明 建立新的優惠活動。支援多語系標題與內容(JSON 格式),可上傳 PC 版與 Mobile 版圖片(各語系各一張)。圖片上傳至 Cloudflare R2。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Header | site-name | string | 是 | 站點名稱(R2 路徑用) | a1 |
| Body (FormData) | title | JSON string | 是 | 活動標題(多語系) | {"zh-TW":"新手首存禮","en-US":"Welcome Bonus"} |
| Body (FormData) | content | JSON string | 是 | 活動內容(多語系 HTML) | {"zh-TW":"<p>送 10 USD</p>","en-US":"<p>10 USD bonus</p>"} |
| Body (FormData) | actionHtml | string | 否 | 渲染連結/按鈕 HTML | <a href="/deposit">立即存款</a> |
| Body (FormData) | startTime | string | 是 | 開始時間 (ISO 8601) | 2026-03-01T00:00:00.000Z |
| Body (FormData) | endTime | string | 是 | 結束時間 (ISO 8601) | 2026-04-01T00:00:00.000Z |
| Body (FormData) | tag | string | 是 | 活動標籤(最長 30 字) | 新手 |
| Body (FormData) | enabled | number | 否 | 是否開啟(0=關 1=開,預設 1) | 1 |
| Body (FormData) | conditionType | string | 是 | 領取條件類型 | none / deposit_threshold / vip_level / first_deposit |
| Body (FormData) | conditionValue | string | 否 | 門檻值(存款金額或 VIP 等級,預設 0) | "0" |
| Body (FormData) | rewardAmount | number | 是 | 獎勵金額(USD) | 10 |
| Body (FormData) | maxClaims | number | 否 | 最大領取總數(0=無限,預設 0) | 100 |
| Body (FormData) | turnoverMultiplier | number | 否 | 打碼量倍數(0=無要求,預設 0) | 3 |
| Body (FormData) | imgPcZhTW | File | 否 | PC 版圖片(繁中版,max 5MB) | |
| Body (FormData) | imgPcEnUS | File | 否 | PC 版圖片(英文版,max 5MB) | |
| Body (FormData) | imgMobileZhTW | File | 否 | Mobile 版圖片(繁中版,max 5MB) | |
| Body (FormData) | imgMobileEnUS | File | 否 | Mobile 版圖片(英文版,max 5MB) |
業務邏輯說明
conditionType條件類型:none:無條件,任何人可領取first_deposit:首次存款後可領取,conditionValue為最低存款金額(0=不限金額)deposit_threshold:累積存款達門檻可領取,conditionValue為門檻金額vip_level:VIP 等級達標可領取,conditionValue為最低 VIP 等級
turnoverMultiplier:領取獎勵後需完成的打碼倍數(如 3 倍 = 獎勵金額 * 3 的有效投注量)- 圖片依語系分版上傳,前端請求時後端依用戶語系回傳對應版本
PATCH /api/promo/:id — 更新活動
功能說明 更新指定活動。所有欄位皆為 optional,只傳需要更新的欄位。上傳新圖片會取代舊圖。
認證需求:JwtAuthGuard
錯誤碼:2001 查無此活動
DELETE /api/promo/:id — 刪除活動
認證需求:JwtAuthGuard
錯誤碼:2001 查無此活動
Response 範例
{ "code": 200, "message": "ok", "result": null }POST /api/promo/:id/claim — 領取活動獎勵
功能說明 用戶領取指定活動的獎勵。後端會檢查所有領取條件(活動進行中、未領取過、名額未滿、滿足條件類型要求),通過後將獎勵金額直接發放到用戶 USD 餘額,並建立 promo-claim 紀錄(含打碼量追蹤)。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| URL Param | id | number | 是 | 活動 ID |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"rewardAmount": "10.000000",
"newBalance": "110.000000"
}
}錯誤碼
| 錯誤碼 | 說明 |
|---|---|
2001 | 此活動已領取過 |
2002 | 此活動領取名額已滿 |
2003 | 尚未滿足領取條件 |
業務邏輯說明
- 獎勵直接加到用戶 USD 餘額(即時到帳)
- 若活動有
turnoverMultiplier > 0,領取後會建立打碼量追蹤紀錄 - 打碼量未完成前,提領會被
2008錯誤碼擋下 claimedCount會即時遞增,達到maxClaims後不可再領取
前端注意事項 / UI 設計提示
- 領取前彈出確認對話框(含獎勵金額與打碼要求說明)
- 領取成功後顯示動畫 + 更新餘額顯示
- 按鈕狀態即時切換:「立即領取」→「已領取」
Inbox 站內信模組
路由前綴:
/api/inbox站內信分為系統廣播(userId=null,全站可見)與個人通知(userId指定用戶)。 後台管理員發送,前台用戶閱讀/標記已讀。
GET /api/inbox — 取得站內信列表
功能說明 取得當前用戶的站內信列表,支援分頁、分類篩選與範圍篩選。系統廣播(userId=null)所有用戶可見;個人通知(userId=當前用戶)僅對應用戶可見。isRead 透過 notification-read 表判斷。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Query | page | number | 否 | 頁碼(預設 1) | 1 |
| Query | pageSize | number | 否 | 每頁筆數(預設 10,最大 50) | 10 |
| Query | category | string | 否 | 分類篩選 | system / promo |
| Query | scope | string | 否 | 範圍:personal=個人通知 / system=系統廣播 | personal |
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為多語系 JSON,後端依用戶語系解析後回傳單一字串isRead透過notification-read表查詢(用戶維度)scope=personal篩選userId=當前用戶的通知scope=system篩選userId=null的系統廣播
前端注意事項 / UI 設計提示
- 站內信頁面分頁籤:全部 / 系統通知 / 活動通知
- 未讀信件加粗顯示或加上紅點
content為 HTML,安全渲染(使用v-html或dangerouslySetInnerHTML)- Header 上顯示未讀數量紅點(搭配
GET /inbox/unread-count)
GET /api/inbox/unread-count — 取得未讀通知數量
功能說明 取得當前用戶的未讀通知數量。前端用於在 Header 導航列的郵件圖示旁顯示未讀紅點 / 數字。支援按範圍篩選。
認證需求:JwtAuthGuard(前台用戶 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Query | scope | string | 否 | 範圍:personal / system |
Response 範例
{
"code": 200,
"message": "ok",
"result": { "unreadCount": 5 }
}前端注意事項 / UI 設計提示
- 建議登入後每 60 秒輪詢一次更新未讀數
- 數量 > 99 時顯示
99+ - 數量 = 0 時隱藏紅點
POST /api/inbox/:id/read — 標記單則通知為已讀
認證需求:JwtAuthGuard
錯誤碼:2001 查無此通知
Response 範例
{ "code": 200, "message": "ok", "result": null }POST /api/inbox/read-all — 全部標記為已讀
認證需求:JwtAuthGuard
Response 範例
{ "code": 200, "message": "ok", "result": null }前端注意事項 / UI 設計提示
- 列表頁面提供「全部已讀」按鈕
- 操作後更新列表中所有項目的
isRead狀態 + 清除未讀數紅點
POST /api/inbox/admin/send — [Admin] 發送通知
功能說明 後台管理員發送站內通知。可發送給特定用戶(指定 userId)或全站廣播(不帶 userId)。標題與內容皆為多語系 JSON 格式,前台依用戶語系自動解析顯示。
認證需求:AdminJwtAuthGuard(後台管理員 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Body | userId | number | 否 | 目標用戶 ID(不傳=全站通知) | 123 |
| Body | title | object | 是 | 通知標題(多語系 JSON) | {"zh-TW":"系統維護通知","en-US":"Maintenance Notice"} |
| Body | content | object | 是 | 通知內容(多語系 HTML) | {"zh-TW":"<p>今晚維護</p>","en-US":"<p>Maintenance tonight</p>"} |
| Body | category | string | 是 | 分類:system / promo | "system" |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"id": 1,
"userId": null,
"title": { "zh-TW": "系統維護通知", "en-US": "System Maintenance Notice" },
"content": { "zh-TW": "<p>系統將於今晚維護</p>", "en-US": "<p>System maintenance tonight</p>" },
"category": "system",
"createdAt": "2026-02-24T00:00:00.000Z"
}
}業務邏輯說明
userId=null時為全站廣播,所有用戶信箱均可見userId指定時為個人通知,僅該用戶可見- 回傳原始多語系 JSON(Admin 看到全部語系版本)
前端注意事項 / UI 設計提示
- 後台發送表單需提供 5 個語系的輸入欄位(zh-TW, en-US, zh-CN, th-TH, vi-VN)
- 內容欄位使用 HTML 編輯器(Tiptap)
- 發送對象:下拉選單可選「全站廣播」或指定用戶 ID
GET /api/inbox/admin/list — [Admin] 通知列表
功能說明 後台管理員查看所有已發送的通知列表。支援分頁、分類篩選、範圍篩選、關鍵字搜尋(用戶 ID)、日期範圍篩選、多站點篩選。回傳原始多語系 JSON。
認證需求:AdminJwtAuthGuard(後台管理員 JWT)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| Query | page | number | 否 | 頁碼 |
| Query | pageSize | number | 否 | 每頁筆數 |
| Query | category | string | 否 | 分類:system / promo |
| Query | scope | string | 否 | 範圍:personal / system |
| Query | keyword | string | 否 | 搜尋用戶 ID |
| Query | startDate | string | 否 | 開始日期 |
| Query | endDate | string | 否 | 結束日期 |
| Header | x-site-code | string | 否 | 站點代碼(多站點篩選) |
DELETE /api/inbox/admin/:id — [Admin] 刪除通知
認證需求:AdminJwtAuthGuard
錯誤碼:2001 查無此通知
Response 範例
{ "code": 200, "message": "ok", "result": null }SiteConfig 站點配置模組
路由前綴:
/api/site-config管理白牌平台的站點設定,包含站點基本資訊、主題色系、域名素材(logo/favicon)、客服管道、吉祥物等。 公開端點供前台取得當前站點設定;Admin 端點供後台管理多站點配置。
GET /api/site-config — 取得當前站點設定(公開)
功能說明 公開 API,不需登入。根據環境變數 SITE_CODE 或 request header 回傳當前站點的完整設定,包含當前選中主題的完整色號體系。前台 App 啟動時呼叫此端點取得站點配置、主題色彩、可用主題列表等資訊。
認證需求:無(公開端點)
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"siteCode": "C9",
"siteName": "C9",
"siteDescription": "最佳線上娛樂平台",
"supportedLocales": ["zh-TW", "en-US"],
"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": "翡翠綠" }
]
}
}錯誤碼:2001 查無此站點設定
業務邏輯說明
activeTheme為當前啟用主題的完整色號,前台用 CSS 變數注入availableThemes為所有可用主題(用戶可在前台切換)supportedLocales決定前台顯示的語系切換選項- 多語系欄位(
siteName、siteDescription)依 request 語系解析
前端注意事項 / UI 設計提示
- App 啟動時呼叫,將主題色號注入 CSS 變數
- 主題色號對應 30+ CSS custom properties(primary-base, surface-page 等)
availableThemes用於主題切換下拉選單
GET /api/site-config/admin/list — [Admin] 取得所有站點設定
功能說明 取得所有站點的設定列表(含主題),回傳原始多語系 JSON。後台站點管理頁面與 SiteFilterInitializer 啟動時呼叫。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:read 權限)
Response 範例
{
"code": 200,
"message": "ok",
"result": [
{
"id": 1,
"siteCode": "C9",
"prefix": "a1",
"siteName": { "zh-TW": "C9 娛樂城", "en-US": "C9 Casino" },
"siteDescription": { "zh-TW": "最佳線上娛樂平台", "en-US": "Best Online Entertainment" },
"supportedLocales": ["zh-TW", "en-US"],
"activeThemeId": 1,
"enabled": 1,
"themes": [...]
}
]
}POST /api/site-config/admin — [Admin] 新增站點設定
功能說明 建立新的站點設定。siteCode 與 prefix 必須唯一。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| Body | siteCode | string | 是 | 站點代碼(唯一,最長 30 碼) | "C9" |
| Body | prefix | string | 是 | 白牌前綴(唯一,對應 R2 路徑) | "a1" |
| Body | layout | string | 否 | 前台模板代碼(預設 a1) | "a1" |
| Body | siteName | object | 是 | 站點名稱(多語系 JSON) | {"zh-TW":"C9 娛樂城"} |
| Body | siteDescription | object | 否 | 站點介紹(多語系 JSON) | |
| Body | supportedLocales | string[] | 否 | 支援語系 | ["zh-TW","en-US"] |
錯誤碼:2004 站點代碼或前綴已存在
PATCH /api/site-config/admin/:id — [Admin] 更新站點設定
功能說明 更新指定站點的設定。此端點支援更新站點的所有配置項目,包含基本資訊、域名設置、客服配置、三方登入、遊戲商配置、服務商配置、佈局配置(底部導航、頁尾、了解更多)等。所有欄位均為 optional,只傳需要更新的欄位。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
Request(常用欄位)
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| URL Param | id | number | 是 | 站點設定 ID |
| Body | prefix | string | 否 | 白牌前綴 |
| Body | layout | string | 否 | 前台模板代碼 |
| Body | siteName | object | 否 | 站點名稱(多語系) |
| Body | siteDescription | object | 否 | 站點介紹(多語系) |
| Body | supportedLocales | string[] | 否 | 支援語系 |
| Body | activeThemeId | number | 否 | 當前使用的主題 ID |
| Body | enabled | number | 否 | 是否啟用(0=關 1=開) |
| Body | agentTourEnabled | number | 否 | 代理導覽功能開關 |
| Body | agentTourIntervalSec | number | 否 | 代理導覽重新提醒間隔(秒) |
| Body | depositMethods | object | 否 | 前台存款通路開關(fiat/credit/crypto) |
| Body | domains | array | 否 | 域名設置(含 logo/favicon/支援語系) |
| Body | bottomBarEnabled | number | 否 | 下導列功能開關 |
| Body | bottomBarConfig | object | 否 | 下導列配置(mobile/desktop) |
| Body | footerConfig | array | 否 | 頁腳配置 |
| Body | learnMoreConfig | array | 否 | 站點介紹 FAQ 配置 |
| Body | customerServiceConfig | object | 否 | 客服設置(8 種管道 + LiveChat) |
| Body | oauthProviders | object | 否 | 三方登入設定(Google/Telegram/白名單) |
| Body | gameProviders | object | 否 | 遊戲商設定(RSG/BetSolutions/LiveSports) |
| Body | notificationConfig | object | 否 | 自動通知設定 |
| Body | templateVariables | array | 否 | 模板變數 |
| Body | serviceProviders | object | 否 | 服務商設定(Resend/Twilio/R2) |
錯誤碼:2001 查無此站點設定
DELETE /api/site-config/admin/:id — [Admin] 刪除站點設定
功能說明 刪除站點及其所有關聯主題(cascade 刪除)。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
錯誤碼:2001 查無此站點設定
GET /api/site-config/admin/:siteConfigId/themes — [Admin] 取得指定站點的主題列表
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:read)
POST /api/site-config/admin/:siteConfigId/themes — [Admin] 新增主題
功能說明 為指定站點建立新主題。包含完整色號體系:主色系(primary)、強調色(accent)、表面色(surface)、文字色(text)、邊框色(border)。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| URL Param | siteConfigId | number | 是 | 站點設定 ID | 1 |
| Body | themeId | string | 是 | 主題識別碼(最長 50 碼) | "default-emerald" |
| Body | themeName | object | 是 | 主題名稱(多語系) | {"zh-TW":"翡翠綠","en-US":"Emerald"} |
| Body | primary | object | 是 | 主色系 | {"base":"#34d399","dark":"#059669","light":"#6ee7b7","glow":"16,185,129"} |
| Body | accent | object | 是 | 強調色 | {"gold":"#fbbf24","info":"#38bdf8","violet":"#a78bfa","cyan":"#22d3ee","error":"#fb7185"} |
| Body | surface | object | 是 | 表面色 | {"page":"#0f172a","navbar":"#1e293b","card":"#131f30","modal":"#0c1a2e","sidebar":["#0d1b2a","#0a1628","#071020"]} |
| Body | text | object | 是 | 文字色 | {"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)"} |
| Body | border | object | 是 | 邊框色 | {"subtle":"rgba(255,255,255,0.06)","default":"rgba(255,255,255,0.1)","strong":"rgba(255,255,255,0.15)"} |
| Body | enabled | number | 否 | 是否啟用(0=關 1=開,預設 1) | 1 |
錯誤碼:2001 查無此站點設定
PATCH /api/site-config/admin/themes/:id — [Admin] 更新主題
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
所有欄位為 optional(PartialType(CreateSiteThemeDto)),只傳需更新的欄位。
錯誤碼:2002 查無此主題
DELETE /api/site-config/admin/themes/:id — [Admin] 刪除主題
功能說明 刪除指定主題。若刪除的是當前 activeTheme(站點正在使用的主題),會自動清除 activeThemeId 欄位。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
錯誤碼:2002 查無此主題
POST /api/site-config/admin/:id/domain-asset — [Admin] 上傳域名資源
功能說明 上傳指定站點的域名資源(logo / favicon)。支援 logoSmall(小 logo)、logoBig(大 logo)、favicon 三種資源類型。圖片上傳至 Cloudflare R2,並更新對應域名的資源 URL。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 | 範例 |
|---|---|---|---|---|---|
| URL Param | id | number | 是 | 站點設定 ID | 1 |
| Body (FormData) | hostname | string | 是 | 域名 | "example.com" |
| Body (FormData) | assetType | string | 是 | 資源類型 | "logoSmall" / "logoBig" / "favicon" |
| Body (FormData) | file | File | 是 | 圖片檔案(image/*,最大 5MB) |
錯誤碼:2001 查無此站點設定
POST /api/site-config/admin/:id/customer-service-icon — [Admin] 上傳客服管道圖示
功能說明 上傳客服管道的自訂圖示。圖片上傳至 R2,回傳 R2 key 供後續設定使用。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| URL Param | id | number | 是 | 站點設定 ID |
| Body (FormData) | file | File | 是 | 圖示圖片(image/*,最大 2MB) |
錯誤碼:2001 查無此站點設定
前端注意事項 / UI 設計提示
- 客服圖示建議尺寸 64x64 或 128x128 px
- 最大 2MB(比其他上傳的 5MB 限制更小)
PATCH /api/site-config/admin/:siteConfigId/mascots — [Admin] 更新吉祥物列表
功能說明 整包替換指定站點的吉祥物清單。傳入完整的吉祥物陣列,會覆蓋原有的吉祥物列表。吉祥物圖片需先上傳至 R2,再將 URL 傳入此端點。
認證需求:AdminJwtAuthGuard + PermissionsGuard(需 site-config:write)
Request
| 位置 | 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
| URL Param | siteConfigId | number | 是 | 站點設定 ID |
| Body | mascots | array | 是 | 吉祥物清單 |
| Body | mascots[].id | string | 是 | 吉祥物識別碼 |
| Body | mascots[].label | string | 是 | 吉祥物名稱 |
| Body | mascots[].url | string | 是 | 吉祥物圖片 R2 URL |
Response 範例
{
"code": 200,
"message": "ok",
"result": {
"mascots": [
{
"id": "c9-dragon",
"label": "翡翠龍",
"url": "https://pub-xxx.r2.dev/a1/avatars/mascots/c9-dragon.png"
}
]
}
}錯誤碼:2001 查無此站點設定
前端注意事項 / UI 設計提示
- 吉祥物管理頁面:拖拽排序 + 新增/刪除
- 圖片先透過 R2 上傳端點取得 URL,再傳入此 API
- 用於前台用戶頭像選擇(非上傳自訂頭像的替代方案)
S2S 伺服器回調(不公開)
遊戲商回調端點
以下端點為遊戲商(BetSolutions、RSG)的 Server-to-Server 回調端點,不包含在 Swagger API 文件中(使用 @ApiExcludeEndpoint() 裝飾器隱藏)。這些端點由遊戲商伺服器直接呼叫,處理遊戲內的下注、派彩、回滾等操作。
BetSolutions S2S 回調
- 路由前綴:
/api/game/betsolutions/ - 功能:處理 BetSolutions 遊戲商的錢包操作回調
- 包含:認證(Auth)、下注(Bet)、派彩(Win)、回滾(Rollback)等端點
- 錢包模式:轉帳錢包(Transfer Wallet),每次操作直接更新用戶餘額
RSG S2S 回調
- 路由前綴:
/api/game/rsg/ - 功能:處理 RSG 遊戲商的錢包操作回調
- 特殊:使用 DES 加解密(
desKey+desIv)保護通訊安全 - 包含:驗證(Verify)、下注(Debit)、派彩(Credit)、取消(Cancel)等端點
設計原則:
- 不對外暴露在 Swagger 文件中,避免外部誤呼叫
- 每個回調端點都有嚴格的簽章/加密驗證機制
- 下注結算完成後自動觸發連鎖操作:VIP 等級重算、活動打碼量累計、任務投注進度更新
- 所有金額操作使用
decimal(18,6)精度,配合truncateUsd()無條件捨去