## Why Stripe checkout used Stripe-only tables. This extracts a shared payment CORE. New checkout writes `payment_order`. Old Stripe tables stay so in-progress Sessions can still settle. This PR is [1/2]. [#2368](https://github.com/moeru-ai/airi/pull/2368) is [2/2]. That PR archives leftover Stripe tables after in-progress Sessions finish or expire. ## Changes - Add `payment_order` and `provider_account`. - Copy `stripe_checkout_session` into `payment_order`. - Copy `stripe_customer` into `provider_account`. - Keep `stripe_*` tables and `user_flux.stripe_customer_id`. - New checkout writes `payment_order` and stores `metadata.payment_order_id`. - Webhook resolves new Sessions by `metadata.payment_order_id`. - Webhook resolves older Sessions by `provider_order_id`, then by a leftover `stripe_checkout_session` row. - That leftover-row lookup covers Sessions opened before this deploy, and rows written while 0023 runs. [#2368](https://github.com/moeru-ai/airi/pull/2368) deletes it after those Sessions finish or expire. ## Test plan - [x] `pnpm exec vitest run server/apps/api/src/services/domain/payment/tests/payment.test.ts server/apps/api/src/routes/stripe` - [x] `pnpm -F @proj-airi/api-server typecheck` - [x] `git diff --check` ## Visual changes No user-visible changes. ## Open: settle after account deletion Account deletion stamps `payment_order.deletedAt`. A Checkout Session that is still open can still pay after that stamp. This PR keeps `main` behavior. `settle` loads the row by id, including a soft-deleted row, then marks it `paid` and credits Flux. Follow-up policy: if `deletedAt` is set, skip. Do not update the archive row. Do not credit Flux. Do not insert a live `provider_account`. Stripe remains the payment record. Skip matches the soft-delete rule: a deleted row is gone, not write it again. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: RainbowBird <git@luoling.moe>