Files
cozsweet-frontend-nextjs/docs/backend/FRONTEND_VIP_CREDIT_API.md
T
2026-06-30 10:49:14 +08:00

7.3 KiB

VIP / Credits Frontend API

本文档是前端接入会员、积分、权益和历史解锁的推荐接口说明。新版接入原则:

  • 前端权益状态只认 GET /api/user/entitlements
  • 游客和登录非会员权益一致,只按 isVip 区分会员/非会员。
  • 支付完成后只刷新一次 GET /api/user/entitlements
  • dolBalance 是旧字段名,等价于 creditBalance,新代码优先使用 creditBalance

推荐调用链

打开充值/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>,游客 token 也可以。

响应示例:

{
  "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 判断国家;登录后创建订单会再次按用户国家校验价格。

响应重点字段:

{
  "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。

请求:

{
  "planId": "vip_monthly",
  "payChannel": "stripe",
  "autoRenew": true
}

响应:

{
  "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,只能查自己的订单。

请求:

GET /api/payment/order-status?order_id=ord-1

响应:

{
  "success": true,
  "data": {
    "orderId": "ord-1",
    "status": "paid",
    "orderType": "vip",
    "planId": "vip_monthly"
  }
}

状态:

  • pending: 支付处理中,继续轮询。
  • paid: 支付成功,停止轮询并调用 /api/user/entitlements
  • failed: 支付失败,停止轮询并提示用户。

WebSocket payment_success / payment_failed

用途:支付服务回调后,后端主动推送支付结果。

成功事件示例:

{
  "type": "payment_success",
  "orderId": "ord-1",
  "payType": "vip",
  "planName": "月度会员",
  "vipExpiresAt": "2026-07-26T00:00:00+00:00"
}

积分充值成功示例:

{
  "type": "payment_success",
  "orderId": "ord-2",
  "payType": "dol",
  "planName": "1000 Credits",
  "dolAmount": 1000,
  "dolBalance": 1120
}

前端收到 payment_success 后:

停止 order-status 轮询
-> GET /api/user/entitlements
-> 用 creditBalance/isVip 更新页面

POST /api/chat/unlock-history

用途:一键解锁历史锁定消息,按创建时间从旧到新处理。

认证:需要登录 token。

请求:无 body。

余额不足响应:

{
  "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
    }
  }
}

余额充足响应:

{
  "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: 用户统计、亲密度、记忆等,不再用于支付后刷新权益。

测试证明

本地测试命令:

.\.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

已通过结果:

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 仍可用。