Complete Stripe payment integration with checkout, subscriptions, and webhooks for Google Antigravity.
# Stripe Checkout Integration for Google Antigravity
Stripe provides a comprehensive payment infrastructure for web applications. Google Antigravity's Gemini 3 helps you implement secure payment flows with checkout sessions, subscriptions, and webhook handling.
## Stripe Client Setup
Configure Stripe for both server and client:
```typescript
// lib/stripe/server.ts
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2023-10-16",
typescript: true,
});
```
```typescript
// lib/stripe/client.ts
import { loadStripe, Stripe } from "@stripe/stripe-js";
let stripePromise: Promise<Stripe | null>;
export const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
}
return stripePromise;
};
```
## Checkout Session
Create checkout sessions for one-time payments:
```typescript
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from "next/server";
import { stripe } from "@/lib/stripe/server";
import { auth } from "@/auth";
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { priceId, quantity = 1 } = await request.json();
const checkoutSession = await stripe.checkout.sessions.create({
mode: "payment",
customer_email: session.user.email!,
line_items: [
{
price: priceId,
quantity,
},
],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/cancel`,
metadata: {
userId: session.user.id,
},
});
return NextResponse.json({ sessionId: checkoutSession.id });
} catch (error) {
console.error("Checkout error:", error);
return NextResponse.json(
{ error: "Failed to create checkout session" },
{ status: 500 }
);
}
}
```
## Subscription Management
Handle subscription checkouts:
```typescript
// app/api/subscriptions/route.ts
import { NextRequest, NextResponse } from "next/server";
import { stripe } from "@/lib/stripe/server";
import { auth } from "@/auth";
import { db } from "@/db";
import { users } from "@/db/schema";
import { eq } from "drizzle-orm";
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { priceId } = await request.json();
const user = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
});
let customerId = user?.stripeCustomerId;
if (!customerId) {
const customer = await stripe.customers.create({
email: session.user.email!,
name: session.user.name!,
metadata: {
userId: session.user.id,
},
});
customerId = customer.id;
await db
.update(users)
.set({ stripeCustomerId: customerId })
.where(eq(users.id, session.user.id));
}
const checkoutSession = await stripe.checkout.sessions.create({
mode: "subscription",
customer: customerId,
line_items: [
{
price: priceId,
quantity: 1,
},
],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
subscription_data: {
metadata: {
userId: session.user.id,
},
},
});
return NextResponse.json({ sessionId: checkoutSession.id });
} catch (error) {
console.error("Subscription error:", error);
return NextResponse.json(
{ error: "Failed to create subscription" },
{ status: 500 }
);
}
}
export async function GET(request: NextRequest) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
});
if (!user?.stripeCustomerId) {
return NextResponse.json(
{ error: "No billing account found" },
{ status: 404 }
);
}
const portalSession = await stripe.billingPortal.sessions.create({
customer: user.stripeCustomerId,
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
});
return NextResponse.json({ url: portalSession.url });
} catch (error) {
console.error("Portal error:", error);
return NextResponse.json(
{ error: "Failed to create portal session" },
{ status: 500 }
);
}
}
```
## Webhook Handler
Process Stripe webhooks securely:
```typescript
// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from "next/server";
import { headers } from "next/headers";
import { stripe } from "@/lib/stripe/server";
import { db } from "@/db";
import { subscriptions } from "@/db/schema";
import { eq } from "drizzle-orm";
import Stripe from "stripe";
export async function POST(request: NextRequest) {
const body = await request.text();
const headersList = headers();
const signature = headersList.get("stripe-signature")!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (error) {
console.error("Webhook signature verification failed:", error);
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.created":
case "customer.subscription.updated": {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionChange(subscription);
break;
}
case "customer.subscription.deleted": {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionDeleted(subscription);
break;
}
case "invoice.payment_succeeded": {
const invoice = event.data.object as Stripe.Invoice;
await handlePaymentSucceeded(invoice);
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;
if (session.mode === "subscription") {
const subscription = await stripe.subscriptions.retrieve(
session.subscription as string
);
await handleSubscriptionChange(subscription);
}
}
async function handleSubscriptionChange(subscription: Stripe.Subscription) {
const userId = subscription.metadata.userId;
if (!userId) return;
const priceId = subscription.items.data[0]?.price.id;
const status = subscription.status;
await db
.insert(subscriptions)
.values({
userId,
stripeSubscriptionId: subscription.id,
stripePriceId: priceId,
status,
currentPeriodStart: new Date(subscription.current_period_start * 1000),
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
})
.onConflictDoUpdate({
target: subscriptions.stripeSubscriptionId,
set: {
stripePriceId: priceId,
status,
currentPeriodStart: new Date(subscription.current_period_start * 1000),
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
updatedAt: new Date(),
},
});
}
async function handleSubscriptionDeleted(subscription: Stripe.Subscription) {
await db
.update(subscriptions)
.set({ status: "canceled", updatedAt: new Date() })
.where(eq(subscriptions.stripeSubscriptionId, subscription.id));
}
async function handlePaymentSucceeded(invoice: Stripe.Invoice) {
console.log(`Payment succeeded for invoice ${invoice.id}`);
}
async function handlePaymentFailed(invoice: Stripe.Invoice) {
console.log(`Payment failed for invoice ${invoice.id}`);
}
```
## Checkout Button Component
Create a reusable checkout button:
```typescript
// components/CheckoutButton.tsx
"use client";
import { useState } from "react";
import { getStripe } from "@/lib/stripe/client";
interface CheckoutButtonProps {
priceId: string;
mode: "payment" | "subscription";
children: React.ReactNode;
}
export function CheckoutButton({ priceId, mode, children }: CheckoutButtonProps) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
try {
const endpoint = mode === "subscription" ? "/api/subscriptions" : "/api/checkout";
const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ priceId }),
});
const { sessionId, error } = await response.json();
if (error) throw new Error(error);
const stripe = await getStripe();
await stripe?.redirectToCheckout({ sessionId });
} catch (error) {
console.error("Checkout error:", error);
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleCheckout}
disabled={loading}
className="px-6 py-3 bg-primary-600 text-white rounded-lg disabled:opacity-50"
>
{loading ? "Loading..." : children}
</button>
);
}
```
## Best Practices
1. **Verify webhook signatures** - Always validate Stripe signatures
2. **Use idempotent operations** - Handle duplicate webhook events
3. **Store customer IDs** - Link Stripe customers to your users
4. **Handle all subscription states** - Track active, canceled, past_due
5. **Test with Stripe CLI** - Use stripe listen for local development
Stripe integration with Google Antigravity enables secure payment processing with intelligent checkout flow suggestions.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.