ThreeKingChess 商戶串接 API (1.0.0)

Download OpenAPI specification:

License: Proprietary

供娛樂城(商戶)後端串接的 HTTP API。涵蓋玩家建立、入金 / 出金、開遊戲、 拉注單、踢出與報表。所有金額以商戶幣別計(Phase 1 多為 TWD)。

轉帳錢包模式:玩家餘額存在本平台。商戶以 充值 / 提現 將額度搬進 / 搬出本平台。

串接流程

  1. 建立玩家:首次以 POST /v1/player/login 帶入商戶端玩家 ID(player_id),平台即建立該玩家與錢包。
  2. 入金POST /v1/wallet/deposit 將額度搬進玩家錢包(每筆給唯一 transaction_id)。
  3. 開遊戲POST /v1/game/launch-url 取得帶 JWT 的 launch_url,以 iframe 嵌入整頁導向讓玩家進場。
  4. 查詢GET /v1/player/balance 查餘額、POST /v1/game/bet-records 拉注單。
  5. 出金 / 踢出POST /v1/wallet/withdraw 出金、POST /v1/player/kick 踢出在線玩家。

安全機制

每個請求皆須帶三個標頭:

標頭 內容
X-API-Key 商戶 API 識別碼(非機密)
X-Timestamp 當前 Unix 秒;伺服器接受 ±300 秒窗口
X-Signature hex( HMAC-SHA256( body + timestamp, api_secret ) )
  • 簽章字串 = 原始 request body 字串 直接接上 timestamp 字串(GET 無 body 時 body 為空字串)。
  • api_secret 只存在商戶後端,絕不可進前端、瀏覽器或 URL。
  • IP 白名單:商戶可設定允許呼叫的來源 IP;未列入者一律 403
  • 重放防護:同一個 X-Signature 在 600 秒內只能用一次。金流端點(deposit/withdraw) 以 transaction_id 自身做冪等,合法重送同簽章不會被誤擋。

⚠️ 最常見的簽章錯誤:簽章用的 body 必須與實際送出的 HTTP body 位元組完全一致。 請先把 body 序列化成字串,同一份字串既拿去簽章也拿去送(切勿序列化兩次,兩次結果可能不同)。

🔧 線上簽章驗證工具signature-tool.html —— 貼上 secret / body / timestamp, 在瀏覽器本機算出 X-Signature 並與你後端產出的比對,快速定位不符問題(secret 不會送出)。

簽章範例

Node.js

import crypto from 'node:crypto';
const sign = (body, ts, secret) =>
  crypto.createHmac('sha256', secret).update(body + ts).digest('hex');

const body = JSON.stringify({ player_id: 'p001', amount: 10000, transaction_id: 'dep_1' });
const ts = Math.floor(Date.now() / 1000).toString();
const headers = {
  'X-API-Key': API_KEY,
  'X-Timestamp': ts,
  'X-Signature': sign(body, ts, API_SECRET),
  'Content-Type': 'application/json',
};
// 送出時用同一個 body 變數:fetch(url, { method: 'POST', headers, body })

PHP

<?php
function sign(string $body, string $ts, string $secret): string {
    return hash_hmac('sha256', $body . $ts, $secret);
}
$body = json_encode(['player_id' => 'p001', 'amount' => 10000, 'transaction_id' => 'dep_1']);
$ts   = (string) time();
$sig  = sign($body, $ts, $apiSecret);

$ch = curl_init("$base/v1/wallet/deposit");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        "X-API-Key: $apiKey",
        "X-Timestamp: $ts",
        "X-Signature: $sig",
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => $body,   // 與簽章同一份字串
    CURLOPT_RETURNTRANSFER => true,
]);
$resp = curl_exec($ch);

Golang

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "time"
)

func sign(body, ts, secret string) string {
    m := hmac.New(sha256.New, []byte(secret))
    m.Write([]byte(body + ts))
    return hex.EncodeToString(m.Sum(nil))
}

body := `{"player_id":"p001","amount":10000,"transaction_id":"dep_1"}`
ts := fmt.Sprintf("%d", time.Now().Unix())
sig := sign(body, ts, apiSecret)
// 設 header:X-API-Key / X-Timestamp=ts / X-Signature=sig;送出時用同一份 body 位元組

bash + openssl

sign() { printf '%s%s' "$1" "$2" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //'; }
ts=$(date +%s); sig=$(sign "$BODY" "$ts")
curl -X POST "$BASE/v1/wallet/deposit" \
  -H "X-API-Key: $KEY" -H "X-Timestamp: $ts" -H "X-Signature: $sig" \
  -H "Content-Type: application/json" -d "$BODY"

錯誤代碼

錯誤回應格式:{ "success": false, "code": <http_status>, "message": "<說明>" }

HTTP 情境
400 缺必填欄位 / 金額非正數(如 player_id is requiredamount must be positive
401 invalid signature(簽錯)/ timestamp expired(時戳超窗)/ duplicate request(重放)
403 IP not in whitelist(來源 IP 未列入白名單)
404 操作 / 踢出不存在或跨商戶玩家(不洩漏存在性)
405 HTTP 方法不允許
500 伺服器內部錯誤
502 上游遊戲伺服器無法連線(踢出時)

版本異動記錄

版本 日期 異動
1.0.0 2026-06-28 首次發佈:玩家 / 錢包 / 遊戲 / 報表 / 系統共 12 端點。新增拉注單、踢出。

玩家帳戶

玩家建立與餘額查詢。

創建 / 登入玩家帳號

首次帶某 player_id 呼叫即在本平台建立該玩家(含錢包)。冪等:重複呼叫回同一內部 ID。

Authorizations:
(ApiKeyTimestampSignature)
Request Body schema: application/json
required
player_id
required
string

商戶端玩家 ID(external,由商戶自訂;同商戶內唯一)

nickname
string

玩家暱稱(顯示用;可省略)

Responses

Request samples

Content type
application/json
{
  • "player_id": "p001",
  • "nickname": "玩家暱稱"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "internal_player_id": 229
}

查詢玩家餘額

以商戶端玩家 ID 查詢餘額。available = balance − frozen。查無此玩家回 0(不建立資料)。

Authorizations:
(ApiKeyTimestampSignature)
query Parameters
player_id
required
string
Example: player_id=p001

Responses

Response samples

Content type
application/json
{
  • "balance": 7000,
  • "frozen": 0,
  • "available": 7000,
  • "currency": "TWD"
}

剔除在線會員(踢出遊戲)

踢出自家玩家並使其當前 session 失效。

  • 玩家在線 → 立刻斷線、登出;其 client 自動重連會被擋(session 已失效)。
  • 再入場:須由商戶重新呼叫 獲取遊戲登錄地址 取得新 launch_url
  • kicked=false 表玩家本就不在線(冪等成功)。
  • 房內進行中的牌局由斷線托管流程安全收尾,不破壞當手結算。
  • 查無此玩家 → 404(不洩漏跨商戶存在性)。
Authorizations:
(ApiKeyTimestampSignature)
Request Body schema: application/json
required
player_id
required
string

商戶端玩家 ID(external)

reason
string

踢出原因(記錄用;可省略)

Responses

Request samples

Content type
application/json
{
  • "player_id": "p001",
  • "reason": "compliance"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "kicked": true
}

錢包

入金 / 出金(轉帳錢包)。

玩家充值(入金)

將額度從商戶搬進本平台玩家錢包。transaction_id 為商戶側唯一交易號(冪等鍵)。 同 transaction_id 重送(含併發 / 逾時重試)一律回原結果(200),不會重複入帳。

Authorizations:
(ApiKeyTimestampSignature)
Request Body schema: application/json
required
player_id
required
string

商戶端玩家 ID(external)

amount
required
number

金額(正數;最多 2 位小數;上限 10000000)。非正數回 400。

transaction_id
required
string

商戶側唯一交易號(冪等鍵)。同值重送回原結果,不重複入帳/扣款。

Responses

Request samples

Content type
application/json
{
  • "player_id": "p001",
  • "amount": 10000,
  • "transaction_id": "dep_p001_001"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "internal_transaction_id": 178,
  • "balance_after": 10000
}

玩家提現(出金)

將額度從本平台玩家錢包搬回商戶。餘額不足回 400 insufficient balance。同 transaction_id 冪等。

Authorizations:
(ApiKeyTimestampSignature)
Request Body schema: application/json
required
player_id
required
string

商戶端玩家 ID(external)

amount
required
number

金額(正數;最多 2 位小數;上限 10000000)。非正數回 400。

transaction_id
required
string

商戶側唯一交易號(冪等鍵)。同值重送回原結果,不重複入帳/扣款。

Responses

Request samples

Content type
application/json
{
  • "player_id": "p001",
  • "amount": 3000,
  • "transaction_id": "wd_p001_001"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "internal_transaction_id": 179,
  • "balance_after": 7000
}

遊戲

開遊戲、拉注單。

獲取遊戲登錄地址

回傳帶 JWT 的 launch_url。商戶可 iframe 嵌入整頁導向讓玩家進場。

  • 落點game_type 決定:不帶 → 選遊戲大廳;帶值 → 直接進該遊戲(詳見 game_type 欄位)。
  • 開放哪些遊戲由商戶設定 enabled_games 決定(後台管理),非每次 launch 帶入。
  • JWT 內含 internal player_id、merchant_id、external_id、game_type(落點)、session 起始、jti。
  • return_url 簽進 JWT(前端只信 token 內值,防釣魚轉址)。
  • 踢出後再入場:須重新呼叫本端點取得新 launch_url(舊 token 已失效)。
Authorizations:
(ApiKeyTimestampSignature)
Request Body schema: application/json
required
player_id
required
string

商戶端玩家 ID(external)

nickname
string

玩家暱稱(顯示用)

game_type
string
Enum: "texas" "mahjong" "bigtwo" "thirteen" "fantan" "darkchess"

選填 — 決定玩家進場「落點」(非用來控制商戶開放哪些遊戲;開放清單見商戶設定 enabled_games)。

  • 不帶 → 進入「選遊戲大廳」:玩家看到本商戶開放的遊戲(由 enabled_games 決定)自行選擇, 選定後進入該遊戲的分區(如德州的低/中/高額場)。目前僅開放德州,故等同進入德州大廳。
  • 帶值(如 texas)→ 等於玩家已選好該遊戲,直接進入該遊戲(仍有分區選擇),不顯示其他遊戲入口。 值必須在商戶 enabled_games 內,否則回 400。

可用值(enum):

  • texas — 德州撲克
  • mahjong — 麻將
  • bigtwo — 大老二
  • thirteen — 十三張
  • fantan — 排七
  • darkchess — 暗棋
return_url
string

玩家退出遊戲後導回娛樂城的網址(簽入 JWT,前端只信 token 內值,防釣魚轉址)

lang
string

語系(如 zh-TW / en);空則前端用預設

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "success": true,
  • "launch_url": "https://play.example.com/?token=eyJhbGciOiJIUzI1Ni... ",
  • "token": "eyJhbGciOiJIUzI1Ni...",
  • "expires_at": 1782593360,
  • "session_expires_at": 1782611360
}

查詢玩家下注記錄(拉注單)

查自家玩家逐手注單(投注 / 輸贏 / 抽水 / 有效投注)。

  • player_id 留空 = 該商戶全部玩家。
  • page_size 預設 20、上限 100;排序 created_at DESC(最新在前)。
  • 同一局所有座位 net_amount 加總 + 抽水 = 0(零和)。
  • 查無此玩家 / 無注單 → 回空頁(不建立資料)。
Authorizations:
(ApiKeyTimestampSignature)
Request Body schema: application/json
required
player_id
string

商戶端玩家 ID;留空 = 該商戶全部玩家

start_time
integer <int64>

起始時間 Unix 秒(含);0 = 不限

end_time
integer <int64>

結束時間 Unix 秒(含);0 = 不限

page
integer
Default: 1

頁碼(1 起)

page_size
integer <= 100
Default: 20

每頁筆數(預設 20,上限 100)

Responses

Request samples

Content type
application/json
{
  • "player_id": "p001",
  • "start_time": 0,
  • "end_time": 0,
  • "page": 1,
  • "page_size": 20
}

Response samples

Content type
application/json
{
  • "success": true,
  • "total": 72,
  • "page": 1,
  • "page_size": 5,
  • "records": [
    ]
}

報表

統計報表(日 / 週 / 月 / 即時)。

查詢每日統計

依日期區間查詢每日統計。data 為日報陣列;無資料時為空陣列。金額欄位為字串(保留精度)。

Authorizations:
(ApiKeyTimestampSignature)
query Parameters
start_date
required
string <date>
Example: start_date=2026-06-01

起始日期(含),格式 YYYY-MM-DD

end_date
required
string <date>
Example: end_date=2026-06-28

結束日期(含),格式 YYYY-MM-DD

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

查詢每週統計

查詢指定年度的每週統計。data 為週報陣列。

Authorizations:
(ApiKeyTimestampSignature)
query Parameters
year
required
integer
Example: year=2026

年度(西元年)

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

查詢每月統計

查詢指定年度的每月統計。data 為月報陣列。

Authorizations:
(ApiKeyTimestampSignature)
query Parameters
year
required
integer
Example: year=2026

年度(西元年)

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

查詢即時統計

查詢指定日期的逐小時即時統計。date 必填(未帶回 400 date is required)。data 為各小時統計陣列。

Authorizations:
(ApiKeyTimestampSignature)
query Parameters
date
required
string <date>
Example: date=2026-06-28

日期(必填),格式 YYYY-MM-DD

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

系統

健康檢查。

健康檢查

無須認證。回 200 表服務正常。

Responses

Response samples

Content type
application/json
{
  • "status": "ok"
}