chore: remove local docs from repo
This commit is contained in:
@@ -1,243 +0,0 @@
|
||||
# ADMIN_PAYMENT_INTEGRATION_API
|
||||
|
||||
> 单文件中英双语文档 / Single-file bilingual documentation (Chinese + English)
|
||||
|
||||
---
|
||||
|
||||
## 中文
|
||||
|
||||
### 目标
|
||||
本文档用于对接外部支付系统(如 `sub2apipay`)与 Sub2API 的 Admin API,覆盖:
|
||||
- 支付成功后充值
|
||||
- 用户查询
|
||||
- 人工余额修正
|
||||
- 前端购买页参数透传
|
||||
|
||||
### 基础地址
|
||||
- 生产:`https://<your-domain>`
|
||||
- Beta:`http://<your-server-ip>: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=<jwt>&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://<your-domain>`
|
||||
- Beta: `http://<your-server-ip>: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=<jwt>&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`
|
||||
287
docs/PAYMENT.md
287
docs/PAYMENT.md
@@ -1,287 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,287 +0,0 @@
|
||||
# 支付系统配置指南
|
||||
|
||||
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 一段时间以便查询历史记录。
|
||||
@@ -1,539 +0,0 @@
|
||||
# Auth Identity Payment Foundation Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Rebuild the auth identity, profile binding, payment routing, and OpenAI advanced scheduler foundation on top of a clean `origin/main` branch while preserving historical compatibility for existing email users, existing LinuxDo users, historical LinuxDo/WeChat/OIDC synthetic-email users, and historical WeChat `openid`-only records.
|
||||
**Architecture:** A unified identity foundation centered on durable provider subjects (`email`, `linuxdo`, `oidc`, `wechat`) and transactional pending-auth sessions; backend-owned payment source routing behind stable frontend methods (`alipay`, `wxpay`); compatibility-first migration/backfill before feature enablement.
|
||||
**Tech Stack:** Go, Gin, Ent, PostgreSQL, Redis, Vue 3, Pinia, TypeScript, Vitest, pnpm.
|
||||
|
||||
---
|
||||
|
||||
## Non-Negotiable Product Rules
|
||||
|
||||
- [ ] Preserve login continuity for existing email users, existing LinuxDo users, and historically migrated third-party users.
|
||||
- [ ] During migration, backfill historical LinuxDo/WeChat/OIDC synthetic-email users into explicit third-party identities before first post-upgrade login whenever deterministic recovery is possible.
|
||||
- [ ] During migration, surface historical WeChat `openid`-only records through explicit migration reports and remediation rules; do not silently reinterpret them as valid canonical identities.
|
||||
- [ ] Keep existing email login and add third-party login/bind for `linuxdo`, `oidc`, and `wechat`.
|
||||
- [ ] On first third-party login:
|
||||
- identity exists: direct login.
|
||||
- identity does not exist: start pending-auth flow.
|
||||
- local email binding is required only when system config says so.
|
||||
- upstream provider email verification never counts as local email verification.
|
||||
- [ ] When user-entered and locally verified email already exists:
|
||||
- offer bind-existing-account after local re-authentication.
|
||||
- offer change-email-and-create-new-account.
|
||||
- when email binding is mandatory, do not allow bypass without changing to another email.
|
||||
- [ ] On first third-party login or first third-party bind, provider nickname/avatar must be presented as independent replace options for the current nickname and avatar. They are not auto-applied.
|
||||
- [ ] Source-specific initial grants must support per-source defaults for balance, concurrency, and subscriptions.
|
||||
- [ ] Default grant timing: on successful new-account creation.
|
||||
- [ ] Optional grant timing: on first successful bind for the configured source.
|
||||
- [ ] Migration/backfill must never trigger first-bind or first-signup grants retroactively.
|
||||
- [ ] Avatar profile supports:
|
||||
- direct URL storage.
|
||||
- image data URL upload compressed to `<=100KB` before storing in DB.
|
||||
- explicit delete.
|
||||
- [ ] Admin user management must expose and sort by `last_login_at` and `last_active_at`.
|
||||
- [ ] WeChat login rules:
|
||||
- WeChat environment uses MP login.
|
||||
- non-WeChat browser uses Open/QR login.
|
||||
- canonical identity uses `unionid`.
|
||||
- when `unionid` is unavailable, fail the login/bind flow under the approved option-1 policy.
|
||||
- [ ] OIDC rules:
|
||||
- browser authorization-code flow always uses PKCE `S256`.
|
||||
- discovery issuer and ID token `iss` must match exactly.
|
||||
- `userinfo.sub` must match ID token `sub` when UserInfo is used.
|
||||
- upstream `email_verified` does not satisfy local email verification.
|
||||
- [ ] Payment UI rules:
|
||||
- user-facing methods stay `支付宝` and `微信支付`.
|
||||
- backend decides whether each method routes to official provider instance or EasyPay.
|
||||
- at runtime, each visible method may only have one active source.
|
||||
- [ ] Alipay rules:
|
||||
- PC: in-page QR.
|
||||
- mobile browser: jump to Alipay payment.
|
||||
- [ ] WeChat Pay rules:
|
||||
- PC: in-page QR.
|
||||
- WeChat H5: MP/JSAPI first, fallback to H5 pay.
|
||||
- non-WeChat H5: H5 pay, or prompt to open in WeChat when unavailable.
|
||||
- [ ] Payment success pages are informational only; actual fulfillment depends on webhook or server-side reconciliation.
|
||||
- [ ] WeChat in-app payment requiring `openid` must use a dedicated server-backed payment OAuth resume flow rather than frontend-only recovery state.
|
||||
- [ ] OpenAI advanced scheduler is available but default-disabled.
|
||||
|
||||
## Hard Technical Constraints From Audit
|
||||
|
||||
- [ ] Browser-based third-party auth must use Authorization Code + PKCE `S256`.
|
||||
- [ ] PKCE must not be admin-configurable off for browser authorization-code providers.
|
||||
- [ ] OIDC identity primary key must be `(issuer, subject)`, not email.
|
||||
- [ ] Email equality must never auto-link accounts.
|
||||
- [ ] Bind-existing-account must require explicit local re-authentication and TOTP verification when enabled.
|
||||
- [ ] Bind-current-user must originate from an already-authenticated local user and preserve explicit bind intent across callback completion.
|
||||
- [ ] OAuth redirect URI must be fixed server config, exact-match, and never derived from user input.
|
||||
- [ ] User-supplied redirect may only choose a normalized same-origin internal route after completion.
|
||||
- [ ] WeChat canonical identity must be `unionid`; `openid` remains channel/app-scoped support data only.
|
||||
- [ ] Every canonical identity uniqueness rule must include provider namespace (`provider_key`) consistently.
|
||||
- [ ] Callback completion must use backend session completion or a one-time opaque exchange code that is short-lived, one-time, browser-session-bound, `POST`-redeemed, and unusable as a bearer token.
|
||||
- [ ] Every payment order must snapshot the selected provider instance plus the order-time verification inputs required for callback verification, reconciliation, refund, and audit.
|
||||
- [ ] Frontend must not receive first-party bearer tokens through callback URL fragments in the rebuilt flow.
|
||||
- [ ] Public payment result polling must not expose order data by raw `out_trade_no` alone; use authenticated lookup or signed opaque result token.
|
||||
- [ ] WeChat Pay webhook handling must verify signature, decrypt payload, and compare `appid`, `mchid`, `out_trade_no`, `amount`, `currency`, and provider trade state against the order snapshot before fulfillment.
|
||||
|
||||
## Baseline Notes
|
||||
|
||||
- [ ] Current clean branch head when this plan was written: `721d7ab3`.
|
||||
- [ ] Baseline backend verification on clean `origin/main`: `cd backend && go test ./...` passes.
|
||||
- [ ] Baseline frontend verification on clean `origin/main`: `cd frontend && pnpm test:run` currently fails in unrelated existing suites. New work must add targeted tests and avoid claiming full frontend green until those baseline failures are addressed separately.
|
||||
- [ ] Existing migration directory currently ends at `107_*`; this rebuild reserves `108` through `111`.
|
||||
|
||||
## Target File Map
|
||||
|
||||
### New backend migrations
|
||||
|
||||
- [ ] `backend/migrations/108_auth_identity_foundation_core.sql`
|
||||
- [ ] `backend/migrations/109_auth_identity_compat_backfill.sql`
|
||||
- [ ] `backend/migrations/110_pending_auth_and_provider_default_grants.sql`
|
||||
- [ ] `backend/migrations/111_payment_routing_and_scheduler_flags.sql`
|
||||
|
||||
### New or rebuilt Ent schema
|
||||
|
||||
- [ ] `backend/ent/schema/auth_identity.go`
|
||||
- [ ] `backend/ent/schema/auth_identity_channel.go`
|
||||
- [ ] `backend/ent/schema/pending_auth_session.go`
|
||||
- [ ] `backend/ent/schema/identity_adoption_decision.go`
|
||||
|
||||
### New or rebuilt backend repositories/services/handlers
|
||||
|
||||
- [ ] `backend/internal/repository/user_profile_identity_repo.go`
|
||||
- [ ] `backend/internal/repository/user_profile_identity_repo_contract_test.go`
|
||||
- [ ] `backend/internal/repository/auth_identity_migration_report.go`
|
||||
- [ ] `backend/internal/service/auth_identity_flow.go`
|
||||
- [ ] `backend/internal/service/auth_identity_flow_test.go`
|
||||
- [ ] `backend/internal/service/auth_pending_identity_service.go`
|
||||
- [ ] `backend/internal/service/auth_pending_identity_service_test.go`
|
||||
- [ ] `backend/internal/service/payment_config_service.go`
|
||||
- [ ] `backend/internal/service/payment_order.go`
|
||||
- [ ] `backend/internal/service/payment_order_lifecycle.go`
|
||||
- [ ] `backend/internal/service/payment_fulfillment.go`
|
||||
- [ ] `backend/internal/service/payment_resume_service.go`
|
||||
- [ ] `backend/internal/service/payment_resume_service_test.go`
|
||||
- [ ] `backend/internal/service/openai_account_scheduler.go`
|
||||
- [ ] `backend/internal/handler/auth_pending_identity_flow.go`
|
||||
- [ ] `backend/internal/handler/auth_linuxdo_oauth.go`
|
||||
- [ ] `backend/internal/handler/auth_oidc_oauth.go`
|
||||
- [ ] `backend/internal/handler/auth_wechat_oauth.go`
|
||||
- [ ] `backend/internal/handler/auth_handler.go`
|
||||
- [ ] `backend/internal/handler/user_handler.go`
|
||||
- [ ] `backend/internal/handler/payment_handler.go`
|
||||
- [ ] `backend/internal/handler/payment_webhook_handler.go`
|
||||
- [ ] `backend/internal/handler/admin/user_handler.go`
|
||||
- [ ] `backend/internal/handler/admin/setting_handler.go`
|
||||
|
||||
### New or rebuilt frontend API/store/views/components
|
||||
|
||||
- [ ] `frontend/src/api/auth.ts`
|
||||
- [ ] `frontend/src/api/user.ts`
|
||||
- [ ] `frontend/src/api/payment.ts`
|
||||
- [ ] `frontend/src/api/admin/settings.ts`
|
||||
- [ ] `frontend/src/api/admin/users.ts`
|
||||
- [ ] `frontend/src/stores/auth.ts`
|
||||
- [ ] `frontend/src/stores/payment.ts`
|
||||
- [ ] `frontend/src/components/auth/ThirdPartyAuthCallbackFlow.vue`
|
||||
- [ ] `frontend/src/components/auth/LinuxDoOAuthSection.vue`
|
||||
- [ ] `frontend/src/components/auth/OidcOAuthSection.vue`
|
||||
- [ ] `frontend/src/components/auth/WechatOAuthSection.vue`
|
||||
- [ ] `frontend/src/components/user/profile/ProfileAccountBindingsCard.vue`
|
||||
- [ ] `frontend/src/components/user/profile/ProfileInfoCard.vue`
|
||||
- [ ] `frontend/src/views/auth/LinuxDoCallbackView.vue`
|
||||
- [ ] `frontend/src/views/auth/OidcCallbackView.vue`
|
||||
- [ ] `frontend/src/views/auth/WechatCallbackView.vue`
|
||||
- [ ] `frontend/src/views/user/ProfileView.vue`
|
||||
- [ ] `frontend/src/views/user/PaymentView.vue`
|
||||
- [ ] `frontend/src/views/user/PaymentQRCodeView.vue`
|
||||
- [ ] `frontend/src/views/user/PaymentResultView.vue`
|
||||
|
||||
## Phase 1: Migration And Compatibility Foundation
|
||||
|
||||
### Task 1. Create core identity schema migration
|
||||
|
||||
- [ ] Implement `backend/migrations/108_auth_identity_foundation_core.sql` with:
|
||||
- `auth_identities`
|
||||
- `auth_identity_channels`
|
||||
- `pending_auth_sessions`
|
||||
- `identity_adoption_decisions`
|
||||
- `users.last_login_at`
|
||||
- `users.last_active_at`
|
||||
- grant-tracking columns/tables required to prevent double-award
|
||||
- [ ] Add uniqueness/index rules:
|
||||
- one canonical identity per `(provider, provider_key, provider_subject)`
|
||||
- one channel record per `(provider, provider_channel, provider_app_id, provider_channel_subject)`
|
||||
- one adoption decision per pending session
|
||||
- [ ] Model `pending_auth_sessions` so immutable upstream claims and mutable local flow state are stored separately; do not reintroduce a mixed `metadata` catch-all.
|
||||
- [ ] Preserve null-safe compatibility defaults so historical rows remain readable before backfill finishes.
|
||||
- [ ] Add explicit rollback blocks only where safe; never repeat the destructive pattern observed in old `112_update_pending_auth_sessions.sql`.
|
||||
|
||||
### Task 2. Materialize historical identities before runtime
|
||||
|
||||
- [ ] Implement `backend/migrations/109_auth_identity_compat_backfill.sql` to backfill:
|
||||
- existing email users into `auth_identities(provider=email, provider_subject=normalized_email)`
|
||||
- historical LinuxDo users into `auth_identities(provider=linuxdo, provider_subject=linuxdo_subject)`
|
||||
- historical synthetic-email LinuxDo users into explicit LinuxDo identity rows by parsing legacy email mode and legacy provider metadata
|
||||
- historical synthetic-email WeChat users into explicit WeChat identities where `unionid` or equivalent deterministic provider identity is recoverable
|
||||
- historical synthetic-email OIDC users into explicit OIDC identities where deterministic provider identity is recoverable
|
||||
- profile/channel rows from historical `user_external_identities`-style data when present in upgraded databases
|
||||
- [ ] Write migration report output in `backend/internal/repository/auth_identity_migration_report.go` so production can inspect unmatched rows, `openid`-only WeChat rows, and non-deterministic synthetic-email rows instead of silently skipping them.
|
||||
- [ ] Set `signup_source` and provider provenance when recoverable from historical data. Do not flatten everything to `email`.
|
||||
|
||||
### Task 3. Provider default grant and scheduler config migration
|
||||
|
||||
- [ ] Implement `backend/migrations/110_pending_auth_and_provider_default_grants.sql` for:
|
||||
- provider-specific initial balance/concurrency/subscription defaults
|
||||
- grant timing flags: `on_signup`, optional `on_first_bind`
|
||||
- email-required-on-third-party-signup flags
|
||||
- profile avatar storage columns/settings
|
||||
- [ ] Implement `backend/migrations/111_payment_routing_and_scheduler_flags.sql` for:
|
||||
- stable payment method to provider-instance routing
|
||||
- visible-method normalization from historical `supported_types`, `payment_mode`, and legacy aliases such as `wxpay_direct`
|
||||
- admin exclusivity flags for `alipay` and `wxpay`
|
||||
- advanced scheduler enable flag defaulting to disabled
|
||||
|
||||
### Task 4. Generate Ent and compile migration-safe model layer
|
||||
|
||||
- [ ] Add the schema definitions in:
|
||||
- `backend/ent/schema/auth_identity.go`
|
||||
- `backend/ent/schema/auth_identity_channel.go`
|
||||
- `backend/ent/schema/pending_auth_session.go`
|
||||
- `backend/ent/schema/identity_adoption_decision.go`
|
||||
- [ ] Run:
|
||||
```bash
|
||||
cd backend
|
||||
go generate ./ent
|
||||
```
|
||||
- [ ] Compile after generation:
|
||||
```bash
|
||||
cd backend
|
||||
go test ./... -run '^$'
|
||||
```
|
||||
- [ ] Commit checkpoint:
|
||||
```bash
|
||||
git add backend/migrations backend/ent/schema backend/ent
|
||||
git commit -m "feat: add auth identity foundation schema"
|
||||
```
|
||||
|
||||
## Phase 2: Backend Identity Flow Rebuild
|
||||
|
||||
### Task 5. Build a single repository contract for identity lookups and grants
|
||||
|
||||
- [ ] Implement `backend/internal/repository/user_profile_identity_repo.go` with transactional helpers for:
|
||||
- get user by canonical identity
|
||||
- get user by channel identity
|
||||
- create canonical + channel identity together
|
||||
- bind identity to existing user after verified re-auth
|
||||
- record one-time provider grant award
|
||||
- record adoption preference decisions
|
||||
- update `last_login_at` and `last_active_at`
|
||||
- [ ] Add repository contract coverage in `backend/internal/repository/user_profile_identity_repo_contract_test.go`.
|
||||
- [ ] Enforce dual-write for email registration/login so `users.email` and `auth_identities(provider=email, ...)` stay consistent from this phase onward.
|
||||
- [ ] Add repository coverage proving `last_login_at` and `last_active_at` use the required field names and are not silently replaced by derived `last_used_at` logic.
|
||||
|
||||
### Task 6. Rebuild transactional pending-auth service
|
||||
|
||||
- [ ] Implement `backend/internal/service/auth_pending_identity_service.go` and tests to own these flows:
|
||||
- create pending session from third-party callback
|
||||
- verify local email code
|
||||
- create new account from pending session with correct `signup_source`
|
||||
- bind pending identity to existing account after password/TOTP re-auth
|
||||
- apply configured provider defaults on the correct trigger only once
|
||||
- store provider nickname/avatar candidates and user opt-in replacement decisions independently
|
||||
- [ ] Implement callback completion so pending auth can finish through backend session completion or a one-time exchange code:
|
||||
- short TTL
|
||||
- one-time use
|
||||
- browser-session binding
|
||||
- `POST` redemption only
|
||||
- safe mixed-version bridge to legacy pending-token aliases during rollout
|
||||
- [ ] Keep pending session payload normalized:
|
||||
- provider identity fields live in typed columns/JSON structure
|
||||
- mutable local progression lives separately from immutable upstream claims
|
||||
- avoid the old branch’s mixed `metadata` and `upstream_identity_payload` ambiguity
|
||||
- [ ] Do not call plain email registration helpers from this flow. The old feature branch bug where pending third-party signup fell back to `RegisterWithVerification` must not reappear.
|
||||
|
||||
### Task 7. Rebuild provider callback adapters
|
||||
|
||||
- [ ] Refactor these handlers to thin adapters over the shared pending-auth service:
|
||||
- `backend/internal/handler/auth_linuxdo_oauth.go`
|
||||
- `backend/internal/handler/auth_oidc_oauth.go`
|
||||
- `backend/internal/handler/auth_wechat_oauth.go`
|
||||
- [ ] For OIDC:
|
||||
- require PKCE `S256`, `state`, and `nonce`
|
||||
- validate discovery issuer, `iss`, `aud`, optional `azp`, `exp`, and `nonce`
|
||||
- verify `userinfo.sub == id_token.sub` when UserInfo is used
|
||||
- persist canonical identity as `(issuer, sub)`
|
||||
- [ ] For WeChat:
|
||||
- MP flow in WeChat UA
|
||||
- Open/QR flow outside WeChat UA
|
||||
- website login uses authorization-code flow and persists channel/app binding
|
||||
- persist channel identity by `(channel, appid, openid)`
|
||||
- persist canonical identity by `unionid`
|
||||
- hard-fail when `unionid` is absent under the approved product policy
|
||||
- [ ] Replace callback URL fragment token delivery with backend session completion or one-time exchange code consumed by `frontend/src/stores/auth.ts`.
|
||||
|
||||
### Task 8. Rebuild auth endpoints and profile binding endpoints
|
||||
|
||||
- [ ] Implement `backend/internal/handler/auth_pending_identity_flow.go` for:
|
||||
- fetch pending session summary
|
||||
- submit verified email
|
||||
- choose create-new-account or bind-existing-account
|
||||
- submit nickname/avatar replacement choices
|
||||
- [ ] Make bind-existing-account and bind-current-user flows explicit:
|
||||
- no automatic linking on matching email
|
||||
- fresh password/TOTP proof is scoped to the intended target account only
|
||||
- no automatic metadata merge beyond explicitly selected nickname/avatar adoption
|
||||
- [ ] Update `backend/internal/handler/auth_handler.go` and `backend/internal/handler/user_handler.go` to expose:
|
||||
- current bindings summary
|
||||
- start-bind endpoints for LinuxDo/OIDC/WeChat
|
||||
- disconnect endpoints with safety checks
|
||||
- avatar upload/delete endpoints
|
||||
- [ ] Avatar handling requirements:
|
||||
- allow external URL
|
||||
- allow data URL upload
|
||||
- compress image payload to `<=100KB`
|
||||
- store compressed value in DB
|
||||
- deleting custom avatar must not implicitly resurrect stale provider avatar unless the user explicitly chooses provider avatar again
|
||||
|
||||
### Task 9. Add admin visibility and sorting
|
||||
|
||||
- [ ] Update `backend/internal/handler/admin/user_handler.go` and supporting query/service code so admin list supports:
|
||||
- `last_login_at`
|
||||
- `last_active_at`
|
||||
- sorting by both
|
||||
- binding/provider summary columns
|
||||
- [ ] Update `backend/internal/handler/admin/setting_handler.go` and setting service code for:
|
||||
- provider initial grant config
|
||||
- mandatory-email-on-third-party-signup config
|
||||
- payment source exclusivity config
|
||||
- advanced scheduler toggle
|
||||
|
||||
### Task 10. Backend verification checkpoint
|
||||
|
||||
- [ ] Run targeted backend tests:
|
||||
```bash
|
||||
cd backend
|
||||
go test ./internal/repository -run 'TestUserProfileIdentity|TestAuthIdentityMigration'
|
||||
go test ./internal/service -run 'TestAuthIdentityFlow|TestPendingAuthIdentity|TestOpenAIAccountScheduler'
|
||||
go test ./internal/handler -run 'TestLinuxDo|TestOidc|TestWechat|TestPaymentWebhook'
|
||||
go test ./...
|
||||
```
|
||||
- [ ] Commit checkpoint:
|
||||
```bash
|
||||
git add backend
|
||||
git commit -m "feat: rebuild auth identity backend flows"
|
||||
```
|
||||
|
||||
## Phase 3: Frontend Third-Party Flow And Profile UX
|
||||
|
||||
### Task 11. Rebuild callback flow UI around pending session decisions
|
||||
|
||||
- [ ] Rebuild `frontend/src/components/auth/ThirdPartyAuthCallbackFlow.vue` so it:
|
||||
- loads pending-session summary from backend
|
||||
- shows provider nickname/avatar candidates
|
||||
- lets user independently choose nickname replacement and avatar replacement
|
||||
- handles create-new-account vs bind-existing-account
|
||||
- enforces verified local email before completion when required
|
||||
- handles “email already exists” by branching to bind-existing-account or change-email-and-create-new-account
|
||||
- [ ] Update:
|
||||
- `frontend/src/views/auth/LinuxDoCallbackView.vue`
|
||||
- `frontend/src/views/auth/OidcCallbackView.vue`
|
||||
- `frontend/src/views/auth/WechatCallbackView.vue`
|
||||
- `frontend/src/api/auth.ts`
|
||||
- `frontend/src/stores/auth.ts`
|
||||
- [ ] Replace any token-fragment bootstrap with backend session completion or one-time exchange code flow.
|
||||
- [ ] During rollout, keep temporary compatibility readers for legacy pending-token aliases behind a bounded bridge contract and explicit removal step.
|
||||
|
||||
### Task 12. Rebuild profile account binding and avatar UX
|
||||
|
||||
- [ ] Rebuild `frontend/src/components/user/profile/ProfileAccountBindingsCard.vue` to:
|
||||
- show linked LinuxDo/OIDC/WeChat providers
|
||||
- start bind/unbind flows
|
||||
- show provider avatars and nicknames as reference only
|
||||
- prevent unsafe disconnect when it would strand the account
|
||||
- [ ] Rebuild `frontend/src/components/user/profile/ProfileInfoCard.vue` and `frontend/src/views/user/ProfileView.vue` to:
|
||||
- support avatar URL entry
|
||||
- support data URL upload/compression preview
|
||||
- support avatar delete
|
||||
- clearly separate current profile nickname/avatar from provider-sourced suggested nickname/avatar
|
||||
|
||||
### Task 13. Add frontend tests for rebuilt auth/profile flows
|
||||
|
||||
- [ ] Add or update:
|
||||
- `frontend/src/components/auth/__tests__/ThirdPartyAuthCallbackFlow.spec.ts`
|
||||
- `frontend/src/components/auth/__tests__/LinuxDoCallbackView.spec.ts`
|
||||
- `frontend/src/components/auth/__tests__/WechatCallbackView.spec.ts`
|
||||
- `frontend/src/components/user/profile/__tests__/ProfileAccountBindingsCard.spec.ts`
|
||||
- `frontend/src/components/user/profile/__tests__/ProfileInfoCard.spec.ts`
|
||||
- [ ] Cover:
|
||||
- email-required branch
|
||||
- email-conflict branch
|
||||
- bind-existing-account with re-auth prompt
|
||||
- nickname replacement only
|
||||
- avatar replacement only
|
||||
- neither replacement
|
||||
- avatar delete after prior provider adoption
|
||||
|
||||
## Phase 4: Payment Routing Rebuild
|
||||
|
||||
### Task 14. Normalize payment routing backend
|
||||
|
||||
- [ ] Rebuild `backend/internal/service/payment_config_service.go` to expose a stable method-routing contract:
|
||||
- frontend visible methods remain `alipay` and `wxpay`
|
||||
- admin chooses which provider instance serves each method
|
||||
- runtime validation guarantees only one active source per visible method
|
||||
- [ ] Add migration logic and tests to normalize historical provider-instance config:
|
||||
- `supported_types`
|
||||
- `payment_mode`
|
||||
- legacy aliases such as `wxpay_direct`
|
||||
- historical limit config
|
||||
- [ ] Rebuild `backend/internal/service/payment_order.go` and `backend/internal/service/payment_order_lifecycle.go` so order creation snapshots:
|
||||
- visible method
|
||||
- selected provider instance id
|
||||
- provider type
|
||||
- provider capability mode
|
||||
- verification-critical provider fields needed for later callback/query/refund validation
|
||||
- [ ] Rebuild `backend/internal/handler/payment_handler.go` for UX rules:
|
||||
- Alipay PC: QR page
|
||||
- Alipay mobile: direct jump
|
||||
- WeChat PC: QR page
|
||||
- WeChat H5 in WeChat: MP/JSAPI first, fallback to H5
|
||||
- WeChat H5 outside WeChat: H5 or “open in WeChat” prompt when unavailable
|
||||
- [ ] Never derive canonical return URL from `Referer`; use configured or signed internal callback targets only.
|
||||
- [ ] Implement `backend/internal/service/payment_resume_service.go` so WeChat in-app payment OAuth resume is server-backed rather than localStorage-backed:
|
||||
- create `oauth_required` resume context
|
||||
- persist amount/order_type/plan_id/visible method/redirect/state
|
||||
- redeem callback into same-origin internal resume target
|
||||
- expire and consume resume context safely
|
||||
|
||||
### Task 15. Make fulfillment and reconciliation provider-instance-safe
|
||||
|
||||
- [ ] Rebuild `backend/internal/handler/payment_webhook_handler.go` and `backend/internal/service/payment_fulfillment.go` so:
|
||||
- verification uses the order’s original provider instance
|
||||
- webhook processing is idempotent by provider event id and internal order id
|
||||
- missed webhook recovery uses server-side provider query, not frontend success return
|
||||
- [ ] For WeChat Pay specifically, enforce:
|
||||
- fixed HTTPS `notify_url` with no query params
|
||||
- no dependency on user login state
|
||||
- signature verification before decrypt
|
||||
- APIv3 decrypt before business parsing
|
||||
- comparison of `appid`, `mchid`, `out_trade_no`, `amount`, `currency`, and trade state against the order snapshot
|
||||
- [ ] Harden `frontend/src/views/user/PaymentResultView.vue` and `frontend/src/api/payment.ts` so result polling uses an authenticated order lookup or signed opaque token, not a raw public `out_trade_no` query.
|
||||
|
||||
### Task 16. Rebuild payment frontend views
|
||||
|
||||
- [ ] Rebuild `frontend/src/views/user/PaymentView.vue`, `frontend/src/views/user/PaymentQRCodeView.vue`, and `frontend/src/stores/payment.ts` so:
|
||||
- only two buttons are shown to user: `支付宝` and `微信支付`
|
||||
- frontend does not leak official-vs-EasyPay distinction
|
||||
- route-specific copy handles QR, jump, MP, H5 fallback correctly
|
||||
- [ ] Rebuild WeChat in-app payment resume UX around the server-backed resume context:
|
||||
- handle `oauth_required`
|
||||
- continue from same-origin resume target
|
||||
- avoid long-lived localStorage as the source of truth
|
||||
- [ ] Add or update:
|
||||
- `frontend/src/views/user/__tests__/PaymentView.spec.ts`
|
||||
- `frontend/src/views/user/__tests__/PaymentResultView.spec.ts`
|
||||
- backend webhook/payment routing tests
|
||||
|
||||
### Task 17. Payment verification checkpoint
|
||||
|
||||
- [ ] Run:
|
||||
```bash
|
||||
cd backend
|
||||
go test ./internal/service -run 'TestPayment'
|
||||
go test ./internal/handler -run 'TestPayment'
|
||||
cd ../frontend
|
||||
pnpm test:run src/views/user/__tests__/PaymentView.spec.ts src/views/user/__tests__/PaymentResultView.spec.ts
|
||||
```
|
||||
- [ ] Commit checkpoint:
|
||||
```bash
|
||||
git add backend frontend
|
||||
git commit -m "feat: rebuild payment routing foundation"
|
||||
```
|
||||
|
||||
## Phase 5: Scheduler, Rollout, And Final Compatibility Pass
|
||||
|
||||
### Task 18. Gate advanced scheduler behind explicit config
|
||||
|
||||
- [ ] Update `backend/internal/service/openai_account_scheduler.go` and related admin setting surfaces so:
|
||||
- advanced scheduler remains compiled and testable
|
||||
- default runtime state is disabled
|
||||
- enablement is explicit through admin settings
|
||||
- legacy scheduling behavior remains default on upgrade
|
||||
- [ ] Add targeted coverage in `backend/internal/service/openai_account_scheduler_test.go`.
|
||||
|
||||
### Task 19. Complete compatibility and rollout safety checks
|
||||
|
||||
- [ ] Add migration/repository tests covering:
|
||||
- historical email-only user login after upgrade
|
||||
- historical LinuxDo user login after upgrade
|
||||
- historical synthetic-email LinuxDo user login after upgrade
|
||||
- historical synthetic-email WeChat user login after upgrade
|
||||
- historical synthetic-email OIDC user login after upgrade
|
||||
- historical WeChat `openid`-only rows are reported or explicitly remediated
|
||||
- no retroactive grant replay during migration
|
||||
- first-bind grant fires once only when enabled
|
||||
- email identity dual-write stays consistent
|
||||
- bind-existing-account requires password and TOTP where configured
|
||||
- mixed-version callback token bridge works during rollout and is removable afterward
|
||||
- historical payment config is normalized into visible-method routing without refund/query regression
|
||||
- [ ] Add deploy sequencing note to release docs or internal runbook:
|
||||
1. deploy schema and backfill release.
|
||||
2. inspect migration report for unmatched rows.
|
||||
3. deploy backend identity/payment compatibility code with exchange bridge and legacy token aliases still enabled.
|
||||
4. deploy frontend callback/profile/payment UI using session completion, exchange code, and server-backed WeChat payment resume.
|
||||
5. remove legacy callback/token parsing after mixed-version window closes.
|
||||
6. enable strict email-required signup or provider bind grants only after metrics are healthy.
|
||||
|
||||
### Task 20. Final verification and handoff
|
||||
|
||||
- [ ] Run final backend verification:
|
||||
```bash
|
||||
cd backend
|
||||
go test ./...
|
||||
```
|
||||
- [ ] Run targeted frontend verification:
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm test:run \
|
||||
src/components/auth/__tests__/ThirdPartyAuthCallbackFlow.spec.ts \
|
||||
src/components/auth/__tests__/LinuxDoCallbackView.spec.ts \
|
||||
src/components/auth/__tests__/WechatCallbackView.spec.ts \
|
||||
src/components/user/profile/__tests__/ProfileAccountBindingsCard.spec.ts \
|
||||
src/components/user/profile/__tests__/ProfileInfoCard.spec.ts \
|
||||
src/views/user/__tests__/PaymentView.spec.ts \
|
||||
src/views/user/__tests__/PaymentResultView.spec.ts
|
||||
```
|
||||
- [ ] Run focused manual smoke checks:
|
||||
- email login with existing account
|
||||
- LinuxDo existing-account login after migration
|
||||
- WeChat synthetic-email account login after migration
|
||||
- OIDC synthetic-email account login after migration
|
||||
- third-party first login create-new-account path
|
||||
- third-party first login bind-existing-account path
|
||||
- first third-party bind with optional nickname/avatar replacement
|
||||
- PC Alipay QR
|
||||
- mobile Alipay jump
|
||||
- PC WeChat QR
|
||||
- WeChat H5 MP/JSAPI path
|
||||
- WeChat in-app OAuth resume path
|
||||
- non-WeChat H5 fallback path
|
||||
- [ ] Commit final checkpoint:
|
||||
```bash
|
||||
git add docs backend frontend
|
||||
git commit -m "feat: rebuild auth identity and payment foundation"
|
||||
```
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- [ ] No flow still relies on provider email equality for account linking.
|
||||
- [ ] No flow still creates third-party users through plain email registration helpers.
|
||||
- [ ] No callback still returns first-party bearer tokens in URL fragments.
|
||||
- [ ] No callback completion path can be replayed as a bearer token substitute.
|
||||
- [ ] No payment result view trusts provider return page as authoritative fulfillment.
|
||||
- [ ] No webhook verification path selects provider credentials from “currently active config” instead of the order snapshot.
|
||||
- [ ] Existing email users, historical LinuxDo/WeChat/OIDC users, and `openid`-only WeChat remediation cases are covered by migration tests.
|
||||
- [ ] Avatar adoption and deletion semantics are explicit and reversible.
|
||||
- [ ] Grant timing is source-aware and one-time only.
|
||||
@@ -1,763 +0,0 @@
|
||||
# Auth Identity And Payment Foundation Design
|
||||
|
||||
**Date:** 2026-04-20
|
||||
|
||||
**Status:** Draft approved in conversation, written for implementation planning
|
||||
|
||||
**Goal**
|
||||
|
||||
Rebuild the `feat/auth-identity-foundation` intent on a clean branch from `main`, covering unified user identity, third-party login and binding, profile adoption, source-based signup defaults, unified payment routing and UX, admin configuration, compatibility with existing `main` data, and an opt-in OpenAI advanced scheduling switch.
|
||||
|
||||
## Scope
|
||||
|
||||
This design includes:
|
||||
|
||||
- Email login and registration
|
||||
- Third-party login and binding for `LinuxDo`, `OIDC`, and `WeChat`
|
||||
- Unified identity storage for email and third-party identities
|
||||
- Pending auth sessions for callback-to-login/register/bind continuation
|
||||
- User-controlled nickname/avatar adoption during first relevant third-party flow
|
||||
- Profile binding management and avatar upload/delete
|
||||
- Source-based initial grants for balance, concurrency, and subscriptions
|
||||
- User management support for `last_login_at` and `last_active_at` sorting
|
||||
- Unified payment display methods (`alipay`, `wxpay`) mapped to a single active backend source each
|
||||
- Alipay and WeChat UX routing rules across PC, mobile, H5, and WeChat environments
|
||||
- Admin settings for auth providers, source defaults, payment sources, and OpenAI advanced scheduling
|
||||
- Incremental migration and compatibility for existing email users, existing LinuxDo users, historical LinuxDo/WeChat/OIDC synthetic-email users, and historical WeChat `openid`-only identity records
|
||||
|
||||
This design does not treat unrelated upstream merges, docs churn, or license changes from the old branch as required scope.
|
||||
|
||||
## Product Rules
|
||||
|
||||
### Auth and identity
|
||||
|
||||
- Existing email users remain valid and continue to log in with no manual action.
|
||||
- Existing LinuxDo, OIDC, and WeChat users represented by historical third-party or synthetic-email data must remain recoverable during migration.
|
||||
- Third-party first login behavior:
|
||||
- Existing bound identity: direct login
|
||||
- Missing identity: start first-login flow
|
||||
- Browser-based third-party authorization-code login always uses PKCE `S256`; this is not an admin-toggleable feature.
|
||||
- If `force_email_on_third_party_signup` is disabled, a first-login user may create an account without binding an email.
|
||||
- If `force_email_on_third_party_signup` is enabled, the user must provide an email.
|
||||
- If the provided and verified email already exists:
|
||||
- show that the email already exists
|
||||
- allow "verify and bind existing account"
|
||||
- allow "change email and continue registration"
|
||||
- do not allow bypassing the email requirement
|
||||
- Upstream provider email verification is not trusted as a local bound email.
|
||||
- Matching upstream email must never auto-link to an existing local account.
|
||||
- Linking to an existing local account is allowed only when:
|
||||
- the user explicitly chooses that target account
|
||||
- the target account passes fresh local re-authentication
|
||||
- required TOTP verification succeeds
|
||||
- New third-party bind initiated from profile must start from an already logged-in local account and preserve explicit bind intent end-to-end.
|
||||
- `redirect_to` may only represent a normalized same-origin internal route. It must never contain a third-party URL and must never be derived from `Referer`.
|
||||
- OIDC validation rules:
|
||||
- canonical identity key is `issuer + sub`
|
||||
- discovery issuer and ID token `iss` must match exactly
|
||||
- `userinfo.sub` must match ID token `sub` when UserInfo is used
|
||||
- upstream `email_verified` may improve UX copy but does not satisfy local email-binding requirements
|
||||
- WeChat login chooses channel by environment:
|
||||
- in WeChat environment: `mp`
|
||||
- outside WeChat: `open`
|
||||
- WeChat primary identity key is `unionid`.
|
||||
- If a WeChat login/bind flow cannot produce `unionid`, the flow fails and no fallback `openid` identity is created.
|
||||
- Historical WeChat records that only contain `openid` are treated as migration-remediation cases, not as a valid long-term canonical identity model.
|
||||
- WeChat website login uses authorization code flow, random `state`, and the provider channel/app binding must be persisted alongside the resolved identity.
|
||||
|
||||
### Profile adoption
|
||||
|
||||
- During the first relevant third-party flow, the user can independently decide:
|
||||
- replace current nickname or not
|
||||
- replace current avatar or not
|
||||
- This applies to first third-party registration and first third-party binding.
|
||||
- The decision is explicit user choice, not automatic replacement.
|
||||
|
||||
### Source-based initial grants
|
||||
|
||||
- Source-specific defaults exist for `email`, `linuxdo`, `oidc`, and `wechat`.
|
||||
- Each source defines:
|
||||
- default balance
|
||||
- default concurrency
|
||||
- default subscriptions
|
||||
- grant on signup
|
||||
- grant on first bind
|
||||
- Default behavior:
|
||||
- grant on signup: enabled
|
||||
- grant on first bind: disabled
|
||||
- First-bind grants are optional and controlled per source.
|
||||
- Grants must be idempotent.
|
||||
|
||||
### Avatar management
|
||||
|
||||
- Avatar supports:
|
||||
- external URL
|
||||
- image `data:` URL
|
||||
- `data:` URL images are compressed to at most `100KB` before persistence.
|
||||
- Avatar storage is database-backed.
|
||||
- Avatar delete is supported.
|
||||
|
||||
### Payment UX and routing
|
||||
|
||||
- Frontend shows only two display methods:
|
||||
- `alipay`
|
||||
- `wxpay`
|
||||
- Users never choose between official providers and EasyPay explicitly.
|
||||
- Backend allows only one active source per display method at a time.
|
||||
- Alipay UX:
|
||||
- PC: show QR code in page
|
||||
- mobile: jump to Alipay app/payment flow
|
||||
- WeChat UX:
|
||||
- PC: show QR code in page
|
||||
- non-WeChat H5: prefer H5 pay; if unavailable, tell the user to open in WeChat
|
||||
- WeChat environment: prefer MP/JSAPI pay; if unavailable, fall back to H5 pay
|
||||
- Payment success is confirmed by backend order state, webhook, and/or query, not only frontend return.
|
||||
- Frontend-visible labels remain `支付宝` and `微信支付`, while internal visible-method identifiers remain `alipay` and `wxpay`.
|
||||
- Public result pages must not verify order state by exposing raw `out_trade_no`; they use authenticated lookup or a signed opaque result token instead.
|
||||
- Payment callback or return URLs must be fixed same-origin internal targets. They must not be inferred from `Referer`.
|
||||
- WeChat payment webhook handling must use a fixed HTTPS `notify_url` with no query parameters and must not depend on user login state.
|
||||
|
||||
### OpenAI advanced scheduling
|
||||
|
||||
- OpenAI advanced scheduling is supported.
|
||||
- It is disabled by default.
|
||||
- Admin can enable it explicitly.
|
||||
|
||||
## Architecture
|
||||
|
||||
Keep `users` as the account owner table and move login identities, channel mappings, pending auth state, callback completion state, and first-bind grant idempotency into dedicated tables and services. Keep email login working while progressively introducing unified identity reads and writes.
|
||||
|
||||
Payment uses a similar split between user-visible display methods and backend provider sources. Frontend works only with stable display methods while backend resolves to the currently active source and capability matrix, and stores enough order-time snapshot data to survive later provider-config changes.
|
||||
|
||||
Compatibility is a first-class concern: migrations are additive, reads are compatibility-aware, and rollout must tolerate existing `main` data and short-lived frontend/backend version skew.
|
||||
|
||||
## Data Model
|
||||
|
||||
### `users`
|
||||
|
||||
Preserve existing account ownership and local-login fields. Extend or use:
|
||||
|
||||
- `email`
|
||||
- `password_hash`
|
||||
- `totp_enabled`
|
||||
- `signup_source`
|
||||
- `last_login_at`
|
||||
- `last_active_at`
|
||||
|
||||
The `users` table remains the primary business subject for balance, concurrency, subscriptions, permissions, and profile.
|
||||
|
||||
### `auth_identities`
|
||||
|
||||
Represents all canonical login or bindable identities.
|
||||
|
||||
Fields:
|
||||
|
||||
- `user_id`
|
||||
- `provider_type`: `email`, `linuxdo`, `oidc`, `wechat`
|
||||
- `provider_key`
|
||||
- `provider_subject`
|
||||
- `verified_at`
|
||||
- `issuer`
|
||||
- `metadata`
|
||||
- timestamps
|
||||
|
||||
Uniqueness:
|
||||
|
||||
- `provider_type + provider_key + provider_subject` must be unique
|
||||
|
||||
Rules:
|
||||
|
||||
- email identity uses canonicalized local email
|
||||
- LinuxDo uses stable provider subject under the configured provider namespace
|
||||
- OIDC uses stable issuer + subject, with issuer namespace represented consistently through `provider_key` and `issuer`
|
||||
- WeChat uses `unionid` as canonical subject under the configured Open Platform namespace
|
||||
|
||||
### `auth_identity_channels`
|
||||
|
||||
Stores channel-specific subject mappings for an identity.
|
||||
|
||||
Primary use:
|
||||
|
||||
- WeChat `open` / `mp` / payment channel mapping
|
||||
|
||||
Fields:
|
||||
|
||||
- `identity_id`
|
||||
- `provider_type`
|
||||
- `provider_key`
|
||||
- `channel`
|
||||
- `channel_app_id`
|
||||
- `channel_subject`
|
||||
- `metadata`
|
||||
- timestamps
|
||||
|
||||
Rules:
|
||||
|
||||
- canonical WeChat identity still keys on `unionid`
|
||||
- `openid` values live here as channel mappings
|
||||
|
||||
### `pending_auth_sessions`
|
||||
|
||||
Stores callback state between third-party callback and final account action.
|
||||
|
||||
Fields:
|
||||
|
||||
- `intent`
|
||||
- `provider_type`
|
||||
- `provider_key`
|
||||
- `provider_subject`
|
||||
- `target_user_id`
|
||||
- `redirect_to`
|
||||
- `resolved_email`
|
||||
- `registration_password_hash`
|
||||
- `upstream_identity_claims`
|
||||
- `local_flow_state`
|
||||
- `browser_session_key`
|
||||
- `completion_code_hash`
|
||||
- `completion_code_expires_at`
|
||||
- `email_verified_at`
|
||||
- `password_verified_at`
|
||||
- `totp_verified_at`
|
||||
- `expires_at`
|
||||
- `consumed_at`
|
||||
- timestamps
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- continue provider callback into register/login/bind flows
|
||||
- persist nickname/avatar suggestions
|
||||
- persist explicit adoption decisions
|
||||
- survive navigation between auth pages
|
||||
- support mixed-version rollout through short-lived legacy token aliases when required
|
||||
|
||||
Security rules:
|
||||
|
||||
- callback completion uses backend session completion or a one-time exchange code
|
||||
- exchange codes are short-lived, one-time, bound to browser session and pending session, and redeemed via `POST`
|
||||
- exchange codes must not behave as bearer tokens and must not be logged, stored in URL fragments, or reused after redemption
|
||||
- `local_flow_state` stores mutable local progression only; immutable upstream claims remain in `upstream_identity_claims`
|
||||
|
||||
### `identity_adoption_decisions`
|
||||
|
||||
Persists user adoption preference collected during a pending-auth flow and resolved onto the bound identity.
|
||||
|
||||
Fields:
|
||||
|
||||
- `pending_auth_session_id`
|
||||
- `identity_id`
|
||||
- `adopt_display_name`
|
||||
- `adopt_avatar`
|
||||
- `decided_at`
|
||||
- timestamps
|
||||
|
||||
Rules:
|
||||
|
||||
- one adoption-decision row exists per pending session
|
||||
- `identity_id` is filled once final account creation or bind succeeds
|
||||
|
||||
### `user_avatars`
|
||||
|
||||
Stores the currently effective custom avatar.
|
||||
|
||||
Fields:
|
||||
|
||||
- `user_id`
|
||||
- `storage_provider`
|
||||
- `storage_key`
|
||||
- `url`
|
||||
- `content_type`
|
||||
- `byte_size`
|
||||
- `sha256`
|
||||
- timestamps
|
||||
|
||||
Rules:
|
||||
|
||||
- supports URL-backed and inline data-backed representations
|
||||
- hard maximum payload size is `100KB`
|
||||
|
||||
### `user_provider_default_grants`
|
||||
|
||||
Stores idempotency state for source grants.
|
||||
|
||||
Fields:
|
||||
|
||||
- `user_id`
|
||||
- `provider_type`
|
||||
- `granted_at`
|
||||
- timestamps
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- prevent duplicate first-bind grants
|
||||
- allow signup grants and first-bind grants to be reasoned about independently
|
||||
|
||||
## Identity Keys And Canonicalization
|
||||
|
||||
- Email canonical key: `lower(trim(email))`
|
||||
- LinuxDo canonical key: provider subject from LinuxDo
|
||||
- OIDC canonical key: `issuer + sub`
|
||||
- WeChat canonical key: `unionid`
|
||||
|
||||
WeChat-specific rule:
|
||||
|
||||
- `openid` never becomes the primary stored identity key
|
||||
- if only `openid` is available, login/bind fails with a configuration/identity error
|
||||
- historical `openid`-only records must be reported and either remediated during migration or explicitly blocked from silent auto-upgrade
|
||||
|
||||
## Core Flows
|
||||
|
||||
### Email register/login
|
||||
|
||||
- Existing email auth flow remains
|
||||
- On email registration, create canonical `email` identity
|
||||
- Apply `email` source signup defaults
|
||||
|
||||
### Third-party login with existing identity
|
||||
|
||||
- Resolve canonical identity
|
||||
- Login mapped `user`
|
||||
- Update `last_login_at`
|
||||
- Do not issue signup or first-bind grants again
|
||||
|
||||
### Third-party first login with no identity
|
||||
|
||||
- Create `pending_auth_session`
|
||||
- Frontend callback flow decides next action
|
||||
- Pending session creation stores immutable upstream claims separately from mutable local progress fields
|
||||
|
||||
Branches:
|
||||
|
||||
- no forced email binding:
|
||||
- user can create account directly
|
||||
- forced email binding:
|
||||
- user must supply local email
|
||||
|
||||
If supplied local email already exists:
|
||||
|
||||
- tell the user the email already exists
|
||||
- allow verify-and-bind-existing-account
|
||||
- allow changing email to continue registration
|
||||
|
||||
On new account creation:
|
||||
|
||||
- create `users` row
|
||||
- create canonical third-party identity
|
||||
- create or update canonical email identity when local email binding succeeds
|
||||
- apply source signup grants
|
||||
- apply adoption choices if selected
|
||||
|
||||
### Bind third-party identity to current logged-in user
|
||||
|
||||
- current user starts bind flow
|
||||
- callback resolves to `bind_current_user`
|
||||
- bind intent is tied to the initiating local user session and cannot be re-targeted by email match
|
||||
- bind canonical identity to current user
|
||||
- if configured and first bind for that provider, apply first-bind grants
|
||||
- present nickname/avatar replacement choice
|
||||
|
||||
### Bind existing account during first-login flow
|
||||
|
||||
- user explicitly selects bind-existing-account
|
||||
- verify password for existing account
|
||||
- if account requires TOTP, verify TOTP
|
||||
- bind canonical identity to target account
|
||||
- optionally apply first-bind grants
|
||||
- present nickname/avatar replacement choice
|
||||
- no automatic profile or metadata merge occurs beyond explicitly selected nickname/avatar replacement
|
||||
|
||||
### Callback completion and exchange flow
|
||||
|
||||
- third-party callback never returns first-party bearer tokens in URL fragments
|
||||
- callback completion uses either:
|
||||
- backend session completion tied to the initiating browser session
|
||||
- one-time opaque exchange code redeemed by `POST`
|
||||
- mixed-version rollout may temporarily emit legacy pending token aliases in addition to the new completion path
|
||||
- legacy alias support is transitional and bounded to rollout windows only
|
||||
|
||||
### WeChat login and channel mapping
|
||||
|
||||
- environment chooses `mp` or `open`
|
||||
- website login uses authorization-code flow with provider-configured app/channel binding
|
||||
- callback must resolve to `unionid`
|
||||
- channel `openid` is optionally recorded in `auth_identity_channels`
|
||||
- failure to obtain `unionid` aborts flow
|
||||
|
||||
### Avatar upload and delete
|
||||
|
||||
- URL avatar: validate and persist reference
|
||||
- data URL avatar:
|
||||
- decode
|
||||
- validate image type
|
||||
- compress to `<=100KB`
|
||||
- persist database-backed inline representation
|
||||
- delete removes current custom avatar entry
|
||||
|
||||
## Payment Routing Model
|
||||
|
||||
### User-visible methods
|
||||
|
||||
- `alipay`
|
||||
- `wxpay`
|
||||
|
||||
### Backend source abstraction
|
||||
|
||||
Each display method maps to exactly one active configured backend source:
|
||||
|
||||
- `official_alipay`
|
||||
- `easypay_alipay`
|
||||
- `official_wechat`
|
||||
- `easypay_wechat`
|
||||
|
||||
Frontend submits display method only. Backend resolves display method to active source and capability set.
|
||||
|
||||
### Legacy payment-config normalization
|
||||
|
||||
- existing provider-instance `supported_types`, legacy aliases such as `wxpay_direct`, and per-type limit structures are migrated into the visible-method model
|
||||
- migration preserves historical payment capability and refund semantics
|
||||
- the system keeps one normalized visible-method mapping per provider instance for rollout and audit
|
||||
|
||||
### Alipay routing
|
||||
|
||||
- PC: create QR-oriented result and show QR in page
|
||||
- mobile: create jump/redirect-oriented result
|
||||
|
||||
### WeChat routing
|
||||
|
||||
- PC: QR result
|
||||
- non-WeChat H5:
|
||||
- prefer H5 pay
|
||||
- if unavailable, show "open in WeChat" requirement
|
||||
- WeChat environment:
|
||||
- prefer MP/JSAPI
|
||||
- if unavailable, fall back to H5 pay
|
||||
|
||||
### WeChat payment OAuth recovery
|
||||
|
||||
- if WeChat in-app payment requires `openid` and the current request does not already hold it, backend returns an `oauth_required` response instead of guessing
|
||||
- backend creates a server-backed payment-resume context containing:
|
||||
- target visible method
|
||||
- amount/order type/plan context
|
||||
- redirect target
|
||||
- anti-replay state
|
||||
- backend redirects through a dedicated WeChat payment OAuth start endpoint
|
||||
- callback exchanges the provider code server-side, stores `openid` in the payment-resume context, and returns a same-origin internal resume target
|
||||
- frontend resumes the original order flow through the resume context instead of trusting raw callback query state or long-lived local storage
|
||||
|
||||
### Payment completion
|
||||
|
||||
- frontend return restores context and UI state
|
||||
- backend order state remains source of truth
|
||||
- webhook and/or order query remain authoritative for fulfillment
|
||||
- order fulfillment validates webhook or query payload against order-time snapshot data including provider instance, merchant identifiers, amount, currency, and provider order references
|
||||
- result pages use authenticated lookup or signed opaque result tokens, never raw public `out_trade_no`
|
||||
|
||||
## Admin Configuration Model
|
||||
|
||||
### Auth provider settings
|
||||
|
||||
- email registration and verification settings
|
||||
- force email on third-party signup
|
||||
- LinuxDo client settings
|
||||
- OIDC issuer/client settings and provider display name
|
||||
- WeChat `open` / `mp` capability indicators derived from environment-backed configuration, surfaced to the frontend/admin read models as effective availability rather than full in-panel credential editing
|
||||
|
||||
### Source default settings
|
||||
|
||||
Per source (`email`, `linuxdo`, `oidc`, `wechat`):
|
||||
|
||||
- default balance
|
||||
- default concurrency
|
||||
- default subscriptions
|
||||
- grant on signup
|
||||
- grant on first bind
|
||||
|
||||
### Payment settings
|
||||
|
||||
- active source for `alipay`
|
||||
- active source for `wechat`
|
||||
- source-specific credentials and enablement
|
||||
- effective WeChat payment capabilities may differ by enabled provider instances and selected visible-method source:
|
||||
- QR available
|
||||
- H5 available
|
||||
- MP/JSAPI available
|
||||
|
||||
### Scheduling settings
|
||||
|
||||
- OpenAI advanced scheduling enabled/disabled
|
||||
- default disabled
|
||||
|
||||
## Compatibility And Rollout
|
||||
|
||||
Compatibility is mandatory, especially for:
|
||||
|
||||
- existing email users
|
||||
- existing LinuxDo users
|
||||
- historical LinuxDo synthetic-email accounts
|
||||
- historical WeChat synthetic-email accounts
|
||||
- historical OIDC synthetic-email accounts
|
||||
- historical WeChat `openid`-only records created by older branches
|
||||
|
||||
### Additive migrations
|
||||
|
||||
- preserve existing `users` data and behavior
|
||||
- add identity and pending-session tables
|
||||
- avoid destructive schema swaps
|
||||
|
||||
### Migration backfill
|
||||
|
||||
- backfill canonical `email` identities for valid existing email users
|
||||
- backfill canonical `linuxdo` identities during migration for historical synthetic-email LinuxDo users
|
||||
- backfill canonical `wechat` and `oidc` identities when historical synthetic-email or `user_external_identities` data allows deterministic reconstruction
|
||||
- emit migration reports for historical WeChat `openid`-only records that cannot be safely promoted to canonical `unionid`
|
||||
- backfill must be idempotent and repeatable
|
||||
|
||||
### Compatibility reads
|
||||
|
||||
During rollout:
|
||||
|
||||
- read new identity model first
|
||||
- where necessary, retain compatibility logic for existing email and historical LinuxDo/WeChat/OIDC synthetic-email recognition
|
||||
|
||||
### Grant idempotency
|
||||
|
||||
- migration backfill must not trigger signup or first-bind grants
|
||||
- first-bind grants must use explicit idempotency tracking
|
||||
|
||||
### API compatibility
|
||||
|
||||
Retain transitional support for legacy/new request and response shapes where needed, including:
|
||||
|
||||
- `pending_auth_token`
|
||||
- `pending_oauth_token`
|
||||
- old callback parsing expectations
|
||||
- historical profile field mappings
|
||||
- legacy callback fragment readers during the bounded rollout window
|
||||
|
||||
### Settings and payment compatibility
|
||||
|
||||
- preserve existing payment configs and order semantics from `main`
|
||||
- add new settings incrementally
|
||||
- avoid rewriting the entire settings schema in one cutover
|
||||
- preserve legacy provider-instance capabilities by explicitly mapping historical `supported_types`, `payment_mode`, and limit config into normalized visible-method routing
|
||||
|
||||
### Rolling upgrade tolerance
|
||||
|
||||
- do not assume simultaneous frontend/backend deployment
|
||||
- new backend must tolerate short-lived older frontend request shapes
|
||||
- rollout must define the deployment order and removal point for legacy callback token parsing and legacy payment resume parsing
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Repository tests
|
||||
|
||||
- identity upsert and lookup
|
||||
- WeChat channel mapping
|
||||
- pending auth session persistence
|
||||
- source grant idempotency
|
||||
- avatar persistence and delete
|
||||
- migration backfill behavior
|
||||
|
||||
### Service tests
|
||||
|
||||
- direct login by existing identity
|
||||
- first third-party signup
|
||||
- forced email flow
|
||||
- existing-email bind-existing-account flow
|
||||
- first-bind grant on/off
|
||||
- nickname/avatar adoption choices
|
||||
- WeChat `unionid` required behavior
|
||||
- payment routing resolution
|
||||
|
||||
### Handler and route tests
|
||||
|
||||
- LinuxDo/OIDC/WeChat callback handling
|
||||
- bind-existing
|
||||
- bind-current-user
|
||||
- create-account
|
||||
- TOTP continuation
|
||||
- payment create and recovery
|
||||
|
||||
### Frontend tests
|
||||
|
||||
- third-party callback flow state machine
|
||||
- register/login continuation
|
||||
- profile bindings card
|
||||
- avatar interactions
|
||||
- payment page routing behavior
|
||||
- admin settings UI
|
||||
|
||||
### Compatibility tests
|
||||
|
||||
- existing email users
|
||||
- historical LinuxDo synthetic-email users
|
||||
- historical WeChat synthetic-email users
|
||||
- historical OIDC synthetic-email users
|
||||
- historical WeChat `openid`-only records reported or remediated correctly
|
||||
- historical payment config
|
||||
- legacy auth payload field names
|
||||
- historical payment result handling
|
||||
- mixed-version callback token bridge behavior
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
1. Add schema, migrations, compatibility backfill, and repository support
|
||||
2. Implement unified identity services and pending auth session flows
|
||||
3. Integrate profile binding, avatar, and adoption decision flows
|
||||
4. Add per-source default grants and admin config surfaces
|
||||
5. Rebuild payment routing abstraction and frontend payment UX
|
||||
6. Add user-management sorting and OpenAI advanced scheduling switch
|
||||
7. Run compatibility, rollout, and regression hardening
|
||||
|
||||
## External Constraints And Best Practices
|
||||
|
||||
Implementation must follow current primary-source guidance:
|
||||
|
||||
- OAuth 2.0 Security BCP (RFC 9700): strict redirect handling, state protection, mix-up resistant design
|
||||
- PKCE (RFC 7636): require `S256` on browser authorization-code flows
|
||||
- OpenID Connect Core: stable issuer/subject handling for OIDC identities
|
||||
- Account linking best practice: require explicit user confirmation or re-authentication before linking to existing accounts
|
||||
- WeChat UnionID and website-login guidance: treat `unionid` as canonical cross-channel subject and persist channel/app binding with website login responses
|
||||
- WeChat Pay webhook guidance: verify signatures, decrypt payloads, and confirm merchant/order/amount fields against order-time state before fulfillment
|
||||
- Payment success-page guidance: custom success pages are informational and must not be the only fulfillment trigger
|
||||
|
||||
References:
|
||||
|
||||
- RFC 9700: <https://www.rfc-editor.org/rfc/rfc9700>
|
||||
- RFC 7636: <https://www.rfc-editor.org/rfc/rfc7636>
|
||||
- OpenID Connect Core 1.0: <https://openid.net/specs/openid-connect-core-1_0.html>
|
||||
- Auth0 account linking guidance: <https://auth0.com/docs/manage-users/user-accounts/user-account-linking>
|
||||
- WeChat UnionID guidance: <https://developers.weixin.qq.com/doc/service/guide/product/unionid.html>
|
||||
- WeChat website login guidance: <https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html>
|
||||
- WeChat Pay callback/signature guidance: <https://pay.weixin.qq.com/doc/v3/merchant/4012075249>
|
||||
- Stripe Checkout fulfillment guidance: <https://docs.stripe.com/checkout/fulfillment>
|
||||
|
||||
## Audit Synthesis
|
||||
|
||||
The clean rebuild direction is not to copy either existing branch directly.
|
||||
|
||||
- `feat/auth-identity-foundation` has the better long-term model:
|
||||
- unified auth identities
|
||||
- pending auth sessions
|
||||
- identity adoption decisions
|
||||
- provider-scoped default grants
|
||||
- payment display-method abstraction
|
||||
- OpenAI advanced scheduler layering
|
||||
- `personal-dev-branch` has the better real-world closure:
|
||||
- LinuxDo and WeChat callback flows are more operationally complete
|
||||
- profile binding and avatar UX is more complete
|
||||
- historical synthetic-email users across multiple providers are recognized and recovered in live flows
|
||||
- WeChat payment OAuth and recovery behavior is more complete
|
||||
- Primary-source guidance supplies hard constraints for OAuth/OIDC, account linking, WeChat identity handling, and payment completion semantics.
|
||||
|
||||
The final rebuild must therefore:
|
||||
|
||||
- keep the `feat/auth-identity-foundation` data model direction
|
||||
- absorb the strongest business-flow behavior from `personal-dev-branch`
|
||||
- reject transitional or half-finished behavior from both branches
|
||||
- treat compatibility and rollout as first-class implementation scope
|
||||
|
||||
## Keep / Adapt / Drop
|
||||
|
||||
### Keep
|
||||
|
||||
Keep these architectural choices essentially intact:
|
||||
|
||||
- `auth_identities`, `auth_identity_channels`, `pending_auth_sessions`, `identity_adoption_decisions`
|
||||
- per-provider default grants with one-time grant tracking
|
||||
- WeChat canonical identity plus channel mapping model
|
||||
- pending-auth verification gates before final bind
|
||||
- payment visible-method abstraction (`alipay`, `wechat`) decoupled from backend provider source
|
||||
- OpenAI advanced scheduler layering and test-backed behavior
|
||||
|
||||
Keep these operational flow ideas from `personal-dev-branch`:
|
||||
|
||||
- LinuxDo pending identity callback flow
|
||||
- WeChat pending identity callback flow
|
||||
- profile bindings UX and “cannot disconnect last usable login method” rule
|
||||
- separate WeChat login OAuth and WeChat payment OAuth entry points
|
||||
- historical synthetic-email recognition logic as a migration bridge
|
||||
- explicit WeChat payment OAuth recovery protocol as a product requirement, but reimplemented with server-backed resume state
|
||||
|
||||
### Adapt
|
||||
|
||||
These areas must be reimplemented with the same intent but stricter boundaries:
|
||||
|
||||
- third-party account creation from pending-auth state must be transactional and must not register a plain local user before identity finalization succeeds
|
||||
- email identity lifecycle must become real dual-write state, not just one migration-time backfill
|
||||
- `signup_source` must be backfilled more accurately for known historical third-party users
|
||||
- WeChat payment recovery state must move from frontend-only storage to server-backed continuation state
|
||||
- avatar adoption fetches must be security-hardened and failure-visible
|
||||
- pending-auth payload modeling must clearly separate immutable upstream payload from mutable local metadata
|
||||
- callback completion must use a real exchange/session model instead of fragment-delivered bearer tokens
|
||||
- profile binding/avatar DTOs must be simplified to one authoritative backend contract instead of sprawling frontend fallback parsing
|
||||
- admin settings should preserve capability while reducing duplicated or transitional config branches
|
||||
|
||||
### Drop
|
||||
|
||||
Drop these as long-term design choices:
|
||||
|
||||
- `user_external_identities` as the primary long-term identity model
|
||||
- synthetic email as a long-term canonical identity representation
|
||||
- OIDC as a side-path that does not participate in the same identity foundation as LinuxDo and WeChat
|
||||
- frontend multi-endpoint probing and broad compatibility parsing once the clean branch becomes the sole supported contract
|
||||
- unrelated branch noise such as generated-file churn, locale-only churn, or upstream merge residue as design inputs
|
||||
|
||||
## Audit-Driven Hard Constraints
|
||||
|
||||
The audit and source review establish these hard constraints:
|
||||
|
||||
### Auth
|
||||
|
||||
- all browser authorization-code providers use PKCE `S256` and do not expose an admin-off switch
|
||||
- callback handling uses strict `redirect_uri` discipline and state validation
|
||||
- OIDC identity key is `issuer + sub`
|
||||
- existing-account linking after email conflict must require explicit user action plus local-account verification
|
||||
- WeChat canonical identity key is `unionid`; `openid` is channel-scoped only
|
||||
|
||||
### Compatibility
|
||||
|
||||
- existing email users must continue to work with no manual intervention
|
||||
- existing LinuxDo users must not split into duplicate accounts
|
||||
- historical LinuxDo/WeChat/OIDC synthetic-email users must be backfilled into canonical identities during migration when deterministic recovery is possible
|
||||
- historical WeChat `openid`-only records must be surfaced through migration reporting and explicit remediation rules
|
||||
- migration backfills must not trigger signup or first-bind grants
|
||||
- legacy `pending_auth_token` and `pending_oauth_token` contracts must remain accepted during rollout
|
||||
- legacy auth/public setting aliases needed by older frontend builds must remain available during rollout
|
||||
- existing payment configs and historical order semantics must remain valid
|
||||
|
||||
### Payment
|
||||
|
||||
- frontend return pages do not determine final payment success
|
||||
- backend order state, webhook processing, and/or provider status query remain authoritative
|
||||
- each visible method (`alipay`, `wxpay`) may have only one active backend source at a time
|
||||
- public result pages must not expose raw `out_trade_no` lookup
|
||||
- WeChat Pay callback handling must verify signature, decrypt payload, and compare order fields against order-time snapshot data
|
||||
|
||||
## Known Risks To Eliminate In Implementation
|
||||
|
||||
These are specifically observed problems in the existing branches that the clean rebuild must eliminate:
|
||||
|
||||
- third-party forced-email account creation currently bypasses the provider-aware account creation path and can leave orphan local accounts if bind finalization fails
|
||||
- post-migration email accounts are not fully dual-written into `auth_identities`
|
||||
- avatar adoption currently risks silent failure and insecure outbound fetch behavior
|
||||
- pending-auth payload responsibilities are internally inconsistent
|
||||
- OIDC parity is incomplete in `personal-dev-branch`; it must become a first-class provider in the unified identity model
|
||||
- WeChat union/open/channel identity handling is conceptually correct in the feature branch but still partially transitional across the codebase
|
||||
- WeChat payment recovery in `personal-dev-branch` is frontend-local and not robust across tabs or concurrent attempts
|
||||
- the existing pending-auth migration update is too destructive to reuse unchanged in a safer rollout
|
||||
- historical provider provenance should not be permanently flattened to `signup_source = email`
|
||||
- design/plan drift can reintroduce ambiguous identity uniqueness or ambiguous adoption-decision ownership if not aligned before implementation
|
||||
|
||||
## Rollout Gates
|
||||
|
||||
The rebuild is not ready for rollout until all of these are satisfied:
|
||||
|
||||
1. Identity schema and migration chain are linearized and production-safe.
|
||||
2. Email identity backfill is complete and idempotent.
|
||||
3. Historical LinuxDo/WeChat/OIDC synthetic-email backfill to canonical identity is complete where deterministic, and non-recoverable rows are reported.
|
||||
4. Historical WeChat `openid`-only rows are either remediated or explicitly blocked with operator-visible reporting.
|
||||
5. `signup_source` backfill is accurate for known historical provider-created users.
|
||||
6. Dual token acceptance, exchange bridge behavior, and required legacy field aliases are present for the bounded rollout window.
|
||||
7. Existing payment configs are normalized and verified against current frontend-visible capabilities.
|
||||
8. New frontend flows are verified against mixed-version backend compatibility windows.
|
||||
9. Duplicate-account creation, first-bind grants, and payment route selection have regression coverage.
|
||||
Reference in New Issue
Block a user