投注数据推送回调
VCT 平台批量向包网(Agent)推送投注、结算及修正流水。
说明
此接口由 VCT 平台调用包网(Agent),用于向包网系统推送或批量推送注单流水与报表数据。
注意(转账钱包模式重要事项): 在 Seamless(实时钱包)模式下,此接口是包网系统处理游戏资金变动的唯一通道。游戏期间的下注(BET)、结算(SETTLED)、额外派奖(BONUS)以及取消和回滚(CANCEL/ROLLBACK/REFUND/VOID)都会推送到此接口;包网必须按 status 对玩家主钱包执行幂等的实时扣款、加款或冲正。
环境地址
| 环境 | Base URL |
|---|---|
| 生产环境 | https://hub-panel-api.v7stech.com |
| 测试环境 | https://hub-panel-api-dev.v7stech.com |
接口信息
| 属性 | 值 |
|---|---|
| 路径 | POST /api/v1/callback/bet-push |
| 调用方 | VCT 平台 → 包网 (Agent) |
鉴权与签名
统一 Header Token 规则见认证机制。本接口为批量注单,Body sign 使用 HMAC-SHA256(agent_api_key + "." + timestamp, agent_api_secret);Header 与 Body 的 timestamp 必须完全一致。
请求参数(VCT 发送)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
operator_code | string | 是 | 运营商编码 |
wagers | array | 是 | 注单列表 |
wagers[].username | string | 是 | 原始玩家用户名(已剔除 VCT 平台的前缀) |
wagers[].currency | string | 是 | 币种代码(如 MYR, VND, USD 等,用于精准定位玩家多币种钱包) |
wagers[].session_id | string | 是 | VCT 游戏会话唯一 ID;同一会话的 BET、SETTLED 和修正日志使用同一个值 |
wagers[].bet_id | string | 是 | 注单编号 |
wagers[].brand_guid | string | 是 | Brand 稳定唯一标识,与 /papi/v1/game/list、/papi/v1/game/providers 的 GUID 一致,用于关联 Brand |
wagers[].brand_code | string | 是 | 当前 t_brand.brand_code,不能作为全局唯一键 |
wagers[].brand_name | string | 是 | 当前 t_brand.brand_name,用于显示 |
wagers[].game_code | string | 是 | 游戏代码 |
wagers[].game_type | string | 是 | 厂商原始类型,保留用于兼容;不能用于佣金分类 |
wagers[].game_genre | integer | 是 | 从目录游戏取得的数字分类;无法唯一确认时为 0 |
wagers[].game_genre_name | string | 是 | 2 Slot、3 Fish、4 Sport、5 Card、6 Live、7 Other;0 时为空字符串 |
wagers[].game_name | string | 是 | 游戏名称 |
wagers[].bet_amount | decimal | 是 | 本条流水包含的下注扣款金额 |
wagers[].valid_bet_amount | decimal | 是 | 有效投注金额 |
wagers[].prize_amount | decimal | 是 | 本条流水包含的派彩、奖金或退款加款金额 |
wagers[].win_loss | decimal | 是 | 本条流水净额,正常流水按 prize_amount - bet_amount 计算;不一定等于整局最终盈亏 |
wagers[].status | string | 是 | 动作状态:BET / SETTLED / CANCEL / ROLLBACK / VOID / REFUND / BONUS |
wagers[].bet_time | string | 是 | 下注时间 |
wagers[].settle_time | string | 是 | 结算时间 |
wagers[].round_id | string | 否 | 统一回合/局号 ID。VCT 会将不同游戏商的局号映射至此字段,用于关联同一玩家、币种和会话中的 BET、SETTLED 及修正流水;实际关联时不要只使用 round_id。 |
wagers[].original_transaction_id | string | 否 | 原始结算/下注交易 ID;当 status=BONUS 或取消操作时建议携带 |
sign | string | 是 | 签名 |
timestamp | long | 是 | 时间戳 |
Brand 身份与关联
每条注单同时携带 brand_guid、brand_code、brand_name,三者从同一条有效 t_brand 主资料读取。Provider 与产品资料用于定位游戏关联的 Brand GUID;Product Code、Product Name 不能作为全局唯一键。相同 Code 或 Name、不同 GUID 的 Brand 必须分别保存。改名或修改编码保留 GUID,删除后重建会产生新 GUID。
产品可以确认 Brand,但不一定能确认具体桌台,因此 game_code、game_name 可以为空。Brand 关联缺失、已删除、存在歧义或 Provider 归属不符时,Hub 保留投送失败原因,不猜测 GUID。已投送的历史注单不会自动回填或补送。新增字段需要部署更新后的 Hub 后端才会生效。
game_genre 只从同一 Platform、Product、Game Code 匹配的目录游戏取得;若一个 Product 下所有游戏类型一致,也可返回该类型。混合类型、游戏编码不匹配或未整理时返回 0 与空名称,绝不从厂商 game_type 猜测。数字 0 的注单标记待核对,结算口径待双方确认。
/game/rates 已支持 brand_guid 精确筛选,使用注单中的 GUID 查询同一 Brand 的费率;按原业务日期传 as_of_date。原 brand 参数仅搜索当前主资料 Code / Name,不能保证唯一。
成功响应
| 字段 | 类型 | 说明 |
|---|---|---|
received | int | 成功接收条数 |
status | string | 处理状态 |
推送范围建议
- 正常流水可能先推送
BET再推送SETTLED,也可能由一条同时包含下注和派彩金额的SETTLED表示 - 默认推送
BONUS,表示额外奖金或独立赢分记录 CANCEL、ROLLBACK、REFUND、VOID会通过此接口推送,用于冲正包网系统内原有的资金交易与报表结果- 包网正式仅需对接
/balance、/offline和/bet-push;取消与冲正动作会作为bet-push的status推送 - 包网按
brand_guid关联目录、注单归属与 Brand 佣金配置;brand_code、brand_name用于显示,不能仅凭同名或同码合并 Brand。费率查询使用相同的brand_guid精确筛选
金额与玩家盈亏计算
bet_amount 表示本条流水实际包含的下注扣款金额;prize_amount 表示本条流水实际包含的派彩、奖金或退款加款金额。
对于正常投注和结算流水:
本条流水净额 = prize_amount - bet_amount正数表示玩家余额增加,负数表示玩家余额减少,0 表示本条流水没有产生净余额变化。包网报表若要显示整局玩家盈亏,必须汇总同一玩家、币种、session_id 和 round_id 下的相关正常流水:
整局玩家盈亏 = 所有相关记录的 prize_amount 合计
- 所有相关记录的 bet_amount 合计BET 与 SETTLED 分开推送
有些游戏商先推送下注,再推送结算:
| status | bet_amount | prize_amount | 本条流水净额 |
|---|---|---|---|
BET | 100 | 0 | -100 |
SETTLED | 0 | 200 | +200 |
这两条属于同一局时,整局玩家盈亏为 200 - 100 = +100。因此,不能只用 SETTLED 行的金额作为整局盈亏。
下注与派彩合并为一条 SETTLED
有些游戏商只推送一条同时包含两个金额的 SETTLED:
| status | bet_amount | prize_amount | 整局玩家盈亏 |
|---|---|---|---|
SETTLED | 100 | 200 | +100 |
这种情况下可以直接使用 prize_amount - bet_amount。不同游戏商的行拆法不同,但两个金额字段的含义不变。
status 与资金处理规则
| status | 含义 | 常见金额形式 | 包网处理 |
|---|---|---|---|
BET | 下注扣款 | bet_amount > 0,prize_amount = 0 | 扣除 bet_amount |
SETTLED | 正常结算 | 可能只有 prize_amount,也可能两个金额都有 | 扣除本条包含的 bet_amount,增加 prize_amount;两个金额都有时必须在同一笔原子交易中处理 |
BONUS | 额外奖金或独立赢分 | 通常 bet_amount = 0,只有 prize_amount | 增加 prize_amount,并单独归类为奖金流水 |
REFUND | 退款 | 通常只有 prize_amount | 增加退款金额,并将原交易标记为已退款 |
CANCEL / ROLLBACK / VOID | 取消、回滚或作废 | 厂商之间可能不同,两个金额也可能都是 0 | 根据 original_transaction_id 对原交易执行一次幂等冲正 |
action 是 Hub-System 内部使用的动作名称;包网在本接口中以 wagers[].status 处理同一语义。
修正流水说明
CANCEL、ROLLBACK、VOID 和 REFUND 不是新的正常输赢。包网必须使用 original_transaction_id 定位原资金交易,检查是否已经冲正,并且只执行一次冲正。
修正流水中,厂商可能把原下注金额同时放在 bet_amount 作为参考;这不代表包网要再次扣款。退款或退回玩家的钱应以 prize_amount 表示。若两个金额都是 0,表示该修正没有余额变化,但仍应更新原交易的报表状态。修正类流水不要单独套用 prize_amount - bet_amount 来判断最终输赢。
示例请求
正常结算注单 SETTLED
{
"operator_code": "AG",
"wagers": [
{
"username": "player001",
"currency": "MYR",
"session_id": "vct-session-uuid-001",
"bet_id": "settle-20260706-0001",
"brand_guid": "30ff77f5-0616-4ecc-a8a3-80e35fcaaf3e",
"brand_code": "1007",
"brand_name": "PG Soft",
"game_code": "PG-SLOT-001",
"game_type": "SLOT",
"game_genre": 2,
"game_genre_name": "Slot",
"game_name": "Fortune Tiger",
"bet_amount": 100.0,
"valid_bet_amount": 100.0,
"prize_amount": 200.0,
"win_loss": 100.0,
"status": "SETTLED",
"bet_time": "2026-07-06T10:00:00+08:00",
"settle_time": "2026-07-06T10:01:00+08:00",
"round_id": "round-10001"
}
],
"sign": "demo-sign",
"timestamp": 1783325100
}额外奖金记录 BONUS
{
"operator_code": "AG",
"wagers": [
{
"username": "player001",
"currency": "MYR",
"session_id": "vct-session-uuid-001",
"bet_id": "bonus-20260706-0001",
"brand_guid": "30ff77f5-0616-4ecc-a8a3-80e35fcaaf3e",
"brand_code": "1007",
"brand_name": "PG Soft",
"game_code": "PG-SLOT-001",
"game_type": "SLOT",
"game_genre": 2,
"game_genre_name": "Slot",
"game_name": "Fortune Tiger",
"bet_amount": 0.0,
"valid_bet_amount": 0.0,
"prize_amount": 30.0,
"win_loss": 30.0,
"status": "BONUS",
"bet_time": "2026-07-06T10:01:00+08:00",
"settle_time": "2026-07-06T10:01:10+08:00",
"round_id": "round-10001",
"original_transaction_id": "settle-20260706-0001"
}
],
"sign": "demo-sign",
"timestamp": 1783325160
}示例响应
{
"received": 1,
"status": "success"
}推送数据逻辑与处理建议
1. 实时钱包记账(极其重要)
- 必须实时处理余额:包网按
status对主钱包执行资金交易。BET扣除bet_amount;SETTLED扣除本条包含的bet_amount并增加prize_amount(如果 bet_amount 为 0,则只增加 prize_amount);BONUS增加对应奖金;CANCEL/ROLLBACK/REFUND/VOID依据original_transaction_id对原交易进行一次幂等冲正。 /offline只用于关会话与最终对账,不能再根据reclaimed_amount对已由 bet-push 处理过的主钱包重复加款或扣款。
2. 幂等去重控制
- 包网系统应以交易唯一键(至少
wagers[].bet_id,并结合status与original_transaction_id)进行幂等排重,避免网络重试造成重复扣款、加款或冲正。
3. 注单状态与修正逻辑
- 正常注单状态:
BET(原始投注)、SETTLED(正常结算)、BONUS(红利或独立赢分)。 - 修正注单状态:
CANCEL/ROLLBACK/REFUND/VOID(注单取消或回退)。 - 修正逻辑处理:当注单状态为修正状态时,推送数据会携带指向原注单的
original_transaction_id。包网必须定位原资金交易,完成一次幂等冲正,并同步更新报表状态;修正记录中的 bet_amount 可能只是原下注金额参考值,不应因此再次扣款。
4. 自动重试机制与响应代码
- 处理成功:包网处理完毕后,请返回
HTTP 200状态码,并响应{"received": 1, "status": "success"},VCT 队列收到后会将该笔推送任务标记为成功。 - 重试保障:若包网侧数据库繁忙或网络短暂中断,应返回
HTTP 5xx或直接超时。VCT 本地队列任务检测到失败或超时后,会启动自动重试策略,在随后的时间段内进行多次重试推送,确保流水日志零丢失。
游戏商来源映射
| 游戏商 | 回调路径 | 请求格式 |
|---|---|---|
| BG | — | 不支持推送 |
| GSC | seamless/pushbetdata | JSON,wagers 数组;支持 status=SETTLED / BONUS |
| SA | — | 不支持推送(通过查询接口拉取) |
| WM | webhook(cmd=SendMemberReport) | form-data,result 数组 |
AI 开发提示词(可直接复制)
请在当前包网项目中实现 VCT 投注数据回调 POST /api/v1/callback/bet-push。这是 Seamless 钱包实际扣款、派彩和冲正的资金入口。
实现要求:
1. 校验 Authorization 三段式 Token;Header 与 Body timestamp 必须一致。Body sign=HMAC-SHA256(agent_api_key + "." + timestamp, agent_api_secret),使用小写 hex 和常量时间比较。
2. 验证 operator_code 属于当前 Agent,wagers 必须是非空数组;逐条校验 username、currency、session_id、bet_id、status 和金额格式。
3. 使用 decimal/定点数,禁止 float 参与资金计算。每批请求在数据库事务中处理,成功提交后才返回 success。
4. 建立稳定幂等键,至少包含 Agent、bet_id、status,并结合 original_transaction_id;VCT 重试不得造成重复扣款、派彩或冲正。
5. BET 扣 bet_amount;SETTLED 扣本条 bet_amount 并加 prize_amount;BONUS 加 prize_amount;同一局可能 BET/SETTLED 分开,也可能合并,不能假设固定顺序。
6. CANCEL/ROLLBACK/REFUND/VOID 必须通过 original_transaction_id 定位原资金交易并只冲正一次;修正记录的 bet_amount 可能仅供参考,不能再次扣款。
7. 保存 hub session_id、round_id、brand_guid、brand_code、brand_name、game_code、game_type、game_name、bet/settle 时间及原始 JSON。按 brand_guid 关联目录和注单,不以 Code/Name 为唯一键;同名同码但 GUID 不同的 Brand 保持独立。历史缺少 GUID 的记录保留并标记归属待补,不猜测 GUID;钱包处理不变;费率查询使用 brand_guid 和原业务日期 as_of_date,保留既有计算规则。不要只用 round_id 做唯一键。
7a. 佣金使用数字 game_genre(2 Slot、3 Fish、4 Sport、5 Card、6 Live、7 Other),game_genre_name 只用于展示;旧 game_type 是厂商原始字段。game_genre=0 的注单标记待核对,不从旧字符串猜类型,不擅自计费。
8. 业务全部成功后返回 HTTP 200 和 {"received": 实际条数, "status":"success"}。任何一条未提交时不得虚报成功;暂时性故障返回 5xx 让 VCT 重试。
9. `/offline` 的 reclaimed_amount 不能再次变更已经由 bet-push 处理的主钱包。
10. 对乱序、重复、迟到日志保持幂等;同一 session 清除后到达的日志仍按交易规则处理并标记审计时间。
11. 日志脱敏 Secret、Token 和 sign,但保留 bet_id、session_id、status、事务结果和错误。
12. 增加测试:BET、拆分/合并 SETTLED、BONUS、四种修正、批量原子性、重复/乱序/迟到、金额精度、签名失败、数据库故障重试,以及同 Code/Name 不同 Brand GUID 的归属隔离。
请复用现有钱包账本、数据库事务、唯一约束和回调认证,不要另建一套余额真值。