
# [Service Name] — Stripe Checkout layer

A thin service that takes payments via Stripe Checkout and processes webhooks safely. Stateless except for what you choose to persist.

## Source of truth
Stripe is the source of truth for payment state. Your DB stores a denormalized copy for fast reads. The webhook reconciles after every event.

## Tech stack
Node 22 + TypeScript + Express or Hono (whatever your existing stack uses). Stripe SDK (`stripe` npm package, pinned to a specific API version). Postgres for the local mirror. JWT or session auth on the create-checkout endpoint.

## Deploy
Anywhere a Node server runs. Webhook endpoint must be reachable from the public internet. For local dev use `stripe listen --forward-to localhost:3000/webhooks/stripe`.

## File map
- `src/routes/checkout.ts` POST `/api/checkout` -> creates a Checkout Session, returns the URL
- `src/routes/billing-portal.ts` POST `/api/billing-portal` -> Customer portal URL for subscription management
- `src/routes/webhook.ts` POST `/webhooks/stripe` -> verifies signature, dispatches by event type
- `src/lib/stripe.ts` SDK init, API version pin
- `src/lib/db.ts` Postgres writes for customers, subscriptions, orders
- `src/events/` one handler per Stripe event type (checkout.session.completed, customer.subscription.updated, etc)
- `db/schema.sql` `stripe_customers`, `subscriptions`, `orders` tables

## .env keys
- `STRIPE_SECRET_KEY` `sk_live_...` or `sk_test_...`
- `STRIPE_WEBHOOK_SECRET` `whsec_...`
- `STRIPE_PRICE_IDS_TIER1`, `STRIPE_PRICE_IDS_TIER2` your product price IDs
- `STRIPE_PORTAL_RETURN_URL` where users land after the portal
- `DATABASE_URL`

## Hard rules
- Webhook signature is verified BEFORE parsing the body. Use the raw body, not `JSON.parse()`.
- Webhook handlers must be idempotent. Same event twice = same result. Store the Stripe event ID, refuse duplicates.
- Webhook responds 200 within 2 seconds; do the slow work in a background queue if needed.
- Never store full card numbers, CVCs, or full names on cards. Stripe holds these.
- API version is PINNED in `stripe.ts` (`apiVersion: '2025-06-30.basil'` or whatever your locked version is). Test before bumping.
- Test mode keys for dev, live mode for prod. Use Stripe's CLI `stripe listen` to forward events to localhost.

## Recent significant changes
- 2026-04-26: Scaffolded. Locked: Stripe Checkout (hosted) over custom Elements UI, webhooks over polling, Postgres mirror over Stripe-as-source-only.

## Next session: start here
1. Create Stripe account, get test keys.
2. Create products + prices in Stripe Dashboard, save IDs.
3. `stripe listen --forward-to localhost:3000/webhooks/stripe` and copy the `whsec_` into `.env`.
4. Implement create-checkout endpoint, test with a real test card (`4242 4242 4242 4242`).
5. Verify a successful webhook updates your DB before going live.
6. Activate live mode keys. Test one real payment for $1.
