The recurring revenue setup behind every SaaS — built right.
Don't worry — you won't write any Stripe code by hand. Your AI tool handles all the technical parts. This guide helps you understand the concepts so you can describe what you want.
When a user subscribes to your app, six things happen. Understanding all six is the hardest part — once you get it, the code is straightforward.
User clicks "Subscribe" on your pricing page
Your app creates a Stripe Checkout session and redirects them
User enters card details on Stripe's page (you never see them)
Stripe creates a subscription and sends a webhook to your app
Your app receives the webhook and saves the subscription to your database
User gets redirected back to your app, now with a paid status
In Stripe, every plan you sell has two layers:
Product
The thing being sold. "Pro Plan", "Team Plan", "Enterprise Plan". One product per tier.
Example: "Acme Pro"
Price
A specific amount + interval for that product. One product can have multiple prices (monthly vs annual, USD vs EUR).
Example: $20/month and $200/year
You create products and prices once in the Stripe Dashboard, then reference their IDs in your code. Your AI tool walks you through it.
Most SaaS apps use a 3-tier pricing structure. It's the sweet spot between "not enough choice" and "decision paralysis."
Free
$0
Hook them in. Limited features. Just enough to fall in love.
Pro
$19/mo
The default. Most users land here. Highlight this one in your UI.
Team
$49/mo
For power users and teams. More seats, more limits, priority support.
Pro tip: Make the middle tier look like the obvious winner. Bigger card, "Most popular" badge, slightly different color. ~70% of users will pick the highlighted plan.
Don't build a payment form from scratch. Stripe gives you a hosted page called Checkout that handles cards, validation, 3D Secure, taxes, and more. You just redirect users to it.
ADD STRIPE CHECKOUT
"Add Stripe Checkout to my app for recurring subscriptions. On the pricing page, each tier's "Subscribe" button should send the visitor to Stripe's hosted checkout page for that plan. After a successful sign-up, bring them back to my /success page. Keep my Stripe keys in the safe spot where secrets go, and walk me through where to find each plan's price ID in the Stripe dashboard."
You need to store the link between your users and their Stripe subscriptions. The minimum schema:
subscriptions table
id — row ID
user_id — your user (foreign key to auth)
stripe_customer_id — Stripe's customer ID
stripe_subscription_id — Stripe's subscription ID
status — active / trialing / past_due / canceled
price_id — which plan they're on
current_period_end — when they're paid through
cancel_at_period_end — true if they canceled
You don't fill this in by hand. The Stripe webhook does it for you.
Once a user is subscribed, they'll want to change plans. Stripe's Customer Portal handles all of this for you — you just point users to it.
Upgrade
Change to a higher-priced plan. Stripe prorates: charges the difference immediately for the rest of the period.
Downgrade
Change to a lower-priced plan. Usually applied at the end of the current billing period — they keep the higher plan until then.
Cancel
User keeps access until current_period_end, then status changes to canceled. Stripe sends a webhook so your app knows to revoke access.
Reactivate
If they canceled but the period hasn't ended yet, they can undo it. Set cancel_at_period_end back to false.
ADD CUSTOMER PORTAL
"In my /settings/billing page, add a "Manage subscription" button. Clicking it should call /api/portal which creates a Stripe Customer Portal session for the current user's stripe_customer_id, then redirects them to it. The portal lets them view invoices, update their card, change plan, or cancel. After they're done, Stripe redirects them back to /settings/billing."
User pays but app doesn't mark them as Pro
Webhook isn't configured or signature verification is failing. Check Stripe Dashboard → Developers → Webhooks for delivery attempts.
Test card number doesn't work
You're in live mode instead of test mode. Use 4242 4242 4242 4242 only with test mode keys.
Subscription status is "past_due" — user is locked out
Their card was declined. Stripe retries automatically for 3-4 weeks. Show them a banner asking them to update their card.
User canceled but still has access
That's correct! They keep access until current_period_end. Show them "Your subscription ends on [date]".
You built a free SaaS that's getting users. Time to charge. You want a Free / Pro / Team setup with monthly and annual options, a 14-day free trial on Pro, and a way for users to manage their subscription themselves. The full setup takes about an hour with the right prompts.
Build this with AI
"Add Stripe subscriptions to my SaaS. Set up three plans: Free ($0), Pro ($19/mo or $190/year, 14-day free trial), and Team ($49/mo or $490/year, no trial). Create the products and prices in Stripe (test mode). Build a /pricing page with all three tiers, a Checkout flow, a webhook handler at /api/webhooks/stripe that updates my subscriptions table on every relevant event, and a /settings/billing page with a Customer Portal button. Lock the Pro features behind an isPro() check that reads from the subscriptions table."
ADD A FREE TRIAL
"Update my Pro plan to include a 14-day free trial. When users subscribe, they shouldn't be charged for 14 days. Email them on day 12 reminding them the trial is ending. They can cancel anytime during the trial."
ANNUAL DISCOUNT
"Add annual pricing to my Pro and Team plans. Annual should cost ~10x monthly (effectively 2 months free). Add a toggle on my pricing page to switch between Monthly and Annual. Highlight the annual savings."
REFERRAL DISCOUNTS
"Add a referral system: when an existing user refers a friend who subscribes, the referrer gets 20% off their next bill. Use Stripe coupons. Track referrals in a referrals table."