hyapp-server/docs/幸运礼物外接接口对接文档.md

259 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 幸运礼物外接接口对接文档
> 文档版本2026-07-15
> 接入方式服务端对服务端Server-to-Server
> 金额单位:对接 App 的金币最小整数单位
## 1. 接口用途
外部 App 在自己的账务系统完成幸运礼物扣费后,调用本接口获取一次最终抽奖结果。
平台负责:
-`app_code` 隔离规则、奖池、RTP 和抽奖流水。
-`app_code + request_id` 保证抽奖幂等。
- 返回最终 `reward_amount` 和倍率。
对接 App 负责:
- 调用前完成用户扣费。
- 从自己的账务和用户主数据提供真实的扣费时间、稳定设备标识和账号注册时间。
- 在自己的鉴权或请求签名边界内绑定上述业务事实,不能让 App 或 Web 客户端直接伪造。
- 成功收到结果后,在自己的账务系统中向用户发放 `reward_amount`
- 对超时或网络失败使用原 `request_id` 重试,不得重新生成业务请求 ID。
HyApp 不会操作外部 App 的用户钱包。`reward_status=granted` 表示抽奖结果已经生成并交给调用方,不表示奖励已在外部 App 入账。
## 2. 接口地址
| 类型 | 地址 |
|---|---|
| Base URL | `https://lucky-api.global-interaction.com` |
| 送礼接口 | `POST https://lucky-api.global-interaction.com/api/v1/lucky-gifts/send` |
| 就绪检查 | `GET https://lucky-api.global-interaction.com/healthz/ready` |
就绪检查:
```text
GET https://lucky-api.global-interaction.com/healthz/ready
```
2026-07-15 实测就绪检查返回 HTTP 200送礼请求可到达幸运礼物网关。
## 3. 调用前置条件
1. 向平台申请唯一 `app_code`,并加入幸运礼物网关白名单。
2. 确认该 App 的幸运礼物规则已启用。
3. 对接 App 必须先扣费,再调用抽奖接口。
4. 如果使用 `dynamic_v3`,对接 App 必须提供真实 `paid_at_ms`;应尽量提供稳定 `device_id` 和真实 `user_registered_at_ms`,以启用完整设备风控与 RTP 补偿资格判断。
5. 本接口只允许对接 App 服务端调用,禁止将请求能力下发到 App 或 Web 客户端。
### 3.1 真实性与信任边界
当前 HTTP 入口只校验 `app_code` 是否在白名单,尚未启用 Authorization Token 或平台侧请求签名。白名单只能决定“这个 App 是否已开通”,不能独立证明某次请求中的用户、设备、扣费时间和注册时间是真实的。
对接 App 必须在自己的服务端边界内完成以下保障:
- `external_user_id` 来自已登录用户,不直接信任客户端自报值。
- 提供 `device_id` 时,必须与该次已鉴权的用户/设备关系绑定,并在同一设备的后续请求中保持稳定;不能用随机数、请求号或会话 token 临时代替。无法提供时应保持为空,平台会跳过设备日上限而不是伪造设备。
- `paid_at_ms` 由扣费成功的账务事实产生,`user_registered_at_ms` 从用户主数据读取,不使用幸运礼物接口的收包时间或用户首次抽奖时间代替。
- 如果对接 App 自身使用请求签名,签名内容必须覆盖这些字段;重试时必须保持原始业务事实不变。
## 4. 发送幸运礼物
### 4.1 请求
```http
POST /api/v1/lucky-gifts/send HTTP/1.1
Host: lucky-api.global-interaction.com
Content-Type: application/json
X-Request-Id: trace-20260715-000001
```
`X-Request-Id` 为可选的链路追踪 ID。如果不传平台会自动生成。它只用于日志排查不是抽奖幂等键。
### 4.2 请求体
```json
{
"app_code": "partner_app",
"request_id": "gift-order-20260715-000001",
"external_user_id": "user-10001",
"device_id": "device-installation-7f80d90b",
"gift_count": 3,
"unit_amount": 100,
"paid_at_ms": 1784073590000,
"user_registered_at_ms": 1783814400000,
"currency": "COIN",
"metadata": {
"external_order_id": "order-1000001",
"gift_id": "lucky-rose",
"room_id": "room-20001"
}
}
```
### 4.3 字段说明
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---:|---|---|
| `app_code` | string | 是 | 去除首尾空格后 132 字符 | 平台分配的 App 编码;按小写存储和校验。 |
| `request_id` | string | 是 | 去除首尾空格后 1128 字符 | 对接 App 生成的业务幂等 ID建议直接使用送礼订单号或 UUID。 |
| `external_user_id` | string | 是 | 去除首尾空格后 1128 字符 | 用户在对接 App 中的稳定唯一 ID。 |
| `device_id` | string | 否 | 空或去除首尾空格后 1128 字符 | 提供可信稳定值时启用设备日返奖上限;缺失时只跳过设备维度,其他返奖上限继续生效。不得每次请求重新生成。 |
| `gift_count` | int64 | 是 | `> 0` | 本次已扣费的礼物数量。 |
| `unit_amount` | int64 | 是 | `> 0` | 单个礼物的金币整数价格。 |
| `paid_at_ms` | int64 | `dynamic_v3` 是 | `> 0`Unix epoch milliseconds | 对接 App 账务系统完成本次扣费的真实 UTC 时间;系统不会用接口收到请求的时间代替。 |
| `user_registered_at_ms` | int64 | `dynamic_v3` 业务必填 | `> 0`Unix epoch milliseconds | 用户账号的真实创建时间,必须从对接 App 的用户主数据获取。技术上缺失不会阻断普通抽奖,但系统会按不满足注册 48 小时处理,关闭该用户的 RTP 补偿大奖资格。 |
| `currency` | string | 否 | 只支持 `COIN` | 缺省或空字符串时按 `COIN` 处理。 |
| `metadata` | object | 否 | JSON object | 对接方的展示或排查元数据;不参与金额、规则或幂等判定。 |
服务端按下式计算本次金额:
```text
total_amount = gift_count * unit_amount
```
请求体不得传入 `total_amount``pool_id``total_amount` 由系统根据数量和单价计算,奖池由平台已发布的规则选择。接口会拒绝所有未定义的顶层字段。
`dynamic_v3` 下,缺失 `paid_at_ms` 会使整次抽奖失败;缺失 `device_id` 不阻断开奖,只跳过设备日返奖上限,用户、房间、主播、单次等其他上限仍生效。缺失、不合理或尚未满 48 小时的 `user_registered_at_ms` 不阻断普通抽奖,但 RTP 补偿大奖资格会严格按“不符合”处理。
一个请求只返回一个聚合抽奖结果;`gift_count` 不会产生多个独立响应。
### 4.4 成功响应
HTTP Status`200 OK`
```json
{
"code": 0,
"message": "ok",
"request_id": "trace-20260715-000001",
"data": {
"draw_id": "external_lucky_draw_xxxxxxxxxx",
"request_id": "gift-order-20260715-000001",
"app_code": "partner_app",
"external_user_id": "user-10001",
"gift_count": 3,
"unit_amount": 100,
"total_amount": 300,
"reward_amount": 600,
"multiplier_ppm": 2000000,
"reward_status": "granted",
"rule_version": 1,
"created_at_ms": 1784073600000
}
}
```
### 4.5 响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` | int | 业务结果码;`0` 表示请求成功。 |
| `message` | string | 结果说明。 |
| 外层 `request_id` | string | HTTP 链路追踪 ID来自 `X-Request-Id` 或由平台生成。 |
| `data.draw_id` | string | 平台生成的唯一抽奖 ID对账时使用。 |
| `data.request_id` | string | 请求体中的业务幂等 ID。 |
| `data.app_code` | string | 归一化后的 App 编码。 |
| `data.external_user_id` | string | 对接 App 的用户 ID。 |
| `data.gift_count` | int64 | 礼物数量。 |
| `data.unit_amount` | int64 | 单件礼物金币价格。 |
| `data.total_amount` | int64 | 服务端计算的已扣费总额。 |
| `data.reward_amount` | int64 | 对接 App 应向用户发放的金币整数金额;以该字段为最终结算依据。 |
| `data.multiplier_ppm` | int64 | 倍率 ppm`1000000` 表示 1x`2000000` 表示 2x。 |
| `data.reward_status` | string | 当前为 `granted`,表示抽奖已完成且结果已交给对接 App 处理。 |
| `data.rule_version` | int64 | 本次抽奖命中的规则版本。 |
| `data.created_at_ms` | int64 | 抽奖结果创建时间Unix epoch millisecondsUTC。 |
未中奖时 `reward_amount``multiplier_ppm``0`。当前 JSON 序列化会省略数值为 `0` 的结果字段,对接方必须将缺失的 `reward_amount``multiplier_ppm``0` 处理,不得将字段缺失视为请求失败。
## 5. 幂等与重试
幂等键固定为:
```text
app_code + request_id
```
- 首次请求成功后,平台保存抽奖结果。
- 使用相同 `app_code + request_id` 重试时,直接返回首次结果,不会重新抽奖。
- 同一 `request_id` 不得对应不同用户、数量或金额。平台在幂等命中时会返回首次结果,不会用新参数覆盖。
- 超时、连接中断或 HTTP 5xx 时,使用原请求体和原 `request_id` 重试。
- 客户端超时建议设置为大于网关内部 3 秒处理超时,并对重试使用有上限的退避。
如果多次重试后仍无法确认结果,请保留 `app_code``request_id`、外层 `request_id` 和扣费流水,由平台根据抽奖记录对账。
## 6. 错误响应
### 6.1 统一格式
```json
{
"code": 40000,
"message": "request_id is required",
"request_id": "trace-20260715-000001"
}
```
### 6.2 错误码
| HTTP Status | `code` | 场景 | 对接方动作 |
|---:|---:|---|---|
| 400 | 40000 | JSON 无效、出现未定义字段、必填字段缺失、金额非正数或币种不支持 | 修正请求,不要原样重试。 |
| 403 | 40300 | `app_code` 未加入当前环境白名单 | 联系平台开通或检查环境。 |
| 404 | 无统一业务码 | 请求路径或 HTTP 方法错误 | 检查 Base URL、路径和请求方法。 |
| 500 | 50000 | 抽奖服务超时、内部处理失败,或 `dynamic_v3` 在规则层发现 `paid_at_ms` 缺失 | 先本地校验 V3 扣费事实已齐全;事实齐全时保持原 `request_id` 重试,不得重新扣费。 |
业务成功必须同时满足 HTTP Status 为 `200` 且响应 `code``0`
## 7. cURL 示例
```bash
curl --request POST \
'https://lucky-api.global-interaction.com/api/v1/lucky-gifts/send' \
--header 'Content-Type: application/json' \
--header 'X-Request-Id: trace-20260715-000001' \
--data '{
"app_code": "partner_app",
"request_id": "gift-order-20260715-000001",
"external_user_id": "user-10001",
"device_id": "device-installation-7f80d90b",
"gift_count": 3,
"unit_amount": 100,
"paid_at_ms": 1784073590000,
"user_registered_at_ms": 1783814400000,
"currency": "COIN",
"metadata": {
"external_order_id": "order-1000001",
"gift_id": "lucky-rose",
"room_id": "room-20001"
}
}'
```
## 8. 标准处理流程
1. 对接 App 在已鉴权的服务端上确定 `external_user_id`、稳定 `device_id` 和用户主数据中的 `user_registered_at_ms`
2. 对接 App 生成全局唯一的 `request_id`
3. 对接 App 在自有账务系统扣除 `gift_count * unit_amount`,并以扣费成功事实的 UTC 毫秒时间作为 `paid_at_ms`
4. 对接 App 调用幸运礼物接口,一次传入上述业务事实。
5. 平台按 `app_code + request_id` 创建或返回已有抽奖结果。
6. 对接 App 在响应成功后按 `reward_amount` 入账,并保存 `draw_id`
7. 任何未知结果都使用原 `request_id` 和原始业务事实重试;禁止重复扣费或重新生成抽奖业务号。
## 9. 联调检查清单
- [ ] `app_code` 已加入幸运礼物网关白名单。
- [ ] 规则已启用,并确认金币单位一致。
- [ ] `dynamic_v3` 请求中的 `paid_at_ms` 来自真实扣费事实,不是当前时间的临时填充值。
- [ ] `device_id` 已绑定已鉴权设备且跨请求稳定,不用请求号或会话 token 代替。
- [ ] `user_registered_at_ms` 来自用户主数据已验证缺失时普通抽奖可继续、RTP 补偿大奖资格会关闭。
- [ ] 调用能力仅保留在对接 App 服务端,上述业务事实已纳入自身鉴权或请求签名边界。
- [ ] 正常送礼可获得 HTTP 200 / `code=0`
- [ ] 重复发送相同 `request_id` 可获得相同 `draw_id` 和结果。
- [ ] 模拟超时后只重试抽奖请求,不重复扣费。
- [ ] 未中奖响应中缺失的数值字段可正确按 `0` 处理。
- [ ] 奖励按 `reward_amount` 在外部 App 入账,并持久化 `draw_id`
- [ ] 上线前完成小额真实扣费、抽奖、返奖和幂等重试验收。