# Where the pieces go

The integration is the same in every framework: the browser opens sub3's hosted pages; your server verifies the
session token and asks `GET /v1/access`; if you sell packs, the server credits receipts from `GET /v1/orders`.

## Browser: React, Vue, Svelte, plain JS

Copy [node/public/sub3.js](node/public/sub3.js). It does not touch `window` when imported, so it is safe with
server-side rendering. Call `openSub3` directly inside a click handler, or browsers block the popup.

- React: call it in `onClick`; read redirect results with `readSub3Result()` in a `useEffect` on mount.
- Vue: a method bound with `@click`; `readSub3Result()` in `onMounted`.
- Svelte: `onclick`; `readSub3Result()` in `onMount`.

Send the login `token` to your server (`Authorization: Bearer …`). After a checkout, send `{ payer, orderId }` to
your server and render what it answers; never unlock anything from the checkout result itself. The flow is in
[node/public/app.js](node/public/app.js) (`signIn`, `buy`, `receive`).

Do not serve the page that opens the popup with `Cross-Origin-Opener-Policy: same-origin`.

## Server: Express, Next.js, SvelteKit, Hono, any Node server

Copy [node/sub3.mjs](node/sub3.mjs) and call it from your routes:

- `verifySession(config, token)` → the user's address, or an error with `status` 401 (sign in again) or 503 (retry).
- `getAccess(config, user)` before anything paid; `getAccess(config, user, { fresh: true })` after a checkout.
- `afterCheckout(config, db, user, { payer, orderId })` when the browser reports a checkout result.
- `syncPacks(config, db, user)` on sign-in and when showing the balance, if you sell packs.

`config` is `{ api, site, origin, product }` (see [node/config.mjs](node/config.mjs)). Keep one process-wide
module instance so the access cache and the JWKS cache are shared. Next.js route handlers and SvelteKit endpoints
receive a Fetch `Request`: read the header with `request.headers.get('authorization')`.

`node/ledger.mjs` uses `node:sqlite` (Node 22.13+, a persistent disk). On Postgres, MySQL or D1, keep the same
primary keys — `receipts (chain_id, contract, payer, order_id)`, `usage (user, cycle)`, `operations (user, id)`
— and the same transactions. Edge runtimes cannot use `node:sqlite`.

## Python, Go, Ruby, Workers

No SDK is needed; everything is HTTP and a standard JWT:

1. Verify the token with any JWT library supporting EdDSA and a JWKS URL (`/.well-known/jwks.json` on the API):
   algorithm `EdDSA` only, `iss` = the sub3 site, `aud` = your origin, not expired. `sub` is the address.
2. `GET /v1/access?product=acme/app&user=<address>`; cache 30–60 s; refresh after checkout. Treat 429 and 5xx as
   "unknown", never "unpaid".
3. For packs, `GET /v1/orders?product=acme/app&user=<address>` and insert each confirmed `kind: "pack"` order
   into a table keyed by `(chainId, contract, payer, orderId)` in the same transaction that adds its `credits`
   (a decimal string; use arbitrary-precision integers).

The full contract of each endpoint is in `/llms.txt`.
