Loading...
Loading...
> Set up one-time payments and subscriptions with Stripe.
This guide walks you through setting up Stripe payments including checkout, webhooks, and promotion codes.
DESC: Add your Stripe API keys to .env.local
1$# Get from https://dashboard.stripe.com/test/apikeys2$STRIPE_SECRET_KEY="sk_test_..."3$NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."4$STRIPE_WEBHOOK_SECRET="whsec_..." # Get from stripe listen command (see step 2)
DESC: Set up your product in Stripe Dashboard with a lookup identifier
1$# Step 1: Go to https://dashboard.stripe.com/products2$# Step 2: Click "+ Add product"3$# Step 3: Fill in product details:4$# - Name: Fabrk Purchase5$# - Description: One-time access to Fabrk boilerplate6$# - Price: $299 (or your chosen amount)7$# - Billing: One-time8$#9$# Step 4: IMPORTANT - Add lookup key:10$# - Scroll to "Pricing" section11$# - Click "Advanced pricing options"12$# - Find "Lookup key" field13$# - Enter: fabrk_purchase14$# - Click "Save product"15$#16$# Step 5: Add to .env.local (use lookup key, NOT price ID):17$NEXT_PUBLIC_STRIPE_PRICE_FABRK="fabrk_purchase" # This is a lookup key1819$# Why lookup keys?20$# - Lookup keys let you change prices in Stripe without changing code21$# - You can update $299 to $399 in Stripe Dashboard22$# - Your code keeps working without any changes
Use the checkout API to create a payment session
1"use client";23import { useState } from "react";4import { Button } from "@/components/ui/button";56export function CheckoutButton() {7 const [loading, setLoading] = useState(false);89 const handleCheckout = async () => {10 setLoading(true);1112 try {13 const response = await fetch("/api/stripe/checkout", {14 method: "POST",15 headers: { "Content-Type": "application/json" },16 body: JSON.stringify({17 /* eslint-disable-next-line no-process-env -- Accessing client-side NEXT_PUBLIC env var */18 priceId: process.env.NEXT_PUBLIC_STRIPE_PRICE_FABRK,19 }),20 });2122 const { url } = await response.json();2324 if (url) {25 window.location.href = url;26 }27 } catch (error) {28 console.error("Checkout error:", error);29 } finally {30 setLoading(false);31 }32 };3334 return (35 <Button onClick={handleCheckout} disabled={loading}>36 {loading ? "Loading..." : "Buy Now"}37 </Button>38 );39}
Forward Stripe webhooks to your local development server
1$# Step 1: Install Stripe CLI (one-time setup)2$brew install stripe/stripe-cli/stripe34$# Step 2: Login to Stripe5$stripe login6$# Opens browser to authenticate78$# Step 3: Forward webhooks to localhost9$stripe listen --forward-to localhost:3000/api/webhooks/stripe1011$# Expected output:12$# > Ready! Your webhook signing secret is whsec_abc123def456...13$# > (this is your local STRIPE_WEBHOOK_SECRET)14$#15$# Copy the webhook secret (starts with whsec_) and add to .env.local:16$# STRIPE_WEBHOOK_SECRET="whsec_abc123def456..."17$#18$# Leave this terminal window open while developing19$# You'll see webhook events appear here in real-time
Add your webhook endpoint in the Stripe Dashboard
1https://yourdomain.com/api/webhooks/stripe
Configure promotion codes in src/config.js
1// src/config.js2stripe: {3 coupons: {4 earlyAdopter: {5 enabled: true,6 code: "EARLY500",7 /* eslint-disable-next-line no-process-env -- Showing coupon config example */8 promotionCodeId: process.env.STRIPE_COUPON_EARLY_ADOPTER,9 discountAmount: 100,10 // ...11 },12 },13}
The webhook handler is at src/app/api/webhooks/stripe/route.ts. Key events handled:
checkout.session.completed - Payment successfulcustomer.subscription.created - New subscriptioncustomer.subscription.updated - Subscription changedcustomer.subscription.deleted - Subscription cancelledinvoice.payment_failed - Payment failedUse these test cards in development:
4242 4242 4242 4242 - Successful payment4000 0000 0000 0002 - Declined4000 0000 0000 3220 - Requires 3D Secure[ERROR]: No such price or lookup key
Solution: Verify lookup key exists in Stripe Dashboard
# Steps to fix:
1. Go to https://dashboard.stripe.com/products
2. Click on your product
3. Scroll to "Pricing" section
4. Click "Advanced pricing options"
5. Add lookup key: fabrk_purchase
6. Save product
# Verify in .env.local
NEXT_PUBLIC_STRIPE_PRICE_FABRK="fabrk_purchase"[ERROR]: Webhook signature verification failed
Solution: Ensure STRIPE_WEBHOOK_SECRET matches stripe listen output
# Run stripe listen and copy the webhook secret
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Output shows: whsec_abc123...
# Add to .env.local (must match exactly)
STRIPE_WEBHOOK_SECRET="whsec_abc123..."[ERROR]: Payment succeeded but webhook not triggered
Solution: Verify stripe listen is running
# Make sure stripe listen is running in a separate terminal
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Should show: "Ready! Your webhook signing secret is..."
# Leave this terminal open while testing[ERROR]: Checkout redirects to 404
Solution: Check success_url and cancel_url configuration
// In checkout API route
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
// Verify NEXT_PUBLIC_APP_URL in .env.local
NEXT_PUBLIC_APP_URL="http://localhost:3000"