Merge branch 'dev' into test

This commit is contained in:
2026-06-23 14:12:48 +08:00
3 changed files with 42 additions and 34 deletions
+34 -19
View File
@@ -327,6 +327,8 @@ if (data.blocked === true && data.blockReason === "daily_limit") {
`data` 字段的**全部字段**(现有 + 新增),所有 `/api/chat/send` 响应都包含:
> 注意:`privateLocked` / `privateHint` / `isPrivate` **不属于** `/api/chat/send` 的响应字段;它们只出现在 `GET /api/chat/history` 的 `data.messages[]` 单条历史消息里。私密消息由后端异步打标,因此前端要在刷新/拉取历史时根据 `privateLocked=true` 渲染模糊锁定卡片。
| 字段 | 类型 | 现有/新增 | 说明 |
|------|------|----------|------|
| `mode` | string | 现有 | `"http"` / `"websocket"` |
@@ -341,8 +343,8 @@ if (data.blocked === true && data.blockReason === "daily_limit") {
| `isGuest` | bool \| null | 现有 | 是否游客 |
| `timestamp` | int | 现有 | Unix 毫秒时间戳 |
| `blocked` | bool \| null | 现有 | 是否每日限流拦截(功能①) |
| `blockReason` | string \| null | 现有 | `"daily_limit"` |
| `blockDetail` | object \| null | 现有 | `{ type, usedToday, limit }` |
| `blockReason` | string \| null | 现有 | `"daily_limit"` / `"total_limit"` |
| `blockDetail` | object \| null | 现有 | 每日限制:`{ type, usedToday, limit }`;游客总量限制:`{ type, usedTotal, limit }` |
| `paywallTriggered` | bool | 新增 | 是否触发图片付费墙(功能②) |
| `showUpgrade` | bool | 新增 | **前端是否展示开会员引导** |
| `imageType` | string \| null | 新增 | `null` / `"elio_schedule"` |
@@ -398,20 +400,21 @@ if (data.blocked === true && data.blockReason === "daily_limit") {
| `plan_name` | string | 展示名称 |
| `order_type` | string | 传给支付服务 B 的订单类型 |
| `amount_cents` | int | 金额,单位为分 |
| `currency` | string | 当前为 `CNY` |
| `original_amount_cents` | int \| null | 原价/划线价,单位为分;仅 VIP 套餐返回 |
| `daily_price_cents` | int \| null | 日均展示价,单位为分;仅 VIP 套餐返回 |
| `currency` | string | VIP 套餐当前为 `USD`DOL 充值项当前为 `CNY` |
| `vip_days` | int \| null | VIP 增加天数,`null` 表示永久或非 VIP 商品 |
| `dol_amount` | int \| null | DOL 积分数量,VIP 商品为 `null` |
当前套餐:
| plan_id | plan_name | order_type | amount_cents | 发放内容 |
|---|---|---:|---:|---|
| `vip_monthly` | 月度会员 | `vip_monthly` | 999 | VIP +30 天 |
| `vip_quarterly` | 季度会员 | `vip_quarterly` | 2499 | VIP +90 天 |
| `vip_annual` | 年度会员 | `vip_annual` | 5999 | VIP +365 天 |
| `vip_lifetime` | 永久会员 | `vip_lifetime` | 19999 | 永久 VIP |
| `dol_100` | 100 DOL | `dol` | 600 | DOL +100 |
| `dol_500` | 500 DOL | `dol` | 2800 | DOL +500 |
| plan_id | plan_name | order_type | amount_cents | original_amount_cents | daily_price_cents | currency | 发放内容 |
|---|---|---:|---:|---:|---:|---|---|
| `vip_monthly` | Monthly | `vip_monthly` | 1990 | 2490 | 66 | USD | VIP +30 天 |
| `vip_quarterly` | Quarterly | `vip_quarterly` | 4990 | 5990 | 55 | USD | VIP +90 天 |
| `vip_annual` | Annual | `vip_annual` | 16990 | 19990 | 47 | USD | VIP +365 天 |
| `dol_100` | 100 DOL | `dol` | 600 | null | null | CNY | DOL +100 |
| `dol_500` | 500 DOL | `dol` | 2800 | null | null | CNY | DOL +500 |
#### POST /api/payment/create-order
@@ -438,7 +441,7 @@ Content-Type: application/json
| 字段 | 必填 | 说明 |
|---|---|---|
| `planId` | 是 | 来自 `GET /api/payment/plans``plan_id` |
| `payChannel` | 是 | 当前支持 `stripe` / `ezpay` |
| `payChannel` | 是 | 支付通道取值约定:`stripe` / `ezpay` / `paycools`。当前生产代码若仍只放开 `stripe` / `ezpay`,需要同步把 A/B 两侧白名单加上 `paycools` |
| `autoRenew` | 是 | 是否自动续费;DOL 和永久会员建议传 `false` |
成功响应:
@@ -455,7 +458,18 @@ Content-Type: application/json
}
```
`payParams` 是支付服务 B 返回给前端的拉起支付信息。对于 PayPal / Stripe Checkout / ezPay 这类浏览器跳转支付,B 必须在 `payParams` 内提供可直接打开的支付链接。
`payParams` 是支付服务 B 返回给前端的拉起支付信息。对于 Stripe Checkout / ezPay / PayCools 这类浏览器跳转支付,B 必须在 `payParams` 内提供可直接打开的支付链接。PayCools 返回的支付链接应放在 `checkout_url``payment_url``url` 中。
当前 Stripe 返回为 Checkout 链接模式。前端创建订单成功后应优先打开:
1. `data.paymentUrl`
2. `data.checkoutUrl`
3. `data.url`
4. `data.payParams.paymentUrl`
5. `data.payParams.checkoutUrl`
6. `data.payParams.url`
打开支付链接后继续轮询 `GET /api/payment/order-status?order_id=...`;支付服务 B 收到 Stripe webhook 后会发布 MQ,后端 A 消费后把订单从 `pending` 更新为 `paid`
前端读取支付链接的优先级:
@@ -470,7 +484,7 @@ Content-Type: application/json
| 状态码 | 场景 |
|---|---|
| `400` | `planId` 无效,或 `payChannel` 不是 `stripe` / `ezpay` |
| `400` | `planId` 无效,或 `payChannel` 不是后端当前放开的支付通道 |
| `401` | 未登录或 token 无效 |
| `502` | `PAYMENT_SERVICE_URL` 未配置,或支付服务 B 创建订单失败 |
| `500` | 后端 A 创建订单过程中发生未知错误 |
@@ -545,10 +559,10 @@ Content-Type: application/json
{
"user_id": "用户 UUID",
"order_type": "vip_monthly",
"pay_channel": "stripe",
"pay_channel": "paycools",
"automatic_renewal": true,
"amount_cents": 999,
"currency": "CNY"
"amount_cents": 1990,
"currency": "USD"
}
```
@@ -567,7 +581,8 @@ B 必须返回:
- `pay_params` 结构由支付服务 B 决定,后端 A 不解析,前端原样使用。
- 浏览器跳转支付必须返回可打开的 URL,推荐统一使用 `pay_params.checkout_url`
- 当前 A/B 已确认支持的 `pay_channel``stripe` / `ezpay`;如果要接 PayPal,需要先由支付服务 B 的 `/orders` 支持 `paypal`,再把后端 A 的渠道校验和前端选项一起放开
- PayCools 返回支付链接时,建议放在 `pay_params.checkout_url`;前端也兼容 `payment_url` / `approval_url` / `url`
- 当前支付对接文档约定的 `pay_channel``stripe` / `ezpay` / `paycools`;生产代码也需要同步支持这些取值。如果要接其他通道,需要先由支付服务 B 的 `/orders` 支持该通道,再把后端 A 的渠道校验和前端选项一起放开。
- 后端 A 会把 `order_id``user_id``order_type``plan_id``status=pending` 写入本地 `orders` 表。
- 发放什么权益由 A 侧 `plan_id` 决定,B 不需要知道 VIP 天数或 DOL 数量。
@@ -684,7 +699,7 @@ PAYMENT_WEBHOOK_SECRET=<MQ 消息签名密钥,可空>
2. GET /api/payment/plans 加载套餐
3. 用户选择 planId、payChannel、autoRenew
4. POST /api/payment/create-order
5. 从 data.payParams 读取 checkout_url/payment_url/approval_url/url 并打开支付页,或按约定调用支付 SDK
5. 从 data.payParams 读取 checkout_url/payment_url/approval_url/url 并打开支付页,PayCools 链接也走这个逻辑,或按约定调用支付 SDK
6. 每 3~5 秒 GET /api/payment/order-status?order_id=...
7. 收到 paid 或 WebSocket payment_success 后停止轮询
8. payType=vip 时调用 /api/payment/vip-status 并刷新本地会员状态
+1 -1
View File
@@ -11,7 +11,7 @@
* - `ChatGuestLogin`:游客进入 /chat —— 拉本地消息 + 初始化配额(不连 WS)
* - `ChatNonVipLogin`:非 VIP 用户进入 /chat —— 拉服务器端首屏,不连 WS
* - `ChatVipLogin`VIP 用户进入 /chat —— 拉服务器端首屏 + 连 WStoken 在 payload
* - `ChatLogout`:登出 / 切换用户 —— 断 WS + 清消息
* - `ChatLogout`正式登录用户登出 —— 断 WS + 清消息
*
* 设计:所有WS / 历史 / 配额相关副作用全部通过派发事件给 chat 机器处理,
* chat-screen 不直接创建 ChatWebSocket / 不读 AuthStorage(除 token 取值)。
+6 -13
View File
@@ -7,7 +7,12 @@
* - `ChatGuestLogin` → 游客会话(断 WS = 不连)
* - `ChatNonVipLogin { token }` → 非 VIP 用户会话(不连 WS)
* - `ChatVipLogin { token }` → VIP 用户会话(连 WS
* - `ChatLogout` → 登出(机器自动 cleanup WS actor
* - `ChatLogout` → 正式登录用户登出(机器自动 cleanup WS actor
*
* 登录态流转约束:
* - 未登录:可以进入游客登录 / 其他登录。
* - 游客登录:只可以升级为其他登录,不响应退出。
* - 其他登录:可以退出,也可以在非 VIP / VIP 间切换,不响应游客登录。
*
* 状态结构(parent state 模式):
* - `idle`:屏没挂 / 登出
@@ -364,10 +369,6 @@ export const chatMachine = setup({
guestSession: {
on: {
ChatLogout: {
target: "#chat.idle",
actions: "clearChatSession",
},
ChatNonVipLogin: {
target: "#chat.nonVipUserSession",
actions: "startUserSession",
@@ -468,10 +469,6 @@ export const chatMachine = setup({
target: "#chat.idle",
actions: "clearChatSession",
},
ChatGuestLogin: {
target: "#chat.guestSession",
actions: "startGuestSession",
},
ChatNonVipLogin: {
target: "#chat.nonVipUserSession",
reenter: true,
@@ -595,10 +592,6 @@ export const chatMachine = setup({
target: "#chat.idle",
actions: "clearChatSession",
},
ChatGuestLogin: {
target: "#chat.guestSession",
actions: "startGuestSession",
},
ChatNonVipLogin: {
target: "#chat.nonVipUserSession",
actions: "startUserSession",