What is wired · 3 min
Payments
Stripe Checkout, an idempotent webhook, and why the price never comes from the browser.
Money moves in two places and nowhere else: a route that opens a Checkout session, and a webhook that decides a sale happened.
Opening checkout
src/app/api/checkout/route.ts takes a plan id. That is all it takes.
const { data: plan } = await admin
.from("plans").select("*").eq("id", planId).single();
const session = await stripe().checkout.sessions.create({
mode: "payment",
line_items: [{ quantity: 1, price_data: {
currency: plan.currency,
unit_amount: plan.price_cents,
tax_behavior: "exclusive",
product_data: { name: plan.name, tax_code: "txcd_10000000" },
}}],
automatic_tax: { enabled: true },
metadata: { plan_id: plan.id, user_id: user?.id ?? "" },
success_url: `${origin}/app?welcome=1`,
cancel_url: `${origin}/#pricing`,
});The price is read from the database. It is never taken from the request body,
because a posted amount is a posted discount. If your checkout route accepts
amount, someone will eventually send it a one.
The webhook is the only truth
checkout.session.completed is where an order becomes real. Three rules make
it safe to run twice, which matters because Stripe retries and sometimes
delivers the same event more than once:
Verify the signature first
An unsigned POST to that URL is someone else pretending to be Stripe. Reject it with a 400 before you read a single field.
Key the row on the session id
upsert on stripe_session_id, which is unique. A replay updates the row it
already wrote instead of creating a second paid order.
Check before you issue
Look for an existing licence or entitlement on that order and return early if there is one. Otherwise a retry sends a second welcome email, which is how customers find out your webhook is broken.
Refunds
Enable charge.refunded on the endpoint and unwind everything the sale
created: mark the order refunded, revoke the entitlement, void any affiliate
commission. If your terms say a refund cancels something, something has to
actually cancel it.
Tax
For a digital product sold from the EU, turn on Stripe Tax and add a registration for your own country at minimum. Two settings decide what the buyer sees:
tax_code: "txcd_10000000"marks the line as a general electronic service. Without it Stripe cannot pick a rate and charges nothing.tax_behavior: "exclusive"means your figure is excluding tax and tax is added on top.inclusivemeans your figure already contains it.
Testing
Card 4242 4242 4242 4242, any future expiry, any CVC. To exercise the webhook
locally, forward events rather than trying to guess the payload:
stripe listen --forward-to localhost:3000/api/webhooks/stripeIt prints a whsec_... for the session. Put that in .env while you test.
Something wrong or missing on this page? Tell us.