E-commerce
Stripe checkout integration, and the four failure modes nobody tests for
Drive fulfilment from the Stripe webhook, never from the browser redirect. A customer who closes the tab after paying is common, and a redirect-driven flow leaves them charged with no order. Everything else in a Stripe integration is detail; that one decision is the difference between a store that works and one that generates support tickets.
Failure mode 1 — the closed tab
The most common integration pattern in tutorials is: redirect the customer to Stripe, and when they come back to your success URL, create the order. It works perfectly in testing, because in testing nobody closes the tab.
In production people close the tab constantly. Their phone rings, the connection drops, the payment succeeds and the redirect never completes. In a redirect-driven flow that customer has been charged and has no order. You will hear about it, and you will hear about it from someone who is already angry.
1. Create a Checkout Session, store your own order row as "pending"
2. Customer pays on Stripe's hosted page
3. Stripe POSTs checkout.session.completed to your webhook endpoint
4. Verify the signature, look up the pending order, mark it paid
5. Fulfil: provision, email, decrement stock
6. The browser redirect only ever DISPLAYS state — it never creates itFailure mode 2 — the webhook that runs twice
Stripe guarantees at-least-once delivery, not exactly-once. Your endpoint will receive the same event twice, usually because your first response was slow and Stripe retried. If your handler is not idempotent, that is two confirmation emails, two stock decrements, and on a subscription build, occasionally two provisioned accounts.
The fix is boring and non-negotiable: record the Stripe event id, and make the handler a no-op if you have processed that id before. Do it in the same transaction as the fulfilment write, or the race you avoided at the HTTP layer reappears at the database layer.
Failure mode 3 — testing only the happy path
Stripe publishes test cards for every scenario that matters, and most integrations only ever exercise the one that succeeds. The four that find real bugs are below.
- A card that requires 3D Secure authentication — does your flow handle the challenge and the abandonment of it?
- A card that is declined — is the customer told clearly, and is your pending order cleaned up?
- A partial refund from the dashboard — does your order status update, or does it silently diverge from Stripe?
- A subscription whose renewal fails — does the customer lose access, get emailed, or neither?
Failure mode 4 — the currency and tax afterthought
Stripe will happily take money in a currency your accounting cannot reconcile. Decide before you build: what currency you present, what currency you settle in, who bears the conversion, and whether tax is calculated by Stripe Tax, by your own rules, or not at all because you are below a threshold.
One operational note specific to Pakistan-based developers, because it catches people out: Stripe does not support Pakistan as a merchant country. I build and test Stripe integrations against client accounts — the client owns the account, the keys and the payouts — but I cannot use Stripe to collect my own fees. If a supplier tells you otherwise, ask which entity the account is registered to.
A short pre-launch checklist
- Webhook endpoint verifies the signature and rejects anything unsigned
- Event ids recorded; replays are no-ops
- Live keys in environment variables, never in the repository
- Success page reads state, never writes it
- Failed and abandoned payments leave no orphaned "paid" rows
- Receipt email sends from an authenticated domain (SPF, DKIM, DMARC aligned)
- A real card, on a real phone, on mobile data — the last test before launch
Questions people ask about this
- Should I use Stripe Checkout or build my own payment form?
- Use hosted Checkout unless you have a specific reason not to. It handles 3D Secure, wallets, local payment methods and PCI scope for you. Custom Elements are for when the checkout UX is genuinely part of your product, and they move PCI burden onto you.
- How do I test webhooks locally?
- The Stripe CLI forwards live test events to your local endpoint. Testing webhooks by clicking "resend" in the dashboard is fine for a smoke test, but it will not exercise the ordering and timing bugs that appear under real traffic.
- Can Stripe handle subscriptions and one-off orders in the same store?
- Yes, and the trap is in the events: subscriptions fulfil on invoice.paid, one-off orders on checkout.session.completed. Handling only the second silently breaks renewals, which is a bug that appears one month after launch when everyone has stopped looking.
Who wrote this
Anas Bin Masud builds e-commerce sites and does technical SEO for businesses in the UK, Canada and Pakistan — fifteen live client sites, six of them stores taking real payments. The examples in these guides come from those builds and from the audit that rebuilt this site, not from a content brief. More about how I work, or read the case studies.