Stripe Payments in an AI App: A Weekend Setup Guide
· 6 min read · Guides
Yes, you can add Stripe payments to an AI-built app in a weekend. Use Stripe's hosted Checkout page, add one webhook that unlocks the purchase, and test everything with fake cards before you touch live keys.
The AI will write most of the code. Your job is to give it a small, specific task and to check the three spots where payment code goes wrong: secret keys, webhook verification, and what happens after someone pays.
Saturday morning: pick the simplest setup
Stripe has several ways to take money. For a first launch, you want the one with the least code. Stripe's own quickstarts list a Stripe-hosted checkout page where customers are redirected to a payment page and then sent back to your site. They also list an embedded checkout and a custom checkout page built with Stripe Elements.
Start with the hosted page. You write less code, there are fewer ways to break it, and you can move to embedded later if you want the form on your own site.
Under the hood, this runs on Checkout Sessions. Stripe describes a Session as one customer's attempt to pay for a one-time purchase or subscription. You create it on your server and redirect the customer to its URL. Stripe also recommends creating a new Session each time a customer tries to pay, so don't cache them.
Stripe also recommends the Checkout Sessions API for most integrations. It covers tax, discounts, subscriptions and shipping, so you write less custom code.
Before you open your editor
- Create a Stripe account and stay in test mode.
- Create a product and a price in the dashboard. Copy the price ID (it starts with
price_). - Copy your test secret key (
sk_test_...) into your environment variables. Never put it in frontend code or commit it. - Decide what "paid" means in your app: a database flag, a role, a credit balance, a download link.
That last one matters most. If you can't say what changes when someone pays, the AI will invent something.
Saturday afternoon: the checkout button
You need one server route that creates a Session and returns its URL, and one button that calls it. Here is a prompt you can paste into Cursor, Claude Code or whatever you use:
Add Stripe Checkout to this app using the official stripe npm package.
1. Create a server-side route POST /api/checkout that:
- requires a logged-in user
- creates a Checkout Session with mode 'payment'
- uses the price ID from the env var STRIPE_PRICE_ID
- sets success_url to /thanks?session_id={CHECKOUT_SESSION_ID}
- sets cancel_url to /pricing
- puts our internal user id in metadata.user_id
- returns the session url
2. Add a Buy button on /pricing that calls the route and redirects to the url.
3. Read STRIPE_SECRET_KEY from env only on the server. Never expose it to the client.
4. Do not mark anything as paid yet. That happens in the webhook.
Show me the diff before changing any existing files.Two details are worth knowing. Stripe's docs show a {CHECKOUT_SESSION_ID} placeholder you can put in the success URL, so you can look up the Session when the customer lands back on your site. The docs also show storing your own reference in metadata, such as an order ID. That's how you'll know who paid.
Run it. Click Buy. You should land on a Stripe-hosted page. If you get an error, paste the full message back to the AI along with the route file, not just "it doesn't work".
Saturday evening: pay with a fake card
In test mode you can't use a real card, and fake cards don't work in live mode. Each Stripe mode has its own keys and data. Use any future expiry date, any CVC and any postal code.
| Card number | What it does |
|---|---|
4242 4242 4242 4242 | Succeeds |
4000 0000 0000 0002 | Declines |
4000 0025 0000 3155 | Asks for 3D Secure authentication |
Try all three. The decline and 3D Secure cards are where hand-rolled flows tend to fall over, and Checkout handles most of it for you. Stripe keeps the full list in its testing docs.
Sunday morning: the webhook (don't skip this)
The redirect to your success page is not proof of payment. People close tabs, and anyone can type a URL. The reliable signal is a webhook, which is Stripe calling your server to say a payment happened.
Stripe's fulfillment guide shows the pattern: verify the signature, then act on checkout.session.completed. It also handles checkout.session.async_payment_succeeded, which covers payment methods that confirm later.
Signature verification is the part AI tools like to skip or get subtly wrong. Stripe's webhook docs say it signs every event, and that checking the signature needs the raw request body. If your framework parses the JSON first, verification will fail. Your endpoint is a public URL, so without this check anyone could send it a fake "payment succeeded" event.
Add a Stripe webhook endpoint at POST /api/webhooks/stripe.
- Read the RAW request body (not parsed JSON).
- Verify it with stripe.webhooks.constructEvent using the
stripe-signature header and STRIPE_WEBHOOK_SECRET.
- If verification fails, return 400 and do nothing else.
- On checkout.session.completed and
checkout.session.async_payment_succeeded, look up the user from
session.metadata.user_id and mark them as paid.
- Make it idempotent: if the same event arrives twice, do not
grant access twice. Store the event id or session id.
- Return 200 quickly.The idempotency line is there because webhooks can be delivered more than once, and you don't want two credit top-ups for one payment.
Test it locally
Install the Stripe CLI and forward events to your dev server. The docs use this command, with your own port and path:
stripe listen --forward-to localhost:4242/webhookThe CLI prints a signing secret that starts with whsec_. Put that in STRIPE_WEBHOOK_SECRET for local work. Then make a test purchase and watch your app flip to "paid". A deployed endpoint has its own, different secret in the Stripe dashboard. Mixing these up is a common reason verification fails in production.
Sunday afternoon: check what the AI wrote
Read the diff with these questions in mind:
- Is the secret key server-only? Search the repo for
sk_and for anyNEXT_PUBLIC_or equivalent variable holding a secret. - Does the price come from the server? If the browser sends an amount, a user can change it. The server should pick the price ID.
- Is access granted only in the webhook? Not on the success page.
- Is the user tied to the payment? You should be able to trace a payment to a user through metadata or a stored customer ID.
- What happens on failure? A declined card or an abandoned checkout should leave the user unpaid, with a clear path to try again.
Know what it costs
Stripe has no monthly fee on its standard plan. A third-party fee guide puts the standard US rate at 2.9% plus 30 cents per successful online card payment, with extras for things like international cards. Rates differ by country and change, so check Stripe's pricing page for your account before you set your own prices. On a small price, the fixed 30 cents takes a bigger share.
Going live
- Finish activating your Stripe account so you can take live payments.
- Swap in the live secret key and live price ID in your production environment variables only.
- Add a live webhook endpoint in the dashboard and copy its new signing secret into production.
- Do one real purchase with your own card, then refund it from the dashboard.
- Add a refund and contact page. Customers will ask.
A weekend is enough for one product with one price. Subscriptions, tax and trials can wait for next weekend.
FAQ
Can Lovable, Bolt or v0 add Stripe for me?
Usually they can generate the code, but check what they built. The checks above apply no matter which tool wrote it: server-only secret key, price chosen on the server, verified webhook, access granted only after the webhook. If the tool can't run server code, you'll need a backend or serverless function for the Session and webhook routes.
Do I need a webhook if I only sell one thing?
Yes. Without it, you're trusting the browser's redirect, which is easy to fake and easy to miss. The webhook is a small file and it's the part that makes payments reliable.
Should I use Payment Links instead of writing code?
If you only need a "buy now" link and can fulfill orders by hand, Payment Links can work. If your app needs to unlock features automatically for a specific logged-in user, Checkout Sessions with metadata and a webhook give you that connection.
Cover photo: CardMapr.nl on Unsplash