# payjsapi nomadvip 对接规范 **问题**:支付成功后,页面显示账号,但 PocketBase `payments` 表和 Memos 账户均无记录,导致会员验证失败。 **原因**:payjsapi 支付回调未正确写入 PocketBase 及创建 Memos 账号。 --- ## 一、nomadvip 调用流程 ``` 用户点击「立即加入」 → nomadvip POST /api/pay (user_id, site_id: "vip", ...) → nomadvip 调用 payjsapi POST /nomadvip/payh5 → payjsapi 返回支付参数,用户完成支付 → 支付渠道回调 payjsapi → 【payjsapi 必须】写入 PocketBase payments + site_vip,创建 Memos 账号 → nomadvip 轮询 GET /api/pay/status?order_id=xxx → nomadvip 调用 payjsapi GET /nomadvip/order_status?order_id=xxx → payjsapi 返回 { paid: true } ``` --- ## 二、payjsapi 支付回调必须完成 ### 2.1 写入 PocketBase `payments` 表 | 字段 | 说明 | 示例 | |------|------|------| | user_id | 订单中的 user_id(匿名格式 `user1773315608560` 或 PB 用户 ID) | `user1773315608560` | | type | 产品类型 | `vip` | | order_id | 支付订单号 | 渠道返回的 out_trade_no | | total_fee | 金额(分) | 8800 | | channel | 支付渠道 | wxpay / alipay | | created | 创建时间 | 自动 | **nomadvip 依赖**:`app/page.tsx` 的 `checkPaymentFromPB` 按 `user_id` 查询 payments 判断是否 VIP。**不写入则会员验证失败**。 ### 2.2 写入 PocketBase `site_vip` 表 | 字段 | 说明 | 示例 | |------|------|------| | user_id | 同 payments.user_id | `user1773315608560` | | site_id | 固定 `vip` | `vip` | | order_id | 支付订单号 | 可选 | | expires_at | 到期时间,null 表示永久 | null | **nomadvip 依赖**:`/api/vip/check` 按 `user_id` + `site_id = "vip"` 校验 Cookie 登录用户的 VIP 状态。 ### 2.3 创建 Memos 账号 - **Memos 地址**:qun.hackrobot.cn - **账号格式**:`user` + 时间戳,如 `user1773315608560` - **默认密码**:`12345678` payjsapi 在支付成功后,需调用 Memos API 或内部服务创建账号,账号名与订单中的 `user_id` 一致(匿名用户即为 `user1773315608560` 格式)。 **nomadvip 依赖**:首页「星球登录信息」展示的账号即为此格式,用户需用该账号登录 qun.hackrobot.cn。 --- ## 三、payjsapi 接口要求 ### 3.1 订单创建 `POST /nomadvip/payh5` **请求体**(nomadvip 已传递): ```json { "user_id": "user1773315608560", "site_id": "vip", "total_fee": 8800, "type": "vip", "channel": "wxpay", "return_url": "https://vip.hackrobot.cn/paid", "client_ip": "可选" } ``` **要求**:订单存储时需保存 `user_id`、`site_id`,供支付回调使用。 ### 3.2 订单状态 `GET /nomadvip/order_status?order_id=xxx` **响应**: ```json { "paid": true } ``` 或 ```json { "paid": false } ``` **要求**:支付回调完成写入 payments/site_vip 后,此接口应返回 `paid: true`。nomadvip 据此判断支付成功并刷新页面。 --- ## 四、order_status 接口(关键) **nomadvip 依赖**:支付成功后,nomadvip 轮询 `order_status`,若返回 `paid: true` 则调用 `/api/pay/confirm` 写入 PocketBase 和 Memos。**若 order_status 返回 false 或接口不存在,则 confirm 失败,payments/site_vip/Memos 均无记录**。 payjsapi 必须实现: ``` GET /nomadvip/order_status?order_id=xxx 响应: { "paid": true } 或 { "paid": false } ``` - 参数名必须为 `order_id`(与 nomadvip 传递一致) - 支付渠道回调完成后,必须返回 `paid: true` - 若 payh5/redirect 返回 301-308 重定向,建议响应头带上 `x-pay-order-id`,以便 nomadvip 正确轮询 ## 五、已有账号登录(可选,优先于 PocketBase 查询) nomadvip 会优先调用 payjsapi 验证 VIP,若接口不存在或失败则回退到 PocketBase。 ### 5.1 按 user_id 查 VIP `GET /nomadvip/check_vip?user_id=xxx` **响应**: ```json { "vip": true, "email": "user@example.com" } ``` 或 ```json { "vip": false } ``` **说明**:payjsapi 需查 users/payments/site_vip 关联,返回是否 VIP;若已关联邮箱可返回 `email` 供前端不再显示绑定表单。 ### 5.2 按邮箱+密码查 VIP `POST /nomadvip/check_vip_by_email` **请求体**: ```json { "email": "user@example.com", "password": "xxx" } ``` **响应**: ```json { "vip": true, "token": "PB token", "record": { "id": "xxx", "email": "xxx" }, "effectiveUserId": "user1773315608560" } ``` 或 ```json { "vip": false } ``` **说明**:payjsapi 需验证邮箱密码(PB users),再查 payments/site_vip 关联,返回 VIP 状态及 PB token/record。 --- ## 六、检查清单(payjsapi 侧) - [ ] 支付回调写入 PocketBase `payments` 表 - [ ] 支付回调写入 PocketBase `site_vip` 表(site_id = "vip") - [ ] 支付回调创建 Memos 账号(user_id 格式,密码 12345678) - [ ] **`order_status` 在支付成功后返回 `paid: true`**(否则 nomadvip confirm 失败) - [ ] 订单创建时正确保存 `user_id`、`site_id` - [ ] (可选)`GET /nomadvip/check_vip?user_id=xxx` 按 user_id 查 VIP - [ ] (可选)`POST /nomadvip/check_vip_by_email` 按邮箱密码查 VIP --- ## 七、PocketBase 配置 payjsapi 需配置: - `POCKETBASE_URL`:PocketBase 地址(如 https://pocketbase.hackrobot.cn) - `POCKETBASE_EMAIL` / `POCKETBASE_PASSWORD`:管理员账号,用于写入 payments、site_vip --- ## 八、日志要求(排查 VIP 登录问题) payjsapi 需在以下接口打印日志,便于排查「已有账号登录」失败: | 接口 | 建议日志 | |------|----------| | `GET /nomadvip/check_vip?user_id=xxx` | 请求 user_id、查询结果 vip | | `POST /nomadvip/check_vip_by_email` | 请求 email、PB 登录结果、vip 查询结果 | | `GET /nomadvip/order_status?order_id=xxx` | 请求 order_id、返回 paid | | 支付回调 | 写入 payments/site_vip 的 user_id、order_id | 示例(Node/Express): ```js // check_vip console.log("[payjsapi] check_vip user_id=%s vip=%s", user_id, vip); // check_vip_by_email console.log("[payjsapi] check_vip_by_email email=%s pbLogin=%s vip=%s", email, !!token, vip); // order_status console.log("[payjsapi] order_status order_id=%s paid=%s", order_id, paid); ``` --- ## 九、Memos 集成 Memos 账号创建方式取决于现有架构,可选: 1. 直接调用 Memos API 创建用户 2. 通过内部服务/队列异步创建 3. 与 PocketBase users 表联动(若 Memos 与 PB 共享用户) 账号名必须与 `user_id` 一致,以便 nomadvip 页面展示与登录流程一致。