docs(subscriptions): Stripe-first checkout via optional-auth POST /sessions + claim (app#2044)

Merged
SW
2mo ago
+188-62

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

PathAuthPurpose
POST /api/subscriptions/checkoutnone required; Bearer / x-api-key honoured when presentMint 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/claimrequired{ 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_ID is 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/sessions takes plan (starter or pro), successUrl, and optional cancelUrl; omit auth for anonymous checkout or send Bearer/x-api-key to attach the session to that account.
  • For anonymous checkout, the checkout.session.completed webhook finds or creates the account from customer_details.email and stamps accountId on the Stripe subscription.
  • POST /api/subscriptions/claim re-stamps an unowned or webhook-created placeholder subscription to the authenticated caller using the session_id from 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_unavailable until STRIPE_STARTER_PRICE_ID is configured.
  • Merge this PR before the API PR that implements the contract.