# 新版 VIP 权限策略 Flutter 客户端对接文档 ## 1. VIP 权限策略 ### 1.1 权限来源 - VIP 等级只用于套餐名称、价格、有效期和等级展示。 - Flutter 只使用 `GET /api/v1/vip/me` 返回的 `data.state.effective_benefits` 判断当前用户是否具有某项权益。 - Flutter 不得使用 `VIP等级 >= N`、`unlock_level`、`app_code` 或本地权益表推导权限。 - `/api/v1/vip/packages` 中的 `packages[].benefits` 是该套餐当前配置的完整权益集合,只用于套餐页和升级引导,不能作为用户已拥有权益。 - 写操作最终以对应业务接口的服务端鉴权结果为准。Flutter 的权益判断只用于入口显隐、锁态和交互预判。 ### 1.2 Flutter 权益状态 ```dart class VipEntitlementState { final VipProgramConfig program; final VipIdentity effectiveVip; final String effectiveSource; // paid / trial / none final Map benefitsByCode; final VipUserSettings settings; final int evaluatedAtMs; bool has(String benefitCode) { return program.status == 'active' && effectiveVip.active && benefitsByCode[benefitCode]?.status == 'active'; } bool allowsRoomEntryNotice() { return has('room_entry_notice') && settings.roomEntryNoticeEnabled; } bool allowsOnlineGlobalNotice() { return has('online_global_notice') && settings.onlineGlobalNoticeEnabled; } bool allowsHideProfileData() { return has('hide_profile_data') && settings.hideProfileDataEnabled; } bool allowsAnonymousProfileVisit() { return has('anonymous_profile_visit') && settings.anonymousProfileVisitEnabled; } bool allowsLeaderboardInvisible() { return has('leaderboard_invisible') && settings.leaderboardInvisibleEnabled; } } ``` `benefitsByCode` 必须由 `state.effective_benefits` 转换。未知 `benefit_code` 保留在状态中但不自动开放功能,不得导致解析失败或页面崩溃。 ### 1.3 权益刷新 | 场景 | Flutter 处理 | |---|---| | 登录成功、切换账号、切换 App | 清空原 VIP 状态,调用 `/vip/me` | | App 冷启动、回到前台 | 调用 `/vip/me` 校准过期、体验卡回落和后台配置变更 | | 进入 VIP 页面 | 并发调用 `/vip/packages` 和 `/vip/me` | | 购买、佩戴体验卡、卸下体验卡成功 | 使用响应中的 `state` 原子替换本地状态 | | 收到 `vip_notice` | 按 `event_id` 去重,再调用 `/vip/me` | | `config_version` 不一致 | 用户权限以 `/vip/me.state` 为准,重新请求 `/vip/packages`,不得合并不同版本的权益数组 | | VIP 到期倒计时结束 | 可更新页面显示,但执行功能前仍以服务端接口结果为准 | 购买、佩戴体验卡、卸下体验卡成功时,`data.state` 与 `GET /api/v1/vip/me` 的 `data.state` **完全同构**,不是局部补丁。Flutter 必须整体替换 VIP store,禁止按字段合并。若灰度期间收到缺少下文任一顶层字段的旧节点响应,应放弃该响应中的 `state` 并立即重拉 `/vip/me`。 ### 1.4 Fami 初始等级权益 下表只用于识别初始配置。Flutter 实际必须渲染 `/vip/packages` 返回的数据。 | 等级 | 本级初始新增权益 | |---|---| | VIP1 | `vip_badge_identity`、`vip_medal`、`avatar_frame`、`vip_title` | | VIP2 | `chat_bubble`、`vip_gift`、`entry_effect`、`visitor_history` | | VIP3 | `voice_wave`、`profile_card`、`custom_room_background`、`room_image_message` | | VIP4 | `animated_avatar`、`animated_room_cover`、`mic_skin`、`room_border`、`vehicle` | | VIP5 | `colored_room_name`、`colored_nickname`、`colored_id`、`gift_tray_skin` | | VIP6 | `hide_profile_data`、`custom_avatar_frame`、`leaderboard_invisible`、`daily_coin_rebate` | | VIP7 | `custom_profile_card`、`anonymous_profile_visit`、`custom_gift` | | VIP8 | `room_entry_notice`、`custom_pretty_id`、`custom_vehicle` | | VIP9 | `anti_kick`、`anti_mute`、`online_global_notice` | ## 2. 通用协议 - API 前缀:`/api/v1`。 - 请求头:`Authorization: Bearer `。 - `app_code`、`user_id` 从登录态解析,Flutter 不传。 - 时间字段统一使用 Unix epoch milliseconds。 - 业务切日使用 UTC,Flutter 仅在展示时转换本地时间。 - 成功响应统一格式: ```json { "code": "OK", "message": "ok", "request_id": "req_xxx", "data": {} } ``` - `request_id` 只用于链路追踪。 - 带 `command_id` 的接口,网络重试必须复用原 `command_id`;新的业务动作必须生成新值。 ### 2.1 `VipState` 唯一结构 `GET /vip/me`、`GET /vip/packages`、购买、佩戴体验卡和卸下体验卡返回的 `state` 使用同一结构,固定包含以下 8 个顶层字段: | 字段 | 含义 | |---|---| | `paid_vip` | 当前付费 VIP;没有时为 `null` | | `equipped_trial_card` | 当前佩戴的体验卡;没有时为 `null` | | `effective_vip` | 付费 VIP 与体验卡合并后的当前展示身份 | | `effective_source` | `paid`、`trial`、`none` | | `effective_benefits` | 服务端计算后的最终权益集合 | | `evaluated_at_ms` | 本次状态计算时间 | | `program_config` | 本次计算使用的完整 App VIP 配置 | | `user_settings` | 当前 App 下完整的 5 项用户开关 | `paid_vip`、`effective_vip` 非空时都返回完整 `UserVip` 字段:`user_id`、`level`、`name`、`active`、`started_at_ms`、`expires_at_ms`、`updated_at_ms`、`program_type`、`config_version`。`program_config` 返回完整字段,不允许写接口只返回 `app_code/program_type` 的裁剪对象。 ### 2.2 特权展示协议 每项 `VipBenefit` 除执行字段外提供结构化 `presentation`,Flutter 不解析 `metadata_json`,也不按 `benefit_code` 维护隐形展示配置: ```json { "description": "进入房间时展示动态背景和用户头像", "preview_items": [ { "preview_id": "room_background_gold", "title": "鎏金房间背景", "media_type": "animation", "asset_url": "https://cdn.example/vip/room-bg.png", "preview_url": "https://cdn.example/vip/room-bg-preview.png", "animation_url": "https://cdn.example/vip/room-bg.svga", "composition_type": "user_avatar_center", "sort_order": 10 } ], "numeric_reward": null } ``` - `media_type`:`image` 或 `animation`;动态资源加载失败时依次使用 `preview_url`、`asset_url`。 - `composition_type`:`none`、`user_avatar_center`、`user_avatar_waveform`,分别表示无合成、头像居中、头像与声波纹合成。 - `preview_items` 可返回多项,按 `sort_order` 升序、`preview_id` 去重。 - `numeric_reward` 用于数值权益,字段为 `label/value/unit/period`,其中 `period` 为 `once/daily/monthly`。该字段只用于展示,结算仍以后端接口为准。 - 没有富预览时 `presentation` 为 `null`;Flutter 回退展示权益名称和通用图标。 ## 3. VIP 套餐 ### 接口地址 `GET /api/v1/vip/packages` ### 参数 无。 ### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_packages", "data": { "current_vip": { "level": 3, "name": "VIP3", "active": true, "started_at_ms": 1780000000000, "expires_at_ms": 1782592000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "packages": [ { "level": 4, "name": "VIP4", "status": "active", "price_coin": 4000, "duration_ms": 2592000000, "reward_resource_group_id": 0, "reward_items": [], "can_purchase": true, "sort_order": 40, "recharge_gate_required": false, "required_recharge_coin_amount": 0, "user_recharge_coin_amount": 0, "purchase_locked_reason": "", "benefits": [ { "benefit_code": "custom_room_background", "name": "自定义房间背景", "benefit_type": "function", "unlock_level": 3, "status": "active", "trial_enabled": true, "resource_id": 0, "resource_type": "", "execution_scope": "room", "auto_equip": false, "sort_order": 110, "metadata_json": "", "presentation": { "description": "进入房间时展示动态背景和用户头像", "preview_items": [ { "preview_id": "room_background_gold", "title": "鎏金房间背景", "media_type": "animation", "asset_url": "https://cdn.example/vip/room-bg.png", "preview_url": "https://cdn.example/vip/room-bg-preview.png", "animation_url": "https://cdn.example/vip/room-bg.svga", "composition_type": "user_avatar_center", "sort_order": 10 } ], "numeric_reward": null } } ], "config_version": 7 } ], "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "state": { "paid_vip": { "user_id": 10001, "level": 3, "name": "VIP3", "active": true, "started_at_ms": 1780000000000, "expires_at_ms": 1782592000000, "updated_at_ms": 1780000000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "equipped_trial_card": null, "effective_vip": { "user_id": 10001, "level": 3, "name": "VIP3", "active": true, "started_at_ms": 1780000000000, "expires_at_ms": 1782592000000, "updated_at_ms": 1780000000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "effective_source": "paid", "effective_benefits": [ { "benefit_code": "custom_room_background", "status": "active" } ], "evaluated_at_ms": 1780000000000, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "user_settings": { "app_code": "fami", "user_id": 10001, "room_entry_notice_enabled": true, "online_global_notice_enabled": true, "hide_profile_data_enabled": true, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true, "updated_at_ms": 0 } } } } ``` ## 4. 当前 VIP 权限状态 ### 接口地址 `GET /api/v1/vip/me` ### 参数 无。 ### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_vip_me", "data": { "level": 8, "name": "VIP8", "active": true, "started_at_ms": 1780100000000, "expires_at_ms": 1782692000000, "program_type": "tiered_privilege_v1", "config_version": 7, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "state": { "paid_vip": { "user_id": 10001, "level": 3, "name": "VIP3", "active": true, "started_at_ms": 1780000000000, "expires_at_ms": 1782592000000, "updated_at_ms": 1780000000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "equipped_trial_card": { "trial_card_id": "vip_trial_card_xxx", "entitlement_id": "ent_xxx", "resource_id": 108, "user_id": 10001, "level": 8, "name": "VIP8", "status": "active", "equipped": true, "duration_ms": 2592000000, "effective_at_ms": 1780100000000, "expires_at_ms": 1782692000000, "remaining_duration_ms": 2591000000, "grant_source": "admin", "source_grant_id": "grant_xxx", "created_at_ms": 1780100000000, "updated_at_ms": 1780100000000 }, "effective_vip": { "user_id": 10001, "level": 8, "name": "VIP8", "active": true, "started_at_ms": 1780100000000, "expires_at_ms": 1782692000000, "updated_at_ms": 1780100000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "effective_source": "trial", "effective_benefits": [ { "benefit_code": "room_entry_notice", "name": "进房高亮通知", "benefit_type": "function", "status": "active", "trial_enabled": true, "resource_id": 0, "resource_type": "", "execution_scope": "room", "auto_equip": false, "sort_order": 290, "metadata_json": "", "presentation": null } ], "evaluated_at_ms": 1780101000000, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "user_settings": { "app_code": "fami", "user_id": 10001, "room_entry_notice_enabled": true, "online_global_notice_enabled": true, "hide_profile_data_enabled": true, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true, "updated_at_ms": 0 } } } } ``` - `effective_source`:`paid`、`trial`、`none`。 - 当前展示身份只读 `state.effective_vip`。 - 当前权限只读 `state.effective_benefits`。 - `daily_coin_rebate` 不会因体验卡进入 `effective_benefits`。 ## 5. 购买、续期、升级 ### 接口地址 `POST /api/v1/vip/purchase` ### 参数 ```json { "command_id": "vip_purchase_10001_4_1780100000000", "level": 4 } ``` | 字段 | 必填 | 说明 | |---|---|---| | `command_id` | 是 | 最长 128 字节,同一次购买重试必须复用 | | `level` | 是 | 必须来自 `/vip/packages` | ### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_purchase", "data": { "order_id": "vip_order_xxx", "transaction_id": "tx_xxx", "vip": { "level": 4, "name": "VIP4", "active": true, "started_at_ms": 1780100000000, "expires_at_ms": 1782692000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "coin_spent": 4000, "coin_balance_after": 6000, "coin_balance": { "asset_type": "COIN", "available_amount": 6000, "frozen_amount": 0, "version": 18 }, "reward_items": [], "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "state": { "paid_vip": { "user_id": 10001, "level": 4, "name": "VIP4", "active": true, "started_at_ms": 1780100000000, "expires_at_ms": 1782692000000, "updated_at_ms": 1780100000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "equipped_trial_card": null, "effective_vip": { "user_id": 10001, "level": 4, "name": "VIP4", "active": true, "started_at_ms": 1780100000000, "expires_at_ms": 1782692000000, "updated_at_ms": 1780100000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "effective_source": "paid", "effective_benefits": [ { "benefit_code": "custom_room_background", "status": "active" } ], "evaluated_at_ms": 1780100000000, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "user_settings": { "app_code": "fami", "user_id": 10001, "room_entry_notice_enabled": true, "online_global_notice_enabled": true, "hide_profile_data_enabled": true, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true, "updated_at_ms": 0 } } } } ``` - 同级购买:从原截止时间继续累加。 - Fami 高等级购买:立即替换低等级,从购买时间重新计时,低等级剩余时间丢弃。 - 低等级购买:服务端拒绝。 - 购买时佩戴体验卡:付费 VIP 会更新,但 `effective_vip` 仍可能来自体验卡,必须使用响应 `state`。 - 钱包缓存只使用 `coin_balance`。应用时比较 `version`,低版本响应不得覆盖本地高版本余额;`coin_balance_after` 仅保留旧客户端兼容。 - 历史幂等订单在升级前没有记录版本时可能返回 `coin_balance.version=0`,Flutter 必须调用现有钱包余额接口刷新,不得把版本 0 写入余额 store。 ## 6. VIP 体验卡 ### 6.1 体验卡列表 #### 接口地址 `GET /api/v1/users/me/resources?resource_type=vip_trial_card` #### 参数 | 参数 | 位置 | 必填 | 值 | |---|---|---|---| | `resource_type` | query | 是 | `vip_trial_card` | #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_trial_cards", "data": { "items": [ { "entitlement_id": "ent_card_a", "resource_id": 101, "resource": { "resource_id": 101, "resource_code": "vip_trial_card_3", "resource_type": "vip_trial_card", "name": "VIP3体验卡", "status": "active", "asset_url": "", "preview_url": "", "animation_url": "", "metadata_json": "{\"vip_level\":3}", "sort_order": 30 }, "status": "active", "quantity": 1, "remaining_quantity": 1, "effective_at_ms": 1780100000000, "expires_at_ms": 1781309600000, "source_grant_id": "grant_a", "created_at_ms": 1780100000000, "updated_at_ms": 1780100000000, "equipped": false } ], "total": 1 } } ``` ### 6.2 佩戴体验卡 #### 接口地址 `POST /api/v1/vip/trial-cards/{entitlement_id}/equip` #### 参数 | 参数 | 位置 | 必填 | 说明 | |---|---|---|---| | `entitlement_id` | path | 是 | 使用体验卡列表中的实例 ID | 无 body。 #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_trial_equip", "data": { "trial_card": { "trial_card_id": "vip_trial_card_b", "entitlement_id": "ent_card_b", "resource_id": 102, "user_id": 10001, "level": 4, "name": "VIP4", "status": "active", "equipped": true, "duration_ms": 1728000000, "effective_at_ms": 1780200000000, "expires_at_ms": 1781928000000, "remaining_duration_ms": 1700000000, "grant_source": "admin", "source_grant_id": "grant_b", "created_at_ms": 1780200000000, "updated_at_ms": 1780200000000 }, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "state": { "paid_vip": { "user_id": 10001, "level": 3, "name": "VIP3", "active": true, "started_at_ms": 1780000000000, "expires_at_ms": 1782592000000, "updated_at_ms": 1780000000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "equipped_trial_card": { "trial_card_id": "vip_trial_card_b", "entitlement_id": "ent_card_b", "resource_id": 102, "user_id": 10001, "level": 4, "name": "VIP4", "status": "active", "equipped": true, "duration_ms": 1728000000, "effective_at_ms": 1780200000000, "expires_at_ms": 1781928000000, "remaining_duration_ms": 1700000000, "grant_source": "admin", "source_grant_id": "grant_b", "created_at_ms": 1780200000000, "updated_at_ms": 1780200000000 }, "effective_vip": { "user_id": 10001, "level": 4, "name": "VIP4", "active": true, "started_at_ms": 1780200000000, "expires_at_ms": 1781928000000, "updated_at_ms": 1780200000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "effective_source": "trial", "effective_benefits": [], "evaluated_at_ms": 1780228000000, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "user_settings": { "app_code": "fami", "user_id": 10001, "room_entry_notice_enabled": true, "online_global_notice_enabled": true, "hide_profile_data_enabled": true, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true, "updated_at_ms": 0 } }, "server_time_ms": 1780228000000 } } ``` ### 6.3 卸下体验卡 #### 接口地址 `DELETE /api/v1/vip/trial-cards/equipped` #### 参数 无。 #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_trial_unequip", "data": { "unequipped": true, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "state": { "paid_vip": { "user_id": 10001, "level": 3, "name": "VIP3", "active": true, "started_at_ms": 1780000000000, "expires_at_ms": 1782592000000, "updated_at_ms": 1780000000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "equipped_trial_card": null, "effective_vip": { "user_id": 10001, "level": 3, "name": "VIP3", "active": true, "started_at_ms": 1780000000000, "expires_at_ms": 1782592000000, "updated_at_ms": 1780000000000, "program_type": "tiered_privilege_v1", "config_version": 7 }, "effective_source": "paid", "effective_benefits": [], "evaluated_at_ms": 1780229000000, "program_config": { "app_code": "fami", "program_type": "tiered_privilege_v1", "level_count": 9, "same_level_expiry_policy": "extend_remaining", "upgrade_expiry_policy": "replace_from_now", "downgrade_purchase_policy": "reject", "benefit_inheritance_policy": "target_only", "grant_mode": "trial_card", "trial_card_enabled": true, "status": "active", "config_version": 7 }, "user_settings": { "app_code": "fami", "user_id": 10001, "room_entry_notice_enabled": true, "online_global_notice_enabled": true, "hide_profile_data_enabled": true, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true, "updated_at_ms": 0 } }, "server_time_ms": 1780229000000 } } ``` - 体验卡可自由切换,不比较等级高低。 - 切换和卸下不会暂停、延长或删除体验卡。 - 每张卡的剩余时间按绝对 `expires_at_ms` 计算。 - 佩戴和卸下成功后立即使用响应 `state` 替换本地 VIP 状态。 ## 7. VIP 功能开关 ### 7.1 查询开关 #### 接口地址 `GET /api/v1/vip/settings` #### 参数 无。 #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_settings_get", "data": { "settings": { "app_code": "fami", "user_id": 10001, "room_entry_notice_enabled": true, "online_global_notice_enabled": false, "hide_profile_data_enabled": true, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true, "updated_at_ms": 1780300000000 }, "evaluated_at_ms": 1780300001000 } } ``` ### 7.2 修改开关 #### 接口地址 `PATCH /api/v1/vip/settings` #### 参数 局部更新,至少传一个字段: ```json { "room_entry_notice_enabled": false, "online_global_notice_enabled": true, "hide_profile_data_enabled": false, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true } ``` #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_settings_patch", "data": { "settings": { "app_code": "fami", "user_id": 10001, "room_entry_notice_enabled": false, "online_global_notice_enabled": true, "hide_profile_data_enabled": false, "anonymous_profile_visit_enabled": true, "leaderboard_invisible_enabled": true, "updated_at_ms": 1780300010000 }, "server_time_ms": 1780300010000 } } ``` 5 个字段都使用 PATCH 语义:不传表示保持原值,显式 `false` 表示关闭。没有持久记录时均默认 `true`;设置只限制用户主动启用对应效果,不会单独授予 VIP 权益。服务端执行时使用“当前有效权益 AND 用户设置”判定,Flutter 不能只看开关。 ## 8. 每日 VIP 金币返现 ### 8.1 查询当前返现 #### 接口地址 `GET /api/v1/vip/coin-rebates/current` #### 参数 无。 #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_rebate_current", "data": { "found": true, "rebate": { "rebate_id": "vip_rebate_xxx", "user_id": "10001", "task_day": "2026-07-15", "vip_level": 6, "vip_name": "VIP6", "coin_amount": 100, "status": "claimable", "available_at_ms": 1784073600000, "expires_at_ms": 1784160000000, "config_version": 8, "claimed_at_ms": 0, "wallet_transaction_id": "", "created_at_ms": 1784073605000, "updated_at_ms": 1784073605000 }, "server_time_ms": 1784073610000 } } ``` 无资格时 `found=false`、`rebate=null`。 ### 8.2 批量查询返现状态 #### 接口地址 `POST /api/v1/vip/coin-rebates/statuses` #### 参数 ```json { "rebate_ids": ["vip_rebate_xxx", "vip_rebate_yyy"], "page": 1, "page_size": 20 } ``` - `rebate_ids` 最多 100 个。 - 不传 `rebate_ids` 时按 `page/page_size` 查询历史。 - `page_size` 最大 100。 #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_rebate_statuses", "data": { "rebates": [ { "rebate_id": "vip_rebate_xxx", "task_day": "2026-07-15", "vip_level": 6, "vip_name": "VIP6", "coin_amount": 100, "status": "claimable", "available_at_ms": 1784073600000, "expires_at_ms": 1784160000000, "claimed_at_ms": 0, "wallet_transaction_id": "" } ], "total": 1, "server_time_ms": 1784073610000 } } ``` ### 8.3 领取返现 #### 接口地址 `POST /api/v1/vip/coin-rebates/{rebate_id}/claim` #### 参数 | 参数 | 位置 | 必填 | 说明 | |---|---|---|---| | `rebate_id` | path | 是 | 从 current、statuses 或系统消息取得 | ```json { "command_id": "vip_rebate_claim_10001_20260715" } ``` #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_rebate_claim", "data": { "rebate": { "rebate_id": "vip_rebate_xxx", "task_day": "2026-07-15", "vip_level": 6, "vip_name": "VIP6", "coin_amount": 100, "status": "claimed", "claimed_at_ms": 1784073700000, "wallet_transaction_id": "wallet_tx_xxx" }, "transaction_id": "wallet_tx_xxx", "coin_balance": { "asset_type": "COIN", "available_amount": 20100, "frozen_amount": 0, "version": 18 }, "server_time_ms": 1784073700000 } } ``` ## 9. 上线全服通知 ### 接口地址 `POST /api/v1/vip/online-notice` ### 参数 ```json { "command_id": "vip_online__2026-07-15" } ``` - `command_id` 最长 128 字节。 - 同一进程同一 UTC 日复用同一个 `command_id`。 - `process_boot_id` 只保存在内存中,不写磁盘。 ### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_online_notice", "data": { "event_id": "vip_online_notice:10001:sha256_digest", "group_id": "hy_fami_bc_g_v2", "status": "pending", "created": true, "evaluated_at_ms": 1784073610000 } } ``` - `status=pending` 表示事件已进入异步投递链路。 - 幂等命中时 `created=false`,不是失败。 - 同一进程每天触发一次;杀掉进程后新进程当天可以再次触发。 ## 10. 进房 VIP 通知 ### 接口地址 `POST /api/v1/rooms/join` ### 参数 ```json { "room_id": "room_1001", "command_id": "join_10001_room_1001_1784073600000", "role": "audience", "password": "", "response_mode": "full" } ``` ### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_join", "data": { "result": { "applied": true, "room_version": 18, "server_time_ms": 1784073600000 }, "room": {}, "viewer": {}, "seats": [], "profiles": [], "im": { "group_id": "room_1001", "need_join_group": true }, "effective_vip": { "program_type": "tiered_privilege_v1", "level": 8, "name": "VIP8", "room_entry_notice_enabled": true }, "server_time_ms": 1784073600000 } } ``` 本人进房首屏使用 HTTP 返回的 `data.effective_vip`。其他用户的进房效果使用当前房间 IM 消息。 ## 11. VIP 装扮资源 ### 11.1 我的资源列表 #### 接口地址 `GET /api/v1/users/me/resources?resource_type={resource_type}` #### 参数 | 参数 | 位置 | 必填 | 说明 | |---|---|---|---| | `resource_type` | query | 否 | `avatar_frame`、`profile_card`、`vehicle`、`chat_bubble` 等资源类型 | #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_resources", "data": { "items": [ { "entitlement_id": "ent_xxx", "resource_id": 108, "resource": { "resource_id": 108, "resource_code": "frame_vip9", "resource_type": "avatar_frame", "name": "VIP头像框", "status": "active", "asset_url": "https://cdn.example/frame.png", "preview_url": "https://cdn.example/frame-preview.png", "animation_url": "https://cdn.example/frame.svga", "metadata_json": "{}", "sort_order": 10 }, "status": "active", "expires_at_ms": 1786665600000, "equipped": true } ], "total": 1 } } ``` ### 11.2 佩戴资源 #### 接口地址 `POST /api/v1/users/me/resources/{resource_id}/equip` #### 参数 | 参数 | 位置 | 必填 | 说明 | |---|---|---|---| | `resource_id` | path | 是 | 资源 ID | 无 body。 ### 11.3 卸下资源 #### 接口地址 `DELETE /api/v1/users/me/resources/{resource_type}/unequip` #### 参数 | 参数 | 位置 | 必填 | 说明 | |---|---|---|---| | `resource_type` | path | 是 | 资源类型 | 无 body。 ### 11.4 当前装扮 #### 接口地址 - `GET /api/v1/appearance` - `GET /api/v1/users/by-id/{user_id}/appearance` #### 参数 第二个接口使用 path 参数 `user_id`。 #### 返回值 ```json { "code": "OK", "message": "ok", "request_id": "req_appearance", "data": { "user_id": "10001", "avatar_frame": {}, "profile_card": {}, "vehicle": {}, "mic_seat_animation": {}, "equipped_badges": [], "badges": [], "resources": [], "server_time_ms": 1784073600000 } } ``` ## 12. 相关 IM ### 12.1 VIP 激活通知 通道:腾讯云 IM C2C `TIMCustomElem`。 ```json { "Ext": "vip_notice", "Desc": "VipActivated", "Data": "{\"event_type\":\"VipActivated\",\"event_id\":\"evt_xxx\",\"app_code\":\"fami\",\"transaction_id\":\"tx_xxx\",\"command_id\":\"vip_purchase_10001_4_1780100000000\",\"user_id\":\"10001\",\"source\":\"vip_purchase\",\"action\":\"upgrade\",\"previous_level\":3,\"previous_expires_at_ms\":1781396000000,\"level\":4,\"vip_name\":\"VIP4\",\"started_at_ms\":1780100000000,\"expires_at_ms\":1782692000000,\"program_type\":\"tiered_privilege_v1\",\"config_version\":7,\"expiry_policy\":\"replace_from_now\",\"created_at_ms\":1780100000000}" } ``` Flutter 按 `event_id` 去重,然后调用 `GET /api/v1/vip/me`,不能直接使用 IM 内容生成权限。 ### 12.2 进房通知 通道:当前房间腾讯云 IM 群 `TIMCustomElem`,不发送到全服群。 ```json { "Ext": "room_system_message", "Desc": "room_user_joined", "Data": "{\"event_id\":\"evt_join_xxx\",\"room_id\":\"room_1001\",\"event_type\":\"room_user_joined\",\"actor_user_id\":10001,\"target_user_id\":10001,\"room_version\":18,\"role\":\"audience\",\"nickname\":\"Nina\",\"avatar\":\"https://cdn.example/avatar.png\",\"display_user_id\":\"100042\",\"pretty_display_user_id\":\"888042\",\"vip_program_type\":\"tiered_privilege_v1\",\"effective_vip_level\":\"8\",\"effective_vip_name\":\"VIP8\",\"room_entry_notice_enabled\":\"true\"}" } ``` 仅当 `event_type=room_user_joined` 且 `room_entry_notice_enabled=true` 时展示 VIP 进房效果。使用 `event_id` 去重。 ### 12.3 上线全服通知 通道:全局播报群 `TIMCustomElem`。 | 字段 | 值 | |---|---| | `Ext` | `im_broadcast` | | `Desc` | `vip_online_notice` | | `Data` | 下方 JSON 字符串 | | `CloudCustomData` | 与 `Data` 相同 | ```json { "event_id": "vip_online_notice:10001:sha256_digest", "broadcast_type": "vip_online_notice", "scope": "global", "app_code": "fami", "user_id": 10001, "sender_user_id": 10001, "nickname": "Nina", "avatar": "https://cdn.example/avatar.png", "avatar_frame_url": "https://cdn.example/frame.png", "avatar_frame_resource_id": 108, "avatar_frame_code": "frame_vip9", "avatar_frame_asset_url": "https://cdn.example/frame.png", "avatar_frame_preview_url": "https://cdn.example/frame-preview.png", "avatar_frame_animation_url": "https://cdn.example/frame-animation.svga", "display_user_id": "888042", "raw_display_user_id": "100042", "pretty_display_user_id": "888042", "vip_program_type": "tiered_privilege_v1", "vip_level": 9, "vip_name": "VIP9", "message": "欢迎进入Fami,祝你有美好的一天", "action": { "type": "none" }, "sent_at_ms": 1784073610500 } ``` Flutter 使用 `event_id` 去重,直接使用事件内的用户、VIP 和头像框快照渲染。 ### 12.4 金币返现系统消息 接口:`GET /api/v1/messages?section=system&page_size=20&page_token=`。 ```json { "message_id": "msg_vip_rebate_xxx", "title": "VIP金币返现", "summary": "尊敬的VIP用户,平台今日返现100金币,请及时领取。", "body": "尊敬的VIP用户,平台今日返现100金币,请及时领取。", "icon_url": "", "image_url": "", "action_type": "vip_coin_rebate_claim", "action_param": "{\"coin_amount\":100,\"expires_at_ms\":1784160000000,\"rebate_id\":\"vip_rebate_xxx\",\"task_day\":\"2026-07-15\"}", "read": false, "sent_at_ms": 1784073600000 } ``` Flutter 将 `action_param` 解析为 JSON,打开页面后调用 `/vip/coin-rebates/statuses` 获取真实领取状态。 ### 12.5 IM 登录与全局群 接口:`GET /api/v1/im/usersig`。 Flutter 使用返回的 UserSig 登录腾讯云 IM,并加入响应 `join_groups` 中 `type=global_broadcast` 的群组后,再消费 `vip_online_notice`。 ## 13. 稳定业务错误码 失败仍使用统一 envelope,Flutter 只判断 `code`,不得解析 `message`: ```json { "code": "VIP_INSUFFICIENT_COIN", "message": "insufficient coin balance", "request_id": "req_purchase", "data": null } ``` | HTTP | `code` | 场景 | Flutter 处理 | |---:|---|---|---| | 409 | `VIP_PROGRAM_INACTIVE` | 当前 App 的 VIP 体系未启用 | 关闭购买/体验卡操作并刷新配置 | | 409 | `VIP_PACKAGE_NOT_PURCHASABLE` | 套餐不存在、停用或不在当前体系范围 | 刷新 `/vip/packages` | | 409 | `VIP_DOWNGRADE_NOT_ALLOWED` | 购买等级低于当前付费等级 | 提示不可降级,刷新 `/vip/me` | | 409 | `VIP_RECHARGE_REQUIRED` | 未达到套餐充值门槛 | 展示充值引导 | | 409 | `VIP_INSUFFICIENT_COIN` | 购买金币余额不足 | 展示金币充值入口 | | 404 | `VIP_TRIAL_CARD_NOT_FOUND` | 卡不存在、不属于本人或背包权益已撤销 | 刷新体验卡列表和 `/vip/me` | | 409 | `VIP_TRIAL_CARD_EXPIRED` | 卡或背包权益已越过绝对截止时间 | 刷新体验卡列表和 `/vip/me` | | 403 | `VIP_BENEFIT_REQUIRED` | 操作需要的权益未生效或被用户关闭 | 刷新 `/vip/me`,保持入口锁态 | | 403 | `VIP_ANTI_KICK` | 普通房主/管理员踢人被目标用户防踢拦截 | 提示目标不可被踢出,不改变房间本地状态 | | 403 | `VIP_ANTI_MUTE` | 普通房主/管理员禁言被目标用户防禁言拦截 | 提示目标不可被禁言,不改变房间本地状态 | | 404 | `VIP_COIN_REBATE_NOT_FOUND` | 返现不存在或不属于当前用户/App | 刷新返现列表 | | 409 | `VIP_COIN_REBATE_EXPIRED` | 已越过 UTC 次日 0 点的领取结束边界 | 标记过期并刷新返现列表 | | 409 | `IDEMPOTENCY_CONFLICT` | 同一 `command_id` 被用于不同业务参数 | 停止重试,为新动作生成新 ID | | 409 | `LEDGER_CONFLICT` | 钱包并发版本冲突 | 刷新余额和 `/vip/me` 后由用户重新发起 | 返现重复领取采用幂等成功语义:相同 `command_id` 重试返回首次成功回执,不返回 `VIP_REBATE_ALREADY_CLAIMED`。因此客户端不应实现该错误分支。