# VIP / Credits Frontend API 本文档是前端接入会员、积分、权益和历史解锁的推荐接口说明。新版接入原则: - 前端权益状态只认 `GET /api/user/entitlements`。 - 游客和登录非会员权益一致,只按 `isVip` 区分会员/非会员。 - 支付完成后只刷新一次 `GET /api/user/entitlements`。 - `dolBalance` 是旧字段名,等价于 `creditBalance`,新代码优先使用 `creditBalance`。 ## 推荐调用链 ```text 打开充值/VIP 弹窗 -> GET /api/payment/plans -> POST /api/payment/create-order -> 打开 data.payParams.url / checkout_url / payment_url -> GET /api/payment/order-status 轮询,或等待 WebSocket payment_success -> GET /api/user/entitlements 刷新权益 ``` 不要再按订单类型分别调用 `/api/payment/vip-status` 和 `/api/user/credits` 刷新状态。 ## GET /api/user/entitlements 用途:当前用户 VIP、积分和权益快照。支持游客 token 和登录 token。 认证:需要 `Authorization: Bearer `,游客 token 也可以。 响应示例: ```json { "success": true, "data": { "userId": "user-1", "isGuest": false, "isVip": false, "vipExpiresAt": null, "creditBalance": 120, "dolBalance": 120, "policy": { "membershipState": "non_vip", "nonVipEntitlementsShared": true, "guestSameAsLoggedInNonVip": true, "refreshAfterPayment": "GET /api/user/entitlements" }, "costs": { "normal_message": 2, "private_message": 10, "voice_message": 20, "photo": 40, "voice_call_minute": 50, "private_album_10": 300, "private_album_20": 600 }, "quotas": { "normalChatFreeDaily": 30, "privateUnlockFreeDaily": 1, "voiceMessageFreeDaily": 0, "photoFreeDaily": 0 }, "historyUnlock": { "enabled": true, "order": "oldest_first", "chargeMode": "highest_cost_per_locked_message", "insufficientBalanceBehavior": "no_deduction", "costs": { "private_message": 10, "voice_message": 20, "photo": 40 } } } } ``` 前端使用: - App 启动、登录成功、游客 token 创建成功后调用。 - 支付成功、充值成功、历史解锁成功后调用。 - `isGuest` 只用于 UI 身份展示,不用于权益判断。 ## GET /api/payment/plans 用途:获取当前国家对应的会员/积分套餐。 认证:不需要登录。后端通过 `CF-IPCountry` 判断国家;登录后创建订单会再次按用户国家校验价格。 响应重点字段: ```json { "plan_id": "vip_monthly", "plan_name": "月度会员", "order_type": "vip_monthly", "amount_cents": 1999, "original_amount_cents": 1999, "daily_price_cents": 66, "currency": "USD", "pricing_tier": "T1", "vip_days": 30, "dol_amount": null } ``` 字段说明: - `amount_cents`: 实付金额,传给支付服务。 - `original_amount_cents`: 划线价;无划线价时为 `null`,字段一定存在。 - `currency`: 支付币种。 - `plan_id`: 创建订单时传回后端。 ## POST /api/payment/create-order 用途:创建支付订单。 认证:需要登录 token。 请求: ```json { "planId": "vip_monthly", "payChannel": "stripe", "autoRenew": true } ``` 响应: ```json { "success": true, "data": { "orderId": "ord-1", "payParams": { "url": "https://pay.example/ord-1" } } } ``` 前端使用: - `planId` 必须来自 `/api/payment/plans`。 - `payParams` 原样交给支付 SDK 或打开其中的支付 URL。 - 后端创建订单时使用 plans 里的 `amount_cents/currency`,不会再固定覆盖成 CNY。 ## GET /api/payment/order-status 用途:轮询支付订单状态。 认证:需要登录 token,只能查自己的订单。 请求: ```text GET /api/payment/order-status?order_id=ord-1 ``` 响应: ```json { "success": true, "data": { "orderId": "ord-1", "status": "paid", "orderType": "vip", "planId": "vip_monthly" } } ``` 状态: - `pending`: 支付处理中,继续轮询。 - `paid`: 支付成功,停止轮询并调用 `/api/user/entitlements`。 - `failed`: 支付失败,停止轮询并提示用户。 ## WebSocket payment_success / payment_failed 用途:支付服务回调后,后端主动推送支付结果。 成功事件示例: ```json { "type": "payment_success", "orderId": "ord-1", "payType": "vip", "planName": "月度会员", "vipExpiresAt": "2026-07-26T00:00:00+00:00" } ``` 积分充值成功示例: ```json { "type": "payment_success", "orderId": "ord-2", "payType": "dol", "planName": "1000 Credits", "dolAmount": 1000, "dolBalance": 1120 } ``` 前端收到 `payment_success` 后: ```text 停止 order-status 轮询 -> GET /api/user/entitlements -> 用 creditBalance/isVip 更新页面 ``` ## POST /api/chat/unlock-history 用途:一键解锁历史锁定消息,按创建时间从旧到新处理。 认证:需要登录 token。 请求:无 body。 余额不足响应: ```json { "success": true, "data": { "unlocked": false, "reason": "insufficient_balance", "totalLocked": 2, "unlockedCount": 0, "privateCount": 1, "imageCount": 1, "voiceCount": 0, "requiredCredits": 50, "currentCredits": 20, "remainingCredits": 20, "shortfallCredits": 30, "costsByMessage": { "private-1": 10, "photo-1": 40 } } } ``` 余额充足响应: ```json { "success": true, "data": { "unlocked": true, "reason": "ok", "requiredCredits": 60, "currentCredits": 100, "remainingCredits": 40, "shortfallCredits": 0, "messageIds": ["voice-1", "photo-1"] } } ``` 计费规则: - 私密消息:10 credits。 - 语音消息:20 credits。 - 照片:40 credits。 - 同一条消息命中多个类型时,按最高成本计一次。 - 余额不足时不扣费、不解锁,只返回差额。 - 已解锁过的消息会跳过。 ## 兼容接口 以下接口保留给旧前端,但新前端不推荐作为权益状态来源: - `GET /api/payment/vip-status`: 只返回 `isVip/vipExpiresAt`。 - `GET /api/user/credits`: 只返回 `dolBalance`。 - `GET /api/user/stats`: 用户统计、亲密度、记忆等,不再用于支付后刷新权益。 ## 测试证明 本地测试命令: ```powershell .\.venv\Scripts\python.exe -m pytest -q tests/test_vip_credit_entitlements.py .\.venv\Scripts\python.exe -m pytest -q tests/test_private_unlock.py tests/test_elio_image_paywall.py ``` 已通过结果: ```text tests/test_vip_credit_entitlements.py: 13 passed tests/test_private_unlock.py tests/test_elio_image_paywall.py: 11 passed ``` 覆盖证明: - `/api/user/entitlements`: 已测游客、登录非会员、VIP;游客和登录非会员的 `policy/costs/quotas` 一致。 - `/api/payment/plans`: 已测 T1/T2 国家;所有计划都有 `original_amount_cents`。 - `/api/payment/create-order`: 已测国家价格计划会传入创建订单;支付服务收到的金额和币种来自 plan。 - `/api/payment/order-status`: 已测 `pending/paid/failed` 和禁止查询他人订单。 - WebSocket `payment_success`: 已测 VIP 和积分发放事件 payload。 - `/api/chat/unlock-history`: 已测无锁定消息、余额不足不扣费、余额充足扣费、已解锁跳过。 - 兼容接口:已测 `/api/payment/vip-status` 和 `/api/user/credits` 仍可用。