Stripe Integration Tutorial
Stripe is the default payment solution for many SaaS products. It can handle cards, subscriptions, invoices, taxes, receipts, customer portal, and webhooks. It works from early products to more complex monetization.
But Stripe integration is not just “add a payment button.” A reliable Stripe integration must handle product pricing, Checkout Session, Customer, webhooks, fulfillment, subscription status, payment failure, and customer self-service.
This article follows Stripe's official documentation:
- Checkout quickstarts
- Checkout fulfillment
- Checkout Sessions API
- Using webhooks with subscriptions
- Customer portal
First Decide What You Sell
Before integrating Stripe, define your business model.
Are you selling a one-time product or a SaaS subscription? One price or multiple plans? Monthly or yearly billing? Free trial? Team seats? Coupon codes?
Stripe usually uses Product and Price. Product is what you sell. Price defines amount, currency, and billing interval.
Example:
Product: Pro Plan
Price: $19 / month
Price: $190 / year
Do not hardcode all pricing only in your code. A safer approach is to configure products and prices in Stripe Dashboard, then store the corresponding price_id in your system.
Prefer Stripe Checkout Early
For early SaaS products, Stripe Checkout is usually the best starting point instead of building a custom payment form from scratch.
Stripe Checkout is a Stripe-hosted payment page. Your server creates a Checkout Session, your frontend redirects the user to the Stripe URL, and the user completes payment on Stripe.
This reduces sensitive payment handling and makes it easier to support payment methods, tax, coupons, and subscriptions.
Only consider embedded or custom flows when you have strong checkout customization needs.
Configure Keys and Environments
Stripe has test and live environments, and different kinds of keys.
Common environment variables:
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_PRICE_PRO_MONTHLY=price_xxx
STRIPE_PRICE_PRO_YEARLY=price_xxx
SECRET_KEY must stay server-side. The frontend must never access the secret key.
Test and live keys, price IDs, and webhook secrets must be separate. Many payment bugs come from mixing test keys, live prices, and wrong webhook secrets.
Create a Checkout Session
When the user clicks “Buy” or “Upgrade,” the frontend should call your server. Your server creates a Checkout Session based on user identity, plan, and price.
Stripe's API docs describe Checkout Session as the user's payment or subscription session. After creating it, you redirect the user to the Session URL.
Core parameters usually include:
mode: payment or subscription
line_items: price and quantity
success_url: where users go after payment
cancel_url: where users go after cancellation
customer or customer_email: related customer
metadata: your local business context
For SaaS subscriptions, use mode=subscription; for one-time payments, use mode=payment.
metadata Matters
Stripe is the payment system. Your application is the business system. They need to be connected.
When creating a Checkout Session, store local business context in metadata:
user_id
plan
team_id
order_id
When a webhook arrives, this helps you identify which user, team, or order the payment belongs to.
Do not put sensitive or large data in metadata. It should contain small fields for linking and debugging.
Do Not Rely Only on success_url
Many beginners grant access after the user lands on success_url. This is unsafe.
A successful redirect does not guarantee reliable payment processing. The user may close the page, the network may fail, the redirect may not happen, or someone may fake a visit to the success page.
Stripe's fulfillment docs recommend using webhooks for automatic fulfillment so every payment is processed. The success page can improve user experience, but it must not be the only source of truth.
The correct flow is: after payment completes, Stripe sends a webhook; your server verifies the event, confirms payment status, and grants access.
Configure Webhooks
Webhooks are the core of Stripe payment integration.
Common events:
checkout.session.completed
invoice.paid
invoice.payment_failed
customer.subscription.updated
customer.subscription.deleted
For one-time payments, checkout.session.completed is often central. For subscriptions, you also need renewal success, payment failure, cancellation, and plan change events.
The webhook endpoint should verify signatures, store events, handle idempotency, and log results. Do not grant the same order twice, and do not duplicate user entitlements because of repeated events.
Make Fulfillment Idempotent
Stripe's fulfillment docs warn that the same Checkout Session fulfillment function may be called multiple times, even concurrently.
Your fulfillment logic must be idempotent.
Use checkout_session_id or local order_id as a unique key. If the order has already been fulfilled, return success. If not, grant access, write subscription records, and send emails.
In payment systems, duplicate processing can be more dangerous than failure. Duplicate access, shipping, or discounts can cause real loss.
Store Stripe Customer and Subscription
For subscription SaaS, store important Stripe IDs locally:
stripe_customer_id
stripe_subscription_id
stripe_price_id
subscription_status
current_period_end
cancel_at_period_end
These fields help you determine whether the user has access, when access expires, whether cancellation is scheduled, and whether payment method update is needed.
Do not call Stripe on every page view to decide access. Use webhooks to sync key state into your local database, then check access locally.
Handle Payment Failure
Subscriptions are not only about first payment.
Renewals may fail, cards may expire, users may cancel, and invoices may retry. Stripe sends webhooks for these changes.
At minimum, handle:
invoice.paid: renewal succeeded, extend access
invoice.payment_failed: payment failed, ask user to update payment method
customer.subscription.deleted: subscription ended, revoke or downgrade access
customer.subscription.updated: plan or status changed
If you only handle the first Checkout success, your subscription state will drift away from real payment state.
Add Customer Portal
Stripe Customer Portal lets users manage payment methods, invoices, subscription cancellation, upgrades, and downgrades.
Stripe's docs describe it as a self-service place where customers manage payment details, invoices, and subscriptions.
For early SaaS products, Customer Portal is faster and more reliable than building a full billing management dashboard yourself.
Create a portal session on your server, then redirect the user to Stripe's portal URL.
Test Locally With Stripe CLI
Use Stripe CLI to forward webhook events to your local service.
At minimum, test:
One-time payment success
Subscription creation success
Renewal success
Payment failure
Subscription cancellation
Duplicate webhook delivery
Invalid webhook signature
Do not only test “payment success.” Failure, cancellation, duplicates, and delayed events are where payment systems break.
A Stripe Integration Flow
Use this flow:
1. Create Product and Price in Stripe
2. Save price_id in your config
3. User clicks buy
4. Server creates Checkout Session
5. Frontend redirects to Stripe Checkout
6. User completes payment
7. Stripe sends webhook
8. Server verifies signature and handles idempotently
9. Update local order or subscription state
10. Grant user access
11. Provide Customer Portal for subscription management
Steps 7 to 10 are the most important part.
Final Thoughts
The hard part of Stripe integration is not the payment button. It is synchronizing business state after payment.
A reliable SaaS payment system connects Checkout, webhooks, idempotent fulfillment, local subscription state, payment failure handling, and Customer Portal.
Next, we continue with another payment option: PayPal Integration Pitfalls.
评论
0分享你对这一章的想法