Stripe Setup
Use Stripe Checkout for payments and finalize via webhooks to grant credits. Configure environment variables, create sessions, handle callbacks, and process signed webhook events.
Overview
This template uses Stripe Checkout for payments and a credit ledger to grant usage credits after purchase.
- UI:
/[locale]/pricing
shows plans from a typed config. - Create session: client calls
POST /api/checkout
to create a Stripe Checkout session and redirects to Stripe. - Redirect back: Stripe sends the user to
/api/pay/callback/stripe
(GET) after checkout. - Finalize server-side: Stripe sends a webhook to
/api/pay/webhook/stripe
(POST). The webhook marks the order paid and grants credits.
The webhook is the source of truth; the callback is only for browser navigation and is safe to keep as a secondary path (code is idempotent if it runs twice).
Quick Start Resources
- Stripe: Get started — https://docs.stripe.com/get-started
Payment Gateways 101
What is a payment gateway?
A payment gateway is a digital tool that online businesses use to process and authorize digital customer payments, such as debit or credit cards, digital wallets, and electronic bank transfers. It’s the online equivalent of a physical card‑reading device. Payment gateways are intermediary services that securely check, approve, or reject electronic transactions on behalf of the online business via the internet.
Nearly $7.4 trillion in global ecommerce sales are forecasted for 2025—a 74% increase from 2020. Businesses should understand how to manage online transactions to tap into this growing market. Integrating a payment gateway into your website is a key step in creating a fully functioning ecommerce platform.
Payment gateway integration isn’t just about accepting online payments—it’s also about enhancing the customer experience, improving conversion rates, and ensuring transaction security. Modern customers overwhelmingly demand an intuitive, quick, and effective online checkout experience. Integrating a payment gateway into your business website can create a smooth and secure checkout process and build customer trust, enthusiasm, and loyalty.
How payment gateways work
Here is an overview of how a payment gateway functions:
- Customer makes a purchase: The customer enters payment details on your checkout page (card or other digital payment).
- Data encryption and secure transfer: The website encrypts the data and sends it to the payment gateway.
- Transaction forwarding: The gateway forwards encrypted data to the payment processor.
- Processor and card company communication: The processor sends details to the issuing bank for authorization.
- Approval or denial: The bank approves or denies based on funds and security checks.
- Response to gateway: The decision flows back to the gateway via the processor.
- Transaction completion: If approved, the customer sees confirmation; if declined, they’re notified.
- Funds settlement: Approved transactions are settled to your acquiring bank and then your business account (timing depends on your agreement).
Despite many steps, the process is automated and typically takes seconds.
How to integrate a payment gateway
While specifics vary by provider and platform, the general flow is:
- Choose a payment gateway: Consider fees, payment methods, security, and platform compatibility.
- Set up a merchant account: Some gateways (e.g., Stripe) bundle this; others require a separate account.
- Obtain API keys: Use them to authenticate your website/app with the gateway.
- Integrate the gateway: Via plugins (Shopify/WooCommerce) or custom code. Stripe’s APIs are notably developer‑friendly.
- Test in sandbox: Use test environments and cards to validate flows.
- Go live: Switch to live keys when ready and monitor.
Endpoints
-
/api/checkout
(POST):- Validates the selected plan against
src/data/pricing.ts
. - Creates a Stripe Checkout session (inline price data; no Stripe Products required).
- Inserts an
orders
row with statuscreated
and returnscheckout_url
.
- Validates the selected plan against
-
/api/pay/callback/stripe
(GET):- Used by Stripe Checkout success redirect. Fetches the session for logging, then redirects the user to your success or failure page.
- Note: Server-of-record updates happen in the webhook.
-
/api/pay/webhook/stripe
(POST):- Verifies the Stripe signature.
- Handles
checkout.session.completed
and callshandleCheckoutSession
to mark the order paid and grant credits.
Credits Flow
On successful payment:
- We look up the order by
order_no
in session metadata. - Update the order to
paid
and persist relevant payment details. - Insert a positive ledger row in
credits
(trans_type: order_pay
) for the purchased amount. The balance reflects non-expired positive entries minus all consumption entries.
Prerequisites
Set these variables in .env.local
and restart dev:
STRIPE_PRIVATE_KEY=sk_test_... # Test mode secret key
STRIPE_WEBHOOK_SECRET=whsec_... # From Stripe CLI / dashboard
NEXT_PUBLIC_WEB_URL=http://localhost:3000 # External URL for callbacks
NEXT_PUBLIC_PAY_CANCEL_URL=/pricing # Optional; relative or absolute
NEXT_PUBLIC_PAY_SUCCESS_URL=/pricing # Optional
NEXT_PUBLIC_PAY_FAIL_URL=/pricing # Optional
Notes:
src/integrations/stripe.ts
expectsSTRIPE_PRIVATE_KEY
(your Stripe secret key).
Local Webhook Setup
Use the Stripe CLI to forward events to your local endpoint.
stripe login
stripe listen --forward-to localhost:3000/api/pay/webhook/stripe
When stripe listen
starts, it prints a whsec_...
secret — copy it to STRIPE_WEBHOOK_SECRET
.
Trigger a test checkout completion:
stripe trigger checkout_session_completed
Common gotcha: triggering payment_intent.succeeded
will hit your webhook but is ignored unless you add a handler for it. This template listens to checkout.session.completed
.
Try It
- Sign up and sign in via
/[locale]/signup
and/[locale]/login
. - Visit
/[locale]/pricing
and click a plan. - Complete the Stripe Checkout with test card
4242 4242 4242 4242
. - Stripe redirects to the callback URL; the webhook finalizes your order and grants credits.
- Verify credits at
/[locale]/credits-test
or viaPOST /api/account/credits
.
If you enable RMB/WeChat Pay, ensure those payment methods are active in your Stripe test account. Otherwise, start with USD + card.
Stripe Integration Guide (High‑Level)
Integrating Stripe into your website as a payment gateway can be done quickly with the right steps:
- Create a Stripe account: Sign up and provide your business and payout details.
- Obtain API keys: In the Stripe Dashboard → Developers, find your publishable and secret keys.
- Install Stripe libraries: Add official Stripe SDKs for your language (JavaScript/TypeScript, Ruby, Python, etc.).
- Integrate on the frontend: Use Stripe.js and prebuilt UI (e.g., Elements/Payment Element) or a platform plugin to securely collect card data.
- Add a server endpoint: Initialize Stripe with your secret key and expose routes to create PaymentIntents or Checkout Sessions.
- Collect payment details: Use Stripe’s UI components to avoid handling raw card data on your servers.
- Submit to your server: Send the client’s intent details to your backend securely over HTTPS.
- Process server‑side: Create PaymentIntents or Checkout Sessions with the Stripe SDK.
- Handle responses: Update your database and inform the client of success/failure.
- Handle errors and edge cases: Insufficient funds, expired cards, authentication (3DS), retries.
- Test thoroughly: Use Stripe’s test mode, webhooks, and test cards for scenarios.
- Go live: Swap test keys for live keys, enable required payment methods, and monitor.
Tip: Start with the official guide — https://docs.stripe.com/get-started
Implementation Details
- Plans live in
src/data/pricing.ts
. Values are typed (Pricing
), shared by the pricing page and/api/checkout
. - Orders are stored in
orders
; credit movements incredits
. - The webhook verifies signatures using the raw request body and
STRIPE_WEBHOOK_SECRET
. handleCheckoutSession
supports both one-time and subscription checkouts:- Payment mode: uses
session.payment_intent
. - Subscription mode: fetches the latest invoice’s payment intent from the subscription.
- Payment mode: uses
Products vs Prices (Stripe)
Stripe separates the catalog item (Product) from how it’s sold (Price):
- Product (
prod_...
): name/description, catalog metadata; a single Product can have many Prices. - Price (
price_...
): currency, amount, recurring interval (month/year), trials, tax behavior, tiers. Subscriptions attach to a Price.
Why we reference Price IDs in Checkout
- Subscriptions need exact commercial terms. Attaching to a
price_...
enables proper renewals, proration, trials, and Stripe Tax. - Renewals reference the same Price on invoice lines so our webhook can map the plan and grant credits each cycle.
- Operationally safer than ad‑hoc prices: you manage amounts and rules in Stripe; code stays stable.
Finding your Price IDs in Stripe
- Dashboard → Products → pick your product.
- In the Prices table, click the 3 dots at the end of the row → “Copy price ID”.
- It begins with
price_...
— don’t confuse it with the Product ID (prod_...
).
Environment variables (optional but recommended)
# Map your plan config to Stripe Prices; if empty, checkout falls back to inline prices
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_MONTHLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_MONTHLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_YEARLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_YEARLY=price_...
# Optional CNY variants
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_MONTHLY_CNY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_MONTHLY_CNY=price_...
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_YEARLY_CNY=price_...
NEXT_PUBLIC_STRIPE_PRICE_SCALE_YEARLY_CNY=price_...
Behavior with/without Price IDs
- If a Price ID is set for a subscription plan, Checkout uses it directly.
- If not, Checkout creates the session with inline
price_data
using amounts fromsrc/data/pricing.ts
(safe fallback).
Renewals and Credits
This template handles renewals automatically:
- Webhook
invoice.payment_succeeded
(withbilling_reason=subscription_cycle
) creates a renewal Order and grants plan credits for the period. - Idempotency: we skip if an Order already exists for the same
(subscription_id, period_start)
. - Initial purchase still uses
checkout.session.completed
; we avoid double‑crediting by ignoring non‑cycle invoices.
Optional Enhancements
- Persist additional Stripe IDs (subscription, invoice) alongside orders for richer admin views.
- Add idempotency keys if you extend the flow to multiple writes per event.
Billing & Subscriptions
Customer Portal
Enable Stripe’s Customer Portal in the dashboard and customize the features you want (cancel/resume, update payment method, view invoices).
This template exposes:
- API:
GET /api/billing/portal
— creates a portal session and redirects to Stripe - UI:
/:locale/account/billing
— contains a “Manage billing” button that opens the portal
Implementation:
- API route:
src/app/api/billing/portal/route.ts
- Finds or creates a Stripe Customer by the signed-in user’s email
- Creates a portal session with a
return_url
back to/:locale/account/billing
Usage in the UI (server component):
src/app/[locale]/account/billing/page.tsx
renders a link to/api/billing/portal?locale=<locale>
Dunning (Failed Payments)
Webhook: invoice.payment_failed
is handled to send a friendly “update your payment method” email via Resend.
- Template:
src/services/email/templates/payment-failed.tsx
- Sender:
sendPaymentFailedEmail()
insrc/services/email/send.ts
- Webhook wiring: see
src/app/api/pay/webhook/stripe/route.ts
Local test via Stripe CLI:
stripe trigger invoice_payment_failed
Note: Emails include a link back to /:locale/account/billing
so users can open the Customer Portal from your app.
Reminder: Forward Webhooks During Testing
When testing locally (or against any non‑production URL), make sure Stripe forwards events to your environment; otherwise payments will complete but credits/orders won’t be finalized.
stripe listen --forward-to localhost:3000/api/pay/webhook/stripe
If you use a tunnel or remote domain, forward to that full URL instead of localhost.
Other Payment Options
Stripe is just one option. If you need alternatives such as crypto payments or PayPal, contact me and I can help you choose and integrate the right provider for your stack and market.
Admin Roles & Authorization
Configure read-only and read/write admin roles, protect admin APIs, and understand the authentication pipeline used by this template.
Notifications - Slack Alerts
Set up simple Slack notifications for uploads and payments, wire them into Stripe webhooks and storage errors, and customize alerts with a tiny server helper.