import { NextResponse } from "next/server"; import { prisma } from "../../../../lib/prisma"; import { stripeClient } from "../../../../lib/stripeClient"; import { cookies } from "next/headers"; import { getServerSession } from "next-auth"; import { authOptions } from "../../../../lib/auth"; export async function POST(req: Request) { try { const body = await req.json(); const slug = body.slug as string; const shipping = body.shipping as | { rateId?: string; amount?: number; currency?: string; provider?: string; servicelevel?: string; } | undefined; const shippingAddress = body.shippingAddress as | { name?: string; street1?: string; city?: string; state?: string; zip?: string; country?: string; phone?: string; } | undefined; if (!slug) { return NextResponse.json({ error: "slug is required." }, { status: 400 }); } const baseUrl = process.env.NEXT_PUBLIC_BASE_URL; if (!baseUrl) { return NextResponse.json( { error: "Missing NEXT_PUBLIC_BASE_URL." }, { status: 500 } ); } const store = await prisma.store.findUnique({ where: { slug } }); if (!store) { return NextResponse.json({ error: "Store not found." }, { status: 404 }); } const session = await getServerSession(authOptions); const cookieStore = cookies(); const sessionId = cookieStore.get("sf_session")?.value; const createdNew = !sessionId; if (!sessionId && !session?.user?.email) { return NextResponse.json({ error: "Missing cart session." }, { status: 400 }); } if (!store.stripeAccountId) { return NextResponse.json( { error: "Store is not connected to Stripe yet." }, { status: 400 } ); } const sessionCart = sessionId ? await prisma.cart.findUnique({ where: { storeId_sessionId: { storeId: store.id, sessionId } }, include: { items: true }, }) : null; let cart = null; if (session?.user?.email) { const user = await prisma.user.findUnique({ where: { email: session.user.email }, }); if (!user) { return NextResponse.json({ error: "User not found." }, { status: 404 }); } const userCart = await prisma.cart.findUnique({ where: { storeId_userId: { storeId: store.id, userId: user.id } }, include: { items: true }, }); if (!userCart) { if (sessionCart) { cart = await prisma.cart.update({ where: { id: sessionCart.id }, data: { userId: user.id }, include: { items: true }, }); } else { cart = await prisma.cart.create({ data: { storeId: store.id, userId: user.id, sessionId: sessionId || "" }, include: { items: true }, }); } } else if (sessionCart && sessionCart.id !== userCart.id) { for (const item of sessionCart.items) { const existing = await prisma.cartItem.findFirst({ where: { cartId: userCart.id, priceId: item.priceId }, }); if (existing) { await prisma.cartItem.update({ where: { id: existing.id }, data: { quantity: existing.quantity + item.quantity }, }); } else { await prisma.cartItem.create({ data: { cartId: userCart.id, productId: item.productId, priceId: item.priceId, name: item.name, unitAmount: item.unitAmount, currency: item.currency, quantity: item.quantity, }, }); } } await prisma.cart.delete({ where: { id: sessionCart.id } }); cart = await prisma.cart.update({ where: { id: userCart.id }, data: { sessionId: sessionId || "" }, include: { items: true }, }); } else if (userCart.sessionId !== sessionId) { cart = await prisma.cart.update({ where: { id: userCart.id }, data: { sessionId: sessionId || "" }, include: { items: true }, }); } else { cart = userCart; } } else if (sessionId) { cart = sessionCart ?? (await prisma.cart.create({ data: { storeId: store.id, sessionId }, include: { items: true }, })); } if (!cart || cart.items.length === 0) { return NextResponse.json( { error: "Cart is empty." }, { status: 400 } ); } const itemsTotal = cart.items.reduce( (sum, item) => sum + item.unitAmount * item.quantity, 0 ); const shippingCents = shipping?.amount && shipping.amount > 0 ? Math.round(shipping.amount * 100) : 0; const totalAmount = itemsTotal + shippingCents; const adjustedTotal = Math.max(0, totalAmount); // Build line items from cart items. const lineItems = cart.items.map((item) => ({ price_data: { currency: item.currency, product_data: { name: item.name }, unit_amount: item.unitAmount, }, quantity: item.quantity, })); if (shippingCents > 0) { lineItems.push({ price_data: { currency: (shipping?.currency || cart.items[0]?.currency || "usd").toLowerCase(), product_data: { name: "Shipping", description: shipping?.servicelevel ? `${shipping.provider || "Carrier"} - ${shipping.servicelevel}` : "Shipping", }, unit_amount: shippingCents, }, quantity: 1, }); } const sessionResult = await stripeClient.checkout.sessions.create( { line_items: lineItems, payment_intent_data: { application_fee_amount: Math.max(50, Math.floor(adjustedTotal * 0.1)), }, mode: "payment", success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${baseUrl}/cancel`, metadata: { storeSlug: slug, }, }, { stripeAccount: store.stripeAccountId, } ); // Persist order draft for webhook processing (shipping label). const orderItems = cart.items.map((i) => ({ name: i.name, productId: i.productId, priceId: i.priceId, unitAmount: i.unitAmount, currency: i.currency, quantity: i.quantity, })); const userId = session?.user?.email ? (await prisma.user.findUnique({ where: { email: session.user.email } })) ?.id : undefined; if (userId && shippingAddress) { await prisma.userAddress.upsert({ where: { userId }, update: { name: shippingAddress.name || "", street1: shippingAddress.street1 || "", city: shippingAddress.city || "", state: shippingAddress.state || "", zip: shippingAddress.zip || "", country: shippingAddress.country || "", phone: shippingAddress.phone || "", }, create: { userId, name: shippingAddress.name || "", street1: shippingAddress.street1 || "", city: shippingAddress.city || "", state: shippingAddress.state || "", zip: shippingAddress.zip || "", country: shippingAddress.country || "", phone: shippingAddress.phone || "", }, }); } await prisma.order.create({ data: { storeId: store.id, userId: userId || null, sessionId: sessionId || null, stripeCheckoutSessionId: sessionResult.id, shippingRateId: shipping?.rateId || null, shippingRateSnapshot: shipping || null, shippingAddress: shippingAddress || null, items: orderItems as any, status: "pending", }, }); // Track checkout started in Klaviyo if configured. try { const apiKey = process.env.KLAVIYO_PRIVATE_API_KEY; const email = session?.user?.email; if (apiKey && email) { await fetch("https://a.klaviyo.com/api/events/", { method: "POST", headers: { Authorization: `Klaviyo-API-Key ${apiKey}`, "Content-Type": "application/json", Accept: "application/json", Revision: "2024-02-15", }, body: JSON.stringify({ data: { type: "event", attributes: { profile: { data: { type: "profile", attributes: { email }, }, }, metric: { data: { type: "metric", attributes: { name: "Checkout Started" }, }, }, properties: { cart_total: adjustedTotal / 100, items: cart.items.map((i) => ({ name: i.name, quantity: i.quantity, unit_amount: i.unitAmount / 100, })), }, }, }, }), }); } } catch { // Ignore marketing errors. } const res = NextResponse.json({ url: sessionResult.url }); if (createdNew && sessionId) { res.cookies.set("sf_session", sessionId, { httpOnly: true, sameSite: "lax", path: "/", }); } return res; } catch (err: any) { return NextResponse.json( { error: err?.message || "Failed to create checkout session." }, { status: 500 } ); } }