|
| 1 | +// POST /api/stripe/checkout — create a Stripe Checkout session for plan upgrade |
| 2 | +export const runtime = "edge"; |
| 3 | + |
| 4 | +import { NextRequest, NextResponse } from "next/server"; |
| 5 | +import Stripe from "stripe"; |
| 6 | +import { createSupabaseCookieClient } from "@/lib/supabase-server"; |
| 7 | +import { createSupabaseServerClient } from "@/lib/supabase-client"; |
| 8 | +import { STRIPE_PRICES, type PlanTier } from "@/lib/plans"; |
| 9 | + |
| 10 | +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? "", { |
| 11 | + apiVersion: "2024-12-18.acacia", |
| 12 | +}); |
| 13 | + |
| 14 | +export async function POST(req: NextRequest) { |
| 15 | + try { |
| 16 | + const supabaseCookie = await createSupabaseCookieClient(); |
| 17 | + const { |
| 18 | + data: { user }, |
| 19 | + } = await supabaseCookie.auth.getUser(); |
| 20 | + if (!user) { |
| 21 | + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); |
| 22 | + } |
| 23 | + |
| 24 | + const body = await req.json(); |
| 25 | + const { plan, interval } = body as { |
| 26 | + plan: PlanTier; |
| 27 | + interval: "monthly" | "yearly"; |
| 28 | + }; |
| 29 | + |
| 30 | + if (!plan || !["pro", "enterprise"].includes(plan)) { |
| 31 | + return NextResponse.json( |
| 32 | + { error: "Invalid plan. Must be pro or enterprise." }, |
| 33 | + { status: 400 } |
| 34 | + ); |
| 35 | + } |
| 36 | + if (!interval || !["monthly", "yearly"].includes(interval)) { |
| 37 | + return NextResponse.json( |
| 38 | + { error: "Invalid interval. Must be monthly or yearly." }, |
| 39 | + { status: 400 } |
| 40 | + ); |
| 41 | + } |
| 42 | + |
| 43 | + const priceKey = `${plan}_${interval}` as keyof typeof STRIPE_PRICES; |
| 44 | + const priceId = STRIPE_PRICES[priceKey]; |
| 45 | + if (!priceId) { |
| 46 | + return NextResponse.json( |
| 47 | + { error: "Stripe price not configured for this plan/interval" }, |
| 48 | + { status: 500 } |
| 49 | + ); |
| 50 | + } |
| 51 | + |
| 52 | + // Get or create Stripe customer |
| 53 | + const supabase = createSupabaseServerClient(); |
| 54 | + const { data: userData } = await supabase |
| 55 | + .from("users") |
| 56 | + .select("stripe_customer_id, email") |
| 57 | + .eq("id", user.id) |
| 58 | + .single(); |
| 59 | + |
| 60 | + let customerId = userData?.stripe_customer_id; |
| 61 | + |
| 62 | + if (!customerId) { |
| 63 | + const customer = await stripe.customers.create({ |
| 64 | + email: userData?.email ?? user.email, |
| 65 | + metadata: { user_id: user.id }, |
| 66 | + }); |
| 67 | + customerId = customer.id; |
| 68 | + |
| 69 | + await supabase |
| 70 | + .from("users") |
| 71 | + .update({ stripe_customer_id: customerId }) |
| 72 | + .eq("id", user.id); |
| 73 | + } |
| 74 | + |
| 75 | + const origin = req.headers.get("origin") ?? process.env.NEXT_PUBLIC_APP_URL ?? "https://www.aluminatai.com"; |
| 76 | + |
| 77 | + const session = await stripe.checkout.sessions.create({ |
| 78 | + customer: customerId, |
| 79 | + mode: "subscription", |
| 80 | + line_items: [{ price: priceId, quantity: 1 }], |
| 81 | + success_url: `${origin}/dashboard/settings?billing=success`, |
| 82 | + cancel_url: `${origin}/dashboard/settings?billing=cancel`, |
| 83 | + metadata: { user_id: user.id, plan }, |
| 84 | + subscription_data: { |
| 85 | + metadata: { user_id: user.id, plan }, |
| 86 | + }, |
| 87 | + }); |
| 88 | + |
| 89 | + return NextResponse.json({ url: session.url }); |
| 90 | + } catch (err: unknown) { |
| 91 | + const message = err instanceof Error ? err.message : "Unknown error"; |
| 92 | + return NextResponse.json({ error: message }, { status: 500 }); |
| 93 | + } |
| 94 | +} |
0 commit comments