Stripe Payments Setup

This guide shows how to configure Stripe for PWASK, create prices, wire the webhook, and test end-to-end. PWASK supports multiple payment methods: Stripe Card, Apple Pay, and Google Pay.

Prerequisites

  • Stripe account with at least one Product created.
  • Supabase project with the service role key available to the app.
  • CLI: pnpm, stripe CLI (optional for local webhook testing).

1) Environment Variables

Create or update .env.local (and your hosting env) with:

# Stripe authentication
STRIPE_SECRET_KEY=sk_live_or_test_xxx                    # required (server only)
STRIPE_WEBHOOK_SECRET=whsec_xxx                          # required for webhooks
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_or_test_xxx   # required (client-side)

# Prices (example IDs from Stripe Dashboard)
STRIPE_PRICE_LIFETIME=price_123
STRIPE_PRICE_ESSENTIAL_MONTHLY=price_abc
STRIPE_PRICE_ESSENTIAL_YEARLY=price_def
STRIPE_PRICE_CREATOR_MONTHLY=price_xyz
STRIPE_PRICE_CREATOR_YEARLY=price_uvx
STRIPE_PRICE_PRO_MONTHLY=price_pro_monthly
STRIPE_PRICE_PRO_YEARLY=price_pro_yearly

2) Database Migration

Add Stripe columns/indexes to the payments table:

pnpm supabase db push   # or supabase db push (if you use Supabase CLI directly)

This applies supabase/migrations/20260321090000_add_stripe_columns.sql.

3) Webhook Configuration

Stripe sends events to /api/checkout/stripe/webhook. There are two methods for testing webhooks locally:

Method 1: Stripe CLI (Recommended for Local Development)

The Stripe CLI provides a local listener that forwards webhook events from Stripe to your local server, perfect for development and testing.

Installation:

# macOS (Homebrew)
brew install stripe/stripe-cli/stripe

# Linux
curl -s https://packages.stripe.com/api/v1/installation/script.sh | bash

# Windows (Scoop)
scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
scoop install stripe

Setup and Usage:

  1. Login to Stripe CLI:

    stripe login
    

    This opens a browser window to authenticate with your Stripe account.

  2. Start the webhook listener:

    stripe listen --forward-to localhost:3000/api/checkout/stripe/webhook
    

    This command will:

    • Start listening for webhook events from Stripe
    • Forward them to your local development server
    • Display a webhook signing secret (starting with whsec_)
  3. Copy the webhook secret to your environment:

    # The CLI will display something like: whsec_xxxxxxxxxxxxxxxxxxxxx
    # Add it to your .env.local:
    STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxx
    

    Or set it directly in your terminal session:

    export STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxx
    
  4. Start your development server (in a separate terminal):

    pnpm dev
    
  5. Trigger test events:

    # In another terminal, trigger specific webhook events
    stripe trigger checkout.session.completed
    stripe trigger payment_intent.succeeded
    stripe trigger invoice.payment_succeeded
    

What events to listen for:

The webhook handler processes these events:

  • checkout.session.completed - Regular card payment completed (Checkout Session)
  • payment_intent.succeeded - Google Pay/Apple Pay payment completed (Payment Intent)
  • invoice.payment_succeeded - Subscription invoice paid
  • customer.subscription.deleted - Subscription cancelled

Viewing webhook events:

# See all webhook events in real-time
stripe listen --forward-to localhost:3000/api/checkout/stripe/webhook

# Filter specific events
stripe listen \
  --events checkout.session.completed,payment_intent.succeeded \
  --forward-to localhost:3000/api/checkout/stripe/webhook

Method 2: ngrok + Stripe Dashboard (Alternative)

If you prefer using ngrok or need to test with the actual Stripe Dashboard webhook configuration:

  1. Start ngrok:

    ngrok http 3000
    
  2. Update your environment:

    NEXT_PUBLIC_BASE_URL=https://YOUR-NGROK-URL.ngrok-free.app
    
  3. Configure webhook in Stripe Dashboard:

    • Go to Stripe Dashboard → Developers → Webhooks
    • Click "Add endpoint"
    • URL: https://YOUR-NGROK-URL.ngrok-free.app/api/checkout/stripe/webhook
    • Select events:
      • checkout.session.completed
      • payment_intent.succeeded (for Google Pay/Apple Pay)
      • invoice.payment_succeeded
      • customer.subscription.deleted
    • Copy the Signing secret to STRIPE_WEBHOOK_SECRET

Production Webhook Configuration

For production, configure the webhook endpoint in Stripe Dashboard:

  • URL: https://yourdomain.com/api/checkout/stripe/webhook
  • Events to subscribe:
    • checkout.session.completed
    • payment_intent.succeeded
    • invoice.payment_succeeded
    • customer.subscription.deleted
  • Copy the Signing secret into STRIPE_WEBHOOK_SECRET

4) Create Prices in Stripe

For each plan/billing combination you support, create a Price in the Stripe Dashboard and map it to the env vars above. For one-time lifetime payments, set STRIPE_PRICE_LIFETIME.

5) Payment Methods: Card, Apple Pay & Google Pay

Stripe Card Payment

  • Standard card payment (Visa, MasterCard, Amex)
  • Works on all browsers and devices
  • Click "Card (Visa, MasterCard, Amex)" button to pay

Apple Pay

  • Supported on: Safari on iOS 12+, Safari on macOS 12+
  • Requirements: HTTPS (localhost works for development), Apple Pay merchant account configured in Stripe
  • How it works: Button automatically appears on compatible devices and hides on unsupported ones
  • Click "Apple Pay" button to open native Apple Pay sheet
  • Users confirm payment with Face ID, Touch ID, or passcode

Google Pay

  • Supported on: Chrome on Android 5+, other Android browsers with Google Pay support
  • Requirements: HTTPS (localhost works for development), Google Pay merchant account configured in Stripe
  • How it works: Button automatically appears on compatible devices and hides on unsupported ones
  • Click "Google Pay" button to open native Google Pay sheet
  • Users confirm payment with biometric or PIN

Implementation Details

The payment buttons are rendered by the StripeButton component (src/components/StripeButton.tsx):

  • For Card: Always visible
  • For Apple Pay: Loads stripe.js and checks canUseApplePay()
  • For Google Pay: Loads stripe.js and checks canUseGooglePay()

The backend API route (/api/checkout/stripe) handles all payment methods through Stripe Checkout, which automatically:

  • Detects user's device and payment method support
  • Shows the appropriate native payment UI
  • Processes the transaction securely

6) Triggering Checkout

  • The payment page shows all available payment methods (/payment).
  • For subscriptions, ensure the plan and billing query params are passed (e.g., ?plan=creator&billing=monthly).
  • The API route /api/checkout/stripe creates a Checkout Session and redirects users to Stripe-hosted checkout.
  • Each payment method (card, Apple Pay, Google Pay) uses the same API endpoint.

7) What the Webhook Does

  • Verifies Stripe signature.
  • Upserts users and profiles in Supabase using the service role key.
  • Logs payments into payments with Stripe IDs (session, subscription, payment intent, invoice).
  • Sends a payment confirmation email via /api/sendmail.

8) Validation & Testing

  • pnpm exec tsc --noEmit and pnpm lint to ensure types/lint pass.
  • Run a test payment in Stripe test mode with 4242 4242 4242 4242 (any future expiry, any CVC).
  • Test Apple Pay: Use Safari on iOS/macOS with test card
  • Test Google Pay: Use Chrome on Android with test card
  • Confirm the payments table records the event and the email is delivered.
  • Verify webhook logs in Stripe Dashboard to ensure 2xx responses.

9) Production Checklist

  • Use live keys and live price IDs in production env variables.
  • Set the production webhook endpoint and signing secret.
  • Configure Apple Pay and Google Pay merchant accounts in Stripe Dashboard.
  • Rotate keys if accidentally exposed.
  • Ensure CSP allows Stripe (already configured in next.config.ts for js.stripe.com and checkout.stripe.com).
  • Keep SUPABASE_SERVICE_ROLE_KEY and STRIPE_SECRET_KEY only on the server (never expose them).
  • Test all payment methods on production with real devices before going live.

10) Troubleshooting

Apple Pay/Google Pay buttons don't appear

  • Ensure HTTPS is enabled (localhost works for dev)
  • Check browser/device compatibility
  • Verify NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is set
  • Check browser console for errors

Payment fails with "Unable to start Stripe checkout"

  • Verify all Stripe environment variables are correct
  • Check webhook logs in Stripe Dashboard
  • Ensure Supabase credentials are valid

Webhook not triggering

  • Verify STRIPE_WEBHOOK_SECRET is correct
  • Check that webhook endpoint is configured in Stripe Dashboard
  • Ensure the endpoint is accessible from the internet (not localhost)

Related Documentation