Skip to content

钱包(转账) ​

本平台使用转账钱包:每位玩家在本平台有一个游戏钱包,你的系统以 API 把金额转入、转出。游戏中的下注、派彩都在这个钱包内自动处理:

  • 下注时本金立即从余额扣除(牛牛押翻倍时连同预扣一起扣,见牛牛的预扣)。
  • 结算时派彩(含本金;牛牛另含退回的预扣)自动入账;结果修正或作废时自动补差额或退款。
  • 所以任何时候的余额都是可用余额,未结算注单的本金不会被转出。
端点用途
POST /wallet/deposit转入(你的系统 → 游戏钱包);玩家不存在时自动创建
POST /wallet/withdraw转出(游戏钱包 → 你的系统)
GET /wallet/transfer?txnId=查询一笔转账的结果
GET /wallet/balance?username=查询目前的可用余额

转入与转出 ​

json
{ "username": "alice", "txnId": "dep-20260924-000123", "amount": "1000", "currency": "TWD" }
字段说明
username玩家账号。转入时玩家不存在会以 currency 自动创建;转出时玩家必须存在
txnId你的转账单号,1–64 个字符(字母或数字与 _ . : -),作为幂等键
amount大于 0 的十进制字符串,最多 4 位小数,例如 "1000"、"0.5"。不要用浮点数
currency必须等于玩家的币种,否则回 CURRENCY_MISMATCH

响应:

json
{ "ok": true, "data": { "txnId": "dep-20260924-000123", "status": "done", "balance": "2500.5", "duplicate": false } }
  • 转账是单阶段的:响应成功就是已经完成(status 一律是 done),没有「处理中」或需要再确认的步骤。
  • balance 是这笔转账完成当下的余额。
  • 响应成功的转账一定查得到:GET /wallet/transfer?txnId=…&username=…。
  • 转出金额大于可用余额时回 409 INSUFFICIENT_BALANCE,余额不变。

幂等:txnId 的规则 ​

情况结果
新的 txnId执行转账,duplicate: false
同一个 txnId、同一位玩家、相同金额与方向不会再执行,返回第一次的结果(balance 为那笔转账完成当下的余额)与 duplicate: true
同一个 txnId,但金额或方向(转入/转出)不同409 TXN_CONFLICT,余额不变
同一个 txnId 在最近两个月内已用在另一位玩家409 TXN_CONFLICT,余额不变
  • 请让 txnId 在你的整个租户内永久唯一,例如 dep-<你的交易编号>。
  • 每一笔新的转账用新的 txnId;重试时则必须沿用同一个 txnId 与完全相同的内容。

超时与重试 ​

网络超时、连接中断、收到 5xx 或 429 时,你无法确定转账是否已经执行。正确的处理方式:

  1. 送出前,先在你的数据库记下这笔转账(txnId、金额、方向,状态「处理中」)。
  2. 调用 deposit/withdraw(建议超时 10 秒)。
  3. 2xx(包含 duplicate: true):标记完成,以响应的 balance 更新显示。
  4. 4xx(例如 INSUFFICIENT_BALANCE、TXN_CONFLICT、INVALID_AMOUNT):明确失败,标记失败;转出失败时把金额退回玩家在你系统的余额。
  5. 超时、网络错误、5xx、429:状态未知。以相同的 txnId、相同内容重送(每次换新的 nonce 与时间戳,退避例如 1、2、4、8 秒),直到得到 2xx 或 4xx。
js
// 以 sign.js 的 call()(见〈认证与签名〉)为例
async function transferWithRetry(kind, payload, auth) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await call('POST', `/api/tenant/v1/wallet/${kind}`, payload, auth); // 2xx:完成
    } catch (err) {
      const unknown = !err.status || err.status >= 500 || err.status === 429;
      if (!unknown || attempt >= 8) throw err; // 4xx:明确失败;或交给人工/调度稍后再试
      await new Promise((r) => setTimeout(r, Math.min(30_000, 1000 * 2 ** attempt)));
      // 同一个 payload(同一个 txnId)重送
    }
  }
}

也可以先用 GET /wallet/transfer?txnId=…&username=… 查询(请带 username):

  • 200:转账已完成,响应包含方向、金额与完成后的余额。响应成功的转账一定查得到。
  • 404 TXN_NOT_FOUND:这笔转账没有执行(或仍在处理中)。请以同一个 txnId 重送转账,直到得到明确结果;不要改用新的 txnId。

绝对不要

超时后换一个新的 txnId 重送。如果第一次其实已经成功,玩家会被转两次。

查询转账 ​

text
GET /api/tenant/v1/wallet/transfer?txnId=dep-20260924-000123&username=alice
json
{
  "ok": true,
  "data": {
    "txnId": "dep-20260924-000123",
    "username": "alice",
    "dir": "in",
    "amount": "1000",
    "status": "done",
    "balance": "2500.5",
    "at": "2026-09-24T03:00:01.250Z"
  }
}
  • dir:in 转入、out 转出、adjust Console 的人工上下分。
  • username 选填但建议带:转账一完成,玩家钱包就有记录,租户流水的副本会在稍后送达;带 username 时两边都会查,所以成功的转账一定查得到。
  • 租户流水的查询范围是当月与前 3 个月(依 UTC 月份)。更早的转账请以 Console 注单与账务 → 转账记录 或导出核对。

查询余额 ​

text
GET /api/tenant/v1/wallet/balance?username=alice
json
{ "ok": true, "data": { "balance": "2500.5", "currency": "TWD" } }

余额是可用余额,已扣除进行中注单的本金(牛牛翻倍另扣预扣)。要转出玩家全部的钱时,先查余额再以这个金额转出;如果玩家在两次调用之间又下注,转出会回 INSUFFICIENT_BALANCE,重新查询后再试即可。

金额与币种 ​

  • 所有金额都是十进制字符串,最多 4 位小数;响应会去掉小数尾端的 0("100"、"0.95"、"1000.5")。
  • 请以字符串(或十进制类型)处理金额,不要转成浮点数再加减。
  • 玩家的币种在创建时决定,之后不能变更;转账的 currency 必须与玩家相同。

每日对账建议 ​

  1. 转账:你的转账记录中状态不是「完成」的,每天以同一个 txnId 重送或查询,直到每一笔都有明确结果。

  2. 余额:对每位有活动的玩家,验证

    text
    期初余额 + 转入 − 转出 + Σ 注单输赢(每张注单取最新版次的 winLoss)± 人工上下分 = 期末余额

    注单输赢以注单同步取得;对账时点若仍有未结算的注单,差额等于那些注单的本金(牛牛再加预扣)。

  3. 每日汇总:以 GET /bets/summary?date= 与你自己汇总的数字核对。

错误码 ​

错误码HTTP原因
INVALID_PARAMETER400缺少字段,或 txnId 格式错误
INVALID_AMOUNT400金额不是大于 0、最多 4 位小数的字符串
INVALID_USERNAME400账号不符合规则
INVALID_CURRENCY400转入时创建新玩家,但币种代码不合法
CURRENCY_MISMATCH400币种与玩家不同
PLAYER_NOT_FOUND404转出或查询的玩家不存在
PLAYER_LIMIT403转入时要创建新玩家,但玩家数已达方案上限(免费展示方案 50、沙箱 500)
TXN_CONFLICT409txnId 已用在不同的转账,或最近两个月内已用在另一位玩家
INSUFFICIENT_BALANCE409可用余额不足
TXN_NOT_FOUND404查不到这笔转账(带 username 时表示没有执行)

其他共通错误见错误码。

elite 租户集成 API v1