Files
sub2apipay/README.md

459 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sub2ApiPay
**语言 / Language**: 中文(当前)| [English](./README.en.md)
Sub2ApiPay 是为 [Sub2API](https://sub2api.com) 平台构建的自托管支付网关。支持 **EasyPay 易支付聚合**、**支付宝官方**、**微信官方**和 **Stripe** 四种支付渠道,提供按量充值与套餐订阅两种计费模式,支付成功后自动调用 Sub2API 管理接口完成到账,无需人工干预。
---
## 目录
- [功能特性](#功能特性)
- [技术栈](#技术栈)
- [快速开始](#快速开始)
- [环境变量](#环境变量)
- [部署指南](#部署指南)
- [集成到 Sub2API](#集成到-sub2api)
- [支付流程](#支付流程)
- [API 端点](#api-端点)
- [开发指南](#开发指南)
---
## 功能特性
- **四渠道支付** — EasyPay 易支付聚合、支付宝官方、微信官方、Stripe
- **双计费模式** — 按量余额充值 + 套餐订阅,灵活适配不同业务场景
- **自动到账** — 支付回调验签后自动调用 Sub2API 充值 / 订阅接口,全程无需人工
- **订单全生命周期** — 超时自动取消、用户主动取消、管理员取消、退款
- **限额控制** — 单笔上限、每日用户累计上限、每日渠道全局限额,多维度风控
- **安全设计** — Token 鉴权、RSA2 / MD5 / Webhook 签名验证、时序安全对比、完整审计日志
- **响应式 UI** — PC + 移动端自适应,暗色 / 亮色主题,支持 iframe 嵌入
- **中英双语** — 支付页面自动适配中英文
- **管理后台** — 数据概览、订单管理(分页/筛选/重试/退款)、渠道管理、订阅管理
> **EasyPay 推荐**:个人推荐 [ZPay](https://z-pay.cn/?uid=23808)`https://z-pay.cn/?uid=23808`)作为 EasyPay 服务商链接含作者邀请码介意可去掉。ZPay 支持**个人用户**(无营业执照)每日 1 万元以内交易;拥有营业执照则无限额。支付渠道的安全性、稳定性及合规性请自行鉴别,本项目不对任何第三方支付服务商做担保或背书。
<details>
<summary>ZPay 申请二维码</summary>
![ZPay 预览](./docs/zpay-preview.png)
</details>
---
## 技术栈
| 类别 | 技术 |
| ------ | --------------------------- |
| 框架 | Next.js 16 (App Router) |
| 语言 | TypeScript 5 + React 19 |
| 样式 | TailwindCSS 4 |
| ORM | Prisma 7adapter-pg 模式) |
| 数据库 | PostgreSQL 16 |
| 容器 | Docker + Docker Compose |
| 包管理 | pnpm |
---
## 快速开始
### 使用 Docker Hub 镜像(推荐)
无需本地安装 Node.js 或 pnpm服务器上只需 Docker。
```bash
mkdir -p /opt/sub2apipay && cd /opt/sub2apipay
# 下载 Compose 文件和环境变量模板
curl -O https://raw.githubusercontent.com/touwaeriol/sub2apipay/main/docker-compose.hub.yml
curl -O https://raw.githubusercontent.com/touwaeriol/sub2apipay/main/.env.example
cp .env.example .env
# 填写必填环境变量
nano .env
# 启动(含自带 PostgreSQL
docker compose -f docker-compose.hub.yml up -d
```
### 从源码构建
```bash
git clone https://github.com/touwaeriol/sub2apipay.git
cd sub2apipay
cp .env.example .env
nano .env
docker compose up -d --build
```
---
## 环境变量
完整模板见 [`.env.example`](./.env.example)。
### 核心(必填)
| 变量 | 说明 |
| ----------------------- | ---------------------------------------------- |
| `SUB2API_BASE_URL` | Sub2API 服务地址,如 `https://sub2api.com` |
| `SUB2API_ADMIN_API_KEY` | Sub2API 管理 API 密钥 |
| `ADMIN_TOKEN` | 管理后台访问令牌(自定义强密码) |
| `NEXT_PUBLIC_APP_URL` | 本服务的公网地址,如 `https://pay.example.com` |
> `DATABASE_URL` 使用自带数据库时由 Compose 自动注入,无需手动填写。
### 支付服务商与支付方式
**第一步**:通过 `PAYMENT_PROVIDERS` 声明启用哪些支付服务商(逗号分隔):
```env
# 可选值: easypay, alipay, wxpay, stripe
# 示例:仅使用 EasyPay 易支付聚合
PAYMENT_PROVIDERS=easypay
# 示例:同时启用支付宝官方 + 微信官方 + Stripe
PAYMENT_PROVIDERS=alipay,wxpay,stripe
```
> **支付宝官方 / 微信官方**与 **EasyPay** 可以共存。官方渠道直接对接支付宝/微信 API资金直达商户账户手续费更低EasyPay 通过第三方平台代收/转发官方,接入门槛更低。使用 EasyPay 时请尽量选择资金直接走转发官方直达自己账户的形式,而非第三方代收的服务商。
#### EasyPay支付宝 / 微信支付聚合)
任何兼容**易支付EasyPay协议**的支付服务商均可接入。
| 变量 | 说明 |
| --------------------- | ------------------------------------------------------------- |
| `EASY_PAY_PID` | EasyPay 商户 ID |
| `EASY_PAY_PKEY` | EasyPay 商户密钥 |
| `EASY_PAY_API_BASE` | EasyPay API 地址 |
| `EASY_PAY_NOTIFY_URL` | 异步回调地址,填 `${NEXT_PUBLIC_APP_URL}/api/easy-pay/notify` |
| `EASY_PAY_RETURN_URL` | 支付完成跳转地址,填 `${NEXT_PUBLIC_APP_URL}/pay/result` |
| `EASY_PAY_CID_ALIPAY` | 支付宝通道 ID可选 |
| `EASY_PAY_CID_WXPAY` | 微信支付通道 ID可选 |
#### 支付宝官方
直接对接支付宝开放平台,支持 PC 页面支付(`alipay.trade.page.pay`)和手机网站支付(`alipay.trade.wap.pay`),自动根据终端类型切换。
| 变量 | 说明 |
| -------------------- | ---------------------------- |
| `ALIPAY_APP_ID` | 支付宝应用 AppID |
| `ALIPAY_PRIVATE_KEY` | 应用私钥(内容或文件路径) |
| `ALIPAY_PUBLIC_KEY` | 支付宝公钥(内容或文件路径) |
| `ALIPAY_NOTIFY_URL` | 异步回调地址 |
| `ALIPAY_RETURN_URL` | 同步跳转地址(可选) |
#### 微信官方
直接对接微信支付 APIv3支持 Native 扫码支付和 H5 支付,移动端优先尝试 H5自动 fallback 到扫码。
| 变量 | 说明 |
| --------------------- | ------------------------------- |
| `WXPAY_APP_ID` | 微信支付 AppID |
| `WXPAY_MCH_ID` | 商户号 |
| `WXPAY_PRIVATE_KEY` | 商户 API 私钥(内容或文件路径) |
| `WXPAY_CERT_SERIAL` | 商户证书序列号 |
| `WXPAY_API_V3_KEY` | APIv3 密钥 |
| `WXPAY_PUBLIC_KEY` | 微信支付公钥(内容或文件路径) |
| `WXPAY_PUBLIC_KEY_ID` | 微信支付公钥 ID |
| `WXPAY_NOTIFY_URL` | 异步回调地址 |
#### Stripe
| 变量 | 说明 |
| ------------------------ | -------------------------------------- |
| `STRIPE_SECRET_KEY` | Stripe 密钥(`sk_live_...` |
| `STRIPE_PUBLISHABLE_KEY` | Stripe 可公开密钥(`pk_live_...` |
| `STRIPE_WEBHOOK_SECRET` | Stripe Webhook 签名密钥(`whsec_...` |
> Stripe Webhook 端点:`${NEXT_PUBLIC_APP_URL}/api/stripe/webhook`
> 需订阅事件:`payment_intent.succeeded`、`payment_intent.payment_failed`
### 业务规则
| 变量 | 说明 | 默认值 |
| -------------------------------- | ---------------------------------------- | -------------------------- |
| `MIN_RECHARGE_AMOUNT` | 单笔最低充值金额(元) | `1` |
| `MAX_RECHARGE_AMOUNT` | 单笔最高充值金额(元) | `1000` |
| `MAX_DAILY_RECHARGE_AMOUNT` | 每日每用户累计最高充值(元,`0` = 不限) | `10000` |
| `MAX_DAILY_AMOUNT_ALIPAY` | 易支付支付宝渠道每日全局限额(可选) | 由提供商默认 |
| `MAX_DAILY_AMOUNT_ALIPAY_DIRECT` | 支付宝官方渠道每日全局限额(可选) | 由提供商默认 |
| `MAX_DAILY_AMOUNT_WXPAY` | 微信支付渠道每日全局限额(可选) | 由提供商默认 |
| `MAX_DAILY_AMOUNT_STRIPE` | Stripe 渠道每日全局限额(可选) | 由提供商默认 |
| `ORDER_TIMEOUT_MINUTES` | 订单超时分钟数 | `5` |
| `PRODUCT_NAME` | 充值商品名称(显示在支付页) | `Sub2API Balance Recharge` |
### UI 定制(可选)
在充值页面右侧可展示客服联系方式、说明图片等帮助内容。
| 变量 | 说明 |
| -------------------- | --------------------------------------------------------------- |
| `PAY_HELP_IMAGE_URL` | 帮助图片地址(支持外部 URL 或本地路径,见下方说明) |
| `PAY_HELP_TEXT` | 帮助说明文字,用 `\n` 换行,如 `扫码加微信\n工作日 9-18 点在线` |
**图片地址两种方式:**
- **外部 URL**(推荐,无需改 Compose 配置):直接填图片的公网地址,如 OSS / CDN / 图床链接。
```env
PAY_HELP_IMAGE_URL=https://cdn.example.com/help-qr.jpg
```
- **本地文件**:将图片放到 `./uploads/` 目录,通过 `/uploads/文件名` 引用。
需在 `docker-compose.app.yml` 中挂载目录(默认已包含):
```yaml
volumes:
- ./uploads:/app/public/uploads:ro
```
```env
PAY_HELP_IMAGE_URL=/uploads/help-qr.jpg
```
> 点击帮助图片可在屏幕中央全屏放大查看。
### Docker Compose 专用
| 变量 | 说明 | 默认值 |
| ------------- | ----------------------------------- | ---------------------------- |
| `APP_PORT` | 宿主机映射端口 | `3001` |
| `DB_PASSWORD` | PostgreSQL 密码(使用自带数据库时) | `password`**生产请修改** |
---
## 部署指南
### 方案一Docker Hub 镜像 + 自带数据库
使用 `docker-compose.hub.yml`,最省事的部署方式:
```bash
docker compose -f docker-compose.hub.yml up -d
```
镜像:[`touwaeriol/sub2apipay:latest`](https://hub.docker.com/r/touwaeriol/sub2apipay)
### 方案二Docker Hub 镜像 + 外部数据库
适用于已有 PostgreSQL 实例(如与其他服务共用):
1. 在 `.env` 中填写 `DATABASE_URL`
2. 使用 `docker-compose.app.yml`(仅启动应用,不含 DB
```bash
docker compose -f docker-compose.app.yml up -d
```
### 方案三:从源码构建
适用于自定义修改后自行构建:
```bash
# 在构建服务器上
docker compose build
docker tag sub2apipay-app:latest touwaeriol/sub2apipay:latest
docker push touwaeriol/sub2apipay:latest
# 在部署服务器上
docker compose -f docker-compose.hub.yml pull
docker compose -f docker-compose.hub.yml up -d
```
### 端口与反向代理
默认宿主机端口为 `3001`(可通过 `APP_PORT` 修改)。建议使用 Nginx/Caddy 作反向代理并配置 HTTPS
```nginx
server {
listen 443 ssl;
server_name pay.example.com;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### 数据库迁移
容器启动时自动执行 `prisma migrate deploy`,无需手动操作。如需手动执行:
```bash
docker compose exec app npx prisma migrate deploy
```
---
## 集成到 Sub2API
假设本服务部署在 `https://pay.example.com`。
### 用户端页面
在 Sub2API 管理后台的**充值设置**中配置以下链接,用户即可从 Sub2API 平台跳转到充值和订单页面:
| 配置项 | URL | 说明 |
| -------- | ------------------------------------ | --------------------------- |
| 充值页面 | `https://pay.example.com/pay` | 用户充值、购买订阅套餐入口 |
| 我的订单 | `https://pay.example.com/pay/orders` | 用户查看自己的充值/订阅记录 |
Sub2API **v0.1.88** 及以上版本会自动拼接以下参数,无需手动添加:
| 参数 | 说明 |
| --------- | ------------------------------------------------ |
| `user_id` | Sub2API 用户 ID |
| `token` | 用户登录 Token有 token 才能查看订单历史) |
| `theme` | `light`(默认)或 `dark` |
| `lang` | 界面语言,`zh`(默认)或 `en` |
| `ui_mode` | `standalone`(默认)或 `embedded`iframe 嵌入) |
### 管理后台
管理后台通过 URL 参数 `token` 鉴权(值为环境变量 `ADMIN_TOKEN`)。在 Sub2API 中集成时只需配置路径,**无需附加任何查询参数**——Sub2API 会自动拼接 `token` 等参数:
| 页面 | URL | 说明 |
| -------- | --------------------------------------------- | ---------------------------------------------- |
| 管理总览 | `https://pay.example.com/admin` | 聚合入口,卡片式导航到各管理模块 |
| 订单管理 | `https://pay.example.com/admin/orders` | 按状态筛选、分页浏览、订单详情、重试/取消/退款 |
| 数据概览 | `https://pay.example.com/admin/dashboard` | 收入统计、订单趋势、支付方式分布 |
| 渠道管理 | `https://pay.example.com/admin/channels` | 配置 API 渠道与倍率,支持从 Sub2API 同步 |
| 订阅管理 | `https://pay.example.com/admin/subscriptions` | 管理订阅套餐与用户订阅 |
> **提示**:若独立访问(不通过 Sub2API 跳转),需手动在 URL 后添加 `?token=YOUR_ADMIN_TOKEN`。管理后台所有页面间共享同一个 token进入任一页面后可通过侧边导航切换。
---
## 支付流程
```
用户选择充值 / 订阅套餐
创建订单 (PENDING)
├─ 校验用户状态 / 待支付订单数 / 每日限额 / 渠道限额
└─ 调用支付提供商获取支付链接
用户完成支付
├─ EasyPay → 扫码 / H5 跳转
├─ 支付宝官方 → PC 页面支付 / H5 手机网站支付
├─ 微信官方 → Native 扫码 / H5 支付
└─ Stripe → Payment Element (PaymentIntent)
支付回调RSA2 / MD5 / Webhook 签名验证)→ 订单 PAID
自动调用 Sub2API 充值 / 订阅接口
├─ 成功 → COMPLETED余额到账 / 订阅生效
└─ 失败 → FAILED管理员可重试
```
---
## API 端点
所有 API 路径前缀为 `/api`。
### 公开 API
用户侧接口,通过 URL 参数 `user_id` + `token` 鉴权。
| 方法 | 路径 | 说明 |
| ------ | ------------------------- | ------------------------------ |
| `GET` | `/api/user` | 获取当前用户信息 |
| `GET` | `/api/users/:id` | 获取指定用户信息 |
| `POST` | `/api/orders` | 创建充值 / 订阅订单 |
| `GET` | `/api/orders/:id` | 查询订单详情 |
| `POST` | `/api/orders/:id/cancel` | 用户取消待支付订单 |
| `GET` | `/api/orders/my` | 查询当前用户的订单列表 |
| `GET` | `/api/channels` | 获取渠道列表(前端展示用) |
| `GET` | `/api/subscription-plans` | 获取在售订阅套餐列表 |
| `GET` | `/api/subscriptions/my` | 查询当前用户的订阅状态 |
| `GET` | `/api/limits` | 查询充值限额与支付方式可用状态 |
### 支付回调
由支付服务商异步调用,签名验证后触发到账流程。
| 方法 | 路径 | 说明 |
| ------ | ---------------------- | ----------------------- |
| `GET` | `/api/easy-pay/notify` | EasyPay 异步回调GET |
| `POST` | `/api/alipay/notify` | 支付宝官方异步回调 |
| `POST` | `/api/wxpay/notify` | 微信官方异步回调 |
| `POST` | `/api/stripe/webhook` | Stripe Webhook 回调 |
### 管理 API
需通过 `token` 参数传递 `ADMIN_TOKEN` 鉴权。
| 方法 | 路径 | 说明 |
| -------- | ----------------------------------- | -------------------------- |
| `GET` | `/api/admin/orders` | 订单列表(分页、状态筛选) |
| `GET` | `/api/admin/orders/:id` | 订单详情(含审计日志) |
| `POST` | `/api/admin/orders/:id/cancel` | 管理员取消订单 |
| `POST` | `/api/admin/orders/:id/retry` | 重试失败的充值 / 订阅 |
| `POST` | `/api/admin/refund` | 发起退款 |
| `GET` | `/api/admin/dashboard` | 数据概览(收入统计、趋势) |
| `GET` | `/api/admin/channels` | 渠道列表 |
| `POST` | `/api/admin/channels` | 创建渠道 |
| `PUT` | `/api/admin/channels/:id` | 更新渠道 |
| `DELETE` | `/api/admin/channels/:id` | 删除渠道 |
| `GET` | `/api/admin/subscription-plans` | 订阅套餐列表 |
| `POST` | `/api/admin/subscription-plans` | 创建订阅套餐 |
| `PUT` | `/api/admin/subscription-plans/:id` | 更新订阅套餐 |
| `DELETE` | `/api/admin/subscription-plans/:id` | 删除订阅套餐 |
| `GET` | `/api/admin/subscriptions` | 用户订阅记录列表 |
| `GET` | `/api/admin/config` | 获取系统配置 |
| `PUT` | `/api/admin/config` | 更新系统配置 |
| `GET` | `/api/admin/sub2api/groups` | 从 Sub2API 同步渠道分组 |
| `GET` | `/api/admin/sub2api/search-users` | 搜索 Sub2API 用户 |
---
## 开发指南
### 环境要求
- Node.js 22+
- pnpm
- PostgreSQL 16+
### 本地启动
```bash
pnpm install
cp .env.example .env
# 编辑 .env填写 DATABASE_URL 和其他必填项
pnpm prisma migrate dev
pnpm dev
```
### 常用命令
```bash
pnpm dev # 开发服务器(热重载)
pnpm build # 生产构建
pnpm test # 运行测试
pnpm typecheck # TypeScript 类型检查
pnpm lint # ESLint 代码检查
pnpm format # Prettier 格式化
pnpm prisma generate # 生成 Prisma 客户端
pnpm prisma migrate dev # 创建迁移(开发)
pnpm prisma migrate deploy # 应用迁移(生产)
pnpm prisma studio # 可视化数据库管理
```
---
## License
MIT