Payments Integration Guide
Charging a card is the easy part; keeping your database honest about what the processor believes is the actual work. Payments integrations live or die on webhooks, idempotency and reconciliation β not the checkout button.
Stripe is the default for developer-run apps, with Paddle and Lemon Squeezy as merchant-of-record alternatives that handle sales tax for you, Razorpay strong in India, and Adyen at enterprise volume. The patterns below apply to all of them.
Pick your integration surface
Start with Stripe Checkout β a hosted page that handles cards, wallets, SCA and receipts with the lightest PCI scope (SAQ A). Move to the embedded Payment Element when the form must live inside your UI, and drop to raw PaymentIntents only for genuinely custom flows. Card numbers should never touch your servers; tokenization is the processor's job.
Webhooks are the source of truth
The success redirect is a UX hint, not a fact β users close tabs and networks fail. Grant access only when the webhook says so:
- Verify signatures with your webhook signing secret before parsing
- Return 2xx fast and process the event asynchronously
- Store processed event IDs to dedupe retried deliveries
- Send idempotency keys on API calls that create charges
Test locally with stripe listen --forward-to localhost using the Stripe CLI.
Subscriptions without drift
Let Stripe Billing own the lifecycle and mirror only a thin projection into your database: customer ID, subscription ID, price ID, status and current period end. Update that row exclusively from webhook events like customer.subscription.updated, and treat past_due as a grace state rather than an instant lockout. Point users at the hosted customer portal for card changes and cancellations instead of rebuilding it.
Taxes and merchant of record
Selling globally means VAT, GST and US sales tax. Stripe Tax calculates and collects, but you remain the merchant who registers and files. A merchant of record such as Paddle or Lemon Squeezy becomes the legal seller instead, handling registration and remittance β a higher take rate traded for far less paperwork, which is why small SaaS teams often choose it.
Frequently asked questions
Do I need PCI compliance to use Stripe?
Checkout and Elements keep you in the lightest self-assessment tier (SAQ A) because card data goes straight to Stripe. You still protect API keys and webhook secrets.
Why did my webhook fire twice?
Delivery is at-least-once, so duplicates are normal. Record each event ID and skip any you have already processed.
How do I test payments before going live?
Use test mode with card 4242 4242 4242 4242, forward webhooks locally with the Stripe CLI, and rehearse declines and disputes with Stripe's failure test cards.
What happens when a renewal payment fails?
Stripe retries on a smart schedule and emits invoice.payment_failed events. Keep access during a short grace window and prompt the user to update their card.