diff --git a/.gitignore b/.gitignore index cf2bda9f..bf7ee064 100644 --- a/.gitignore +++ b/.gitignore @@ -126,7 +126,10 @@ backend/cmd/server/server deploy/docker-compose.override.yml .gocache/ vite.config.js -docs/ +docs/* +!docs/PAYMENT.md +!docs/PAYMENT_CN.md +!docs/ADMIN_PAYMENT_INTEGRATION_API.md .serena/ .codex/ frontend/coverage/ diff --git a/docs/ADMIN_PAYMENT_INTEGRATION_API.md b/docs/ADMIN_PAYMENT_INTEGRATION_API.md new file mode 100644 index 00000000..f674f86c --- /dev/null +++ b/docs/ADMIN_PAYMENT_INTEGRATION_API.md @@ -0,0 +1,243 @@ +# ADMIN_PAYMENT_INTEGRATION_API + +> 单文件中英双语文档 / Single-file bilingual documentation (Chinese + English) + +--- + +## 中文 + +### 目标 +本文档用于对接外部支付系统(如 `sub2apipay`)与 Sub2API 的 Admin API,覆盖: +- 支付成功后充值 +- 用户查询 +- 人工余额修正 +- 前端购买页参数透传 + +### 基础地址 +- 生产:`https://` +- Beta:`http://:8084` + +### 认证 +推荐使用: +- `x-api-key: admin-<64hex>` +- `Content-Type: application/json` +- 幂等接口额外传:`Idempotency-Key` + +说明:管理员 JWT 也可访问 admin 路由,但服务间调用建议使用 Admin API Key。 + +### 1) 一步完成创建并兑换 +`POST /api/v1/admin/redeem-codes/create-and-redeem` + +用途:原子完成“创建兑换码 + 兑换到指定用户”。 + +请求头: +- `x-api-key` +- `Idempotency-Key` + +请求体示例: +```json +{ + "code": "s2p_cm1234567890", + "type": "balance", + "value": 100.0, + "user_id": 123, + "notes": "sub2apipay order: cm1234567890" +} +``` + +幂等语义: +- 同 `code` 且 `used_by` 一致:`200` +- 同 `code` 但 `used_by` 不一致:`409` +- 缺少 `Idempotency-Key`:`400`(`IDEMPOTENCY_KEY_REQUIRED`) + +curl 示例: +```bash +curl -X POST "${BASE}/api/v1/admin/redeem-codes/create-and-redeem" \ + -H "x-api-key: ${KEY}" \ + -H "Idempotency-Key: pay-cm1234567890-success" \ + -H "Content-Type: application/json" \ + -d '{ + "code":"s2p_cm1234567890", + "type":"balance", + "value":100.00, + "user_id":123, + "notes":"sub2apipay order: cm1234567890" + }' +``` + +### 2) 查询用户(可选前置校验) +`GET /api/v1/admin/users/:id` + +```bash +curl -s "${BASE}/api/v1/admin/users/123" \ + -H "x-api-key: ${KEY}" +``` + +### 3) 余额调整(已有接口) +`POST /api/v1/admin/users/:id/balance` + +用途:人工补偿 / 扣减,支持 `set` / `add` / `subtract`。 + +请求体示例(扣减): +```json +{ + "balance": 100.0, + "operation": "subtract", + "notes": "manual correction" +} +``` + +```bash +curl -X POST "${BASE}/api/v1/admin/users/123/balance" \ + -H "x-api-key: ${KEY}" \ + -H "Idempotency-Key: balance-subtract-cm1234567890" \ + -H "Content-Type: application/json" \ + -d '{ + "balance":100.00, + "operation":"subtract", + "notes":"manual correction" + }' +``` + +### 4) 购买页 / 自定义页面 URL Query 透传(iframe / 新窗口一致) +当 Sub2API 打开 `purchase_subscription_url` 或用户侧自定义页面 iframe URL 时,会统一追加: +- `user_id` +- `token` +- `theme`(`light` / `dark`) +- `lang`(例如 `zh` / `en`,用于向嵌入页传递当前界面语言) +- `ui_mode`(固定 `embedded`) + +示例: +```text +https://pay.example.com/pay?user_id=123&token=&theme=light&lang=zh&ui_mode=embedded +``` + +### 5) 失败处理建议 +- 支付成功与充值成功分状态落库 +- 回调验签成功后立即标记“支付成功” +- 支付成功但充值失败的订单允许后续重试 +- 重试保持相同 `code`,并使用新的 `Idempotency-Key` + +### 6) `doc_url` 配置建议 +- 查看链接:`https://github.com/Wei-Shaw/sub2api/blob/main/ADMIN_PAYMENT_INTEGRATION_API.md` +- 下载链接:`https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/ADMIN_PAYMENT_INTEGRATION_API.md` + +--- + +## English + +### Purpose +This document describes the minimal Sub2API Admin API surface for external payment integrations (for example, `sub2apipay`), including: +- Recharge after payment success +- User lookup +- Manual balance correction +- Purchase page query parameter forwarding + +### Base URL +- Production: `https://` +- Beta: `http://:8084` + +### Authentication +Recommended headers: +- `x-api-key: admin-<64hex>` +- `Content-Type: application/json` +- `Idempotency-Key` for idempotent endpoints + +Note: Admin JWT can also access admin routes, but Admin API Key is recommended for server-to-server integration. + +### 1) Create and Redeem in one step +`POST /api/v1/admin/redeem-codes/create-and-redeem` + +Use case: atomically create a redeem code and redeem it to a target user. + +Headers: +- `x-api-key` +- `Idempotency-Key` + +Request body: +```json +{ + "code": "s2p_cm1234567890", + "type": "balance", + "value": 100.0, + "user_id": 123, + "notes": "sub2apipay order: cm1234567890" +} +``` + +Idempotency behavior: +- Same `code` and same `used_by`: `200` +- Same `code` but different `used_by`: `409` +- Missing `Idempotency-Key`: `400` (`IDEMPOTENCY_KEY_REQUIRED`) + +curl example: +```bash +curl -X POST "${BASE}/api/v1/admin/redeem-codes/create-and-redeem" \ + -H "x-api-key: ${KEY}" \ + -H "Idempotency-Key: pay-cm1234567890-success" \ + -H "Content-Type: application/json" \ + -d '{ + "code":"s2p_cm1234567890", + "type":"balance", + "value":100.00, + "user_id":123, + "notes":"sub2apipay order: cm1234567890" + }' +``` + +### 2) Query User (optional pre-check) +`GET /api/v1/admin/users/:id` + +```bash +curl -s "${BASE}/api/v1/admin/users/123" \ + -H "x-api-key: ${KEY}" +``` + +### 3) Balance Adjustment (existing API) +`POST /api/v1/admin/users/:id/balance` + +Use case: manual correction with `set` / `add` / `subtract`. + +Request body example (`subtract`): +```json +{ + "balance": 100.0, + "operation": "subtract", + "notes": "manual correction" +} +``` + +```bash +curl -X POST "${BASE}/api/v1/admin/users/123/balance" \ + -H "x-api-key: ${KEY}" \ + -H "Idempotency-Key: balance-subtract-cm1234567890" \ + -H "Content-Type: application/json" \ + -d '{ + "balance":100.00, + "operation":"subtract", + "notes":"manual correction" + }' +``` + +### 4) Purchase / Custom Page URL query forwarding (iframe and new tab) +When Sub2API opens `purchase_subscription_url` or a user-facing custom page iframe URL, it appends: +- `user_id` +- `token` +- `theme` (`light` / `dark`) +- `lang` (for example `zh` / `en`, used to pass the current UI language to the embedded page) +- `ui_mode` (fixed: `embedded`) + +Example: +```text +https://pay.example.com/pay?user_id=123&token=&theme=light&lang=zh&ui_mode=embedded +``` + +### 5) Failure handling recommendations +- Persist payment success and recharge success as separate states +- Mark payment as successful immediately after verified callback +- Allow retry for orders with payment success but recharge failure +- Keep the same `code` for retry, and use a new `Idempotency-Key` + +### 6) Recommended `doc_url` +- View URL: `https://github.com/Wei-Shaw/sub2api/blob/main/ADMIN_PAYMENT_INTEGRATION_API.md` +- Download URL: `https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/ADMIN_PAYMENT_INTEGRATION_API.md` diff --git a/docs/PAYMENT.md b/docs/PAYMENT.md new file mode 100644 index 00000000..9322f7bf --- /dev/null +++ b/docs/PAYMENT.md @@ -0,0 +1,287 @@ +# Payment System Configuration Guide + +Sub2API has a built-in payment system that enables user self-service top-up without deploying a separate payment service. + +--- + +## Table of Contents + +- [Supported Payment Methods](#supported-payment-methods) +- [Quick Start](#quick-start) +- [System Settings](#system-settings) +- [Provider Configuration](#provider-configuration) +- [Provider Instance Management](#provider-instance-management) +- [Webhook Configuration](#webhook-configuration) +- [Payment Flow](#payment-flow) +- [Migrating from Sub2ApiPay](#migrating-from-sub2apipay) + +--- + +## Supported Payment Methods + +| Provider | Payment Methods | Description | +|----------|----------------|-------------| +| **EasyPay** | Alipay, WeChat Pay | Third-party aggregation via EasyPay protocol | +| **Alipay (Direct)** | Desktop QR code, mobile Alipay redirect | Direct integration with Alipay Open Platform, returning desktop QR codes and mobile WAP/app launch links | +| **WeChat Pay (Direct)** | Native QR, H5, MP/JSAPI Pay | Direct integration with WeChat Pay APIv3 with environment-aware routing | +| **Stripe** | Card, Alipay, WeChat Pay, Link, etc. | International payments, multi-currency support | + +> Alipay/WeChat Pay direct and EasyPay can both exist as backend provider instances, but the frontend always exposes only two visible buttons: `Alipay` and `WeChat Pay`. Admins choose exactly one source for each visible method: direct or EasyPay. Direct channels connect to payment APIs directly with lower fees; EasyPay aggregates through third-party platforms with easier setup. + +> **EasyPay Provider Recommendations**: Both options below are third-party aggregators compatible with the EasyPay protocol. Pick based on the funding channel and settlement currency you need: +> +> - **Domestic channel / CNY settlement** — [ZPay](https://z-pay.cn/?uid=23808) (`https://z-pay.cn/?uid=23808`): direct integration with official Alipay / WeChat Pay APIs, fee **1.6%**; funds go straight to the merchant account with **T+1 automatic settlement**. Supports **individual users** (no business license required) with up to 10,000 CNY daily transactions; business-licensed accounts have no limit. Link contains the referral code of [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) original author [@touwaeriol](https://github.com/touwaeriol) — feel free to remove it. +> - **International channel / USDT or USD settlement** — [Kyren Topup](https://kyren.top/?code=SUB2API) (`https://kyren.top/?code=SUB2API`): a ready-to-launch global payment stack for AI startups with WeChat Pay and Alipay support, local-currency checkout, and USD settlement. Fees: WeChat 2%, Alipay 2.5%; withdrawal 0.1% (min $40, max $150), settled in **USDT or USD**. No qualification review required — sign up and use immediately, making it the lowest barrier to entry. Withdrawal threshold is relatively high, recommended for users **who do not use domestic Chinese payment channels, cannot tolerate Stripe's 6%+ fees, have high transaction volume, and have USD or USDT channels to receive withdrawn funds**. Kyren Topup charges a $200 account opening fee; signing up via this link (which contains Sub2Api author [@Wei-Shaw](https://github.com/Wei-Shaw)'s referral code) **waives the opening fee**. Feel free to remove it if you prefer. +> +> Please evaluate the security, reliability, and compliance of any third-party payment provider on your own — this project does not endorse or guarantee any of them. + +--- + +## Quick Start + +1. Go to Admin Dashboard → **Settings** → **Payment Settings** tab +2. Enable **Payment** +3. Configure basic parameters (amount range, timeout, etc.) +4. Add at least one provider instance in **Provider Management** +5. Users can now top up from the frontend + +--- + +## System Settings + +Configure the following in Admin Dashboard **Settings → Payment Settings**: + +### Basic Settings + +| Setting | Description | Default | +|---------|-------------|---------| +| **Enable Payment** | Enable or disable the payment system | Off | +| **Product Name Prefix** | Prefix shown on payment page | - | +| **Product Name Suffix** | Suffix (e.g., "Credits") | - | +| **Minimum Amount** | Minimum single top-up amount | 1 | +| **Maximum Amount** | Maximum single top-up amount (empty = unlimited) | - | +| **Daily Limit** | Per-user daily cumulative limit (empty = unlimited) | - | +| **Order Timeout** | Order timeout in minutes (minimum 1) | 30 | +| **Max Pending Orders** | Maximum concurrent pending orders per user | 3 | +| **Load Balance Strategy** | Strategy for selecting provider instances | Round Robin | + +### Frontend Visible Method Routing + +The current payment UX keeps the frontend method list unified and does not expose provider brands directly: + +- **Alipay**: when enabled, this button must be routed to either `Alipay (Direct)` or `EasyPay Alipay` +- **WeChat Pay**: when enabled, this button must be routed to either `WeChat Pay (Direct)` or `EasyPay WeChat` +- Each visible method can route to only one source at a time +- If a visible method is enabled without a selected source, the frontend will not expose that method + +### Load Balance Strategies + +| Strategy | Description | +|----------|-------------| +| **Round Robin** | Distribute orders to instances in rotation | +| **Least Amount** | Prefer instances with the lowest daily cumulative amount | + +### Cancel Rate Limiting + +Prevents users from repeatedly creating and canceling orders: + +| Setting | Description | +|---------|-------------| +| **Enable Limit** | Toggle | +| **Window Mode** | Sliding / Fixed window | +| **Time Window** | Window duration | +| **Window Unit** | Minutes / Hours | +| **Max Cancels** | Maximum cancellations allowed within the window | + +### Help Information + +| Setting | Description | +|---------|-------------| +| **Help Image** | Customer service QR code or help image (supports upload) | +| **Help Text** | Instructions displayed on the payment page | + +--- + +## Provider Configuration + +Each provider type requires different credentials. Select the type when adding a new provider instance in **Provider Management → Add Provider**. + +> **Callback URLs are auto-generated**: When adding a provider, the Notify URL and Return URL are automatically constructed from your site domain. You only need to confirm the domain is correct. + +### EasyPay + +Compatible with any payment service that implements the EasyPay protocol. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Merchant ID (PID)** | EasyPay merchant ID | Yes | +| **Merchant Key (PKey)** | EasyPay merchant secret key | Yes | +| **API Base URL** | EasyPay API base address | Yes | +| **Alipay Channel ID** | Specify Alipay channel (optional) | No | +| **WeChat Channel ID** | Specify WeChat channel (optional) | No | + +### Alipay (Direct) + +Direct integration with Alipay Open Platform. Desktop flows return a QR code for in-page display, while mobile flows return an Alipay WAP/app redirect URL. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **AppID** | Alipay application AppID | Yes | +| **Private Key** | RSA2 application private key | Yes | +| **Alipay Public Key** | Alipay public key | Yes | + +### WeChat Pay (Direct) + +Direct integration with WeChat Pay APIv3. Supports Native QR code payment, H5 payment, and MP/JSAPI payment inside the WeChat environment. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **AppID** | WeChat Pay AppID | Yes | +| **Merchant ID (MchID)** | WeChat Pay merchant ID | Yes | +| **Merchant API Private Key** | Merchant API private key (PEM format) | Yes | +| **APIv3 Key** | 32-byte APIv3 key | Yes | +| **WeChat Pay Public Key** | WeChat Pay public key (PEM format) | Yes | +| **WeChat Pay Public Key ID** | WeChat Pay public key ID | Yes | +| **Certificate Serial Number** | Merchant certificate serial number | Yes | + +### Stripe + +International payment platform supporting multiple payment methods and currencies. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Secret Key** | Stripe secret key (`sk_live_...` or `sk_test_...`) | Yes | +| **Publishable Key** | Stripe publishable key (`pk_live_...` or `pk_test_...`) | Yes | +| **Webhook Secret** | Stripe Webhook signing secret (`whsec_...`) | Yes | + +--- + +## Provider Instance Management + +You can create **multiple instances** of the same provider type for load balancing and risk control: + +- **Multi-instance load balancing** — Distribute orders via round-robin or least-amount strategy +- **Independent limits** — Each instance can have its own min/max amount and daily limit +- **Independent toggle** — Enable/disable individual instances without affecting others +- **Refund control** — Enable or disable refunds per instance +- **Payment methods** — Each instance can support a subset of payment methods +- **Ordering** — Drag to reorder instances + +### Instance Limit Configuration + +Each instance supports these limits: + +| Limit | Description | +|-------|-------------| +| **Minimum Amount** | Minimum order amount accepted by this instance | +| **Maximum Amount** | Maximum order amount accepted by this instance | +| **Daily Limit** | Daily cumulative transaction limit for this instance | + +> During load balancing, instances that exceed their limits are automatically skipped. + +--- + +## Webhook Configuration + +Payment callbacks are essential for the payment system to work correctly. + +### Callback URL Format + +When adding a provider, the system auto-generates callback URLs from your site domain: + +| Provider | Callback Path | +|----------|-------------| +| **EasyPay** | `https://your-domain.com/api/v1/payment/webhook/easypay` | +| **Alipay (Direct)** | `https://your-domain.com/api/v1/payment/webhook/alipay` | +| **WeChat Pay (Direct)** | `https://your-domain.com/api/v1/payment/webhook/wxpay` | +| **Stripe** | `https://your-domain.com/api/v1/payment/webhook/stripe` | + +> Replace `your-domain.com` with your actual domain. For EasyPay / Alipay / WeChat Pay, the callback URL is auto-filled when adding the provider — no manual configuration needed. + +### Stripe Webhook Setup + +1. Log in to [Stripe Dashboard](https://dashboard.stripe.com/) +2. Go to **Developers → Webhooks** +3. Add an endpoint with the callback URL +4. Subscribe to events: `payment_intent.succeeded`, `payment_intent.payment_failed` +5. Copy the generated Webhook Secret (`whsec_...`) to your provider configuration + +### Important Notes + +- Callback URLs must use **HTTPS** (required by Stripe, strongly recommended for others) +- Ensure your firewall allows callback requests from payment platforms +- The system automatically verifies callback signatures to prevent forgery +- Balance top-up is processed automatically upon successful payment — no manual intervention needed + +--- + +## Payment Flow + +``` +User selects amount and payment method + │ + ▼ + Create Order (PENDING) + ├─ Validate amount range, pending order count, daily limit + ├─ Load balance to select provider instance + └─ Call provider to get payment info + │ + ▼ + User completes payment + ├─ EasyPay → QR code / H5 redirect + ├─ Alipay → Desktop QR / mobile Alipay redirect + ├─ WeChat Pay → Desktop Native QR / non-WeChat H5 / in-WeChat JSAPI + └─ Stripe → Payment Element (card/Alipay/WeChat/etc.) + │ + ▼ + Webhook callback verified → Order PAID + │ + ▼ + Auto top-up to user balance → Order COMPLETED +``` + +### Order Status Reference + +| Status | Description | +|--------|-------------| +| `PENDING` | Waiting for user to complete payment | +| `PAID` | Payment confirmed, awaiting balance credit | +| `COMPLETED` | Balance credited successfully | +| `EXPIRED` | Timed out without payment | +| `CANCELLED` | Cancelled by user | +| `FAILED` | Balance credit failed, admin can retry | +| `REFUND_REQUESTED` | Refund requested | +| `REFUNDING` | Refund in progress | +| `REFUNDED` | Refund completed | + +### Timeout and Fallback + +- Before marking an order as expired, the background job queries the upstream payment status first +- If the user has actually paid but the callback was delayed, the system will reconcile automatically +- The background job runs every 60 seconds to check for timed-out orders + +--- + +## Migrating from Sub2ApiPay + +If you previously used [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) as an external payment system, you can migrate to the built-in payment system: + +### Key Differences + +| Aspect | Sub2ApiPay | Built-in Payment | +|--------|-----------|-----------------| +| Deployment | Separate service (Next.js + PostgreSQL) | Built into Sub2API, no extra deployment | +| Payment Methods | EasyPay, Alipay, WeChat, Stripe | Same | +| Configuration | Environment variables + separate admin UI | Unified in Sub2API admin dashboard | +| Top-up Integration | Via Admin API callback | Internal processing, more reliable | +| Subscription Plans | Supported | Not yet (planned) | +| Order Management | Separate admin interface | Integrated in Sub2API admin dashboard | + +### Migration Steps + +1. Enable payment in Sub2API admin dashboard and configure providers (use the same payment credentials) +2. Update webhook callback URLs to Sub2API's callback endpoints +3. Verify that new orders are processed correctly via built-in payment +4. Decommission the Sub2ApiPay service + +> **Note**: Historical order data from Sub2ApiPay will not be automatically migrated. Keep Sub2ApiPay running for a while to access historical records. diff --git a/docs/PAYMENT_CN.md b/docs/PAYMENT_CN.md new file mode 100644 index 00000000..0fbc198a --- /dev/null +++ b/docs/PAYMENT_CN.md @@ -0,0 +1,287 @@ +# 支付系统配置指南 + +Sub2API 内置支付系统,支持用户自助充值,无需部署独立的支付服务。 + +--- + +## 目录 + +- [支持的支付方式](#支持的支付方式) +- [快速开始](#快速开始) +- [系统设置](#系统设置) +- [服务商配置](#服务商配置) +- [服务商实例管理](#服务商实例管理) +- [Webhook 配置](#webhook-配置) +- [支付流程](#支付流程) +- [从 Sub2ApiPay 迁移](#从-sub2apipay-迁移) + +--- + +## 支持的支付方式 + +| 服务商 | 支付方式 | 说明 | +|--------|---------|------| +| **EasyPay(易支付)** | 支付宝、微信支付 | 兼容易支付协议的第三方聚合支付 | +| **支付宝官方** | 桌面二维码扫码、移动端支付宝跳转 | 直接对接支付宝开放平台,桌面端返回二维码,移动端返回 WAP/唤起链接 | +| **微信官方** | Native 扫码、H5、公众号/JSAPI 支付 | 直接对接微信支付 APIv3,按终端环境自动分流 | +| **Stripe** | 银行卡、支付宝、微信支付、Link 等 | 国际支付,支持多币种 | + +> 支付宝官方 / 微信官方与易支付可以同时作为后台服务商实例存在,但前台始终只展示 `支付宝`、`微信支付` 两个可见按钮。管理员需要分别为这两个按钮选择唯一支付来源:官方或易支付。官方渠道直接对接 API,资金直达商户账户,手续费更低;易支付通过第三方平台聚合,接入门槛更低。 + +> **易支付服务商推荐**:以下两家均为兼容易支付协议的第三方聚合支付,按资金通道与结算方式选择: +> +> - **国内渠道 / 人民币结算** — [ZPay](https://z-pay.cn/?uid=23808)(`https://z-pay.cn/?uid=23808`):支付宝 / 微信官方 API 直连,手续费 **1.6%**;资金直达商家账户,**T+1 自动到账**。支持**个人用户**(无营业执照)每日 1 万元以内交易;拥有营业执照则无限额。链接含 [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) 原作者 [@touwaeriol](https://github.com/touwaeriol) 的邀请码,介意可去掉。 +> - **国际渠道 / USDT 或美元结算** — [启润支付](https://kyren.top/?code=SUB2API)(`https://kyren.top/?code=SUB2API`):为 AI 项目提供低门槛国际收款通道,支持国际版微信支付与支付宝,本地货币支付、美元结算。手续费:微信 2%、支付宝 2.5%;提现 0.1%(最低 40 美元、最高 150 美元),以 **USDT 或美元**到账。无资质审核、注册即用,使用门槛最低;提现门槛略高,适合**不使用国内支付渠道、无法接受 Stripe 高达 6%+ 手续费、流水较大,且拥有美元或 USDT 渠道可接收提现资金**的用户。启润支付开户费 200 美元,通过本链接注册(含 Sub2Api 作者 [@Wei-Shaw](https://github.com/Wei-Shaw) 邀请码)可**免开户费**,介意可去掉。 +> +> 支付渠道的安全性、稳定性及合规性请自行鉴别,本项目不对任何第三方支付服务商做担保或背书。 + +--- + +## 快速开始 + +1. 进入管理后台 → **设置** → **支付设置** 标签页 +2. 开启 **启用支付** +3. 配置基本参数(金额范围、超时时间等) +4. 在 **服务商管理** 中添加至少一个服务商实例 +5. 用户即可在前端页面进行充值 + +--- + +## 系统设置 + +在管理后台 **设置 → 支付设置** 中配置以下参数: + +### 基本设置 + +| 设置项 | 说明 | 默认值 | +|--------|------|--------| +| **启用支付** | 启用或禁用支付系统 | 关闭 | +| **商品名前缀** | 支付页面显示的商品名前缀 | - | +| **商品名后缀** | 商品名后缀(如"元") | - | +| **最低金额** | 单笔最低充值金额 | 1 | +| **最高金额** | 单笔最高充值金额(留空表示不限制) | - | +| **每日限额** | 每用户每日累计充值上限(留空表示不限制) | - | +| **订单超时时间** | 订单超时分钟数,至少 1 分钟 | 30 | +| **最大待支付订单数** | 同一用户最大并行待支付订单数 | 3 | +| **负载均衡策略** | 多服务商实例时的选择策略 | 轮询 | + +### 前台可见支付方式路由 + +当前版本对用户统一展示支付方式,不区分官方渠道还是易支付: + +- **支付宝**:后台启用后,需要额外指定该按钮路由到 `支付宝官方` 或 `易支付支付宝` +- **微信支付**:后台启用后,需要额外指定该按钮路由到 `微信官方` 或 `易支付微信` +- 同一个可见支付方式在同一时刻只能路由到一个来源 +- 支付来源未选择时,即使对应按钮被开启,前台也不会暴露该支付方式 + +### 负载均衡策略 + +| 策略 | 说明 | +|------|------| +| **轮询(round-robin)** | 按顺序轮流分配到各服务商实例 | +| **最少金额(least-amount)** | 优先分配到当日累计金额最少的实例 | + +### 取消频率限制 + +防止用户频繁创建并取消订单: + +| 设置项 | 说明 | +|--------|------| +| **启用限制** | 开关 | +| **窗口模式** | 滚动窗口 / 固定窗口 | +| **时间窗口** | 窗口长度 | +| **窗口单位** | 分钟 / 小时 | +| **最大次数** | 窗口内允许的最大取消次数 | + +### 帮助信息 + +| 设置项 | 说明 | +|--------|------| +| **帮助图片** | 充值页面显示的客服二维码等图片(支持上传) | +| **帮助文本** | 充值页面显示的说明文字 | + +--- + +## 服务商配置 + +每种服务商需要不同的凭证和参数。在 **服务商管理 → 添加服务商** 中选择类型后填写。 + +> **回调地址自动生成**:添加服务商时,异步回调地址(Notify URL)和同步跳转地址(Return URL)由系统根据你的站点域名自动拼接,无需手动填写。管理员只需确认域名正确即可。 + +### EasyPay(易支付) + +兼容任何 EasyPay 协议的支付服务商。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **商户 ID(PID)** | EasyPay 商户 ID | 是 | +| **商户密钥(PKey)** | EasyPay 商户密钥 | 是 | +| **API 地址** | EasyPay API 基础地址 | 是 | +| **支付宝通道 ID** | 指定支付宝通道(可选) | 否 | +| **微信通道 ID** | 指定微信通道(可选) | 否 | + +### 支付宝官方 + +直接对接支付宝开放平台。桌面端返回二维码供页面内展示和扫码,移动端返回支付宝手机网站支付跳转链接。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **AppID** | 支付宝应用 AppID | 是 | +| **应用私钥** | RSA2 应用私钥 | 是 | +| **支付宝公钥** | 支付宝公钥 | 是 | + +### 微信官方 + +直接对接微信支付 APIv3,支持 Native 扫码支付、H5 支付,以及在微信环境内的公众号/JSAPI 支付。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **AppID** | 微信支付 AppID | 是 | +| **商户号(MchID)** | 微信支付商户号 | 是 | +| **商户 API 私钥** | 商户 API 私钥(PEM 格式) | 是 | +| **APIv3 密钥** | 32 位 APIv3 密钥 | 是 | +| **微信支付公钥** | 微信支付公钥(PEM 格式) | 是 | +| **微信支付公钥 ID** | 微信支付公钥 ID | 是 | +| **商户证书序列号** | 商户证书序列号 | 是 | + +### Stripe + +国际支付平台,支持多种支付方式和币种。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **Secret Key** | Stripe 密钥(`sk_live_...` 或 `sk_test_...`) | 是 | +| **Publishable Key** | Stripe 可公开密钥(`pk_live_...` 或 `pk_test_...`) | 是 | +| **Webhook Secret** | Stripe Webhook 签名密钥(`whsec_...`) | 是 | + +--- + +## 服务商实例管理 + +同一种服务商可以创建**多个实例**,实现负载均衡和风控: + +- **多实例负载均衡** — 按轮询或最少金额策略分流订单 +- **独立限额** — 每个实例可独立配置单笔最小/最大金额和每日限额 +- **独立启停** — 可单独启用/禁用某个实例,不影响其他实例 +- **退款控制** — 每个实例可单独开启或关闭退款功能 +- **支付方式** — 每个实例可选择支持的支付方式子集 +- **排序** — 拖拽调整实例顺序 + +### 实例限额配置 + +每个实例支持以下限额: + +| 限额项 | 说明 | +|--------|------| +| **单笔最小金额** | 该实例接受的最小订单金额 | +| **单笔最大金额** | 该实例接受的最大订单金额 | +| **每日限额** | 该实例每日累计交易上限 | + +> 负载均衡时,系统会自动跳过超出限额的实例。 + +--- + +## Webhook 配置 + +支付回调是支付系统的核心环节,必须正确配置: + +### 回调地址格式 + +添加服务商时,系统会自动根据站点域名拼接回调地址,格式如下: + +| 服务商 | 回调路径 | +|--------|---------| +| **EasyPay** | `https://your-domain.com/api/v1/payment/webhook/easypay` | +| **支付宝官方** | `https://your-domain.com/api/v1/payment/webhook/alipay` | +| **微信官方** | `https://your-domain.com/api/v1/payment/webhook/wxpay` | +| **Stripe** | `https://your-domain.com/api/v1/payment/webhook/stripe` | + +> 将 `your-domain.com` 替换为你的实际域名。EasyPay / 支付宝 / 微信的回调地址在添加服务商时自动填入,无需手动配置。 + +### Stripe Webhook 设置 + +1. 登录 [Stripe Dashboard](https://dashboard.stripe.com/) +2. 进入 **Developers → Webhooks** +3. 添加端点,填写回调地址 +4. 订阅事件:`payment_intent.succeeded`、`payment_intent.payment_failed` +5. 将生成的 Webhook Secret(`whsec_...`)填入服务商配置 + +### 注意事项 + +- 回调地址必须是 **HTTPS**(Stripe 强制要求,其他服务商强烈推荐) +- 确保服务器防火墙允许支付平台的回调请求 +- 系统会自动进行签名验证,防止伪造回调 +- 支付成功后自动完成余额充值,无需人工干预 + +--- + +## 支付流程 + +``` +用户选择充值金额和支付方式 + │ + ▼ + 创建订单 (PENDING) + ├─ 校验金额范围、待支付订单数、每日限额 + ├─ 负载均衡选择服务商实例 + └─ 调用服务商获取支付信息 + │ + ▼ + 用户完成支付 + ├─ EasyPay → 扫码 / H5 跳转 + ├─ 支付宝官方 → 桌面二维码 / 移动端支付宝跳转 + ├─ 微信官方 → 桌面 Native 扫码 / 非微信 H5 / 微信内 JSAPI + └─ Stripe → Payment Element(银行卡/支付宝/微信等) + │ + ▼ + 支付回调验签 → 订单 PAID + │ + ▼ + 自动充值到用户余额 → 订单 COMPLETED +``` + +### 订单状态说明 + +| 状态 | 说明 | +|------|------| +| `PENDING` | 待支付,等待用户完成支付 | +| `PAID` | 已支付,等待充值到账 | +| `COMPLETED` | 已完成,余额已到账 | +| `EXPIRED` | 已过期,超时未支付 | +| `CANCELLED` | 已取消,用户主动取消 | +| `FAILED` | 充值失败,可管理员重试 | +| `REFUND_REQUESTED` | 已申请退款 | +| `REFUNDING` | 退款处理中 | +| `REFUNDED` | 已退款 | + +### 超时与兜底 + +- 订单超时后,后台任务会先查询上游支付状态再标记过期 +- 如果用户实际已支付但回调延迟,系统会通过查询补单 +- 后台任务每 60 秒执行一次超时检查 + +--- + +## 从 Sub2ApiPay 迁移 + +如果你之前使用 [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) 作为外部支付系统,现在可以迁移到内置支付: + +### 主要差异 + +| 对比项 | Sub2ApiPay | 内置支付 | +|--------|-----------|---------| +| 部署方式 | 独立服务(Next.js + PostgreSQL) | 内置于 Sub2API,无需额外部署 | +| 支付方式 | EasyPay、支付宝、微信、Stripe | 相同 | +| 配置方式 | 环境变量 + 独立管理后台 | Sub2API 管理后台内统一配置 | +| 充值对接 | 通过 Admin API 回调 | 内部直接处理,更可靠 | +| 订阅套餐 | 支持 | 暂不支持(计划中) | +| 订单管理 | 独立管理界面 | 集成在 Sub2API 管理后台 | + +### 迁移步骤 + +1. 在 Sub2API 管理后台启用支付并配置服务商(使用相同的支付凭证) +2. 更新 Webhook 回调地址为 Sub2API 的回调地址 +3. 确认新订单通过内置支付正常处理 +4. 停用 Sub2ApiPay 服务 + +> **注意**:Sub2ApiPay 中的历史订单数据不会自动迁移。建议保留 Sub2ApiPay 一段时间以便查询历史记录。 diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index 90daae5f..1144dfb4 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -4136,7 +4136,7 @@

{{ t("admin.settings.payment.enabledPaymentTypesHint") }} locale.value.startsWith("zh") - ? "https://github.com/Wei-Shaw/sub2api/blob/main/README_CN.md#%E6%94%AF%E4%BB%98" - : "https://github.com/Wei-Shaw/sub2api/blob/main/README.md#payment", + ? "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md" + : "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT.md", +); + +const paymentMethodsHref = computed(() => + locale.value.startsWith("zh") + ? "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md#支持的支付方式" + : "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT.md#supported-payment-methods", ); type SettingsTab = diff --git a/frontend/src/views/admin/__tests__/SettingsView.spec.ts b/frontend/src/views/admin/__tests__/SettingsView.spec.ts index 210fd868..ba3e595b 100644 --- a/frontend/src/views/admin/__tests__/SettingsView.spec.ts +++ b/frontend/src/views/admin/__tests__/SettingsView.spec.ts @@ -508,13 +508,13 @@ describe("admin SettingsView payment visible method controls", () => { expect(paymentLinks).toHaveLength(2); expect(paymentLinks[0]?.attributes("href")).toBe( - "https://github.com/Wei-Shaw/sub2api/blob/main/README_CN.md#%E6%94%AF%E4%BB%98", + "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md", ); expect(paymentLinks[1]?.attributes("href")).toBe( - "https://github.com/Wei-Shaw/sub2api/blob/main/README_CN.md#%E6%94%AF%E4%BB%98", + "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md#支持的支付方式", ); for (const link of paymentLinks) { - expect(link.attributes("href")).not.toContain("docs/PAYMENT"); + expect(link.attributes("href")).toContain("docs/PAYMENT"); } });