Stripe Checkout Flow
Add real Stripe payments with checkout, webhooks, and payment status - not a button that pretends to charge.
The route
7 steps to Done
- 01
Define the payment spec
Lock what is being sold, prices, and payment states before touching Stripe.
Preview prompt + verify gate ▾ Hide ▴
Define the payment spec for this app. Specify: what is being sold (one-time purchase or subscription plans with exact prices and currency), the payment states we must track (pending, paid, failed, canceled), what a user gets when paid, and where payment records are stored. State explicitly that all amounts live on the server and that payment success is only confirmed server-side. Do not implement yet.
- ✓ Products and exact prices are listed
- ✓ One-time vs subscription is decided
- ✓ Payment states are enumerated
- ✓ Server-side confirmation is stated as a rule
- 02
Set up Stripe keys and packages securely
Wire Stripe credentials the right way so keys never leak to the client.
Preview prompt + verify gate ▾ Hide ▴
Set up the Stripe integration foundation. Install the official Stripe server library, put STRIPE_SECRET_KEY (test mode) in backend environment variables only, and expose nothing secret to the frontend. Create a config check that fails loudly at startup if the key is missing. Show me exactly which files changed and confirm the secret key appears in zero frontend files.
- ✓ Secret key is only in backend environment config
- ✓ No secret appears in any frontend file
- ✓ Missing key produces a clear startup error
- 03
Create the checkout session endpoint
Payments start server-side: build the endpoint that creates a real Stripe Checkout session.
Preview prompt + verify gate ▾ Hide ▴
Implement a backend endpoint that creates a Stripe Checkout session. Requirements: the endpoint receives only a product/plan identifier from the client and looks up the amount server-side; it creates a session with success and cancel URLs pointing back to our app; it stores a pending payment record (id, user/session, product, amount, status=pending, created_at) in the database before returning the checkout URL; it returns the session URL for redirect. Reject unknown product identifiers.
- ✓ Amounts are looked up server-side, never sent by the client
- ✓ A pending payment record is created in the database
- ✓ The endpoint returns a live checkout URL
- ✓ Unknown products are rejected with a clear error
- 04
Build the pay button and redirect
Connect the UI to the real session endpoint with proper loading and error states.
Preview prompt + verify gate ▾ Hide ▴
Wire the frontend payment trigger. Requirements: the pay/upgrade button calls our checkout endpoint, shows a loading state while the session is created, redirects the browser to the returned Stripe URL, and shows a visible error message if session creation fails. Do not build any fake success behavior in this step - the user must land on real Stripe checkout.
- ✓ Button shows loading while the session is created
- ✓ Browser lands on checkout.stripe.com
- ✓ Endpoint failure shows a visible UI error
- 05
Confirm payment server-side
The success redirect proves nothing - verify payment status with Stripe before granting anything.
Preview prompt + verify gate ▾ Hide ▴
Implement server-side payment confirmation. Requirements: build the success page to call a backend verification endpoint with the session id; the backend retrieves the session from Stripe and checks payment_status; only then update the payment record to paid and grant the purchased value; handle the cancel URL by marking the record canceled and showing a clear 'payment not completed' state with a retry option. If webhooks are feasible in this environment, also implement the checkout.session.completed webhook as the authoritative confirmation.
- ✓ Success page triggers backend verification, not a local status change
- ✓ Payment record flips to paid only after Stripe confirms
- ✓ Cancel path shows a non-success state with retry
- ✓ Refreshing the success page does not double-grant
- 06
Reflect paid state in the UI
Users must see what they paid for - surface payment status everywhere it matters.
Preview prompt + verify gate ▾ Hide ▴
Surface payment state in the product. Requirements: show the user's plan/purchase status in the UI (badge, settings row, or dashboard banner); gate paid features on the server-verified paid state; show payment history (date, product, amount, status) from our database records; add empty state for no payments and a clear pending state while verification is running.
- ✓ Paid status is visible in the UI after purchase
- ✓ Paid features check the server-verified state
- ✓ Payment history lists real database records
- ✓ Pending and empty states exist
- 07
Test with Stripe test cards
Prove the whole loop with real test-mode payments including failure paths.
Preview prompt + verify gate ▾ Hide ▴
Run an end-to-end payment test in Stripe test mode and report PASS/FAIL with evidence for: successful payment with card 4242 4242 4242 4242; declined payment with card 4000 0000 0000 0002; user cancels on the Stripe page and returns; refresh of the success page (no double grant); and payment record status transitions in the database for each case. Fix every failure and re-run.
- ✓ 4242 test card completes and unlocks paid state
- ✓ Declined card shows a visible failure state
- ✓ Cancel returns cleanly without fake success
- ✓ Database records show correct status transitions