开放娱乐平台

回调与结算

webhook是登记的完整回调地址。提供方直接向该URL发送POST请求,不追加路径。模板提供POST /callback。三种事件统一由签名payload中的event字段区分:open、transaction、close。第三方负责自己的玩家、余额和账本;收到open不能自动创建任意玩家。

验签格式

所有回调使用JSON信封,payload是原始业务JSON字符串。

{
  "payload": "{\"event\":\"open\",\"user\":\"test-player-001\",\"user_key\":\"SESSION\"}",
  "timestamp": "UNIX_SECONDS",
  "nonce": "RANDOM_HEX",
  "sign": "HMAC_HEX"
}

sign = HMAC-SHA256(secret, timestamp + ":" + nonce + ":" + payload),结果为小写十六进制。必须使用收到的payload原文验签,不能先解析再重新序列化。验证时间误差不超过300秒,并使用恒定时间比较签名;验签通过才解析业务数据。

event包含在payload原文中,必须先验签再按event分发,不能根据未签名的请求头选择操作。每次新建回调请求都会签名。不能靠nonce代替业务幂等;重复投递合法,必须按业务事件唯一键处理。

open

payload包含event="open"、user和user_key。user_key标识一次钱包会话。用玩家行锁检查尚无其他未关闭会话,保存会话与初始余额。相同open重试返回第一次确认的余额,不能重新充值。

{
  "success": true,
  "message": 10000000
}

transaction

{
  "event": "transaction",
  "transaction_id": "123456",
  "user_key": "SESSION",
  "user": "test-player-001",
  "game_code": "baccarat",
  "amount": -100,
  "balance": 9999900,
  "timestamp": 1791900000
}

amount为带符号整数,本次余额应等于旧余额+amount。必须检查user_key是当前会话。以合作方、玩家、transaction_id作为唯一业务身份;相同事件重复返回原结果,编号相同但金额或报文冲突必须拒绝并对账。

余额、流水、防重记录必须在一个数据库事务提交。事务提交后即使HTTP应答丢失,也不能重复入账。应先检查历史事件,再判断当前会话,允许旧交易在钱包关闭后安全重试。

close

payload包含event="close"、user、user_key、balance。必须匹配当前会话并核对本地余额。迟到的旧close不能关闭新会话;相同close重试返回成功。

transaction和close成功均返回HTTP 200及{"success":true,"message":"ok"}。错误或非JSON响应不会被当作确认。

故障处理

transaction与close先写入提供方回调队列,再按同会话顺序签名发送。投递失败递增退避,20次失败后进入死信并阻塞后续事件;管理员核查后可重试原事件。已落库的事件可在后台重启后继续投递,每次重新签名,第三方必须按业务编号去重。open仍为即时请求。

提供方不维护独立钱包账本和钱包会话表,也不保留GameServer磁盘补发队列。回调队列不代表结算全链路的原子提交:游戏消息尚未写入队列时发生崩溃,仍需人工核对。

第三方必须保存独立账本,不依赖提供方的短期游戏日志。出现余额不一致时保留会话、暂停相关资金操作并提交事件编号;不要手动覆盖余额或把失败事件标成成功。

发生进程崩溃后,第三方保留未关闭的本地钱包状态,人工核对日志;提供方当前没有自动恢复接口。当前正式准入默认关闭,未经重启、应答丢失和结算顺序验收的游戏不得开放正式接入。

地址示例

登记 https://operator.example/callback 后,三个事件均发送到该地址。回调接口不使用浏览器登录态,应直接返回JSON,不要跳转到登录页面。服务器出口 IP 白名单约束第三方发起的游戏API请求;回调接收端则必须校验平台签名,二者用途不同。

字段类型与金额单位

所有事件发往登记的同一个完整 URL,HTTP 方法为 POST,请求与响应均为 application/json。以下表格的业务字段位于 payload 字符串内,不是信封顶层。

信封字段 类型 说明
payload string 必填,原始业务 JSON 文本;必须保留原文验签
timestamp string 必填,本次投递签名的 Unix 秒
nonce string 必填,本次签名的随机文本
sign string 必填,HMAC-SHA256 小写十六进制文本
业务字段 类型 所属事件及含义
event string 所有事件:open、transaction 或 close
user string 所有事件:第三方稳定玩家编码
user_key string 所有事件:本次钱包会话标识,必须与玩家一起核对
transaction_id string transaction:唯一业务交易编号,原样保存,不转浮点数
game_code string transaction:发生交易的游戏标识
amount integer(int64) transaction:本次有符号余额变化,负数扣减、正数增加
balance integer(int64) transaction / close:变更后/关闭时的余额,用于核对
timestamp integer transaction:交易发生的 Unix 秒;区别于信封 timestamp 字符串

金额统一使用原始整数,1000 原始单位 = 1.00 金币。展示汇率只影响显示,回调不换算币种、不传小数金额。open 响应的 message 必须是非负 int64 JSON 数字,不能是字符串。处理端应使用支持 int64 精度的 JSON 解析器;JavaScript Number 超过安全整数范围会丢失精度,不能用于大额账务运算。

{
  "event": "close",
  "user": "test-player-001",
  "user_key": "SESSION",
  "balance": 9999900
}

确认响应与失败响应

{
  "success": true,
  "message": "ok"
}
{
  "success": false,
  "message": "wallet balance mismatch"
}

失败 message 由第三方定义,上例不是平台统一错误码。签名不合法应拒绝执行;事务未提交、玩家不存在、会话冲突或余额不符时,不得返回 success=true。不要先应答成功再异步入账。

transaction / close 投递端仅在 HTTP 200、响应为有效 JSON 且 success=true 时确认成功;message 不参与确认。单次请求超时 8 秒,响应最大 64 KiB,不跟随重定向。超时、非 200、无效 JSON、success=false 均视为失败。重试延迟按失败次数指数增加,最大约 256 秒;第 20 次失败进入死信。业务 payload 保持不变,信封 timestamp/nonce/sign 在每次投递时重新生成。

open 必须及时返回已确认的初始余额;它不进入 outbox 自动重试队列。open 失败会阻止游戏开户。请保存原始业务事件、处理结果和对账依据;敏感签名与密钥不写入普通日志。