Estimated time: 3-4 hours
npm install stripe @stripe/stripe-js
# .env.local
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Stripe Price IDs
STRIPE_PRICE_PRO=price_...
STRIPE_PRICE_UNLIMITED=price_...
Create lib/stripe/client.ts:
import Stripe from 'stripe'
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-11-20.acacia',
typescript: true,
})
Create app/api/stripe/create-checkout/route.ts:
import { stripe } from '@/lib/stripe/client'
export async function POST(request: NextRequest) {
const { priceId } = await request.json()
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Create checkout session
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{ price: priceId, quantity: 1 }],
mode: 'subscription',
success_url: `${request.nextUrl.origin}/dashboard`,
cancel_url: `${request.nextUrl.origin}/pricing`,
})
return NextResponse.json({ sessionId: session.id })
}
Create app/api/stripe/webhook/route.ts:
export async function POST(request: NextRequest) {
const body = await request.text()
const signature = headers().get('stripe-signature')!
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
)
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutCompleted(event.data.object)
break
case 'customer.subscription.updated':
await handleSubscriptionUpdated(event.data.object)
break
case 'customer.subscription.deleted':
await handleSubscriptionDeleted(event.data.object)
break
}
return NextResponse.json({ received: true })
}
Create lib/rate-limit.ts:
const LIMITS = {
free: 50,
pro: 500,
unlimited: 999999,
}
export async function checkRateLimit(userId: string) {
const supabase = createClient()
// Get subscription tier
const { data: profile } = await supabase
.from('profiles')
.select('subscription_tier')
.eq('id', userId)
.single()
const tier = profile?.subscription_tier || 'free'
const limit = LIMITS[tier]
// Get today's usage
const today = new Date()
today.setHours(0, 0, 0, 0)
const { count } = await supabase
.from('usage_logs')
.select('*', { count: 'exact', head: true })
.eq('user_id', userId)
.gte('created_at', today.toISOString())
const remaining = Math.max(0, limit - (count || 0))
const allowed = (count || 0) < limit
return { allowed, remaining, limit }
}
const rateLimit = await checkRateLimit(user.id)
if (!rateLimit.allowed) {
return NextResponse.json(
{ error: 'Daily limit reached' },
{ status: 429 }
)
}
Create app/dashboard/analytics/page.tsx showing:
Infrastructure:
AI APIs (50 queries/user/day):
Revenue:
https://your-domain.vercel.app/api/stripe/webhookcheckout.session.completed, customer.subscription.*Over this 7-part series, we built a complete production RAG system:
| Part | Topic |
|---|---|
| 1 | Understanding RAG architecture |
| 2 | Next.js + Supabase + Auth |
| 3 | Vector embeddings + pgvector |
| 4 | PDF ingestion pipeline |
| 5 | RAG query pipeline |
| 6 | Vision support for diagrams |
| 7 | Production features + deployment |
Final stats:
You've built a complete, production-ready RAG system from scratch!
This is real, working code that can: