# 幸运礼物 Flutter 对接 本文只描述 App 需要接入的 HTTP、送礼返回、错误处理和腾讯云 IM 自定义消息。所有接口响应都使用 gateway 业务 envelope: ```json { "code": "OK", "message": "", "request_id": "req_xxx", "data": {} } ``` ## 1. 送礼前检查 `POST /api/v1/activities/lucky-gifts/check` 请求头: | 参数 | 必填 | 说明 | | --- | --- | --- | | `Authorization` | 是 | `Bearer ` | | `X-App-Code` | 否 | 不传时使用默认 App | 请求体: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `room_id` | string | 是 | 当前房间 ID | | `gift_id` | string | 是 | 实际送出的礼物 ID,例如 `rose` | | `pool_id` | string | 是 | 幸运礼物奖池 ID,例如 `lucky`、`super_lucky`;当前 check 接口必填 | 返回 `data`: | 字段 | 类型 | 说明 | | --- | --- | --- | | `enabled` | bool | 是否可按幸运礼物发送 | | `reason` | string | `enabled`、`not_configured`、`disabled` | | `room_id` | string | 请求房间 ID | | `gift_id` | string | 请求礼物 ID | | `pool_id` | string | 生效奖池 ID | | `gift_price` | int | 单次抽奖价格,金币 | | `rule_version` | int | 后端规则版本 | | `target_rtp_ppm` | int | RTP 目标,ppm | | `experience_pool` | string | `novice`、`intermediate`、`advanced` | 客户端处理: - `enabled=true`:允许用户继续点击送礼。 - `enabled=false`:不要走幸运礼物送礼;可降级普通礼物或提示当前不可用。 - 检查结果只用于展示和拦截,最终仍以送礼接口为准。 ## 2. 发送礼物 `POST /api/v1/rooms/gift/send` 请求体关键字段: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `room_id` | string | 是 | 房间 ID | | `command_id` | string | 是 | 本次送礼动作幂等键;同一次超时重试必须复用 | | `target_user_id` | int/string | 兼容 | 旧单目标字段;新客户端优先使用 `target_user_ids` | | `target_user_ids` | int[] | 是 | 当前 `target_type=user` 时必须且只能 1 个 | | `target_type` | string | 否 | 当前只支持 `user`,为空按 `user` | | `gift_id` | string | 是 | 礼物 ID,例如 `rose` | | `gift_count` | int | 是 | 数量,必须大于 0 | | `pool_id` | string | 幸运礼物必填 | `lucky` 或 `super_lucky` | 返回 `data.lucky_gift`: | 字段 | 类型 | 说明 | | --- | --- | --- | | `draw_id` | string | 抽奖 ID,客户端去重锚点 | | `command_id` | string | 送礼命令 ID | | `pool_id` | string | 生效奖池 | | `gift_id` | string | 礼物 ID | | `rule_version` | int | 抽奖规则版本 | | `experience_pool` | string | 体验池 | | `selected_tier_id` | string | 命中奖档 | | `multiplier_ppm` | int | 倍率,`2000000` 表示 2 倍 | | `base_reward_coins` | int | RTP 基础返奖 | | `room_atmosphere_reward_coins` | int | 房间气氛池返奖 | | `activity_subsidy_coins` | int | 活动补贴返奖 | | `effective_reward_coins` | int | 用户实际获得金币 | | `reward_status` | string | 有返奖或阶段反馈时初始通常为 `pending`,到账和房间 IM 由后端 outbox 补偿 | | `stage_feedback` | bool | 是否阶段反馈,可播放轻量中奖表现 | | `high_multiplier` | bool | 是否高倍 | | `created_at_ms` | int | UTC epoch ms | 客户端处理: - `data.lucky_gift != null`:立即调用 `_applyLuckyGiftResult()`,播放中奖或阶段反馈表现。 - `effective_reward_coins > 0 && reward_status == "pending"`:展示中奖金额和“奖励处理中”,余额不要本地相加。 - `effective_reward_coins > 0 && reward_status == "granted"`:可以展示已到账状态;余额仍以 `WalletBalanceChanged` 或余额刷新为准。 - `data.lucky_gift == null`:按普通礼物表现处理。 ## 3. 错误处理 | code | 场景 | 客户端处理 | | --- | --- | --- | | `INVALID_ARGUMENT` | 参数缺失、数量非法、`pool_id` 缺失、`target_user_ids` 非单目标 | 停止提交,提示用户重试或刷新礼物面板 | | `UNAUTHENTICATED` | token 失效 | 重新登录 | | `INSUFFICIENT_BALANCE` | 金币不足 | 打开充值入口或提示余额不足 | | `RULE_NOT_ACTIVE` | 幸运礼物规则未启用 | 降级普通礼物或刷新礼物配置 | | `UPSTREAM_ERROR`/`UNAVAILABLE` | 内部服务或 IM/钱包临时不可用 | 不重复扣本地余额;允许用户稍后重试 | | `IDEMPOTENCY_CONFLICT`/`CONFLICT` | 同一 `command_id` 对应的请求参数变化,或规则/奖池/风控冲突 | 参数变化时重新生成 `command_id`;规则冲突时刷新礼物面板 | 送礼失败时后端不会返回 `lucky_gift`,客户端不要播放中奖 UI。送礼成功但返奖到账延迟时,房间 IM 和钱包 IM 会继续补偿。 幂等规则: - HTTP 超时、断网、502:同一次用户动作使用原 `command_id` 重试。 - 已收到成功响应或钱包余额已扣减:不要生成新 `command_id` 再发同一笔礼物。 - 需要重新点击送礼:生成新的 `command_id`。 ## 4. 房间 IM 腾讯云 IM 群自定义消息: | 字段 | 值 | | --- | --- | | `MsgType` | `TIMCustomElem` | | `Desc` | `lucky_gift_drawn` | | `Ext` | `room_system_message` | | `Data` | JSON 字符串 | `Data` 示例: ```json { "event_type": "lucky_gift_drawn", "event_id": "lucky_gift_drawn:lucky_draw_xxx", "draw_id": "lucky_draw_xxx", "command_id": "cmd_xxx", "pool_id": "super_lucky", "room_id": "room_1", "gift_id": "rose", "gift_count": 1, "user_id": 42, "sender_user_id": 42, "target_user_id": 99, "coin_spent": 100, "rule_version": 1, "experience_pool": "novice", "selected_tier_id": "novice_2x", "multiplier_ppm": 2000000, "base_reward_coins": 200, "room_atmosphere_reward_coins": 0, "activity_subsidy_coins": 0, "effective_reward_coins": 200, "stage_feedback": true, "high_multiplier": false, "created_at_ms": 1760000000000 } ``` 处理规则: - 只处理 `Desc == "lucky_gift_drawn"` 且 `event_type == "lucky_gift_drawn"`。 - 用 `event_id` 或 `draw_id` 去重;同一抽奖不要重复播放。 - 当前用户已通过 HTTP 送礼响应播放过同一 `draw_id` 时,收到 IM 只更新状态,不重复播放。 - `effective_reward_coins > 0` 但还没有收到钱包余额 IM 时,只展示中奖结果和处理中状态,不本地加金币。 ## 5. 钱包余额 IM 中奖金币到账后,wallet-service 会写 `WalletBalanceChanged`,notice-service 通过腾讯云 IM C2C 投递。 | 字段 | 值 | | --- | --- | | `MsgType` | `TIMCustomElem` | | `Desc` | `WalletBalanceChanged` | | `Ext` | `wallet_notice` | 客户端处理: - 只处理 `Desc == "WalletBalanceChanged"`。 - 用 `event_id` 去重。 - 当 `asset_type == "COIN"` 时,用 `available_after` 更新金币余额;字段缺失时调用 `GET /api/v1/wallet/me/balances` 刷新。 ## 6. 接入顺序 1. 礼物面板拿到 `pool_id` 后调用 check。 2. check 可用后调用送礼接口,并把 `pool_id` 放入请求。 3. 送礼响应含 `data.lucky_gift` 时立即播放本机中奖 UI。 4. 房间内监听 `lucky_gift_drawn`,给其他用户同步中奖 UI。 5. 私聊监听 `WalletBalanceChanged`,更新金币余额。