What you learn
- Embedded versus hosted Stripe Checkout and why you never handle card data yourself
- The products-and-prices model and subscriptions with monthly versus yearly billing
- Test mode with the 4242 test card, and the clean path to going to production
Summary
Auth, data and secrets are in place. Now your product can make money. Stripe is the standard for taking payments, and the headline benefit is that you never touch a raw credit card number - Stripe collects it, so your compliance burden shrinks from terrifying to manageable. This lesson gets you to a working subscription: the two flavours of Checkout, how products and prices model your pricing, how subscriptions handle recurring billing including the monthly-versus-yearly toggle your pricing page needs, and the test-mode discipline that lets you build and verify the whole thing without moving a cent of real money.
What you will learn
You will learn the difference between hosted and embedded Checkout and when to use each, how Stripe's products-and-prices model maps to your pricing tiers, how subscriptions handle recurring monthly and yearly billing, how to build entirely in test mode using Stripe's test cards, and the clean checklist for flipping to production once everything works.
Prerequisites
A working app with auth and data, and the secrets discipline from the previous lesson, because Stripe keys are among the most sensitive you will ever hold - a leaked live key is a direct line to your money. You should also be comfortable that test and production are separate worlds, exactly as with Clerk.
An API is a way for two programs to talk to each other. Learn what an API is, how it works, and why it matters for building with AI.
A .env file stores secrets like API keys outside your code so they never get published. Learn what it is, how it works and how to keep it safe.
Tokens are the chunks of text AI models read and are billed in. Learn what a token is, why it matters for cost, and how it differs from a password token.
The problem
Beginners assume taking payments means building a credit-card form and storing card numbers. That path is a nightmare: handling raw cards puts you under the full weight of PCI compliance, and a mistake exposes you to fraud and legal liability you are not equipped to carry. So people either avoid charging at all, leaving money on the table, or they build something unsafe. Stripe exists precisely so you never store a card number. You hand the payment step to Stripe, it collects the card on its own secure infrastructure, and it tells you the result. Your job shrinks to "set up products and react to what Stripe tells you", which is completely achievable.
Hosted versus embedded Checkout
Stripe Checkout is a prebuilt, secure payment page that Stripe maintains, so you get a polished, PCI-compliant payment flow without building a form. It comes in two flavours. Hosted Checkout redirects the customer to a Stripe-hosted page (checkout.stripe.com), they pay, and Stripe redirects them back to your success URL - simplest to set up, fully maintained by Stripe. Embedded Checkout renders the same secure payment experience inside your own page, so the customer never leaves your app, which can improve conversion and feels more native. Both are equally secure because Stripe still handles the card data either way; the difference is purely whether the payment happens on Stripe's page or embedded in yours. Start with hosted to get working fastest, then move to embedded for a smoother experience (Part 2 covers embedded in depth).
- Hosted Checkout: redirect to a Stripe page, pay, redirect back. Fastest to ship, zero card handling.
- Embedded Checkout: the same secure flow rendered inside your own app. Better conversion, more polish.
- Both are equally secure - Stripe handles the card in both cases. The choice is about user experience.
- You never see or store a card number with either option.
Products and prices
Stripe models your pricing with two linked objects: a product and one or more prices. A product is the thing you sell ("Pro plan"). A price is a specific way to pay for it ("20 USD per month" or "200 USD per year"). One product can have several prices - which is exactly how you build a pricing page with a monthly/yearly toggle: the same Pro product has a monthly price and a yearly price, and your toggle switches which price the Checkout uses. You create products and prices in the Stripe dashboard (or via the API), and each price gets an ID like price_xxx that you reference when starting a Checkout. Keeping prices as separate objects, rather than hard-coding amounts in your code, means you can change pricing in the dashboard without redeploying.
- Product: what you sell (the "Pro plan").
- Price: a way to pay for it (monthly vs yearly are two prices on the same product).
- A monthly/yearly toggle is just choosing between two price IDs at checkout time.
- Reference prices by their price_xxx ID; change amounts in the dashboard without touching code.
Subscriptions: monthly and yearly
A subscription is a price billed on a recurring schedule. When a customer checks out with a recurring price, Stripe creates a subscription and charges their card automatically every cycle - monthly or yearly - handling renewals, failed-payment retries and cancellations for you. This is the engine of SaaS revenue, and Stripe runs the billing cycle so you do not have to. For the pricing page this course's house style calls for, you model two recurring prices per tier (a monthly price and a yearly price), default the page to showing the yearly price per month, and let the toggle switch to monthly. Here is the shape of starting a subscription Checkout - the exact API surface evolves, so follow Stripe's current docs, but the structure stays stable.
// Backend only (uses the SECRET key). Creates a Checkout Session for a
// recurring price. The price ID decides monthly vs yearly.
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function createSubscriptionCheckout(opts: {
priceId: string // price_xxx for the chosen monthly OR yearly plan
customerEmail: string
}) {
return stripe.checkout.sessions.create({
mode: 'subscription', // recurring billing, not a one-off payment
line_items: [{ price: opts.priceId, quantity: 1 }],
customer_email: opts.customerEmail,
success_url: 'https://app.yoursite.com/welcome?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://app.yoursite.com/pricing',
})
}This runs on the backend with your secret key, never in the browser. The session it returns is what you redirect the customer to (hosted) or render in your page (embedded). Either way, the card is collected by Stripe.
Test mode and test cards
Stripe gives you two completely separate modes - test and live - each with its own keys and its own data. In test mode you build and verify the entire payment flow without moving any real money, using special test card numbers that Stripe recognises. The famous one is 4242 4242 4242 4242: a Visa that always succeeds. Stripe also provides cards that simulate failures, declines and authentication challenges, so you can test the unhappy paths too. Use any future expiry date, any three-digit CVC, and any postal code. Do all your development here. Your test keys start with sk_test and pk_test, mirroring the same prefix convention you saw with Clerk.
- 4242 4242 4242 4242 - succeeds every time. Your default for happy-path testing.
- 4000 0000 0000 0002 - always declined, so you can test failure handling.
- 4000 0025 0000 3155 - requires authentication (3D Secure), so you can test that flow.
- Use any future expiry, any CVC, any postal code. Real cards do nothing in test mode.
Test-mode data (customers, subscriptions, payments) is entirely separate from live data and never converts over. When you go live, you start with a clean live dashboard. That separation is a feature: you can experiment freely without ever fearing a real charge.
Going to production
Once everything works in test mode, going live is a short, careful checklist rather than a rebuild. You activate your Stripe account (Stripe needs your business and bank details to pay you out), recreate your products and prices in live mode if you only made them in test, swap your test keys for live keys in your deployed environment, and update any price IDs your code references to the live ones. Then you run one real, small transaction yourself to confirm the whole loop works end to end with live keys. Crucially, you keep test and live strictly separate - never paste a live key into a development env file. Part 2 adds the webhook setup that production billing genuinely needs, so treat going live as "ready to take payments" rather than "fully production-hardened" until you have done Part 2.
- Activate your Stripe account with real business and bank details so payouts work.
- Recreate products and prices in live mode; note the new live price_xxx IDs.
- Swap sk_test / pk_test for sk_live / pk_live in your deployed environment only.
- Do one small real transaction yourself to confirm the live loop, then refund it.
- Do not call it done until Part 2 wires up webhooks - they are how your app learns what actually happened.
Typical mistakes
The frequent ones: putting the Stripe secret key in frontend code where every visitor can read it (it belongs on the backend only); hard-coding price amounts in code instead of referencing price IDs, so a price change means a redeploy; forgetting that test and live data never merge, then panicking that production looks empty; and the big one this course warns about repeatedly - thinking Checkout alone is enough. Without webhooks (Part 2), your app is guessing whether a payment really succeeded. Build in test mode, keep the secret key on the backend, and treat Part 2 as mandatory, not optional.
Business ROI
Payments are the moment your product stops being a cost and starts being a business. Stripe lets a solo founder accept money from anywhere in the world, on a recurring schedule, without a payments team or PCI auditors, in an afternoon. Subscriptions specifically turn one-time effort into recurring revenue, which is the entire financial appeal of SaaS - you build once and earn monthly. And the monthly-versus-yearly toggle is not cosmetic: yearly plans improve cash flow and reduce churn, which is why this course's house style defaults the pricing page to the yearly price. Getting paid well is as much a product decision as a technical one.
Checklist
You are ready for Part 2 when all of these are true in test mode.
- You can explain why you never handle raw card data and what Checkout does for you.
- You created a product with both a monthly and a yearly price.
- A subscription Checkout works in test mode with card 4242 4242 4242 4242.
- The Stripe secret key lives only on the backend, in a gitignored env file.
Resources
The Stripe docs and the test-cards reference are essential and always current - keep them open while you build. The Stripe CLI (used heavily in Part 2) is worth installing now. Your pricing page should follow this course's house style: yearly price shown per month by default, with a monthly/yearly toggle. Next, Part 2 makes billing actually reliable with webhooks.
Your task
In test mode, create a Pro product with a monthly and a yearly price, then build a subscription Checkout that a logged-in user can complete with the 4242 test card. Confirm the subscription appears in your Stripe test dashboard. Then try the declined card (4000 0000 0000 0002) and notice how your app handles the failure - that unhappy-path awareness is what separates a real billing flow from a demo.
Next lesson
Checkout is only half the story. The next lesson handles the parts that make billing trustworthy: webhooks (and why polling is the wrong instinct), signature verification, idempotency, proration when a customer upgrades mid-cycle, coupons and promotion codes, and embedded checkout. These are the details that separate a toy from a real billing system.

Comments
Loading comments.
Post a comment