Files
sub2api/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md
2026-04-20 14:41:12 +08:00

540 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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