Stripe Integration
Learn how to implement payments and subscriptions using Stripe in your application.
Setup
The boilerplate comes with pre-configured Stripe integration. Follow these steps to set it up:
1. Environment Variables
Add your Stripe API keys to .env.local:
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...2. Stripe CLI Setup
Install and configure the Stripe CLI for local webhook testing:
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login to Stripe
stripe login
# Forward webhooks to your local server
stripe listen --forward-to localhost:3000/api/webhooks/stripeImplementing Payments
One-time Payments
Implement one-time payments using Stripe Checkout:
// app/api/checkout/route.ts
import { stripe } from "@/lib/stripe";
import { createClient } from "@/lib/supabase/server";
export async function POST(request: Request) {
try {
const supabase = createClient();
// Get authenticated user
const { data: { user }, error: authError } =
await supabase.auth.getUser();
if (authError || !user) {
return new Response("Unauthorized", { status: 401 });
}
// Create Stripe checkout session
const session = await stripe.checkout.sessions.create({
customer_email: user.email,
line_items: [
{
price: "price_H5ggYwtDq4fbrJ",
quantity: 1,
},
],
mode: "payment",
success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/success`,
cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/cancel`,
});
return Response.json({ url: session.url });
} catch (error) {
console.error("Stripe error:", error);
return new Response("Error creating checkout session", {
status: 500
});
}
}Subscription Payments
Implement recurring subscriptions:
// app/api/subscription/route.ts
import { stripe } from "@/lib/stripe";
import { createClient } from "@/lib/supabase/server";
export async function POST(request: Request) {
try {
const supabase = createClient();
// Get authenticated user
const { data: { user }, error: authError } =
await supabase.auth.getUser();
if (authError || !user) {
return new Response("Unauthorized", { status: 401 });
}
// Create Stripe subscription checkout
const session = await stripe.checkout.sessions.create({
customer_email: user.email,
line_items: [
{
price: "price_H5ggYwtDq4fbrJ", // Your subscription price ID
quantity: 1,
},
],
mode: "subscription",
success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/dashboard`,
cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/pricing`,
subscription_data: {
metadata: {
userId: user.id,
},
},
});
return Response.json({ url: session.url });
} catch (error) {
console.error("Stripe error:", error);
return new Response("Error creating subscription", {
status: 500
});
}
}Webhook Handling
Handle Stripe webhooks to update your database when events occur:
// app/api/webhooks/stripe/route.ts
import { headers } from "next/headers";
import { stripe } from "@/lib/stripe";
import { createClient } from "@/lib/supabase/server";
export async function POST(request: Request) {
const body = await request.text();
const signature = headers().get("Stripe-Signature") as string;
let event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (error) {
console.error("Webhook signature verification failed");
return new Response("Webhook signature verification failed", {
status: 400
});
}
const supabase = createClient();
try {
switch (event.type) {
case "customer.subscription.created":
const subscription = event.data.object;
await supabase
.from("subscriptions")
.insert({
id: subscription.id,
user_id: subscription.metadata.userId,
status: subscription.status,
price_id: subscription.items.data[0].price.id,
quantity: subscription.items.data[0].quantity,
cancel_at_period_end: subscription.cancel_at_period_end,
cancel_at: subscription.cancel_at
? new Date(subscription.cancel_at * 1000)
: null,
canceled_at: subscription.canceled_at
? new Date(subscription.canceled_at * 1000)
: null,
current_period_start: new Date(
subscription.current_period_start * 1000
),
current_period_end: new Date(
subscription.current_period_end * 1000
),
created: new Date(subscription.created * 1000),
ended_at: subscription.ended_at
? new Date(subscription.ended_at * 1000)
: null,
trial_start: subscription.trial_start
? new Date(subscription.trial_start * 1000)
: null,
trial_end: subscription.trial_end
? new Date(subscription.trial_end * 1000)
: null,
});
break;
// Handle other webhook events...
}
return new Response(null, { status: 200 });
} catch (error) {
console.error("Error handling webhook:", error);
return new Response("Webhook handler failed", { status: 500 });
}
}Client Implementation
Implement the payment UI in your React components:
// components/checkout-button.tsx
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
export function CheckoutButton() {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
try {
setLoading(true);
const response = await fetch("/api/checkout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
// Redirect to Stripe Checkout
window.location.href = data.url;
} catch (error) {
console.error("Error:", error);
} finally {
setLoading(false);
}
};
return (
<Button
onClick={handleCheckout}
disabled={loading}
>
{loading ? "Loading..." : "Buy Now"}
</Button>
);
}