chore(docs): remove deprecated API documentation files

This commit is contained in:
2026-07-15 15:07:31 +08:00
parent 078774f9b9
commit 1cbb306cee
5 changed files with 0 additions and 934 deletions
@@ -1,267 +0,0 @@
# Cozsweet 全站行为埋点前端接入
> 发布状态(2026-07-14):自动页面/按钮采集 SDK 已部署 `pro` 预发,生产仍是旧版支付专项 SDK。前端应先接 `pro` 完成联调;生产自动采集要等 unified 下一次生产镜像发布后再启用。
## 1. 目标
前端接入一次后,自动统计:
- 用户访问了哪个页面。
- Next.js 站内路由切换。
- 所有原生按钮、链接和带 `role="button"` 的控件点击。
-`data-analytics-key` 的自定义可点击组件。
支付漏斗另外上报明确业务事件,统计付费墙原因、套餐曝光、套餐点击、创建订单、打开支付页和最终支付结果。
## 2. 环境地址
| 环境 | SDK | 事件接口 |
| --- | --- | --- |
| pro 预发 | `https://proapi.banlv-ai.com/js/cozsweet-payment-analytics.js` | `https://proapi.banlv-ai.com/api/behavior/events` |
| production 生产 | `https://api.banlv-ai.com/js/cozsweet-payment-analytics.js` | `https://api.banlv-ai.com/api/behavior/events` |
## 3. Next.js 根布局接入
只在根布局加载一次。预发必须先指定预发事件地址,否则 SDK 默认发送到生产。
```tsx
import Script from 'next/script'
export function BehaviorAnalyticsScripts() {
const isPro = process.env.NEXT_PUBLIC_APP_ENV === 'pro'
const apiBase = isPro
? 'https://proapi.banlv-ai.com'
: 'https://api.banlv-ai.com'
return (
<>
<Script id="cozsweet-analytics-config" strategy="beforeInteractive">
{`window.COZSWEET_ANALYTICS_ENDPOINT=${JSON.stringify(
`${apiBase}/api/behavior/events`,
)}`}
</Script>
<Script
src={`${apiBase}/js/cozsweet-payment-analytics.js`}
strategy="afterInteractive"
/>
</>
)
}
```
SDK 会自动生成并持久化:
- `anonymousId`:未登录用户标识。
- `sessionId`30 分钟会话标识。
- `pagePath/pageTitle/referrer`
- `siteHost/pageUrl/UTM`
登录成功或恢复会话后设置 Token,让事件同时绑定 `userId`
```ts
window.CozsweetPaymentAnalytics?.setToken(accessToken)
```
## 4. 所有按钮点击
以下元素会自动上报 `eventType=element_click`,前端不需要在每个 `onClick` 里手工请求接口:
- `button`
- `a[href]`
- `[role="button"]`
- `[role="link"]`
- `input[type="button|submit|reset"]`
- `[data-analytics-key]`
重要按钮必须提供稳定且不随语言变化的 `data-analytics-key`
```tsx
<button data-analytics-key="chat.send_message">Send</button>
<button data-analytics-key="recharge.open" data-analytics-label="Open recharge">
Recharge
</button>
<div
role="button"
data-analytics-key="private_album.unlock"
data-analytics-label="Unlock private album"
onClick={unlockAlbum}
/>
```
`div onClick` 如果没有 `role="button"`,必须添加 `data-analytics-key`,否则浏览器无法可靠识别它是按钮。
不需要统计的区域:
```tsx
<div data-analytics-ignore>
{/* 此节点及其子节点不自动记录点击 */}
</div>
```
程序触发、没有真实 DOM 点击时可以手工记录:
```ts
window.CozsweetPaymentAnalytics?.elementClick(
'chat.retry_generation',
'Retry generation',
{ messageId },
)
```
不要把聊天正文、邮箱、手机号、Token 或支付参数放进 `data-analytics-key`、标签或 metadata。
## 5. 支付漏斗必须手工上报
通用按钮点击只能知道“点了某个按钮”,不能知道套餐价格、订单 ID 和进入充值页的原因。以下事件必须在支付业务代码对应步骤调用。
```ts
const analytics = window.CozsweetPaymentAnalytics
analytics?.paywallShown('daily_chat_limit', {
entryPoint: 'chat_input',
isVip: false,
})
analytics?.rechargeModalOpen('chat_input', 'daily_chat_limit')
plans.forEach((plan, index) => {
analytics?.planImpression(plan, index + 1)
})
analytics?.planClick(plan, {
entryPoint: 'paywall_modal',
triggerReason: 'daily_chat_limit',
})
analytics?.createOrderStart(plan, payChannel)
try {
const response = await createOrder({
planId: plan.planId,
payChannel,
autoRenew,
})
const data = response.data
const orderId = data.orderId
const checkoutUrl = analytics?.extractCheckoutUrl(data)
analytics?.createOrderSuccess(plan, orderId, payChannel)
if (checkoutUrl) {
analytics?.checkoutOpened(orderId, plan, payChannel, checkoutUrl)
window.location.href = checkoutUrl
} else {
analytics?.checkoutFailed(
orderId,
plan,
'missing_checkout_url',
payChannel,
)
}
} catch (error) {
analytics?.createOrderFailed(
plan,
error instanceof Error ? error.message : 'create_order_error',
payChannel,
)
}
```
轮询订单状态:
```ts
analytics?.statusPollStart(orderId, plan)
analytics?.statusPollUpdate(orderId, data.status, plan, {
expiresAt: data.expiresAt,
expiresInSeconds: data.expiresInSeconds,
})
// 前端主动放弃轮询且订单仍未进入终态时调用
analytics?.statusPollTimeout(orderId, plan)
```
## 6. 固定事件名
| 步骤 | 事件名 | 必要 metadata |
| --- | --- | --- |
| 触发付费墙 | `paywall_shown` | `triggerReason`, `entryPoint` |
| 打开充值页 | `recharge_modal_open` | `triggerReason`, `entryPoint` |
| 套餐曝光 | `payment_plan_impression` | `planId`, `amountCents`, `currency`, `position` |
| 点击套餐 | `payment_plan_click` | `planId`, `amountCents`, `currency` |
| 开始建单 | `payment_create_order_start` | `planId`, `payChannel` |
| 建单成功 | `payment_create_order_success` | `planId`, `orderId`, `payChannel` |
| 建单失败 | `payment_create_order_failed` | `planId`, `reason`, `payChannel` |
| 打开支付页 | `payment_checkout_opened` | `planId`, `orderId`, `payChannel` |
| 支付页缺失/打开失败 | `payment_checkout_failed` | `orderId`, `reason` |
| 支付状态 | `payment_status_paid/failed/expired` | `orderId`, `status` |
| 轮询超时 | `payment_status_poll_timeout` | `orderId` |
`triggerReason` 使用固定值:
- `daily_chat_limit`
- `private_topic_limit`
- `insufficient_credits`
- `sidebar_recharge`
- `vip_cta`
- `ad_landing`
- `manual_recharge`
- `unknown`
不要继续使用已经废弃的 `weekly_chat_limit` 作为新事件原因;当前免费聊天口径是每日额度。
## 7. 请求格式
SDK 最终批量发送:
```http
POST /api/behavior/events
Content-Type: application/json
Authorization: Bearer <TOKEN> // Token
```
```json
{
"events": [
{
"eventType": "element_click",
"eventName": "element_click",
"anonymousId": "browser-uuid",
"sessionId": "session-uuid",
"pagePath": "/chat",
"pageTitle": "Chat",
"elementKey": "recharge.open",
"elementText": "Recharge",
"elementTag": "button",
"metadata": {
"siteHost": "cozsweet.com",
"pageUrl": "https://cozsweet.com/chat"
}
}
]
}
```
成功响应:
```json
{"ok": true, "inserted": 1}
```
埋点失败不能阻塞聊天、解锁或支付流程。SDK 已吞掉网络异常,业务代码不要 `await` 埋点后再继续支付。
## 8. 联调验收
1. 打开浏览器 Network,筛选 `behavior/events`
2. 首次打开页面应看到 `page_view`
3. Next.js 路由切换后应再次看到新 `pagePath``page_view`
4. 点击任意按钮应看到 `element_click` 和稳定 `elementKey`
5. 从付费墙点击一个套餐,依次检查专用支付事件及 `planId`
6. 建单成功后必须看到同一 `orderId` 出现在建单、支付页和状态事件中。
7. 接口响应必须为 `ok=true``inserted>0`
8. Manager 选择同一日期范围,确认数据出现;统计不是实时推送时,刷新页面即可。
## 9. 当前线上为 0 的原因
截至 2026-07-14,线上 `cozsweet.com` 页面及加载的前端 JS 中没有找到本 SDK,也没有找到支付漏斗事件代码。数据库近期大部分 `element_click/page_view` 来自 Manager 后台自己的追踪,而不是 Cozsweet 用户端。后端只能自动记录建单开始/成功/失败,无法替前端知道付费墙是否展示、套餐是否曝光、用户点击了哪个套餐、支付页是否真正打开,因此这些步骤显示 0 是符合当前数据事实的。
@@ -1,225 +0,0 @@
# Facebook ASID / PSID 绑定与登录接口说明
## 总原则
后端不再依赖 Meta 做 ASID / PSID 实时互转。前端拿到哪个 ID 就传哪个 ID,后端只用本地数据库的绑定关系判断是不是同一个用户。
- ASID: Facebook Login 返回的 app-scoped id。
- PSID: Messenger / ManyChat / Page 链接带来的 page-scoped id。
- 只有当同一个后端用户已经同时绑定 ASID 和 PSID 时,`psid` 才能直接当作 Facebook 登录凭据换 token。
生产 API Base URL:
```text
https://api.banlv-ai.com
```
## 场景 2: 已登录用户绑定 PSID / ASID
### Purpose
用户已经登录 Cozsweet,前端从 URL 或 Messenger 环境拿到 `psid` 后,调用该接口把 PSID 绑定到当前登录用户。
### Request URL
```http
POST https://api.banlv-ai.com/api/user/facebook/identity
```
### Authentication
需要登录 token:
```http
Authorization: Bearer <TOKEN>
```
### Request Format
```http
Content-Type: application/json
```
### Parameters
| Field | Type | Required | Example | Meaning |
| --- | --- | --- | --- | --- |
| psid | string | no | `37370387172559600` | Messenger PSID。 |
| asid | string | no | `1582386133449953` | Facebook ASID。 |
| fbPsid | string | no | `37370387172559600` | `psid` 的别名。 |
| fbAsid | string | no | `1582386133449953` | `asid` 的别名。 |
至少传一个字段。推荐前端只拿到 PSID 时传:
```json
{ "psid": "37370387172559600" }
```
### Request Example
```bash
curl -X POST 'https://api.banlv-ai.com/api/user/facebook/identity' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
--data-raw '{
"psid": "37370387172559600"
}'
```
### Response Format
```json
{
"code": 200,
"message": "Facebook 身份绑定成功",
"success": true,
"data": {
"fbAsid": "1582386133449953",
"fbPsid": "37370387172559600",
"facebookBinding": {
"conflicts": [],
"bound": []
}
}
}
```
### Failures
| Status | Meaning |
| --- | --- |
| 400 | 没有传 `asid/fbAsid``psid/fbPsid`。 |
| 401 | 未登录或 token 失效。 |
| 409 | 该 ASID 或 PSID 已绑定到其他正式账号。 |
## 场景 3: PSID 直接登录
### Purpose
前端只有 `psid`,希望后端用本地绑定关系直接换登录 token,不再触发 Facebook Login。
### Request URL
```http
POST https://api.banlv-ai.com/api/auth/login/facebook/psid
```
### Authentication
不需要 Bearer token。
### Request Format
```http
Content-Type: application/json
```
### Parameters
| Field | Type | Required | Example | Meaning |
| --- | --- | --- | --- | --- |
| psid | string | yes | `37370387172559600` | Messenger PSID。 |
| deviceId | string | no | `web-abc` | 未匹配到账号时创建/返回游客 token 用;不传则后端按 PSID 生成稳定设备 ID。 |
| bindToGuest | boolean | no | `true` | 未匹配到任何用户时,是否把 PSID 先绑定到游客账号,默认 true。 |
### Request Example
```bash
curl -X POST 'https://api.banlv-ai.com/api/auth/login/facebook/psid' \
-H 'Content-Type: application/json' \
--data-raw '{
"psid": "37370387172559600"
}'
```
### Success Response: 已有完整绑定,直接登录
同一个用户已有 ASID 和 PSID 时返回正式登录 token:
```json
{
"code": 200,
"message": "Facebook PSID 登录成功",
"success": true,
"data": {
"token": "<JWT>",
"refreshToken": "<REFRESH_TOKEN>",
"matchedBy": "psid",
"fbAsid": "1582386133449953",
"fbPsid": "37370387172559600",
"hasCompleteFacebookIdentity": true,
"isGuest": false,
"user": {
"id": "<USER_ID>",
"fbAsid": "1582386133449953",
"fbPsid": "37370387172559600"
}
}
}
```
### Success Response: 未知或绑定不完整,返回游客
查不到 PSID,或 PSID 对应用户还没有 ASID 时,不当作 Facebook 登录,只返回游客 token:
```json
{
"code": 200,
"message": "Facebook PSID 未匹配,已返回游客登录",
"success": true,
"data": {
"token": "<GUEST_JWT>",
"refreshToken": "",
"matchedBy": "guest",
"fbPsid": "37370387172559600",
"fbAsid": null,
"hasCompleteFacebookIdentity": false,
"isGuest": true,
"userId": "<GUEST_USER_ID>"
}
}
```
如果 PSID 查到了用户,但该用户缺 ASID,`matchedBy` 会是:
```text
psid_incomplete
```
前端应继续按游客态处理,或引导用户进行 Facebook Login。
### Failures
| Status | Meaning |
| --- | --- |
| 422 | PSID 格式无效。 |
## Profile 字段
`GET /api/user/profile` 和登录响应里的 `user` 会包含:
```json
{
"fbAsid": "1582386133449953",
"fbPsid": "37370387172559600"
}
```
没有绑定时字段为 `null` 或不存在。
## 推荐前端流程
1. 页面打开时从 URL / Messenger 环境读取 `psid`
2. 如果本地已有登录 token:
-`POST /api/user/facebook/identity` 绑定 `psid` 到当前用户。
3. 如果本地没有登录 token:
- 先调 `POST /api/auth/login/facebook/psid`
- 如果返回 `hasCompleteFacebookIdentity=true`,保存 token,当作登录成功。
- 如果返回 `isGuest=true`,保存游客 token。
## 重要注意
- 后端不会再调用 Meta 做 ASID / PSID 互转。
- PSID 直登只相信我们数据库里已有的绑定关系。
- `409 FACEBOOK_ID_CONFLICT` 必须提示用户换账号或联系客服,不要在前端覆盖绑定。
- 前端保存 token 的逻辑和原登录流程一致。
-307
View File
@@ -1,307 +0,0 @@
# Cozsweet 独立付费图片包前端接口
## 1. 用途
Cozsweet 前端通过本接口展示独立的 8/15 张私密图片包,并使用用户积分解锁。FB 只导入原始图片素材,后端素材池负责跨批次自动凑成 8 张或 15 张图片包。该功能不走现金支付,不使用旧 `/moments` 接口。
## 2. 接口地址
| 功能 | 方法 | 路径 |
| --- | --- | --- |
| 图片包列表 | GET | `/api/private-room/albums` |
| 积分解锁 | POST | `/api/private-room/albums/{albumId}/unlock` |
## 4. 固定价格
| 图片数量 | `unlockCost` | 单张价格 |
| ---: | ---: | ---: |
| 8 | 320 credits | 40 credits |
| 15 | 600 credits | 40 credits |
接口只会返回 `currency: "credits"`。前端不得显示现金价格,也不得调用支付套餐或创建订单接口。
## 5. GET /api/private-room/albums
### 请求参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `character` | string | 否 | `elio` | 角色 ID。 |
| `collectionKey` | string | 否 | 全部 | 只查看一个素材分组。 |
| `limit` | integer | 否 | 20 | 1-50。 |
| `cursor` | ISO datetime | 否 | - | 上一页返回的 `nextCursor`。 |
### 请求示例
```bash
curl 'https://api.banlv-ai.com/api/private-room/albums?character=elio&limit=20' \
-H 'Authorization: Bearer <TOKEN>'
```
### 锁定图片包响应
```json
{
"code": 200,
"message": "success",
"success": true,
"data": {
"items": [
{
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
"momentId": "album:a1b2c3d4-0000-0000-0000-000000000000",
"characterId": "elio",
"collectionKey": "manila_202607",
"title": "Private Manila set",
"content": null,
"previewText": "Only for you.",
"imageCount": 8,
"mediaCount": 8,
"images": [
{
"url": "https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg",
"type": "image",
"locked": true,
"index": 0
}
],
"locked": true,
"unlocked": false,
"unlockCost": 320,
"requiredCredits": 320,
"creditCostPerImage": 40,
"currency": "credits",
"canUnlockWithCredits": false,
"publishedAt": "2026-07-13T00:00:00+00:00",
"lockDetail": {
"locked": true,
"showContent": false,
"showUpgrade": true,
"reason": "private_album",
"requiredCredits": 320,
"currentCredits": 100,
"shortfallCredits": 220,
"mediaCount": 8,
"unlockCostPerImage": 40
}
}
],
"nextCursor": null,
"hasMore": false,
"creditBalance": 100,
"currency": "credits",
"creditCostPerImage": 40,
"packageOptions": [
{"imageCount": 8, "creditCost": 320},
{"imageCount": 15, "creditCost": 600}
]
}
}
```
锁定和解锁状态都会返回完整 `images[].url`。前端必须根据 `locked` / `unlocked` / `lockDetail.locked` 决定是否显示锁层和是否允许查看原图,不能用 URL 是否为空判断解锁状态。
### 已解锁图片包
同一个用户解锁后,再次请求列表会返回:
```json
{
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
"locked": false,
"unlocked": true,
"content": "Only for you.",
"imageCount": 8,
"images": [
{
"url": "https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg",
"type": "image",
"locked": false,
"index": 0
}
]
}
```
### 分页
`hasMore=true` 时,把 `nextCursor` 原样传回:
```http
GET /api/private-room/albums?cursor=<nextCursor>&limit=20
```
## 6. POST /api/private-room/albums/{albumId}/unlock
### 请求格式
```http
Content-Type: application/json
Authorization: Bearer <TOKEN>
```
Body
```json
{
"expectedCost": 320
}
```
| 字段 | 类型 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- | --- |
| `expectedCost` | integer | 否但建议 | 320 | 用户确认时看到的价格;价格变化则拒绝扣分。 |
### 请求示例
```bash
curl -X POST 'https://api.banlv-ai.com/api/private-room/albums/a1b2c3d4-0000-0000-0000-000000000000/unlock' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"expectedCost":320}'
```
### 解锁成功
```json
{
"code": 200,
"message": "success",
"success": true,
"data": {
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
"locked": false,
"unlocked": true,
"reason": "ok",
"unlockCost": 320,
"creditsCharged": 320,
"previousCreditBalance": 500,
"creditBalance": 180,
"images": [
{
"url": "https://...",
"type": "image",
"locked": false,
"index": 0
}
]
}
}
```
前端直接使用响应里的 `images` 替换锁卡,并用 `creditBalance` 刷新余额。
### 积分不足
业务失败仍返回 HTTP 200
```json
{
"code": 200,
"message": "success",
"success": true,
"data": {
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
"locked": true,
"unlocked": false,
"reason": "insufficient_credits",
"unlockCost": 320,
"requiredCredits": 320,
"creditBalance": 100,
"shortfallCredits": 220,
"creditsCharged": 0,
"images": [
{"url": "https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg", "type": "image", "locked": true, "index": 0}
]
}
}
```
前端可以打开积分充值页,但后端不会创建图片包现金订单。用户充值积分后,再次点击同一个解锁接口。
### 重复解锁
```json
{
"unlocked": true,
"locked": false,
"reason": "already_unlocked",
"creditsCharged": 0,
"creditBalance": 180,
"images": [{"url": "https://...", "locked": false, "index": 0}]
}
```
## 7. `reason` 处理
| `data.reason` | 含义 | 前端动作 |
| --- | --- | --- |
| `ok` | 已扣积分并解锁 | 显示真实图片并更新余额。 |
| `already_unlocked` | 以前已解锁,本次不扣 | 直接显示返回图片。 |
| `insufficient_credits` | 积分不足,未扣分 | 保持锁定,可引导购买积分。 |
| `cost_changed` | 价格与 `expectedCost` 不一致 | 刷新列表并重新确认。 |
| `unlock_in_progress` | 同一图片包正在并发解锁 | 禁用按钮并稍后刷新。 |
| `deduct_failed` | 扣积分失败 | 保持锁定并允许重试。 |
| `persist_failed_refunded` | 解锁记录失败,积分已尽力退回 | 保持锁定、刷新余额并告警。 |
| `not_found` | 图片包不存在或已停用 | 移除卡片。 |
## 8. TypeScript 建议
```ts
export interface PrivateAlbumImage {
url: string;
type: "image";
locked: boolean;
index: number;
}
export interface PrivateAlbum {
albumId: string;
momentId: string;
characterId: string;
collectionKey: string;
title: string;
content: string | null;
previewText: string;
imageCount: 8 | 15;
mediaCount: 8 | 15;
images: PrivateAlbumImage[];
locked: boolean;
unlocked: boolean;
unlockCost: 320 | 600;
requiredCredits: 320 | 600;
creditCostPerImage: 40;
currency: "credits";
canUnlockWithCredits: boolean;
publishedAt: string;
}
```
## 9. 前端流程
1. 进入图片包页面,调用 `GET /api/private-room/albums`
2. `locked=true` 时可使用返回 URL 渲染封面/模糊图,但必须覆盖锁层并禁止打开原图。
3. 用户确认后调用解锁接口并传当前 `expectedCost`
4. `reason=ok/already_unlocked` 时使用返回的真实图片。
5. `reason=insufficient_credits` 时保持锁定并引导购买积分。
6. 充值完成后重新调用解锁接口,不需要恢复现金订单。
7. 解锁请求进行中禁用按钮,避免重复点击。
## 10. HTTP 错误
| HTTP 状态 | 原因 | 处理 |
| ---: | --- | --- |
| 401 | Token 无效或缺失 | 刷新 Token/重新登录。 |
| 422 | `albumId`、query 或 body 格式错误 | 修正请求。 |
| 500 | 数据库或服务异常 | 保持当前锁定状态并允许重试。 |
## 11. 测试方法
1. 先在 pro 执行 `database/private-albums-migration.sql`
2. 使用 FB 测试导入生成一个 8 张图片包。
3. 用少于 320 积分的测试账号请求解锁,确认不扣积分、不出现现金订单。
4. 给测试账号补足积分,再请求解锁,确认扣 320 且返回 8 个真实 URL。
5. 重复解锁,确认 `creditsCharged=0`
6. 刷新列表,确认仍为 `unlocked=true`
7. 15 张图片包按同样方式验证扣 600。
8. 测试结束后删除测试素材、图片包、图片和解锁记录。
-132
View File
@@ -1,132 +0,0 @@
# Cozsweet 咖啡打赏接口
## 1. 用途
前端展示三个一次性咖啡打赏档位,并复用现有支付建单、支付跳转和订单轮询流程。
## 2. 接口地址
| 功能 | 方法 | 路径 | 鉴权 |
| --- | --- | --- | --- |
| 查询打赏品类 | GET | `/api/payment/tip-plans` | 不需要 |
| 创建打赏订单 | POST | `/api/payment/create-order` | Bearer Token |
| 查询订单状态 | GET | `/api/payment/order-status?order_id=...` | Bearer Token |
## 3. 查询打赏品类
```bash
curl 'https://api.banlv-ai.com/api/payment/tip-plans'
```
成功响应:
```json
{
"code": 200,
"message": "success",
"success": true,
"data": {
"plans": [
{
"planId": "tip_coffee_usd_4_99",
"planName": "Small Coffee",
"orderType": "tip",
"tipType": "coffee_small",
"description": "Buy Elio a small coffee",
"amountCents": 499,
"currency": "USD",
"autoRenew": false,
"isFirstRechargeOffer": false,
"firstRechargeDiscountPercent": 0
},
{
"planId": "tip_coffee_usd_9_99",
"planName": "Medium Coffee",
"orderType": "tip",
"tipType": "coffee_medium",
"description": "Buy Elio a medium coffee",
"amountCents": 999,
"currency": "USD",
"autoRenew": false,
"isFirstRechargeOffer": false,
"firstRechargeDiscountPercent": 0
},
{
"planId": "tip_coffee_usd_19_99",
"planName": "Large Coffee",
"orderType": "tip",
"tipType": "coffee_large",
"description": "Buy Elio a large coffee",
"amountCents": 1999,
"currency": "USD",
"autoRenew": false,
"isFirstRechargeOffer": false,
"firstRechargeDiscountPercent": 0
}
]
}
}
```
当前三个档位如下,顺序就是接口返回顺序:
| `planId` | `tipType` | 价格 |
| --- | --- | ---: |
| `tip_coffee_usd_4_99` | `coffee_small` | USD 4.99 |
| `tip_coffee_usd_9_99` | `coffee_medium` | USD 9.99 |
| `tip_coffee_usd_19_99` | `coffee_large` | USD 19.99 |
三个档位都不按国家换币、不参与首充半价,也不发会员或积分。前端必须使用接口返回的 `planId`,不要根据价格自行拼接。
## 4. 创建打赏订单
```bash
curl -X POST 'https://api.banlv-ai.com/api/payment/create-order' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"planId": "tip_coffee_usd_9_99",
"payChannel": "stripe",
"autoRenew": false
}'
```
| 字段 | 类型 | 必填 | 固定/可选值 | 说明 |
| --- | --- | --- | --- | --- |
| `planId` | string | 是 | 上表三个 ID 之一 | 咖啡打赏产品 ID。 |
| `payChannel` | string | 是 | `stripe` / `ezpay` | 支付渠道。 |
| `autoRenew` | boolean | 是 | `false` | 打赏是一次性支付,必须传 false。 |
成功响应与现有充值相同:
```json
{
"code": 200,
"message": "success",
"success": true,
"data": {
"orderId": "pay_xxx",
"payParams": {
"url": "https://checkout.example/..."
},
"expiresAt": "2026-07-14T10:30:00+00:00",
"expiresInSeconds": 1800
}
}
```
前端必须打开 `payParams` 中的支付 URL,并每 3-5 秒轮询订单状态。
## 5. 订单状态与 WebSocket
支付完成后:
```json
{
"orderId": "pay_xxx",
"status": "paid",
"orderType": "tip",
"planId": "tip_coffee_usd_9_99",
"creditsAdded": 0
}
```
-3
View File
@@ -1,3 +0,0 @@
curl -X POST "https://flow.banlv-ai.com/webhooks/action-notify?chat_id=oc_xxx" \
-H "Content-Type: application/json" \
-d '{"message":"Gitea 内容操作完成,已同步。"}'