Manage Subscription Portal

Course: Integrate Stripe Subscriptions with Next.js

Introduction

Once the customer has an active subscription, they'll need some way to manage it: updating payment details, viewing invoices, upgrading/downgrading, or canceling. Instead of building these flows from scratch, Stripe provides the Billing Portal, a secure hosted page where users can manage their subscription.

This API route (/api/stripe/portal) creates a portal session and returns a URL the user can be redirected to. By keeping this logic on the server, you ensure that only authenticated users with valid Stripe customer records can access their portal.

Create API Endpoint

Add the following code to the route.ts file in /api/stripe/portal:

import { NextRequest, NextResponse } from "next/server"
import { getSession } from "@/lib/auth"
import { db } from "@/lib/db"
import { stripe } 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 })
    }

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

    if (!user || !user.stripeCustomerId) {
      return NextResponse.json(
        { error: "No subscription found" },
        { status: 404 }
      )
    }

    // Create portal session
    const portalSession = await stripe.billingPortal.sessions.create({
      customer: user.stripeCustomerId,
      return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
    })

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

The code is relatively straightforward for this endpoint. First, we verify the user is signed in and find them in the database.

We then create a portal session with their Stripe customer ID and also a return url, which is where the user will be sent to when they're done managing their subscription.

The portal url is sent to the front end, which is used to redirect the user. The next step is to build the button in the frontend for the user to click.

Create the Manage Subscription Button

The following is our Manage Subscription Button component:

"use client"

import React, { useState } from "react"

const ManageSubscriptionButton = () => {
  const [loading, setLoading] = useState(false)

  const handleManageSubscription = async () => {
    setLoading(true)

    try {
      const response = await fetch("/api/stripe/portal", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
      })

      const { url } = await response.json()

      if (url) {
        window.location.href = url
      } else {
        throw new Error("No portal URL received")
      }
    } catch (error) {
      console.error("Error creating portal session:", error)
      alert("Something went wrong. Please try again.")
    } finally {
      setLoading(false)
    }
  }

  return (
    <button
      onClick={handleManageSubscription}
      disabled={loading}
      className="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition-colors disabled:opacity-50"
    >
      {loading ? "Loading..." : "Manage Subscription"}
    </button>
  )
}

export default ManageSubscriptionButton

It's simply a button the user can click to redirect them to the portal. Inside the portal they can manage their subscription.

You'll also need to enable the customer portal link to allow customers to manage their subscription here: https://dashboard.stripe.com/test/settings/billing/portal.

You can implement the settings for how they can manage their subscription in the portal.

Back to Course
Placeholder