## 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>
69 lines
2.7 KiB
Markdown
69 lines
2.7 KiB
Markdown
# `@proj-airi/api-server`
|
|
|
|
Project AIRI's resource API. Authentication is a separate workspace app at
|
|
`server/apps/auth`; this package does not instantiate Better Auth or expose
|
|
auth/OIDC routes.
|
|
|
|
## Responsibilities
|
|
|
|
- Hono business APIs and WebSocket endpoints.
|
|
- Characters, chats, providers, Flux, Stripe, model routing, and billing.
|
|
- PostgreSQL migration ownership for the currently shared database. Drizzle reads the checked-in `drizzle/` journal and SQL files at startup.
|
|
- Redis cache, configuration KV, and cross-instance Pub/Sub.
|
|
- Local verification of Auth-issued OIDC JWTs through public JWKS.
|
|
|
|
## Payment
|
|
|
|
`src/services/domain/payment` owns pack grant and `payment_order` rows.
|
|
CORE exposes `openPending`, `bindProcessorOrder`, `abandon`, `settle`,
|
|
and `deleteAllForUser`. Checkout and package list live in the Stripe
|
|
adapter on `/api/v1/stripe/*`. CORE never sees a raw processor event.
|
|
The adapter maps the processor result onto a `ClaimReceipt`, then calls
|
|
`settle`.
|
|
|
|
## Run locally
|
|
|
|
```sh
|
|
pnpm -F @proj-airi/api-server dev
|
|
pnpm -F @proj-airi/api-server typecheck
|
|
pnpm -F @proj-airi/api-server exec vitest run
|
|
pnpm -F @proj-airi/api-server build
|
|
```
|
|
|
|
Run the complete local backend from the repository root:
|
|
|
|
```sh
|
|
pnpm dev:backend
|
|
```
|
|
|
|
For source-level debugging, start `@proj-airi/api-server` and
|
|
`@proj-airi/auth-server` separately instead.
|
|
|
|
`server/docker-compose.yaml` exposes the local Caddy gateway at `http://localhost:6112` and keeps
|
|
the API and Auth container ports private.
|
|
|
|
## Service boundaries
|
|
|
|
- `AUTH_SERVER_URL` is Auth's canonical public issuer origin used for JWKS,
|
|
issuer, and audience validation. It must exactly equal Auth's `PUBLIC_URL`.
|
|
- `/internal/auth/*` is reachable only on the deployment's trusted private
|
|
network. The public edge must reject `/internal/*` and the API service must
|
|
not have its own public ingress.
|
|
- `AUTH_SERVER_INTERNAL_URL` optionally sends JWKS fetches directly to Auth on
|
|
the private network while issuer and audience remain `AUTH_SERVER_URL`.
|
|
- Auth tables and principal types come from `@proj-airi/auth-shared`; no module
|
|
under `server/apps/auth` is imported.
|
|
|
|
## Railway
|
|
|
|
Deploy this as the Resource API Railway service with Config File Path
|
|
`/server/apps/api/railway.toml`; keep the service Root Directory at the
|
|
repository root because the Dockerfile copies shared workspace packages. The
|
|
config owns its Dockerfile, start command, `/readyz` healthcheck, and the
|
|
watch patterns for every copied build input.
|
|
|
|
Set `AUTH_SERVER_INTERNAL_URL` from Auth's Railway private domain. It is only
|
|
the private JWKS route; `AUTH_SERVER_URL` remains the public Auth issuer URL.
|
|
See [`server/README.md`](../../README.md#railway-deployment) for the complete
|
|
cross-service variable and migration contract.
|