實作錢包回調
這通常是整個整合裡工作量最大的一部分。其他所有事情——簽名一次啟動呼叫、嵌入一個 iframe、監聽 shell 事件——都只是幾行程式碼的事。而這裡是你把平台接到自己真正的玩家帳本上的地方。
Moose 遊戲平台從不持有玩家資金。你的錢包才是唯一的真相來源,平台的職責是即時呼叫你實作的兩個介面,讓每一筆下注和派彩都恰好一次地落到你的錢包上。本指南所依據的欄位級契約,見錢包回調介面參考。
兩個介面
// POST /v1/wallet/transaction —— 結算一筆 BET、WIN、ROLLBACK 或 ADJUSTMENT
// GET /v1/wallet/balance?playerRef=<ref> —— 回答一次餘額查詢兩者都由平台呼叫,並用你的共用密鑰簽名——在信任任何一次呼叫之前先驗證它(見簽名與身份驗證:校驗平台的簽名(錢包回調))。平台會把呼叫送到哪個基礎網址,由你透過租戶的自助入口網站註冊——見註冊你的錢包回調 URL。
處理每一種交易類型
async function handleTransaction(req: TransactionRequest): Promise<TransactionResponse> {
const cached = await ledger.getCachedResponse(req.transactionId)
if (cached) return cached // 冪等重放 —— 見下文,永遠排在其他任何邏輯之前
let result: TransactionResponse
switch (req.type) {
case 'BET':
result = await ledger.debit(req.playerRef, req.amount, req.currency)
// 如果玩家餘額不足,result.status 在這裡會是 'DECLINED' ——
// 這是正常的業務結果,不是錯誤
break
case 'WIN':
result = await ledger.credit(req.playerRef, req.amount, req.currency)
// 這裡永遠不會是 DECLINED —— 真正的失敗應該拋出例外,而不是回傳 DECLINED
break
case 'ROLLBACK':
const original = await ledger.getTransaction(req.originalTransactionId!)
result = await ledger.credit(req.playerRef, original.amount, req.currency)
break
case 'ADJUSTMENT':
result = req.direction === 'CREDIT'
? await ledger.credit(req.playerRef, req.amount, req.currency)
: await ledger.debit(req.playerRef, req.amount, req.currency)
break
}
await ledger.cacheResponse(req.transactionId, result)
return result
}BET是唯一一種DECLINED屬於合法回應的類型——餘額不足,或超出你自己設定的下注限額。其餘類型(WIN、ROLLBACK、ADJUSTMENT)必須要嘛以OK成功,要嘛讓這次 HTTP 呼叫直接失敗(非200)——絕不能回傳DECLINED。WIN是和它的BET相互獨立的一筆交易。 不要等一筆WIN來核銷一筆還沒結束的BET——它們是各自獨立到達的呼叫,兩者中帶有roundComplete: true的那一筆才是這一局的最後一筆。ROLLBACK透過originalTransactionId沖正某一筆特定的BET——從你自己的帳本查出那筆下注的金額並退款回去。你也會看到一種兜底回滾,其sessionToken是"system:stale-round-reconciler",代表玩家客戶端斷線後平台強制關閉了這一局——按同樣的方式處理即可。ADJUSTMENT很少見,由平台管理員發起。direction告訴你餘額該往哪個方向調整。
冪等性是強制要求
平台會用完全相同的 transactionId 重試失敗或逾時的呼叫。如果你的處理器在重試時重新套用一次餘額變動,玩家就會被重複扣款或重複派彩。在做任何帳本操作之前,先為你處理過的每一個 transactionId 快取回應,並在收到重複請求時原樣回放——上面的偽代碼之所以第一步就做這個檢查,正是這個原因。
一個安全的模式:把 transactionId 設成帳本資料表上的唯一約束,讓插入時的重複鍵錯誤成為訊號,去查出並回傳快取的回應,而不是再寫一次。
查詢餘額
async function handleBalance(playerRef: string): Promise<{ balance: number }> {
return { balance: await ledger.getBalance(playerRef) }
}請讓它保持快速且可靠——它會被遊戲廠商(透過平台)即時呼叫用來顯示最新餘額,而不只是一個背景檢查。雙方呼叫方的細節見錢包回調介面參考:GET /v1/wallet/balance。
Jackpot 與其他 metadata
TransactionRequest.metadata 是一個不透明的 JSON 物件,平台只會儲存並原樣轉發給你——它從不校驗或解讀其內容。最常見的情況是 jackpot WIN 攜帶著獎池/等級/金額等細節:
if (req.metadata?.jackpot?.won) {
// 展示或記錄 jackpot 細節;你帳本裡的 amount 欄位依然是
// 權威的派彩金額 —— metadata 只是補充資訊,不是「該入帳多少」
// 的第二個真相來源
}精確(且不被強制校驗)的結構,見資料模型與列舉:Metadata 與 jackpot 派彩。
在你的逾時內回應
平台會等待你回應一段有界的時間(預設 5000ms——見環境與基礎網址:註冊你的錢包回調 URL),超過就會把這次呼叫視為 TIMED_OUT,並重試或之後透過補償機制解決。只要你的冪等性處理正確,偶爾一次慢回應觸發重試並無大礙——但如果你的錢包後端持續偏慢,就意味著更多重試、更多補償流量,以及更差的玩家體驗(因為遊戲客戶端也在等待同一個往返)。