hyapp-server/docs/新版VIP权限策略_Flutter客户端对接文档.md

1385 lines
39 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.

# 新版 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<String, VipBenefit> 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 <access_token>`
- `app_code``user_id` 从登录态解析Flutter 不传。
- 时间字段统一使用 Unix epoch milliseconds。
- 业务切日使用 UTCFlutter 仅在展示时转换本地时间。
- 成功响应统一格式:
```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_<process_boot_id>_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. 稳定业务错误码
失败仍使用统一 envelopeFlutter 只判断 `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`。因此客户端不应实现该错误分支。