antrix.dev
Ship Your Product · $79

Payments with Stripe

Accept real payments with Stripe. Set up products, checkout sessions, and webhooks for your business.

What it does

Stripehandles your payments — one-time purchases, subscriptions, invoicing. You create products and prices in the dashboard, integrate Checkout Sessions in your app, and Stripe handles the entire payment UI, card processing, receipts, and compliance. You never touch sensitive card data directly.

Checkout Sessionshandle the entire payment UI — card form, validation, error messages, receipts, and compliance. You redirect the user to Stripe, they pay, and Stripe redirects them back. For subscriptions, Stripe manages the recurring billing, dunning (failed payment retries), and cancellation flows automatically. You don't build any of this yourself.

Why we use it

Industry standard. Stripe handles PCI complianceso you don't touch card numbers. Checkout Sessions mean you don't build payment forms. The rate is 2.9% + 30 cents per transaction with no monthly fee. You only pay when you make money. The API is clean, the docs are excellent, and the developer experience is unmatched.

Stripe handles PCI compliance — you never touch card numbers, so you don't need to pass a PCI audit. The alternative (building your own payment form with PaymentIntents) requires more code, more security responsibility, and more edge case handling. Start with Checkout Sessions. Only move to custom forms when you have a specific UX requirement that Checkout can't handle.

Setup checklist

1

Create a Stripe account

Go to stripe.comand sign up. You'll need to complete identity verification, which takes 1–2 business days. Don't wait around for it — you can start building in test mode immediately while verification processes. Test mode is fully functional with its own API keys, products, and data. No real money moves until you explicitly go live.

2

Understand test vs live mode

There's a toggle in the top-right of the dashboard that switches between test and live mode. These are completely separate environments — different API keys, different products, different transaction data. Build everything in test mode first. Test mode keys start with sk_test_ and pk_test_. Live keys start with sk_live_ and pk_live_. Only switch to live when you're ready for real payments.

3

Create your products and prices

Go to Products > Add Productin the dashboard. Give it a name, description, and image. Then add a price — either one-time or recurring (for subscriptions). Each price gets a unique ID that looks like price_xxx. This is what you reference in your code when creating Checkout Sessions.

Create test mode products that mirror exactly what you plan to sell. A product can have multiple prices — different tiers, different billing intervals, different currencies. The dashboard is your source of truth for pricing, not your codebase.

4

Get your API keys

Go to Developers > API Keys. You need two keys. The Secret Key(starts with sk_) is for server-side code only — never expose this in the browser or in any NEXT_PUBLIC_ environment variable. The Publishable Key (starts with pk_) is safe for client-side code.

Copy both test mode keys first. Add them to your .env.local file as STRIPE_SECRET_KEY and STRIPE_PUBLISHABLE_KEY. You'll swap these for live keys later when you go to production.

5

Install the Stripe SDK

Run npm install stripe in your project. Then create a Stripe client file at lib/stripe.tsthat imports Stripe, initializes it with your secret key from environment variables, and exports the client. Use the latest API version. This file is server-side only — never import it in a client component.

6

Implement Checkout Sessions

This is the core integration. Create an API route (e.g., app/api/checkout/route.ts) that creates a Checkout Session. The session needs: line_items (your price IDs and quantities), a success_url where the customer lands after paying, a cancel_url where they go if they bail, and a mode("payment" for one-time, "subscription" for recurring).

Your frontend calls this API route, gets back a session URL, and redirects the user to it. Stripe hosts the entire payment page — card inputs, validation, Apple Pay, Google Pay, all of it. After payment, the customer redirects back to your success URL. You can include {CHECKOUT_SESSION_ID} in the success URL to retrieve session details on the success page.

7

Set up webhooks

Webhooks are how Stripe tells your app that something happened — a payment succeeded, a subscription was canceled, a refund was issued. Go to Developers > Webhooks > Add endpoint. Set the URL to your webhook route: https://yourdomain.com/api/webhook.

Select the events you care about. At minimum: checkout.session.completed and payment_intent.succeeded. For subscriptions, also add customer.subscription.updated and customer.subscription.deleted. After creating the endpoint, Stripe gives you a webhook signing secret (starts with whsec_). Save this as STRIPE_WEBHOOK_SECRET in your environment variables.

8

Handle webhook events

Create an API route at app/api/webhook/route.ts. This route needs to: read the raw request body(not parsed JSON — the signature verification needs the raw bytes), verify the signature using your webhook signing secret, parse the event, and handle it based on the event type.

For checkout.session.completed, this is where you grant access to whatever the customer bought — update the database, send a confirmation email, deliver a digital product. This webhook is your single source of truth for whether a payment succeeded. Don't rely on the success page redirect alone — customers can close the browser before it loads.

9

Test the full flow

Stripe provides test card numbers. Use 4242 4242 4242 4242 for a successful payment and 4000 0000 0000 0002 for a declined card. Any future expiry date works. Any 3-digit CVC works.

Test the complete path: click buy, go through checkout, enter the test card, complete payment, verify the webhook fires, confirm the database is updated, check that the confirmation email sends, and land on the success page. Then test the decline path — make sure the user sees a clear error and can retry.

10

Handle edge cases

Card declines should show a friendly error message, not a stack trace. Duplicate webhooksare the biggest gotcha — Stripe retries failed webhook deliveries, so your handler must be idempotent. Before granting access, check if you already processed this session ID. Use the session ID as a unique key in your database.

Also handle: expired sessions (Checkout Sessions expire after 24 hours), refunds (listen for charge.refundedevents and revoke access), and partial failures (webhook received but email failed to send — log the error, don't throw).

11

Go live

Switch to live modein the dashboard. Create the same products and prices in live mode — the IDs will be different from test mode. Update your environment variables with live API keys (sk_live_, pk_live_). Add a new webhook endpoint for your production URL with the live signing secret.

Do a real test: charge yourself $1using a real card, verify the webhook fires, confirm the product is delivered, then refund the charge. This catches issues that test mode can't — like domain verification and payment method availability in your country.

12

Set up Stripe CLI for local development

Install the Stripe CLI: brew install stripe/stripe-cli/stripe. Run stripe login to authenticate. Then forward webhooks to your local server: stripe listen --forward-to localhost:3000/api/webhook.

The CLI prints a temporary webhook signing secret for local testing — use this as your STRIPE_WEBHOOK_SECRET during development. This is essential for testing the webhook flow without deploying. You can also trigger test events with stripe trigger checkout.session.completed.

Pro tips

Use Stripe's Customer Portalfor subscription management — one line of code gives users a self-service page to update payment methods, view invoices, and cancel. You don't build any of that UI yourself.

Set up Stripe Taxfor automatic sales tax calculation if you sell to customers in multiple jurisdictions. Tax compliance gets complicated fast — let Stripe handle the rates, rules, and reporting.

Use Stripe's test clocks to simulate subscription lifecycle events (renewal, trial end, payment failure) in test mode. This lets you verify your webhook handlers work correctly for events that would normally take days or weeks to occur.

Enable Stripe Radarfor fraud detection — it's on by default for Checkout Sessions and catches suspicious transactions using machine learning trained on data from millions of businesses.

Checkout session code

// app/api/checkout/route.ts import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(req: Request) { const { priceId } = await req.json(); if (!priceId) return Response.json({ error: "Missing price" }, { status: 400 }); const session = await stripe.checkout.sessions.create({ line_items: [{ price: priceId, quantity: 1 }], mode: "payment", success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.NEXT_PUBLIC_URL}`, }); return Response.json({ url: session.url }); }

AI prompt to get started

I'm integrating Stripe into my Next.js app. I need: 1) A checkout API route that creates a session for [product type], 2) A webhook handler that verifies signatures and processes checkout.session.completed events, 3) A success page that retrieves the session details, 4) Environment variable setup for test and live modes.

Mistakes to avoid

  • Trusting client-side amounts — NEVER pass prices from the frontend. Use Stripe price IDs, which are set in the dashboard
  • Not verifying webhook signatures — without verification, anyone can fake a payment event and get free access
  • Hardcoding price IDs — use environment variables so you can switch between test and live without code changes
  • Not handling duplicate webhooks — Stripe retries failed webhooks. Your handler must be idempotent (use session ID as a unique key)
  • Going live without testing the full flow — test every path: success, decline, webhook delivery, email confirmation
  • Exposing your secret key — it goes in server-side code only, never in client components or NEXT_PUBLIC_ variables