Hands-on

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


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:

  1. Customer makes a purchase: The customer enters payment details on your checkout page (card or other digital payment).
  2. Data encryption and secure transfer: The website encrypts the data and sends it to the payment gateway.
  3. Transaction forwarding: The gateway forwards encrypted data to the payment processor.
  4. Processor and card company communication: The processor sends details to the issuing bank for authorization.
  5. Approval or denial: The bank approves or denies based on funds and security checks.
  6. Response to gateway: The decision flows back to the gateway via the processor.
  7. Transaction completion: If approved, the customer sees confirmation; if declined, they’re notified.
  8. 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:

  1. Choose a payment gateway: Consider fees, payment methods, security, and platform compatibility.
  2. Set up a merchant account: Some gateways (e.g., Stripe) bundle this; others require a separate account.
  3. Obtain API keys: Use them to authenticate your website/app with the gateway.
  4. Integrate the gateway: Via plugins (Shopify/WooCommerce) or custom code. Stripe’s APIs are notably developer‑friendly.
  5. Test in sandbox: Use test environments and cards to validate flows.
  6. 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 status created and returns checkout_url.
  • /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 calls handleCheckoutSession to mark the order paid and grant credits.

Credits Flow

On successful payment:

  1. We look up the order by order_no in session metadata.
  2. Update the order to paid and persist relevant payment details.
  3. 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 expects STRIPE_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

  1. Sign up and sign in via /[locale]/signup and /[locale]/login.
  2. Visit /[locale]/pricing and click a plan.
  3. Complete the Stripe Checkout with test card 4242 4242 4242 4242.
  4. Stripe redirects to the callback URL; the webhook finalizes your order and grants credits.
  5. Verify credits at /[locale]/credits-test or via POST /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:

  1. Create a Stripe account: Sign up and provide your business and payout details.
  2. Obtain API keys: In the Stripe Dashboard → Developers, find your publishable and secret keys.
  3. Install Stripe libraries: Add official Stripe SDKs for your language (JavaScript/TypeScript, Ruby, Python, etc.).
  4. Integrate on the frontend: Use Stripe.js and prebuilt UI (e.g., Elements/Payment Element) or a platform plugin to securely collect card data.
  5. Add a server endpoint: Initialize Stripe with your secret key and expose routes to create PaymentIntents or Checkout Sessions.
  6. Collect payment details: Use Stripe’s UI components to avoid handling raw card data on your servers.
  7. Submit to your server: Send the client’s intent details to your backend securely over HTTPS.
  8. Process server‑side: Create PaymentIntents or Checkout Sessions with the Stripe SDK.
  9. Handle responses: Update your database and inform the client of success/failure.
  10. Handle errors and edge cases: Insufficient funds, expired cards, authentication (3DS), retries.
  11. Test thoroughly: Use Stripe’s test mode, webhooks, and test cards for scenarios.
  12. 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 in credits.
  • 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.

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

  1. Dashboard → Products → pick your product.
  2. In the Prices table, click the 3 dots at the end of the row → “Copy price ID”.
  3. 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 from src/data/pricing.ts (safe fallback).

Renewals and Credits

This template handles renewals automatically:

  • Webhook invoice.payment_succeeded (with billing_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() in src/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.