docs(subscriptions): Stripe-first checkout via optional-auth POST /sessions + claim (app#2044)
Implements row 7 of recoupable/app#2044: the contract for Stripe-first checkout (decision 1 on the issue: the Pro CTA goes to Stripe directly; sign-in moves after payment). Docs-first; the api PR that fulfils it links back here and merges after this.
What is documented
| Path | Auth | Purpose |
|---|---|---|
POST /api/subscriptions/checkout | none required; Bearer / x-api-key honoured when present | Mint a Stripe Checkout for plan: starter | pro with successUrl (+ optional cancelUrl). Response reuses CreateSubscriptionSessionResponse ({ id, url }). 400 for an invalid body or starter_unavailable (Starter price not configured), 401 only when an auth header is supplied and invalid, 500. |
POST /api/subscriptions/claim | required | { session_id } from the success URL's {CHECKOUT_SESSION_ID}; re-stamps the subscription to the caller when unowned or owned by the webhook-created placeholder account; 200 { status, subscription_id, plan }, 400 no subscription, 404 session_not_found, 409 already_claimed. |
The checkout description states the webhook behaviour: on checkout.session.completed for an unauthenticated session the api finds the account by customer_details.email, creates one (with the welcome email) when none exists, and stamps accountId on the Stripe subscription and customer, so the buyer holds the plan before their first sign-in.
Schemas added: CreateCheckoutRequest, ClaimSubscriptionRequest, ClaimSubscriptionResponse, ClaimSubscriptionErrorResponse. Both pages are in the Billing nav group (so they land in llms.txt). Edits are anchored text insertions; the JSON round-trips.
Assumptions carried from the contract
- Starter has no trial; Pro keeps the 30-day trial.
STRIPE_STARTER_PRICE_IDis an environment variable Sweets sets in Vercel after creating the $19/mo price in the Stripe dashboard.
Local Mintlify screenshots are in the comment below. Merge order: this PR, then the api PR for row 8.
Summary by cubic
Documents the Stripe-first checkout contract so the Pro CTA can send visitors straight to Stripe before they sign in: auth becomes optional on POST /api/subscriptions/sessions, and a new POST /api/subscriptions/claim re-attaches a subscription to the signed-in account.
Contract details
POST /api/subscriptions/sessionstakesplan(starterorpro),successUrl, and optionalcancelUrl; omit auth for anonymous checkout or send Bearer/x-api-keyto attach the session to that account.- For anonymous checkout, the
checkout.session.completedwebhook finds or creates the account fromcustomer_details.emailand stampsaccountIdon the Stripe subscription. POST /api/subscriptions/claimre-stamps an unowned or webhook-created placeholder subscription to the authenticated caller using thesession_idfrom the success URL; it's a no-op returning 200 when the webhook already linked an account with the matching email.- Starter has no trial; Pro keeps the 30-day trial. Starter returns 400
starter_unavailableuntilSTRIPE_STARTER_PRICE_IDis configured. - Merge this PR before the API PR that implements the contract.