Create Checkout API Endpoint

Course: Integrate Stripe Subscriptions with Next.js

Introduction

With the front end built, we now need to build the API endpoint to initialize the purchase or checkout process. In this lesson, we’ll walk through the /api/stripe/checkout endpoint. This route ties together authentication, database lookups, and Stripe’s API to create a subscription checkout session.

Create the Checkout Endpoint

Create a route.ts file in /api/stripe/checkout/ and add the following code:

import { NextRequest, NextResponse } from "next/server"
import { getSession } from "@/lib/auth"
import { db } from "@/lib/db"
import { stripe, STRIPE_PRICE_IDS } from "@/lib/stripe"

export async function POST(request: NextRequest) {
  try {
    const session = await getSession()

    if (!session || !session.isLoggedIn || !session.userId) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
    }

    const { priceId } = await request.json()

    if (!priceId || !(priceId in STRIPE_PRICE_IDS)) {
      return NextResponse.json({ error: "Invalid price ID" }, { status: 400 })
    }

    // Get user from database
    const user = await db.user.findUnique({
      where: { id: session.userId },
    })

    if (!user) {
      return NextResponse.json({ error: "User not found" }, { status: 404 })
    }

    // Create or retrieve Stripe customer
    let customerId = user.stripeCustomerId

    if (!customerId) {
      const customer = await stripe.customers.create({
        email: user.email,
        metadata: {
          userId: user.id,
        },
      })
      customerId = customer.id

      await db.user.update({
        where: { id: user.id },
        data: { stripeCustomerId: customerId },
      })
    }

    const checkoutSession = await stripe.checkout.sessions.create({
      customer: customerId,
      payment_method_types: ["card"],
      line_items: [
        {
          price: STRIPE_PRICE_IDS[priceId as keyof typeof STRIPE_PRICE_IDS],
          quantity: 1,
        },
      ],
      mode: "subscription",
      success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
      cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing?canceled=true`,
      metadata: {
        userId: user.id,
        priceId: STRIPE_PRICE_IDS[priceId as keyof typeof STRIPE_PRICE_IDS],
      },
    })

    return NextResponse.json({ url: checkoutSession.url })
  } catch (error) {
    console.error("Error creating checkout session:", error)
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 }
    )
  }
}

Breaking down the code above:

Imports

These are the imports we'll need for this file. The getSession and db imports are from our starter code. You can review that in the authentication course. These functions are used to check if a user is logged in, and to interact with Prisma (our ORM).

Verify Session and Validate Body

We use getSession to verify the user session to ensure that only authenticated users can hit this endpoint. If the session is missing or incomplete we return 401 Unauthorized.

We then validate the body to ensure we are getting a priceId. This will let us know which product they are checking out.

Create or Retrieve Stripe Customer

After that, we'll find the user that is making the request in the database. Then we're going to check and see if the user has a Stripe Customer ID. If they don't, we're going to create one and store it in our database. We'll also add the userId in the metadata for future reference.

Create Stripe Checkout Session

Next we create the Stripe checkout session which will contain the details of the purchase, like the product, payment method and customer information. The line items are what's being purchased, and we also have redirect URL's for if the purchase is successful or is cancelled. The metadata is used to store our own application data, in this case, the userId and priceId in our database.

Return URL

This is the URL for the hosted checkout page from Stripe. We send that to the front end where the user will be redirected.

If we save this and go back into the client, it should redirect us to the payment portal. The next step is to set up a webhook, which is used notify us of a purchase, renewal or cancellation.