diff --git a/.gitignore b/.gitignore index 1a92ea3e..cf2bda9f 100644 --- a/.gitignore +++ b/.gitignore @@ -126,12 +126,9 @@ backend/cmd/server/server deploy/docker-compose.override.yml .gocache/ vite.config.js -docs/* -!docs/PAYMENT.md -!docs/PAYMENT_CN.md +docs/ .serena/ .codex/ frontend/coverage/ aicodex output/ - diff --git a/docs/ADMIN_PAYMENT_INTEGRATION_API.md b/docs/ADMIN_PAYMENT_INTEGRATION_API.md deleted file mode 100644 index f674f86c..00000000 --- a/docs/ADMIN_PAYMENT_INTEGRATION_API.md +++ /dev/null @@ -1,243 +0,0 @@ -# ADMIN_PAYMENT_INTEGRATION_API - -> 单文件中英双语文档 / Single-file bilingual documentation (Chinese + English) - ---- - -## 中文 - -### 目标 -本文档用于对接外部支付系统(如 `sub2apipay`)与 Sub2API 的 Admin API,覆盖: -- 支付成功后充值 -- 用户查询 -- 人工余额修正 -- 前端购买页参数透传 - -### 基础地址 -- 生产:`https://` -- Beta:`http://:8084` - -### 认证 -推荐使用: -- `x-api-key: admin-<64hex>` -- `Content-Type: application/json` -- 幂等接口额外传:`Idempotency-Key` - -说明:管理员 JWT 也可访问 admin 路由,但服务间调用建议使用 Admin API Key。 - -### 1) 一步完成创建并兑换 -`POST /api/v1/admin/redeem-codes/create-and-redeem` - -用途:原子完成“创建兑换码 + 兑换到指定用户”。 - -请求头: -- `x-api-key` -- `Idempotency-Key` - -请求体示例: -```json -{ - "code": "s2p_cm1234567890", - "type": "balance", - "value": 100.0, - "user_id": 123, - "notes": "sub2apipay order: cm1234567890" -} -``` - -幂等语义: -- 同 `code` 且 `used_by` 一致:`200` -- 同 `code` 但 `used_by` 不一致:`409` -- 缺少 `Idempotency-Key`:`400`(`IDEMPOTENCY_KEY_REQUIRED`) - -curl 示例: -```bash -curl -X POST "${BASE}/api/v1/admin/redeem-codes/create-and-redeem" \ - -H "x-api-key: ${KEY}" \ - -H "Idempotency-Key: pay-cm1234567890-success" \ - -H "Content-Type: application/json" \ - -d '{ - "code":"s2p_cm1234567890", - "type":"balance", - "value":100.00, - "user_id":123, - "notes":"sub2apipay order: cm1234567890" - }' -``` - -### 2) 查询用户(可选前置校验) -`GET /api/v1/admin/users/:id` - -```bash -curl -s "${BASE}/api/v1/admin/users/123" \ - -H "x-api-key: ${KEY}" -``` - -### 3) 余额调整(已有接口) -`POST /api/v1/admin/users/:id/balance` - -用途:人工补偿 / 扣减,支持 `set` / `add` / `subtract`。 - -请求体示例(扣减): -```json -{ - "balance": 100.0, - "operation": "subtract", - "notes": "manual correction" -} -``` - -```bash -curl -X POST "${BASE}/api/v1/admin/users/123/balance" \ - -H "x-api-key: ${KEY}" \ - -H "Idempotency-Key: balance-subtract-cm1234567890" \ - -H "Content-Type: application/json" \ - -d '{ - "balance":100.00, - "operation":"subtract", - "notes":"manual correction" - }' -``` - -### 4) 购买页 / 自定义页面 URL Query 透传(iframe / 新窗口一致) -当 Sub2API 打开 `purchase_subscription_url` 或用户侧自定义页面 iframe URL 时,会统一追加: -- `user_id` -- `token` -- `theme`(`light` / `dark`) -- `lang`(例如 `zh` / `en`,用于向嵌入页传递当前界面语言) -- `ui_mode`(固定 `embedded`) - -示例: -```text -https://pay.example.com/pay?user_id=123&token=&theme=light&lang=zh&ui_mode=embedded -``` - -### 5) 失败处理建议 -- 支付成功与充值成功分状态落库 -- 回调验签成功后立即标记“支付成功” -- 支付成功但充值失败的订单允许后续重试 -- 重试保持相同 `code`,并使用新的 `Idempotency-Key` - -### 6) `doc_url` 配置建议 -- 查看链接:`https://github.com/Wei-Shaw/sub2api/blob/main/ADMIN_PAYMENT_INTEGRATION_API.md` -- 下载链接:`https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/ADMIN_PAYMENT_INTEGRATION_API.md` - ---- - -## English - -### Purpose -This document describes the minimal Sub2API Admin API surface for external payment integrations (for example, `sub2apipay`), including: -- Recharge after payment success -- User lookup -- Manual balance correction -- Purchase page query parameter forwarding - -### Base URL -- Production: `https://` -- Beta: `http://:8084` - -### Authentication -Recommended headers: -- `x-api-key: admin-<64hex>` -- `Content-Type: application/json` -- `Idempotency-Key` for idempotent endpoints - -Note: Admin JWT can also access admin routes, but Admin API Key is recommended for server-to-server integration. - -### 1) Create and Redeem in one step -`POST /api/v1/admin/redeem-codes/create-and-redeem` - -Use case: atomically create a redeem code and redeem it to a target user. - -Headers: -- `x-api-key` -- `Idempotency-Key` - -Request body: -```json -{ - "code": "s2p_cm1234567890", - "type": "balance", - "value": 100.0, - "user_id": 123, - "notes": "sub2apipay order: cm1234567890" -} -``` - -Idempotency behavior: -- Same `code` and same `used_by`: `200` -- Same `code` but different `used_by`: `409` -- Missing `Idempotency-Key`: `400` (`IDEMPOTENCY_KEY_REQUIRED`) - -curl example: -```bash -curl -X POST "${BASE}/api/v1/admin/redeem-codes/create-and-redeem" \ - -H "x-api-key: ${KEY}" \ - -H "Idempotency-Key: pay-cm1234567890-success" \ - -H "Content-Type: application/json" \ - -d '{ - "code":"s2p_cm1234567890", - "type":"balance", - "value":100.00, - "user_id":123, - "notes":"sub2apipay order: cm1234567890" - }' -``` - -### 2) Query User (optional pre-check) -`GET /api/v1/admin/users/:id` - -```bash -curl -s "${BASE}/api/v1/admin/users/123" \ - -H "x-api-key: ${KEY}" -``` - -### 3) Balance Adjustment (existing API) -`POST /api/v1/admin/users/:id/balance` - -Use case: manual correction with `set` / `add` / `subtract`. - -Request body example (`subtract`): -```json -{ - "balance": 100.0, - "operation": "subtract", - "notes": "manual correction" -} -``` - -```bash -curl -X POST "${BASE}/api/v1/admin/users/123/balance" \ - -H "x-api-key: ${KEY}" \ - -H "Idempotency-Key: balance-subtract-cm1234567890" \ - -H "Content-Type: application/json" \ - -d '{ - "balance":100.00, - "operation":"subtract", - "notes":"manual correction" - }' -``` - -### 4) Purchase / Custom Page URL query forwarding (iframe and new tab) -When Sub2API opens `purchase_subscription_url` or a user-facing custom page iframe URL, it appends: -- `user_id` -- `token` -- `theme` (`light` / `dark`) -- `lang` (for example `zh` / `en`, used to pass the current UI language to the embedded page) -- `ui_mode` (fixed: `embedded`) - -Example: -```text -https://pay.example.com/pay?user_id=123&token=&theme=light&lang=zh&ui_mode=embedded -``` - -### 5) Failure handling recommendations -- Persist payment success and recharge success as separate states -- Mark payment as successful immediately after verified callback -- Allow retry for orders with payment success but recharge failure -- Keep the same `code` for retry, and use a new `Idempotency-Key` - -### 6) Recommended `doc_url` -- View URL: `https://github.com/Wei-Shaw/sub2api/blob/main/ADMIN_PAYMENT_INTEGRATION_API.md` -- Download URL: `https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/ADMIN_PAYMENT_INTEGRATION_API.md` diff --git a/docs/PAYMENT.md b/docs/PAYMENT.md deleted file mode 100644 index 9322f7bf..00000000 --- a/docs/PAYMENT.md +++ /dev/null @@ -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. diff --git a/docs/PAYMENT_CN.md b/docs/PAYMENT_CN.md deleted file mode 100644 index 0fbc198a..00000000 --- a/docs/PAYMENT_CN.md +++ /dev/null @@ -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 一段时间以便查询历史记录。 diff --git a/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md b/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md deleted file mode 100644 index 2d44e058..00000000 --- a/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md +++ /dev/null @@ -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. diff --git a/docs/superpowers/specs/2026-04-20-auth-identity-payment-foundation-design.md b/docs/superpowers/specs/2026-04-20-auth-identity-payment-foundation-design.md deleted file mode 100644 index 23823cf0..00000000 --- a/docs/superpowers/specs/2026-04-20-auth-identity-payment-foundation-design.md +++ /dev/null @@ -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: -- RFC 7636: -- OpenID Connect Core 1.0: -- Auth0 account linking guidance: -- WeChat UnionID guidance: -- WeChat website login guidance: -- WeChat Pay callback/signature guidance: -- Stripe Checkout fulfillment guidance: - -## 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.