| name | stripe-payments-integration |
| description | Integrate Stripe payments into Next.js and Expo applications with subscriptions, one-time payments, webhooks, and customer management. Use for SaaS billing and e-commerce. |
| allowed-tools | fs_read fs_write execute_bash |
| metadata | {"tags":"billing,payments,stripe","groups":"nextjs-saas","invocation":"model","author":"kiro-cli","version":"1.0","category":"fullstack","compatibility":"Requires Stripe account, Next.js or Expo"} |
Stripe Payments Integration
Instructions
1. Next.js stripe setup
Install Stripe dependencies:
npm install stripe @stripe/stripe-js @stripe/react-stripe-js
Configure Stripe client:
import { loadStripe } from '@stripe/stripe-js';
import Stripe from 'stripe';
export const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
});
Subscription checkout API:
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
export async function POST(req: NextRequest) {
try {
const session = await auth.api.getSession({ headers: req.headers });
const userId = session?.user?.id;
if (!session || !userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { priceId, successUrl, cancelUrl } = await req.json();
let customer = await db.user.findUnique({
where: { authUserId: userId },
select: { : , : },
});
(!customer?.) {
stripeCustomer = stripe..({
: customer?.,
: { : userId },
});
db..({
: { : userId },
: { : stripeCustomer. },
});
customer = { ...customer, : stripeCustomer. };
}
session = stripe...({
: customer.,
: [],
: [
{
: priceId,
: ,
},
],
: ,
: successUrl,
: cancelUrl,
: {
userId,
},
});
.({ : session. });
} (error) {
.(, error);
.(
{ : },
{ : }
);
}
}
Checkout component:
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
interface Plan {
id: string;
name: string;
price: number;
priceId: string;
features: string[];
}
const plans: Plan[] = [
{
id: 'basic',
name: 'Basic',
price: 9.99,
priceId: 'price_basic',
features: ['Feature 1', 'Feature 2', 'Feature 3'],
},
{
id: 'pro',
name: 'Pro',
: ,
: ,
: [, , ],
},
];
() {
[loading, setLoading] = useState< | >();
= () => {
(planId);
{
response = (, {
: ,
: { : },
: .({
priceId,
: ,
: ,
}),
});
{ sessionId } = response.();
stripe = stripePromise;
stripe?.({ sessionId });
} (error) {
.(, error);
} {
();
}
};
(
);
}
2. Stripe webhooks
Webhook handler:
import { NextRequest, NextResponse } from 'next/server';
import { headers } from 'next/headers';
import Stripe from 'stripe';
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/db';
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = headers().get('stripe-signature')!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (err) {
console.error('Webhook signature verification failed:', err);
return NextResponse.json({ error: }, { : });
}
{
(event.) {
:
(event.. ..);
;
:
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
(event.. .);
;
:
.();
}
.({ : });
} (error) {
.(, error);
.({ : }, { : });
}
}
() {
userId = session.?.;
(!userId) ;
db..({
: { : userId },
: {
: session. ,
: ,
},
});
}
() {
customer = stripe..(subscription. );
(customer.) ;
userId = customer.?.;
(!userId) ;
db..({
: { : subscription. },
: {
: subscription.,
: (subscription. * ),
: (subscription. * ),
: subscription..[]?..,
},
: {
userId,
: subscription. ,
: subscription.,
: subscription.,
: (subscription. * ),
: (subscription. * ),
: subscription..[]?..,
},
});
}
3. Customer portal
Customer portal API:
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
export async function POST(req: NextRequest) {
try {
const session = await auth.api.getSession({ headers: req.headers });
const userId = session?.user?.id;
if (!session || !userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const user = await db.user.findUnique({
where: { authUserId: userId },
select: { stripeCustomerId: true },
});
if (!user?.stripeCustomerId) {
return .({ : }, { : });
}
{ returnUrl } = req.();
session = stripe...({
: user.,
: returnUrl || ,
});
.({ : session. });
} (error) {
.(, error);
.({ : }, { : });
}
}
Portal button component:
import { useState } from 'react';
import { Button } from '@/components/ui/button';
export function CustomerPortalButton() {
const [loading, setLoading] = useState(false);
const handlePortal = async () => {
setLoading(true);
try {
const response = await fetch('/api/stripe/create-portal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
returnUrl: window.location.href,
}),
});
const { url } = await response.json();
window.location.href = url;
} catch (error) {
console.error('Portal error:', error);
} finally {
setLoading();
}
};
(
);
}
4. Expo stripe integration
Install Stripe for Expo:
npx expo install @stripe/stripe-react-native
Configure Stripe provider:
import { StripeProvider } from '@stripe/stripe-react-native';
export default function App() {
return (
<StripeProvider
publishableKey={process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY!}
merchantIdentifier="merchant.com.yourapp"
>
<YourAppContent />
</StripeProvider>
);
}
Payment screen:
import { useState } from 'react';
import { View, Alert } from 'react-native';
import { useStripe } from '@stripe/stripe-react-native';
import { Button } from '../components/Button';
export function PaymentScreen() {
const { initPaymentSheet, presentPaymentSheet } = useStripe();
const [loading, setLoading] = useState(false);
const initializePaymentSheet = async () => {
const response = await fetch('/api/stripe/payment-sheet', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 1999 }),
});
const { paymentIntent, ephemeralKey, customer } = await response.json();
const { error } = await initPaymentSheet({
: ,
: customer,
: ephemeralKey,
: paymentIntent,
: ,
});
(error) {
.(, error.);
}
};
= () => {
();
();
{ error } = ();
(error) {
.(, error.);
} {
.(, );
}
();
};
(
);
}
5. Subscription management
Subscription status component:
import { useQuery } from '@tanstack/react-query';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { CustomerPortalButton } from './CustomerPortalButton';
export function SubscriptionStatus() {
const { data: subscription, isLoading } = useQuery({
queryKey: ['subscription'],
queryFn: async () => {
const response = await fetch('/api/subscription');
return response.json();
},
});
if (isLoading) {
return <div>Loading subscription...</div>;
}
if (!subscription) {
return (
<Card>
<CardHeader>
<CardTitle>No Active Subscription
You don't have an active subscription.
);
}
(
);
}
6. Usage-based billing
Usage tracking API:
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
export async function POST(req: NextRequest) {
try {
const session = await auth.api.getSession({ headers: req.headers });
const userId = session?.user?.id;
if (!session || !userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { quantity, action } = await req.json();
const subscription = await db.subscription.findFirst({
where: { userId },
include: { user: },
});
(!subscription?.) {
.({ : }, { : });
}
stripe..(
subscription.,
{
quantity,
: .(.() / ),
: action || ,
}
);
.({ : });
} (error) {
.(, error);
.({ : }, { : });
}
}
Examples
Complete checkout flow
export function PricingPage() {
return (
<div className="container mx-auto py-12">
<h1 className="text-4xl font-bold text-center mb-12">Choose Your Plan</h1>
<SubscriptionCheckout />
</div>
);
}
Dashboard with subscription management
export function Dashboard() {
return (
<div className="container mx-auto p-6">
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<div className="grid gap-6">
<SubscriptionStatus />
{/* Other dashboard content */}
</div>
</div>
);
}
Mobile payment integration
export function SubscriptionScreen() {
return (
<View style={{ flex: 1, padding: 20 }}>
<Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 20 }}>
Upgrade to Pro
</Text>
<PaymentScreen />
</View>
);
}
Troubleshooting
- Webhook not receiving events: Check endpoint URL and webhook secret
- Payment sheet not loading: Verify Stripe keys and network connectivity
- Subscription not updating: Check webhook event handling and database updates
- Customer portal not working: Ensure customer has valid Stripe customer ID
- Mobile payments failing: Check bundle identifier and merchant settings
- Test payments not working: Use Stripe test card numbers and test keys