Complete Stripe payment integration with subscriptions, webhooks, and checkout flows for web applications
# Stripe Payment Integration for Google Antigravity
Implement secure payment processing with Stripe using Google Antigravity's Gemini 3 engine. This guide covers checkout sessions, subscriptions, webhooks, and customer portal integration.
## Server-Side Stripe Setup
```typescript
// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
typescript: true,
});
export interface CreateCheckoutParams {
customerId?: string;
customerEmail?: string;
priceId: string;
successUrl: string;
cancelUrl: string;
metadata?: Record<string, string>;
}
export async function createCheckoutSession(
params: CreateCheckoutParams
): Promise<Stripe.Checkout.Session> {
const sessionParams: Stripe.Checkout.SessionCreateParams = {
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: params.priceId,
quantity: 1,
},
],
success_url: `${params.successUrl}?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: params.cancelUrl,
metadata: params.metadata,
subscription_data: {
metadata: params.metadata,
},
allow_promotion_codes: true,
};
if (params.customerId) {
sessionParams.customer = params.customerId;
} else if (params.customerEmail) {
sessionParams.customer_email = params.customerEmail;
}
return stripe.checkout.sessions.create(sessionParams);
}
export async function createCustomerPortalSession(
customerId: string,
returnUrl: string
): Promise<Stripe.BillingPortal.Session> {
return stripe.billingPortal.sessions.create({
customer: customerId,
return_url: returnUrl,
});
}
export async function getSubscription(
subscriptionId: string
): Promise<Stripe.Subscription> {
return stripe.subscriptions.retrieve(subscriptionId, {
expand: ['default_payment_method', 'items.data.price.product'],
});
}
```
## Webhook Handler
```typescript
// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { headers } from 'next/headers';
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/database';
import type Stripe from 'stripe';
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(request: NextRequest) {
const body = await request.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');
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
}
try {
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
await handleCheckoutCompleted(session);
break;
}
case 'customer.subscription.updated': {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionUpdated(subscription);
break;
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionDeleted(subscription);
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice;
await handlePaymentFailed(invoice);
break;
}
}
return NextResponse.json({ received: true });
} catch (error) {
console.error('Webhook handler error:', error);
return NextResponse.json(
{ error: 'Webhook handler failed' },
{ status: 500 }
);
}
}
async function handleCheckoutCompleted(session: Stripe.Checkout.Session) {
const userId = session.metadata?.userId;
if (!userId) return;
await db.user.update({
where: { id: userId },
data: {
stripeCustomerId: session.customer as string,
stripeSubscriptionId: session.subscription as string,
subscriptionStatus: 'active',
subscriptionPlan: session.metadata?.plan ?? 'pro',
},
});
}
async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
const customerId = subscription.customer as string;
await db.user.updateMany({
where: { stripeCustomerId: customerId },
data: {
subscriptionStatus: subscription.status,
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
},
});
}
```
## Client-Side Checkout
```typescript
// components/PricingCard.tsx
'use client';
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY!);
interface PricingCardProps {
plan: {
name: string;
price: number;
priceId: string;
features: string[];
};
}
export function PricingCard({ plan }: PricingCardProps) {
const [loading, setLoading] = useState(false);
const handleSubscribe = async () => {
setLoading(true);
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId: plan.priceId }),
});
const { sessionId } = await response.json();
const stripe = await stripePromise;
await stripe?.redirectToCheckout({ sessionId });
} catch (error) {
console.error('Checkout error:', error);
} finally {
setLoading(false);
}
};
return (
<div className="pricing-card">
<h3>{plan.name}</h3>
<p className="price">${plan.price}/month</p>
<ul>
{plan.features.map((feature, i) => (
<li key={i}>{feature}</li>
))}
</ul>
<button onClick={handleSubscribe} disabled={loading}>
{loading ? 'Processing...' : 'Subscribe'}
</button>
</div>
);
}
```
## Best Practices
Google Antigravity's Gemini 3 engine recommends these Stripe integration patterns: Always verify webhook signatures to prevent fraudulent events. Use idempotency keys for payment operations. Store customer and subscription IDs for reference. Implement proper error handling for failed payments. Use Stripe's Customer Portal for subscription management to reduce maintenance burden.This Stripe prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their stripe implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Stripe projects, consider mentioning your framework version, coding style, and any specific libraries you're using.