chore: remove local docs from repo

This commit is contained in:
IanShaw027
2026-04-21 14:54:42 +08:00
parent 147ed42ad3
commit 422f3449a2
6 changed files with 1 additions and 2123 deletions

View File

@@ -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`

View File

@@ -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.

View File

@@ -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 协议的支付服务商。
| 参数 | 说明 | 必填 |
|------|------|------|
| **商户 IDPID** | 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 一段时间以便查询历史记录。

View File

@@ -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 branchs 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 orders 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.

View File

@@ -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.