diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..e6d43f2 --- /dev/null +++ b/README.en.md @@ -0,0 +1,316 @@ +# Sub2ApiPay + +**Language**: [中文](./README.md) | English (current) + +Sub2ApiPay is a self-hosted recharge payment gateway built for the [Sub2API](https://sub2api.com) platform. It supports Alipay, WeChat Pay (via EasyPay aggregator), and Stripe. Once a payment is confirmed, the system automatically calls the Sub2API management API to credit the user's balance — no manual intervention required. + +--- + +## Table of Contents + +- [Features](#features) +- [Tech Stack](#tech-stack) +- [Quick Start](#quick-start) +- [Environment Variables](#environment-variables) +- [Deployment](#deployment) +- [Sub2API Integration](#sub2api-integration) +- [Admin Panel](#admin-panel) +- [Payment Flow](#payment-flow) +- [Development](#development) + +--- + +## Features + +- **Multiple Payment Methods** — Alipay, WeChat Pay (EasyPay), Stripe credit card +- **Auto Balance Credit** — Automatically calls Sub2API after payment verification, fully hands-free +- **Full Order Lifecycle** — Auto-expiry, user cancellation, admin cancellation, refunds +- **Limit Controls** — Configurable per-transaction cap and daily cumulative cap per user +- **Security** — Token auth, MD5/Webhook signature verification, timing-safe comparison, full audit log +- **Responsive UI** — PC + mobile adaptive layout, dark mode support, iframe embed support +- **Admin Panel** — Order list (pagination/filtering), order details, retry recharge, refunds + +--- + +## Tech Stack + +| Category | Technology | +|----------|------------| +| Framework | Next.js 16 (App Router) | +| Language | TypeScript 5 + React 19 | +| Styling | TailwindCSS 4 | +| ORM | Prisma 7 (adapter-pg mode) | +| Database | PostgreSQL 16 | +| Container | Docker + Docker Compose | +| Package Manager | pnpm | + +--- + +## Quick Start + +### Using Docker Hub Image (Recommended) + +No Node.js or pnpm required on the server — just Docker. + +```bash +mkdir -p /opt/sub2apipay && cd /opt/sub2apipay + +# Download Compose file and env template +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 + +# Fill in required environment variables +nano .env + +# Start (includes bundled PostgreSQL) +docker compose -f docker-compose.hub.yml up -d +``` + +### Build from Source + +```bash +git clone https://github.com/touwaeriol/sub2apipay.git +cd sub2apipay +cp .env.example .env +nano .env +docker compose up -d --build +``` + +--- + +## Environment Variables + +See [`.env.example`](./.env.example) for the full template. + +### Core (Required) + +| Variable | Description | +|----------|-------------| +| `SUB2API_BASE_URL` | Sub2API service URL, e.g. `https://sub2api.com` | +| `SUB2API_ADMIN_API_KEY` | Sub2API admin API key | +| `ADMIN_TOKEN` | Admin panel access token (use a strong random string) | +| `NEXT_PUBLIC_APP_URL` | Public URL of this service, e.g. `https://pay.example.com` | + +> `DATABASE_URL` is automatically injected by Docker Compose when using the bundled database. + +### Payment Methods + +Control which payment methods are enabled via `ENABLED_PAYMENT_TYPES` (comma-separated): + +```env +ENABLED_PAYMENT_TYPES=alipay,wxpay,stripe +``` + +#### EasyPay (Alipay / WeChat Pay) + +| Variable | Description | +|----------|-------------| +| `EASY_PAY_PID` | EasyPay merchant ID | +| `EASY_PAY_PKEY` | EasyPay merchant secret key | +| `EASY_PAY_API_BASE` | EasyPay API base URL | +| `EASY_PAY_NOTIFY_URL` | Async callback URL: `${NEXT_PUBLIC_APP_URL}/api/easy-pay/notify` | +| `EASY_PAY_RETURN_URL` | Redirect URL after payment: `${NEXT_PUBLIC_APP_URL}/pay` | +| `EASY_PAY_CID_ALIPAY` | Alipay channel ID (optional) | +| `EASY_PAY_CID_WXPAY` | WeChat Pay channel ID (optional) | + +#### Stripe + +| Variable | Description | +|----------|-------------| +| `STRIPE_SECRET_KEY` | Stripe secret key (`sk_live_...`) | +| `STRIPE_PUBLISHABLE_KEY` | Stripe publishable key (`pk_live_...`) | +| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret (`whsec_...`) | + +> Stripe webhook endpoint: `${NEXT_PUBLIC_APP_URL}/api/stripe/webhook` +> Subscribe to: `checkout.session.completed`, `checkout.session.expired` + +### Business Rules + +| Variable | Description | Default | +|----------|-------------|---------| +| `MIN_RECHARGE_AMOUNT` | Minimum amount per transaction (CNY) | `1` | +| `MAX_RECHARGE_AMOUNT` | Maximum amount per transaction (CNY) | `1000` | +| `MAX_DAILY_RECHARGE_AMOUNT` | Daily cumulative max per user (`0` = unlimited) | `10000` | +| `ORDER_TIMEOUT_MINUTES` | Order expiry in minutes | `5` | +| `PRODUCT_NAME` | Product name shown on payment page | `Sub2API Balance Recharge` | + +### UI Customization (Optional) + +| Variable | Description | +|----------|-------------| +| `NEXT_PUBLIC_PAY_HELP_IMAGE_URL` | Help image URL (e.g. customer service QR code) | +| `NEXT_PUBLIC_PAY_HELP_TEXT` | Help text displayed on payment page | + +### Docker Compose Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `APP_PORT` | Host port mapping | `3001` | +| `DB_PASSWORD` | PostgreSQL password (bundled DB) | `password` (**change in production**) | + +--- + +## Deployment + +### Option 1: Docker Hub Image + Bundled Database + +Use `docker-compose.hub.yml` — the simplest deployment: + +```bash +docker compose -f docker-compose.hub.yml up -d +``` + +Image: [`touwaeriol/sub2apipay:latest`](https://hub.docker.com/r/touwaeriol/sub2apipay) + +### Option 2: Docker Hub Image + External Database + +For existing PostgreSQL instances (shared with other services): + +1. Set `DATABASE_URL` in `.env` +2. Use `docker-compose.app.yml` (app only, no DB): + +```bash +docker compose -f docker-compose.app.yml up -d +``` + +### Option 3: Build from Source + +For custom builds after modifications: + +```bash +# On the build server +docker compose build +docker tag sub2apipay-app:latest touwaeriol/sub2apipay:latest +docker push touwaeriol/sub2apipay:latest + +# On the deploy server +docker compose -f docker-compose.hub.yml pull +docker compose -f docker-compose.hub.yml up -d +``` + +### Reverse Proxy + +The default host port is `3001` (configurable via `APP_PORT`). Use Nginx or Caddy as a reverse proxy with 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; + } +} +``` + +### Database Migrations + +Migrations run automatically on container startup via `prisma migrate deploy`. To run manually: + +```bash +docker compose exec app npx prisma migrate deploy +``` + +--- + +## Sub2API Integration + +Configure the recharge URL in the Sub2API admin panel: + +``` +https://pay.example.com/pay?user_id={USER_ID}&token={TOKEN}&theme={THEME} +``` + +| Parameter | Description | +|-----------|-------------| +| `user_id` | Sub2API user ID (required) | +| `token` | User login token (optional — required to view order history) | +| `theme` | `light` (default) or `dark` | +| `ui_mode` | `standalone` (default) or `embedded` (for iframe) | + +--- + +## Admin Panel + +Access: `https://pay.example.com/admin?token=YOUR_ADMIN_TOKEN` + +| Feature | Description | +|---------|-------------| +| Order List | Filter by status, paginate, choose 20/50/100 per page | +| Order Detail | View all fields and audit log timeline | +| Retry Recharge | Re-trigger recharge for paid-but-failed orders | +| Cancel Order | Force-cancel pending orders | +| Refund | Issue refund and deduct Sub2API balance | + +--- + +## Payment Flow + +``` +User submits recharge amount + │ + ▼ + Create Order (PENDING) + ├─ Validate user status / pending order count / daily limit + └─ Call payment provider to get payment link + │ + ▼ + User completes payment + ├─ EasyPay → QR code / H5 redirect + └─ Stripe → Checkout Session + │ + ▼ + Payment callback (signature verified) → Order PAID + │ + ▼ + Auto-call Sub2API recharge API + ├─ Success → COMPLETED, balance credited automatically + └─ Failure → FAILED (admin can retry) +``` + +--- + +## Development + +### Requirements + +- Node.js 22+ +- pnpm +- PostgreSQL 16+ + +### Local Setup + +```bash +pnpm install +cp .env.example .env +# Edit .env with DATABASE_URL and other required values +pnpm prisma migrate dev +pnpm dev +``` + +### Commands + +```bash +pnpm dev # Dev server with hot reload +pnpm build # Production build +pnpm test # Run tests +pnpm typecheck # TypeScript type check +pnpm lint # ESLint +pnpm format # Prettier format + +pnpm prisma generate # Generate Prisma client +pnpm prisma migrate dev # Create and apply migration (dev) +pnpm prisma migrate deploy # Apply migrations (production) +pnpm prisma studio # Visual database browser +``` + +--- + +## License + +MIT diff --git a/README.md b/README.md index e215bc4..9ff8118 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,316 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Sub2ApiPay -## Getting Started +**语言 / Language**: 中文(当前)| [English](./README.en.md) -First, run the development server: +Sub2ApiPay 是为 [Sub2API](https://sub2api.com) 平台构建的自托管充值支付网关。支持支付宝、微信支付(通过 EasyPay 聚合)和 Stripe,订单支付成功后自动调用 Sub2API 管理接口完成余额到账,无需人工干预。 + +--- + +## 目录 + +- [功能特性](#功能特性) +- [技术栈](#技术栈) +- [快速开始](#快速开始) +- [环境变量](#环境变量) +- [部署指南](#部署指南) +- [集成到 Sub2API](#集成到-sub2api) +- [管理后台](#管理后台) +- [支付流程](#支付流程) +- [开发指南](#开发指南) + +--- + +## 功能特性 + +- **多支付方式** — 支付宝、微信支付(EasyPay 聚合)、Stripe 信用卡 +- **自动到账** — 支付回调验签后自动调用 Sub2API 充值接口,全程无需人工 +- **订单全生命周期** — 超时自动取消、用户主动取消、管理员取消、退款 +- **限额控制** — 可配置单笔上限与每日累计上限,按用户维度统计 +- **安全设计** — Token 鉴权、MD5/Webhook 签名验证、时序安全对比、完整审计日志 +- **响应式 UI** — PC + 移动端自适应,支持深色模式,支持 iframe 嵌入 +- **管理后台** — 订单列表(分页/筛选)、订单详情、重试充值、退款 + +--- + +## 技术栈 + +| 类别 | 技术 | +|------|------| +| 框架 | Next.js 16 (App Router) | +| 语言 | TypeScript 5 + React 19 | +| 样式 | TailwindCSS 4 | +| ORM | Prisma 7(adapter-pg 模式) | +| 数据库 | PostgreSQL 16 | +| 容器 | Docker + Docker Compose | +| 包管理 | pnpm | + +--- + +## 快速开始 + +### 使用 Docker Hub 镜像(推荐) + +无需本地安装 Node.js 或 pnpm,服务器上只需 Docker。 ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +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 ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +### 从源码构建 -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +```bash +git clone https://github.com/touwaeriol/sub2apipay.git +cd sub2apipay +cp .env.example .env +nano .env +docker compose up -d --build +``` -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +--- -## Learn More +## 环境变量 -To learn more about Next.js, take a look at the following resources: +完整模板见 [`.env.example`](./.env.example)。 -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +### 核心(必填) -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +| 变量 | 说明 | +|------|------| +| `SUB2API_BASE_URL` | Sub2API 服务地址,如 `https://sub2api.com` | +| `SUB2API_ADMIN_API_KEY` | Sub2API 管理 API 密钥 | +| `ADMIN_TOKEN` | 管理后台访问令牌(自定义强密码) | +| `NEXT_PUBLIC_APP_URL` | 本服务的公网地址,如 `https://pay.example.com` | -## Deploy on Vercel +> `DATABASE_URL` 使用自带数据库时由 Compose 自动注入,无需手动填写。 -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +### 支付方式 -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +通过 `ENABLED_PAYMENT_TYPES` 控制开启哪些支付方式(逗号分隔): + +```env +ENABLED_PAYMENT_TYPES=alipay,wxpay,stripe +``` + +#### 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` | +| `EASY_PAY_CID_ALIPAY` | 支付宝通道 ID(可选) | +| `EASY_PAY_CID_WXPAY` | 微信支付通道 ID(可选) | + +#### 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` +> 需订阅事件:`checkout.session.completed`、`checkout.session.expired` + +### 业务规则 + +| 变量 | 说明 | 默认值 | +|------|------|--------| +| `MIN_RECHARGE_AMOUNT` | 单笔最低充值金额(元) | `1` | +| `MAX_RECHARGE_AMOUNT` | 单笔最高充值金额(元) | `1000` | +| `MAX_DAILY_RECHARGE_AMOUNT` | 每日累计最高充值(元,`0` = 不限) | `10000` | +| `ORDER_TIMEOUT_MINUTES` | 订单超时分钟数 | `5` | +| `PRODUCT_NAME` | 充值商品名称(显示在支付页) | `Sub2API Balance Recharge` | + +### UI 定制(可选) + +| 变量 | 说明 | +|------|------| +| `NEXT_PUBLIC_PAY_HELP_IMAGE_URL` | 帮助图片 URL(如客服二维码) | +| `NEXT_PUBLIC_PAY_HELP_TEXT` | 帮助说明文字 | + +### 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 + +在 Sub2API 管理后台将充值链接配置为: + +``` +https://pay.example.com/pay?user_id={USER_ID}&token={TOKEN}&theme={THEME} +``` + +| 参数 | 说明 | +|------|------| +| `user_id` | Sub2API 用户 ID(必填) | +| `token` | 用户登录 Token(可选,有 token 才能查看订单历史) | +| `theme` | `light`(默认)或 `dark` | +| `ui_mode` | `standalone`(默认)或 `embedded`(iframe 嵌入) | + +--- + +## 管理后台 + +访问:`https://pay.example.com/admin?token=YOUR_ADMIN_TOKEN` + +| 功能 | 说明 | +|------|------| +| 订单列表 | 按状态筛选、分页浏览,支持每页 20/50/100 条 | +| 订单详情 | 查看完整字段与操作审计日志 | +| 重试充值 | 对已支付但充值失败的订单重新发起充值 | +| 取消订单 | 强制取消待支付订单 | +| 退款 | 对已完成订单发起退款并扣减 Sub2API 余额 | + +--- + +## 支付流程 + +``` +用户提交充值金额 + │ + ▼ + 创建订单 (PENDING) + ├─ 校验用户状态 / 待支付订单数 / 每日限额 + └─ 调用支付提供商获取支付链接 + │ + ▼ + 用户完成支付 + ├─ EasyPay → 扫码 / H5 跳转 + └─ Stripe → Checkout Session + │ + ▼ + 支付回调(签名验证)→ 订单 PAID + │ + ▼ + 自动调用 Sub2API 充值接口 + ├─ 成功 → COMPLETED,余额自动到账 + └─ 失败 → FAILED(管理员可重试) +``` + +--- + +## 开发指南 + +### 环境要求 + +- 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 diff --git a/docker-compose.app.yml b/docker-compose.app.yml new file mode 100644 index 0000000..9858a6f --- /dev/null +++ b/docker-compose.app.yml @@ -0,0 +1,14 @@ +# docker-compose.app.yml +# 使用 Docker Hub 镜像部署(仅应用,外部数据库) +# 适合:已有 PostgreSQL 实例,将 DATABASE_URL 填入 .env +# +# 启动:docker compose -f docker-compose.app.yml up -d +# 更新:docker compose -f docker-compose.app.yml pull && docker compose -f docker-compose.app.yml up -d + +services: + app: + image: touwaeriol/sub2apipay:latest + ports: + - '${APP_PORT:-3001}:3000' + env_file: .env + restart: unless-stopped diff --git a/docker-compose.hub.yml b/docker-compose.hub.yml new file mode 100644 index 0000000..61f5e16 --- /dev/null +++ b/docker-compose.hub.yml @@ -0,0 +1,37 @@ +# docker-compose.hub.yml +# 使用 Docker Hub 镜像部署(含自带 PostgreSQL) +# 适合:全新服务器,无现有数据库 +# +# 启动:docker compose -f docker-compose.hub.yml up -d +# 更新:docker compose -f docker-compose.hub.yml pull && docker compose -f docker-compose.hub.yml up -d + +services: + app: + image: touwaeriol/sub2apipay:latest + ports: + - '${APP_PORT:-3001}:3000' + env_file: .env + environment: + - DATABASE_URL=postgresql://sub2apipay:${DB_PASSWORD:-password}@db:5432/sub2apipay + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: sub2apipay + POSTGRES_PASSWORD: ${DB_PASSWORD:-password} + POSTGRES_DB: sub2apipay + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U sub2apipay'] + interval: 5s + timeout: 5s + retries: 10 + restart: unless-stopped + +volumes: + pgdata: