SuperTerminal 文件

支付代理對接文檔

接入 SuperTerminal 的支付代理規格。無 API key;必須實作詢價。公開頁面,可列印 PDF。

快速開始

這份規格給支付代理實作與 SuperTerminal 的對接。若只做 sessions + 回調、漏掉詢價 API,對客平台的 quote 會 502。

你沒有 X-Api-Key。Terminal 打你、你打 Terminal,都只有 X-Timestamp + X-Signature(sessions 另有 Idempotency-Key)。

禁止直連對客平台。不要打對客平台的 callback_url。對方也不該打你的下單 API。GoCash 只出現在:你用自己的 GoCash partner 金鑰購券/redeem。

環境

名稱位置型別必填範例
你的端點(每個支付方式一個)gocashorigin是GoCash 主控台 自己 → 對接端點 登記,wechat / visa 可以是不同主機
TERMINALenvorigin是SuperTerminal 公開主機,回調打這裡
CASHIER_HMACenvstring是GoCash 自己 → 對接端點 產生的 Terminal HMAC(gocash-agent 為 SPARK_HMAC_SECRET)
GoCash partner keygocashstring是只打 https://gocash.w8et.com

amountMinor

最小貨幣單位整數。兩位小數幣別時 50000 = 500.00。

你必須實作的入站 API(Terminal → 你)

  • POST /api/internal/sessions
  • POST /api/internal/sessions/cancel
  • POST /api/internal/deposit-quote(漏做 → 對客平台 deposit-quote 502)
  • POST /api/internal/withdraw-quote

你必須打的出站回調(你 → SuperTerminal)

Host 是 TERMINAL,不是對客平台網域。路徑二選一,簽名 path 必須等於實際請求的 pathname:

  • 建議:/api/v1/cashier/callbacks/{kind}
  • 相容 pathname(歷史別名):/api/internal/gocash/{kind}

簽 A 路徑、打 B 路徑會 401。

兩套金鑰,不要混用

名稱說明
SuperTerminal 支付代理 HMAC在 GoCash 自己 → 對接端點 產生。用來驗 Terminal 打你的 sessions/quote,以及你回調 Terminal。沒有 API key。
GoCash partner key / secretGoCash 目錄夥伴憑證。只打 gocash.w8et.com 購券、redeem。不能拿來簽 SuperTerminal。
用 GoCash secret 簽 Terminal 回調、或用支付代理 HMAC 打 GoCash,都會 401。兩套鑰匙分開放。

上線檢查清單

discover 列出你,必須同時滿足:

  1. 已是 GoCash 目錄夥伴:在 Cashier 分頁登記 payment_methods(WeChat/Visa 等)、merchant/customer 金鑰、幣別與額度。未登記的方式,SuperTerminal 營運無法指派。
  2. 在 GoCash 自己 → 對接端點 登記每個支付方式的 URL,並產生 Terminal HMAC(密文只顯示一次,設定到你的服務;gocash-agent 為 SPARK_HMAC_SECRET)。SuperTerminal 每分鐘同步,不需在 SuperTerminal 登記任何 URL。
  3. 至少一個對客平台把你的特定支付方式指派到該接入方(不是整個代理一次全開)。
  4. 四條入站 API 與五條回調都已上線,且用支付代理 HMAC 互簽。
  5. 對客平台 discover 出現你的通道;deposit-quote 不再 502。

只接 sessions、不接 quote,資格看起來「半套」,對客平台詢價會失敗。

錯誤格式

SuperTerminal 失敗回應:

{
  "error": {
    "code": "AUTH_BAD_SIGNATURE",
    "message": "Invalid HMAC"
  }
}

讀 error.code。你回給 Terminal 的 sessions/quote 也建議用同一形狀;非 2xx 或沒有有效 url 會觸發 failover。

HMAC 簽名規範

timestamp\\nMETHOD\\npath\\nsha256(rawBody),HMAC-SHA256 hex,±300 秒。

path = 實際 pathname,不含 query。Terminal 打你時 path 是 /api/internal/sessions、/api/internal/deposit-quote 等。你打 Terminal 時 path 必須是你請求的那一條(/api/v1/cashier/callbacks/voucher-deposit 或 /api/internal/gocash/voucher-deposit)。

raw body 位元組必須一致。沒有 X-Api-Key。

通道資格來源

Discover 是否列出你,取決於 GoCash 目錄同步的 payment_methods、currencies、額度、金鑰旗標、該方式是否有端點,以及對客平台是否被指派你的該支付方式。沒有端點的方式永遠不會出現。主控台 cashier_channels 表不影響 discover。請在 GoCash 營運的 Cashier 分頁登記方式,再到自己 → 對接端點 填 URL。

POST {endpoint[paymentMethod]}/api/internal/sessions

概覽

Terminal 在 checkout 時即時向你要付款連結。付款 URL 不是預先註冊,而是這次請求動態回傳。

請求參數

名稱位置型別必填範例
X-Timestampheaderunix seconds是1700000000
X-Signatureheaderhex HMAC-SHA256是a1b2…
Idempotency-Keyheaderstring是att_…(attempt id)
sparkSessionIdbodystring是att_…(Terminal attempt id)
sparkUserIdbodystring是player 或 order id
typebody"deposit" | "withdraw"是deposit
amountMinorbodyint64是50000
currencybodystring是CNY
paymentMethodbodystring是wechat
localebodystring否zh-Hant
gocashPartnerIdbodystring是GoCash partner id
feeBearerbody"user" | "client"是client
platformClientCodebodystring是acme

呼叫範例(你收到的 body)

{
  "sparkSessionId": "att_…",
  "sparkUserId": "player-or-order-ref",
  "type": "deposit",
  "amountMinor": 50000,
  "currency": "CNY",
  "paymentMethod": "wechat",
  "locale": "zh-Hant",
  "gocashPartnerId": "partner-id",
  "feeBearer": "client",
  "platformClientCode": "acme"
}

成功回應

{ "id": "your-session-id", "url": "https://you.example/pay/…" }

回應欄位

名稱說明
url必須是可開啟的 http(s)。無效 URL 會觸發 failover。
id可選,Terminal 記為 cashier_tx_id

錯誤

非 2xx 或沒有有效 url 時,Terminal 視此次 attempt 失敗並可能改派其他支付代理。

注意事項

  • 同一 sparkSessionId(Terminal attempt id)重試必須回同一付款意圖(Idempotency-Key = attempt id)。
  • 用 platformClientCode 區分不同對客平台,不要假設同一時間只有一個接入方。

POST {endpoint[paymentMethod]}/api/internal/sessions/cancel

概覽

對客平台放棄訂單或出款過期時,Terminal 盡力取消 session。

請求參數

名稱位置型別必填範例
X-Timestampheaderunix seconds是1700000000
X-Signatureheaderhex HMAC-SHA256是a1b2…
sparkSessionIdbodystring是att_…

呼叫範例

{ "sparkSessionId": "att_…" }

成功回應

{ "ok": true }

回應欄位

名稱說明
alreadyCompleted若已付款可回此旗標

錯誤

取消失敗只記事件,不會回滾對客平台的放棄。

注意事項

  • 若玩家其實已付款,仍應走 voucher-deposit;Terminal 會依 attempt 是否仍為現行 attempt 判斷 stale。

POST {endpoint[paymentMethod]}/api/internal/deposit-quote

概覽

對客平台打 Terminal /api/v1/client/deposit-quote 時,Terminal 會立刻轉打你這支 API。未實作會讓對方看到 CASHIER_QUOTE_FAILED 502。不建單、不開付款頁。

請求參數

名稱位置型別必填範例
X-Timestampheaderunix seconds是1700000000
X-Signatureheaderhex HMAC-SHA256是a1b2…
amountMinorbodyint64是50000
currencybodystring是CNY
gocashPartnerIdbodystring是目錄 partner id
feeBearerbody"user" | "client"是client
platformClientCodebodystring是acme

呼叫範例(你收到的 body)

{
  "amountMinor": 50000,
  "currency": "CNY",
  "gocashPartnerId": "partner-id",
  "feeBearer": "client",
  "platformClientCode": "acme"
}

成功回應

{
  "amountMinor": 50000,
  "totalPriceMinor": 50400,
  "handlingFeeMinor": 400,
  "receiveMinor": 50000,
  "currency": "CNY"
}

回應欄位

名稱說明
amountMinor券面額
totalPriceMinor玩家應付(含費)
handlingFeeMinor手續費
receiveMinor到帳面額

錯誤

非 2xx 時 Terminal 回給對客平台 CASHIER_QUOTE_FAILED。建議用 { error: { code, message } }。

注意事項

  • 用 gocashPartnerId 對應你的 GoCash 帳戶做試算,不要用支付代理 HMAC 打 GoCash。
  • 驗簽 path 固定為 /api/internal/deposit-quote。

POST {endpoint[paymentMethod]}/api/internal/withdraw-quote

概覽

出款詢價。請求欄位與 deposit-quote 相同。取款也依 checkout 選中的支付方式走到 endpoint[paymentMethod]。

請求參數

名稱位置型別必填範例
X-Timestampheaderunix seconds是1700000000
X-Signatureheaderhex HMAC-SHA256是a1b2…
amountMinorbodyint64是50000
currencybodystring是CNY
gocashPartnerIdbodystring是目錄 partner id
feeBearerbody"user" | "client"是client
platformClientCodebodystring是acme

呼叫範例

{
  "amountMinor": 50000,
  "currency": "CNY",
  "gocashPartnerId": "partner-id",
  "feeBearer": "client",
  "platformClientCode": "acme"
}

成功回應

{
  "amountMinor": 50000,
  "handlingFeeMinor": 400,
  "receiveMinor": 49600,
  "currency": "CNY"
}

回應欄位

名稱說明
amountMinor申請出款面額
receiveMinor玩家實收;若手續費超過面額應回錯而不是負數

錯誤

例如手續費大於金額可回 FEE_EXCEEDS_AMOUNT。Terminal 仍會包成對客平台側的 quote 失敗。

注意事項

  • 驗簽 path 為 /api/internal/withdraw-quote。

POST /api/v1/cashier/callbacks/voucher-deposit

概覽

玩家支付成功後,向 SuperTerminal 主機申請上分。相容 pathname(歷史別名):POST /api/internal/gocash/voucher-deposit。

請求參數

名稱位置型別必填範例
X-Timestampheaderunix seconds是1700000000
X-Signatureheaderhex HMAC-SHA256是a1b2…
sessionIdbodystring是att_…(即 sparkSessionId)
userIdbodystring否player
amountMinorbodyint64建議50000
currencybodystring否CNY
paymentMethodbodystring否wechat
voucherNobodystring建議GC-…
verifySecretbodystring建議XXXX-XXXX

呼叫範例(可直接跑;path 與簽名一致)

下列打相容 pathname。若改打 v1 路徑,四個分頁裡的 path 都要一起改。

const crypto = require("crypto");

function sign({ secret, timestamp, method, path, rawBody }) {
  const bodySha = crypto.createHash("sha256").update(rawBody ?? "", "utf8").digest("hex");
  const stringToSign = [timestamp, method.toUpperCase(), path, bodySha].join("\n");
  return crypto.createHmac("sha256", secret).update(stringToSign).digest("hex");
}

const TERMINAL = process.env.TERMINAL;
const CASHIER_HMAC = process.env.CASHIER_HMAC; // SuperTerminal 支付代理 HMAC,不要用 GoCash partner key
const path = "/api/internal/gocash/voucher-deposit";
const rawBody = "{\"sessionId\":\"att_EXAMPLE\",\"userId\":\"player-or-order-ref\",\"amountMinor\":50000,\"currency\":\"CNY\",\"paymentMethod\":\"wechat\",\"voucherNo\":\"GC-EXAMPLE\",\"verifySecret\":\"XXXX-XXXX-XXXX-XXXX\"}";
const timestamp = String(Math.floor(Date.now() / 1000));
const signature = sign({ secret: CASHIER_HMAC, timestamp, method: "POST", path, rawBody });

const res = await fetch(TERMINAL + path, {
  method: "POST",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "X-Timestamp": timestamp,
    "X-Signature": signature,
    // 不要帶 X-Api-Key
  },
  body: rawBody,
});
console.log(await res.text());

v1 等價:/api/v1/cashier/callbacks/voucher-deposit,body 相同,簽名 path 改成該 pathname。

成功回應

{ "ok": true }

回應欄位

名稱說明
ok含重複完成、stale、hold 都可能回 ok

錯誤

代碼HTTP訊息處理方式
VALIDATION_ERROR400sessionId required帶上 sparkSessionId(Terminal attempt id)。
ATTEMPT_NOT_FOUND404Unknown attemptsessionId 不是 att_… 或尚未建單。
AUTH_BAD_SIGNATURE401Invalid HMAC用支付代理 HMAC 簽實際 pathname;不要用 GoCash key。

注意事項

  • 金額不符或 voucher 已被其他訂單使用時,Terminal 會攔停人工確認,不會立刻回調對客平台。
  • 已被 failover 取代的舊 attempt 回報完成會標 stale_completion。
  • 連結過期後若你重開並收款,請先打 deposit-reopen 再打 voucher-deposit。

其他回調

概覽

皆打 SuperTerminal。可用 /api/v1/cashier/callbacks/… 或相容 /api/internal/gocash/…。sessionId 一律是 att_…。

出款完成

POST /api/v1/cashier/callbacks/withdraw-complete
{
  "sessionId": "att_…",
  "agentTxId": "your-internal-tx-id",
  "voucherNo": "GC-…"
}

出款失敗

POST /api/v1/cashier/callbacks/withdraw-fail
{
  "sessionId": "att_…",
  "errorCode": "WITHDRAW_FAILED",
  "errorMessage": "insufficient float"
}

存款過期

POST /api/v1/cashier/callbacks/deposit-expire
{
  "sessionId": "att_…",
  "errorCode": "QUOTE_EXPIRED",
  "errorMessage": "payment link expired"
}

存款重開

POST /api/v1/cashier/callbacks/deposit-reopen
{
  "sessionId": "att_…"
}

成功回應

{ "ok": true }

回應欄位

名稱說明
ok已忽略的過期/重開也回 ok

錯誤

代碼HTTP訊息處理方式
ATTEMPT_NOT_FOUND404Unknown attemptsessionId 必須是 attempt id。
CALLBACK_FAILED500callback failed查看 Terminal 日誌後重試。

注意事項

  • deposit-reopen 會把訂單與最新 failed/cancelled attempt 一併復活,否則後續付款會被判 STALE。
  • 相容路徑對應:withdraw-complete、withdraw-fail、deposit-expire、deposit-reopen。

多平台關聯

全域唯一鍵是 order_attempts.id(att_,即 sparkSessionId)。訂單唯一鍵是 (client_id, client_order_ref)。voucher_no 全站不可重複使用。你必須把 sparkSessionId 存到你自己的交易列,回調時原樣帶回。

你回調的 sessionId 必須是 att_(sparkSessionId)。Terminal 轉發給對客平台時會改成對方的 clientOrderRef。不要自己猜對方的 ref,也不要打對客平台 webhook。