PayJS API
支付 API 服务,支持多支付渠道(XorPay、ZPAY),按项目模块化拆分。
路由与项目对应
| 项目 | 路由前缀 | 说明 |
|---|---|---|
| nomadvip | /nomadvip/* |
固定 ZPAY,独立实例 |
| digital | /digital/* |
使用 PAYMENT_PROVIDER |
| cnomadcna | /cnomadcna/* |
使用 PAYMENT_PROVIDER |
| nomadlms | /nomadlms/* |
使用 PAYMENT_PROVIDER |
| legacy | /payh5、/zpay_notify 等 |
向后兼容 |
各项目业务互不影响,可单独配置。
环境
本地调试:BASE_URL 需为 ZPAY 可访问地址(如 ngrok 暴露 8700 端口)
线上部署:BASE_URL=https://api.hackrobot.cn
模块结构
app/
├── config.py # 配置(环境变量)
├── main.py # FastAPI 入口,挂载路由
├── sdk/ # SDK 组件化封装
│ ├── payment/ # 支付网关(ZPAY/XorPay)
│ ├── pocketbase.py # PocketBase 数据库
│ ├── minio.py # MinIO 对象存储
│ └── memos.py # Memos 用户(仅 nomadvip 使用)
├── services/ # 共享业务逻辑(基于 SDK)
│ ├── pb.py # PocketBase 客户端
│ ├── memos.py # Memos 用户创建
│ ├── minio.py # MinIO 上传
│ └── payment.py # 支付成功处理
├── routers/ # 项目路由
│ ├── nomadvip.py # /nomadvip/*
│ ├── digital.py # /digital/*
│ ├── cnomadcna.py # /cnomadcna/*
│ ├── nomadlms.py # /nomadlms/*
│ ├── legacy.py # /payh5 等(向后兼容)
│ └── common.py # /submit_meetup_application、/create_user
└── payment/
├── base.py # 支付抽象基类
├── xorpay.py # XorPay 实现
├── zpay.py # ZPAY 实现(易支付兼容)
└── factory.py # 渠道工厂
支付渠道
| 渠道 | 环境变量 | 说明 |
|---|---|---|
| XorPay | PAYMENT_PROVIDER=xorpay |
默认 |
| ZPAY | PAYMENT_PROVIDER=zpay |
需配置 ZPAY_PID、ZPAY_KEY |
切换渠道:设置环境变量 PAYMENT_PROVIDER=xorpay 或 zpay,参考 .env.example。
本地调试
# 复制配置
cp .env.example .env
# 本地调试时 BASE_URL 需 ZPAY 可访问,可用 ngrok:
# ngrok http 8700 -> 将 BASE_URL 设为 ngrok 提供的 https 地址
python run.py
# 或 uvicorn app.main:app --host 0.0.0.0 --port 8700 --reload
线上部署
域名:https://api.hackrobot.cn
# .env 中设置
BASE_URL=https://api.hackrobot.cn
PAYMENT_PROVIDER=zpay
# 其他 ZPAY、PocketBase、Memos 配置见 .env.example
线上「支付失败」排查
本地正常、Ubuntu 部署后 api.hackrobot.cn 调用支付失败,常见原因:
-
BASE_URL 配置错误:必须为 ZPAY 可公网访问的地址。错误示例:
http://127.0.0.1:8700、http://localhost。正确:https://api.hackrobot.cn(域名需解析到本机,nginx 反向代理对应端口)。 -
服务器无法访问 zpayz.cn:防火墙、DNS 或网络限制。访问
https://api.hackrobot.cn/health?check_zpay=1自检:zpayz_reachable: false→ 开放出站或检查网络zpayz_error含 "SSL" → 执行apt install ca-certificates后重启
-
client_ip 内网导致 ZPAY 风控拒单:前端(digital/cnomadcna/nomadvip)通过 nginx 代理时,需配置
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for,否则 payjsapi 收到 127.0.0.1 会触发风控。已做兼容:内网 IP 时自动省略 clientip 参数。 -
启动时控制台有 BASE_URL 警告:说明配置了内网地址,需修改
.env中的BASE_URL为公网地址后重启。 -
查看详细错误:支付失败时,payjsapi 会输出
ZPAY create_order_api 失败日志,含 status/code/msg,便于定位。
自检结果(2026-04-01)
本次联动自检覆盖了 nomadvip 前端与本仓库 nomadvip 路由的支付闭环:
- 创建订单:
/nomadvip/payh5、/nomadvip/payh5/redirect - 异步通知:
/nomadvip/xorpay_notify、/nomadvip/zpay_notify - 支付查询:
/nomadvip/order_status - VIP 识别:
/nomadvip/check_vip、/nomadvip/check_vip_by_email
发现问题
- 业务链路问题发生在前端
nomadvip:/api/pay/confirm兜底写payments时使用默认金额,可能与实际支付金额不一致(例如测试价、活动价)。
处理状态
- 已在前端仓库修复:confirm 兜底写库时优先使用网关返回
pay_price,失败才回退默认金额。 - 本仓库暂未发现新的阻断型业务 bug(以静态代码审阅为准)。
维护建议
- 建议保持
payjsapi为唯一支付入账源,前端 confirm 仅作为短时兜底。 - 建议为
order_status返回结构固定增加字段约束(如统一返回pay_price字段),避免调用侧重复兼容。