# 游戏接口

---

## 启动游戏 {#launch}

获取上游游戏启动 URL。后端按 `internal_code` 选择 source_priority 最优的渠道，写入 `a_game_session` 后回调上游 launch API 获取启动地址。

本接口使用**真实余额**（`user_money` / `bonus_money`）。游客（`is_guest=true`）若选择用免费账户（体验金）试玩，请改调【[免费账户启动游戏](#launch_free)】。

**URL:** `POST` /game/launch

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
internal_code | string | 是 | 游戏代码（`provider_game_code`）
device_type | string | 否 | `mobile` / `desktop`，默认 `desktop`
currency | string | 否 | 货币代码，默认 `BRL`
return_url | string | 否 | 游戏结束后跳转 URL
lang | string | 否 | 语言代码，如 `zh-CN`

**输出:**

字段 | 类型 | 说明
---|---|---
url | string | 启动 URL
launch_url | string | 启动 URL（同 `url`）
session_id | string | 会话 ID（用于 `/game/close`）
session_token | string | 会话 token

---

## 免费账户启动游戏 {#launch_free}

以**免费账户余额**（体验金，【[账户余额](200_finance.md#account_balance)】的 `free_money`）启动游戏。需登录；游客与已绑定会员均可调用，以免费账户状态为准。

**免费账户语义**

- 额度、上限、目标与限时窗口由【[免费注册奖金](418_promotion_free_register_bonus.md)】活动配置决定（`bonus_amount` / `free_money_cap` / `target_amount` / `window_hours`），**仅在游客注册时一次性发放**，本接口不补发，**用完不补**
- 与真实余额 `user_money` / `bonus_money` **完全隔离**：不可提现、不可转入真实账户、不计打码量、不触发任何活动 / 奖励 / 返水 / VIP 经验；免费游戏的投注记录**不进入**【[投注汇总](#bet_summary)】等投注记录相关接口
- **任一笔充值到账即锁定**免费账户：余额冻结、不再可进免费模式；累计充值达标后余额转入活动拆笔记录按打码逐笔解锁（见【[免费注册奖金 · 规则](418_promotion_free_register_bonus.md#rules)】）。【[绑定账号](100_auth.md#bind_account)】转正**不影响**免费账户
- 免费游戏走商户配置的另一套高 RTP 365 商户资料，目前**仅支持 365 平台的游戏**

**行为说明**

- 由客户端判定（免费奖金 `stage=playing` 且玩家选择体验）后调用；真钱游戏仍调【[启动游戏](#launch)】
- 已到期时本接口会先作废免费余额再拒绝

**URL:** `POST` /game/launch_free

**输入:**（与【[启动游戏](#launch)】完全相同）

字段 | 类型 | 必填 | 说明
---|---|---|---
internal_code | string | 是 | 游戏代码（`provider_game_code`），须为 365 平台游戏
device_type | string | 否 | `mobile` / `desktop`，默认 `desktop`
currency | string | 否 | 货币代码，默认 `BRL`
return_url | string | 否 | 游戏结束后跳转 URL
lang | string | 否 | 语言代码，如 `zh-CN`

**请求示例：**

```bash
curl -X POST 'https://<your-domain>/api/game/game/launch_free' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <jwt>' \
  -d '{
    "internal_code": "365_fortune_tiger",
    "device_type": "mobile",
    "currency": "BRL",
    "lang": "pt-BR"
  }'
```

**输出:**

字段 | 类型 | 说明
---|---|---
url | string | 启动 URL
launch_url | string | 启动 URL（同 `url`）
session_id | string | 会话 ID（用于 `/game/close`）
session_token | string | 会话 token
free_balance | string | 启动时的免费账户余额（金额字符串，见【[金额与币种](200_finance.md#amount_currency)】）

**业务错误:**

message | 触发条件
---|---
`请先登录` | 未登录
`尚未领取免费奖金` | 账户从未发放过免费额度（非游客注册，或注册时活动未开启）
`已完成充值，免费账户不再可用` | 已有任一笔充值到账（免费账户已锁定）
`免费奖金已到期` | 倒计时（`window_hours`）已归零；余额同时作废清零
`免费账户余额不足` | 余额低于最低可下注额 `0.1`
`该游戏不支持游客试玩` | `internal_code` 对应游戏不属于 365 平台，或商户未配置免费试玩凭据；该校验在免费账户判定之前执行，客户端应提示并不打开游戏
其余 | 与【[启动游戏](#launch)】相同（游戏未上架 / 暂无可用游戏渠道 / 启动游戏失败 等）

---

## 关闭游戏 {#close}

玩家端主动通知服务端会话结束，服务端会更新 `GameSession.session_status=closed` 并发布关闭事件。

**URL:** `POST` /game/close

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
session_id | string | 否 | 由 `launch` 返回的 `session_id`

**输出:**

字段 | 类型 | 说明
---|---|---
ok | bool | 固定为 `true`

---

## 添加收藏 {#favorite_add}

幂等：已收藏返回成功。每个玩家最多收藏 100 个游戏。

**URL:** `POST` /game/favorite

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
provider_game_code | string | 是 | 游戏代码

**输出:** 无

---

## 取消收藏 {#favorite_remove}

**URL:** `DELETE` /game/favorite

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
provider_game_code | string | 是 | 游戏代码

**输出:** 无

---

## 收藏列表 {#favorite_list}

返回玩家收藏的完整游戏卡片，可直接渲染「收藏夹」页面。排序与【[游戏列表](#list)】顶部「我的收藏」分类一致：最近游玩过的收藏排最前（`last=true`），其余按收藏时间倒序；已下架 / 不在当前模板范围内的收藏自动忽略。游客返回空数组。

**URL:** `GET` /game/favorite/list

**输出:**

字段 | 类型 | 说明
---|---|---
items | list<Game\> | 游戏列表，`Game` 字段见【[游戏列表](#list)】

---

## 最近游玩列表 {#recent_list}

返回玩家最近游玩过的完整游戏卡片（按最近一次游玩倒序、按游戏去重），可直接渲染「常玩的游戏」页面。免费账户（游客体验金）会话不计入；已下架 / 不在当前模板范围内的游戏自动忽略，因此实际条数可能少于 `limit`。游客返回空数组。

**URL:** `GET` /game/recent/list

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
limit | number | 否 | 返回条数，默认 `20`，最大 `50`

**输出:**

字段 | 类型 | 说明
---|---|---
items | list<Game\> | 游戏列表，`Game` 字段见【[游戏列表](#list)】（本接口不下发 `last`）

---

## 游戏列表 {#list}

按厂商聚合的玩家端首页列表，顶端附加合成的「热门」类别（`type=2`）；登录态且有有效收藏时，最顶部再插入「我的收藏」类别（`id="favorite"`、`type=3`），其中最近游玩过的收藏排最前并标记 `last=true`。游客可访问。

**URL:** `GET` /game/list

**输出:**

字段 | 类型 | 说明
---|---|---
items | list<Category\> | 类别列表
version | string | 游戏列表版本号（不透明字符串）。客户端拉取列表时应保存，供【[游戏列表版本号](#list_version)】比对使用

`Category` 字段：

字段 | 类型 | 说明
---|---|---
id | string | `"favorite"` / `"hot"` 或 `provider_id`
name | string | 类别显示名
type | number | `1` 普通厂商 / `2` 热门 / `3` 我的收藏
sort | number | 排序值
logo_url | string | 厂商 logo（热门 / 收藏类别为空）
count | number | 游戏总数
display_count | number | 玩家端展示游戏数，`0` 为不限制
games | list<Game\> | 游戏列表

`Game` 字段：

字段 | 类型 | 说明
---|---|---
id | string | 游戏 ID
provider_game_code | string | 游戏代码
game_name | string | 游戏名称
cover_url | string | 封面 URL
landscape_cover_url | string | 横版封面 URL，未配置时为空串，客户端应回退 `cover_url`
game_type | string | 游戏类型 code
provider_id | string | 厂商 ID
rtp | string | 标称 RTP（理论返还率，百分数，两位小数，如 `96.75`）；未配置时不返回该字段，仅供展示，不代表实际返奖
is_hot | bool | 是否热门
last | bool | 是否最近游玩（仅「我的收藏」类别内出现）

---

## 游戏列表版本号 {#list_version}

轻量接口，返回当前游戏列表版本号。游客可访问，无参数。

用途：玩家端为 PWA 长驻不刷新，本地缓存的游戏列表可能过期（商户切换了游戏模板、游戏上下架等）。当**启动游戏失败**时调用本接口，与本地保存的 `version` 比对：不一致说明游戏列表已更新，应重新拉取【[游戏列表](#list)】并刷新界面。

**URL:** `GET` /game/list/version

**输出:**

字段 | 类型 | 说明
---|---|---
version | string | 游戏列表版本号（不透明字符串）

**推荐客户端流程:**

1. 拉取【[游戏列表](#list)】时保存响应中的 `version`；
2. 启动游戏失败时调用本接口获取最新版本号；
3. 与本地 `version` **只做相等比较**（不透明字符串，不保证单调）：不一致 → 重拉【[游戏列表](#list)】并刷新界面、更新本地 `version`；一致 → 按普通启动失败处理。

**注意事项:**

- 列表内容变更（模板指派/内容修改、商户游戏上下架、平台游戏库变动）时版本号变化；
- 版本键 30 天 TTL 过期会自然轮换一次（客户端多一次无害重拉），因此**不要长期缓存比对结果**。

---

## 投注汇总 {#bet_summary}

按游戏汇总当前玩家投注，结果按参数 hash 缓存 60s。时间窗口最长 30 天，结算口径仅统计 `bet_status='settled'`。

**URL:** `GET` /game/bet/summary

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
start_time | string | 否 | Unix 秒或 `2006-01-02 15:04:05`，默认 `now-1h`
end_time | string | 否 | 同上，默认 `now`

**输出:**

字段 | 类型 | 说明
---|---|---
items | list<object\> | 汇总行列表，按 `bet_amount DESC`

单项字段：

字段 | 类型 | 说明
---|---|---
game_id | string | 游戏代码
title | string | 游戏名称
cover | string | 封面 URL
bet_amount | number | 投注总额
win_amount | number | 派彩总额
valid_bet_amount | number | 有效投注额
bet_count | number | 投注笔数

---

## 投注流水 {#bet_list}

分页查询玩家投注流水，结果按参数 hash 缓存 60s。

**URL:** `GET` /game/bet/list

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
start_time | string | 否 | 同 `bet/summary`，默认 `now-1h`
end_time | string | 否 | 同 `bet/summary`，默认 `now`
game_id | string | 否 | 按 `game_code` 过滤
limit | number | 否 | 每页条数，默认 10，最大 100
offset | number | 否 | 偏移量，默认 0

**输出:**

字段 | 类型 | 说明
---|---|---
list | list<object\> | 流水列表（按 `created_at DESC, id DESC`）
total | number | 总数

单项字段：

字段 | 类型 | 说明
---|---|---
id | string | 流水 ID
member_id | string | 会员 ID
game_id | string | 游戏代码
game_name | string | 游戏名称
round_id | string | 上游回合号
bet_amount | number | 投注金额
win_amount | number | 派彩金额
bet_status | string | 投注状态（如 `settled`）
currency | string | 货币代码
created_at | string | 创建时间
req_time | string | 请求时间（同 `created_at`）

---

## 投注统计 {#bet_stats}

当前玩家**终身**投注统计（不限时间窗口），供个人中心展示；结果缓存 30s。口径与商户后台报表一致：`bet_count` 只计已结算注单，`win_count` 为已结算且派彩 > 0 的注单数，`bet_amount` 为已结算 / 已取消 / 已回滚注单的原始投注额之和（不扣退款）。

**URL:** `GET` /game/bet/stats

**输入:** 无

**输出:**

字段 | 类型 | 说明
---|---|---
data.bet_count | number | 总投注场数
data.win_count | number | 总胜场数
data.bet_amount | string | 总投注额（两位小数）

---

## 跑马灯 {#marquee}

前端展示用的累计派彩金额与高额派彩滚动消息。**无需登录**。

- `total_payout` 为展示用累计派彩，创建时从 `863126.80` 起算，服务端按秒推进（每秒随机增加 `step_min` ~ `step_max`），达到 `10000000.00` 后回落到 `863126.80`。前端拿到值后可自行按秒动画自增，无需轮询。
- `items` 为滚动消息，一次返回 50 条基础消息；若近 20 分钟内存在派彩超过 100 的真实注单，会追加最多 20 条并随机插入列表中（因此实际条数为 50 ~ 70）。列表按商户缓存，每 5 分钟更新一次。前端进页面取一次即可无限循环滚动。
- `phone` 为脱敏展示串，格式 `<商户手机区号><7 个星号><4 位随机尾号>`，如 `55*******1234`；商户未配置区号时无区号前缀。不含任何真实玩家信息。

**URL:** `GET` /marquee/list

**输入:** 无

**输出:**

字段 | 类型 | 说明
---|---|---
total_payout | string | 当前累计派彩金额（两位小数字符串）
step_min | string | 每秒自增下限，固定 `10.00`
step_max | string | 每秒自增上限，固定 `499.99`
items | list&lt;object&gt; | 滚动消息列表，元素结构见下表

**items 元素:**

字段 | 类型 | 说明
---|---|---
phone | string | 脱敏手机号，如 `55*******1234`
provider_game_code | string | 游戏代码，直接作为 [启动游戏](#launch) 的 `internal_code`
game_name | string | 游戏名称
cover_url | string | 游戏封面图 URL
landscape_cover_url | string | 横版封面图 URL，未配置时为空串
amount | string | 派彩金额（两位小数字符串）

---

## 转出余额 {#transfer_out}

占位接口，当前固定返回 422 「接口未开放」。

**URL:** `POST` /transfer_out

---

## 弹窗游戏列表 {#pop_game_list}

占位接口，当前固定返回 422 「接口未开放」。

**URL:** `GET` /pop_game_list
