Test cards, CLI event replay, error taxonomy, retry logic, and the pre-launch checklist. After today, you'll know what separates a demo Stripe integration from a production one — and have a concrete list of everything to verify before you flip the switch.
Build a complete pre-launch checklist for your Stripe integration, understand all error types, and run a full end-to-end test using the Stripe CLI.
Most payment bugs are silent — a user's card gets declined but they see a spinner forever, or a webhook fails and nobody finds out until a subscriber discovers their account was never provisioned. Production Stripe means every failure path is handled and monitored.
Stripe provides test card numbers that simulate specific outcomes. These work in test mode only and are rejected in live mode.
4242 4242 4242 4242 → succeeds 4000 0000 0000 9995 → insufficient funds 4000 0000 0000 3220 → 3D Secure required 4000 0000 0000 0341 → attaches but charge fails 4000 0025 0000 3155 → requires authentication on payment
Stripe throws different error types for different failure modes. Handle them at the right granularity.
try { const charge = await stripe.charges.create({...}); } catch (err) { switch (err.type) { case 'StripeCardError': // card declined — show user-friendly message break; case 'StripeRateLimitError': // too many requests — retry with exponential backoff break; case 'StripeInvalidRequestError': // bad parameters — fix your code break; case 'StripeAPIError': case 'StripeConnectionError': // Stripe-side problem — retry, alert your team break; } }
Before switching to live keys, verify every item on this list:
checkout.session.completed, invoice.payment_failed, customer.subscription.deleted.Before moving on, you should be able to answer these without looking:
StripeCardError and a StripeAPIError?