All files / src/app/api/payments/create-intent route.ts

97.5% Statements 78/80
91.66% Branches 11/12
100% Functions 2/2
97.5% Lines 78/80

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 811x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 1x 1x     1x 1x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 4x 4x 7x 7x 7x 7x 7x 6x 11x 1x 1x 5x 11x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 3x 3x 3x 3x 3x 3x 1x 1x  
import { NextRequest, NextResponse } from 'next/server';
import {
  withAuth,
  withErrorHandling,
  successResponse,
  ApiError,
  ApiSuccessResponse,
  ApiErrorResponse } from "@/lib/api";
import { RouteContext } from "@/lib/api/middleware";
import { prisma } from "@/lib/prisma";
import Stripe from "stripe";
import { z } from "zod";
import { Session } from "next-auth";
 
// Lazy initialization to avoid build-time errors when STRIPE_SECRET_KEY is not set
let stripeClient: Stripe | null = null;
function getStripe(): Stripe {
  if (!stripeClient) {
    const key = process.env.STRIPE_SECRET_KEY;
    if (!key) {
      throw new Error("STRIPE_SECRET_KEY is not configured");
    }
    stripeClient = new Stripe(key);
  }
  return stripeClient;
}
 
const IntentSchema = z.object({
  orderId: z.number(),
  amount: z.number().positive()});
 
// Response type
interface PaymentIntentResponse {
  clientSecret: string | null;
  amount: number;
  orderId: number;
}
 
// POST /api/payments/create-intent - Create Stripe payment intent
async function handlePost(
  request: NextRequest,
  _context: RouteContext | undefined,
  session: Session
): Promise<NextResponse<ApiSuccessResponse<PaymentIntentResponse> | ApiErrorResponse>> {
  const body = await request.json();
 
  // Validate input
  const validationResult = IntentSchema.safeParse(body);
  if (!validationResult.success) {
    throw ApiError.validation("Invalid data", validationResult.error.issues);
  }
  const validatedData = validationResult.data;
 
  // Verify order exists and belongs to user
  const order = await prisma.order.findUnique({
    where: { id: validatedData.orderId }});
 
  if (!order) {
    throw ApiError.notFound("Order", validatedData.orderId);
  }
 
  if (order.userId !== session.user.id) {
    throw ApiError.forbidden("You do not have access to this order");
  }
 
  // Create Stripe payment intent
  const paymentIntent = await getStripe().paymentIntents.create({
    amount: Math.round(validatedData.amount * 100), // Convert to cents
    currency: "usd",
    metadata: {
      orderId: validatedData.orderId.toString(),
      userId: session.user.id.toString()}});
 
  return successResponse({
    clientSecret: paymentIntent.client_secret,
    amount: validatedData.amount,
    orderId: validatedData.orderId});
}
 
export const POST = withErrorHandling(withAuth(handlePost));