How to Integrate Stripe Into an E-Commerce Store (The Right Way)

Stripe is the right choice for custom e-commerce payment processing. The documentation is excellent, the API is well-designed, and the feature set — cards, Apple Pay, Google Pay, SEPA, BACS, subscriptions, invoicing — covers almost every e-commerce use case. But "just use Stripe" glosses over a lot of implementation decisions that have real consequences for reliability, security, and user experience.
Here's what a proper Stripe integration for e-commerce actually looks like.
Checkout vs Elements vs Payment Intents: Which to Use
Stripe offers multiple integration paths and picking the right one upfront saves significant rework.
Stripe Checkout — A hosted checkout page Stripe manages entirely. You redirect customers to a Stripe URL; they pay; Stripe redirects them back. Implementation is fast — a handful of API calls and you're done. The trade-off is control: you can customise colours and logo, but the layout, form fields, and UX are Stripe's. No custom multi-step flows, no embedding inside your own checkout shell.
Use Stripe Checkout when you need something working fast, your checkout is standard, and you're not optimising for brand consistency in the checkout experience.
Stripe Elements — Embeddable UI components — specifically PaymentElement — that you drop into your own checkout page. The card input, expiry, CVC are rendered in an iframe by Stripe (so card data never touches your server), but everything around them — layout, step flow, order summary, address fields — is yours to build. This is the right level for most custom e-commerce builds.
Payment Intents API — The underlying API that both Checkout and Elements use. You create a PaymentIntent server-side (specifying amount, currency, and metadata), pass the client_secret to the frontend, and confirm it with the payment details. Understanding the Payment Intents flow is essential even if you're using Elements, because it's what you're working with when handling webhooks, retries, and edge cases.
For most custom builds: use Payment Intents + Payment Element. You get design control without building your own card capture (which would take you out of SAQ A compliance).
Webhooks: The Part Most Guides Skip
A webhook is an HTTP request Stripe sends to your server when something happens — payment succeeds, payment fails, subscription renews, refund processed. Getting this right is the most important part of a reliable Stripe integration.
Never confirm an order based on redirect alone. Stripe redirects the customer to your success page, but redirects can fail — the customer closes the browser, loses connection, or goes back. If you're confirming orders on redirect, you'll have payments that cleared but no order was created, or orders created without payment.
The correct pattern:
- Create a PaymentIntent server-side when the customer reaches checkout
- Customer completes payment on the frontend
- Stripe sends a payment_intent.succeeded webhook to your server
- Your webhook handler creates the order, triggers fulfilment, and sends confirmation email
- The frontend redirect shows a success page (which can be optimistic — the webhook is what matters)
Webhook signature verification: Every incoming webhook should be verified using stripe.webhooks.constructEvent() with your webhook signing secret. This prevents spoofed requests. Non-negotiable.
Idempotency in webhook handling: Stripe may deliver the same webhook more than once. Your handler must be idempotent — processing the same event twice should not create duplicate orders or send duplicate emails. Track processed event IDs in your database and check before acting.
Idempotency keys on API calls: Any Stripe API call that creates a resource (create PaymentIntent, create Customer, create Subscription) should include an idempotency key — typically a UUID you generate for that operation. If the network fails and you retry, Stripe returns the original response rather than creating a duplicate.
Handling Declined Cards Gracefully
Payment declines are not rare — industry averages suggest 5–15% of payment attempts fail on the first try. How you handle them affects both conversion and customer trust.
Stripe returns detailed decline codes: insufficient_funds, card_declined, expired_card, incorrect_cvc, do_not_honor (generic bank decline). Surface the right message to the customer — "Your card was declined by your bank" is more useful than "Payment failed."
For soft declines (where retrying might succeed), don't immediately show failure. Stripe's Payment Element handles some retry logic automatically when you use it with the appropriate confirmation settings. For hard declines, show a clear error and give the customer an obvious path to try a different card.
Don't clear the form on decline. Making a customer re-enter their card number after a decline is a significant source of cart abandonment.
Apple Pay and Google Pay
Apple Pay and Google Pay are not separate integrations — if you're using Stripe's Payment Element, they appear automatically for eligible customers on eligible browsers and devices. The Payment Element detects the browser environment and shows the wallet button when available.
What you need to do:
- Register your domain with Stripe for Apple Pay (adds a verification file to your .well-known/ directory)
- Ensure your site is served over HTTPS
- Set payment_method_types: ['card'] (or 'automatic' to let Stripe optimise) when creating the PaymentIntent
That's it. The Payment Element handles the rest. Google Pay works on Chrome on Android and desktop; Apple Pay works on Safari on iOS and macOS. Together these typically represent 20–35% of payment method selections on modern e-commerce stores, and they have meaningfully higher conversion rates than manual card entry because they skip the card number input entirely.
Subscriptions vs One-Time Payments
Subscription billing uses Stripe Billing (formerly Stripe Subscriptions), which is a distinct layer on top of the core payments API. Key differences:
For one-time payments, you create a PaymentIntent and confirm it. For subscriptions, you create a Customer, attach a payment method, then create a Subscription. Stripe handles the recurring billing cycle, sending invoices, attempting payment, and managing subscription states (active, past_due, canceled, etc.).
Don't try to implement recurring billing yourself with scheduled PaymentIntents. Stripe Billing handles dunning, tax calculation, proration on plan changes, and trial periods correctly — rebuilding this from scratch is months of work and introduces significant compliance and reliability risk.
PCI Compliance Reality
PCI DSS is the payment card industry's security standard. The compliance level you need depends on how you handle card data.
Using Stripe Elements or Checkout: card data never touches your server. Stripe renders the payment fields in an iframe, and only a token passes to your backend. This puts you at SAQ A — the lightest compliance tier. You complete a self-assessment questionnaire annually; no penetration testing required.
If you were to accept raw card numbers (building your own card form, storing card data) you'd be at SAQ D, which requires quarterly network scans and annual penetration testing. Don't do this. There is no good reason to handle raw card data when Stripe exists.
Test Mode Checklist Before Go-Live
Before switching from Stripe test mode to live mode, verify:
- Webhook endpoint is registered in the Stripe dashboard with the correct events selected
- Webhook signature verification is implemented and tested
- Idempotency keys are used on all create operations
- Declined card scenarios tested with Stripe's test card numbers (4000000000000002, 4000000000009995, etc.)
- Apple Pay domain verification completed
- Order creation is driven by webhook, not by redirect
- Stripe publishable key and secret key environment variables are set for production (not test keys)
- Webhook signing secret is set for production endpoint (separate from test endpoint secret)
- Email receipts tested end-to-end
- Refund flow tested and confirmed working
- Metadata on PaymentIntents includes enough information to identify the order (order ID, customer ID, line items) for debugging
Payment integration is a significant line item in any custom e-commerce build. For context on how Stripe integration fits into overall development costs, see our cost breakdown. For the full picture of how Stripe fits into a custom e-commerce architecture, see our complete custom e-commerce development guide.
We've built payment integrations for a range of e-commerce stores and B2B platforms. If you're planning a custom build and want to talk through the payment architecture, get in touch or see what's included in our e-commerce packages.
Related Posts

Custom E-Commerce Development: The Complete Guide
Everything you need to know about building a custom e-commerce store — Shopify vs custom, costs, architecture, payments, and what separates a good store from a great one.

How to Add Subscription Products to Your E-Commerce Store
Subscription e-commerce is more complex than standard checkout. Here's how recurring billing, free trials, pauses, and cancellations actually work — and what to build.