<!--
ROBOTS ON PAYROLL / THE VAULT
What this is: the test-mode to live-mode Stripe checklist to run before, and right after, your first real charge.
How to use it: download this file and give it to your AI (Claude, ChatGPT,
Cursor, Lovable chat) as a reference. Say: "Use this as my playbook for
taking Stripe live."
Latest version + full guide: https://robotsonpayroll.com/resources/stripe-afternoon
-->

# Stripe Go-Live Checklist

Run this top to bottom before you flip to live mode, then the live-mode section right after your first real charge. Everything in the test-mode section uses your sandbox; nothing costs money. Time budget: 60-90 minutes if the webhook handler already exists.

As of July 2026. Card numbers are Stripe's official test cards and only work in test mode.

---

## 1. Test-mode matrix (sandbox)

### Happy path

- [ ] Full checkout with `4242 4242 4242 4242` (any future expiry, any CVC, any ZIP) completes and redirects to your success URL
- [ ] Your DB row now has: stripe_customer_id, stripe_subscription_id, subscription_status active/trialing, current_period_end set
- [ ] The app actually unlocks (log in as that user, hit a gated feature)
- [ ] Receipt email arrives (Dashboard -> Settings -> Emails, enable customer emails in live mode too)

### Declines and authentication

- [ ] Generic decline `4000 0000 0000 0002`: checkout shows the error, no DB changes, user still locked out
- [ ] Insufficient funds `4000 0000 0000 9995`: same result
- [ ] 3D Secure required `4000 0027 6000 3184`: the authentication modal appears and completing it succeeds (hosted Checkout handles this for you; confirm it end to end anyway)
- [ ] A decline followed by a successful retry with 4242 produces exactly ONE subscription, not two

### Subscription lifecycle

- [ ] Cancel mid-period via the Customer Portal: `customer.subscription.updated` arrives with cancel_at_period_end true, your DB flags it, access REMAINS until period end
- [ ] At period end (advance with a test clock, below): `customer.subscription.deleted` arrives, status flips to canceled, access is revoked
- [ ] Plan switch in the portal (if you offer 2+ plans): price_id updates in your DB, proration invoice looks right in the Dashboard
- [ ] If you offer trials: trial checkout sets status trialing, and converting to paid flips it to active

### Webhook robustness

- [ ] Replay: Dashboard -> Developers -> Webhooks -> your endpoint -> pick a delivered event -> Resend. Your handler returns 200 and does NOT double-provision (idempotency table catches it)
- [ ] Wrong signature: `curl -X POST your-endpoint -d '{}'` returns 400, not 500, not 200
- [ ] Unhandled event type (e.g. `stripe trigger product.created`) returns 200 and logs nothing scary
- [ ] Endpoint responds in under ~5 seconds (slow handlers trigger Stripe retries and duplicate work)

### Renewals with test clocks

Renewals are where silent breakage lives. Simulate a month in 2 minutes:

- [ ] Dashboard -> a test-mode customer created WITH a test clock (create the clock first, then the customer attached to it), subscribe them to your plan
- [ ] Advance the clock past the renewal date ("Run simulation" on the subscription page, or `POST /v1/test_helpers/test_clocks/:id/advance`)
- [ ] `invoice.paid` fires, your DB's current_period_end moves forward
- [ ] Repeat with a card that fails on renewal (attach `4000 0000 0000 0341` as the default payment method): `invoice.payment_failed` fires, status goes past_due, access follows your grace-period rule
- [ ] Check Settings -> Billing -> Automatic collection: your retry/dunning schedule and cancellation behavior are set on purpose, not on defaults you never read

## 2. Account and site prep (do this BEFORE first live charge)

Payout holds on new accounts are usually self-inflicted. Close these off:

- [ ] Business verification 100% complete in live mode: legal name, EIN/tax id, address, bank account, identity doc if asked. Name/EIN/URL must MATCH each other and your website
- [ ] Statement descriptor set and recognizable (Settings -> Business -> Public details). "ACMEAPP.COM" not "JS HOLDINGS LLC". Unrecognizable descriptors = disputes = reviews
- [ ] Website has: Terms of Service, Privacy Policy, Refund policy, a real contact method, and clear pricing. Stripe's risk team looks for these on new accounts
- [ ] Your business category is not on stripe.com/legal/restricted-businesses (check before you build, ideally)
- [ ] If you ship physical goods or pre-orders: fulfillment timelines stated on the site
- [ ] Support email on the Stripe account is one you actually read (verification requests have deadlines)

## 3. The live-mode flip

- [ ] Create the LIVE webhook endpoint (test and live endpoints and secrets are separate). Point it at production, subscribe to the same event types
- [ ] Swap env vars in production: live secret key, live webhook secret, live price ids. Grep your deploy for `sk_test` and `whsec_` values from dev
- [ ] Live products/prices exist (test-mode products do not carry over automatically; use the "copy to live mode" button or recreate)
- [ ] Customer Portal activated and configured in live mode (Settings -> Billing -> Customer portal)
- [ ] Customer emails (receipts, failed payment notifications) enabled in live mode

## 4. First real charge

- [ ] Make one real purchase yourself with a real card (you can refund it after; one refund on day one is fine, a 5%+ refund RATE is not)
- [ ] Webhook delivered to production endpoint, DB row correct, app unlocked
- [ ] Refund it from the Dashboard and confirm your handler's behavior is what you expect (a refund alone does not cancel the subscription; cancel separately if that's the intent)
- [ ] Calendar note: first payout typically lands 7-14 days after the first successful live charge. That delay is normal, not a freeze. After the first one, US accounts default to 2-day rolling payouts

## 5. Week-one hygiene

- [ ] Check Developers -> Webhooks daily for failed deliveries (Stripe retries for up to ~3 days, then gives up and can disable chronically failing endpoints)
- [ ] Respond to any Stripe email asking for information within 48 hours; slow responses extend reviews
- [ ] Ramp volume roughly in line with what you told Stripe at signup; a $0 to $50K weekend on a fresh account is a review trigger even when it's legitimate. If a launch spike is coming, tell Stripe support beforehand
- [ ] Watch the dispute count. Under ~0.75% of charges is the zone; each dispute costs $15 and disputes are the fastest route to a reserve
