Skip to main content
This is a complete working example showing how to handle payment success and gate features in a Next.js app.

API route

Create app/api/subscription/route.ts:
import { NextRequest, NextResponse } from "next/server";

const CUPRICE_BASE = process.env.CUPRICE_BASE!;
const SHARE_ID = process.env.CUPRICE_SHARE_ID!;

export async function GET(req: NextRequest) {
  const sessionId = req.nextUrl.searchParams.get("session_id");
  if (!sessionId) {
    return NextResponse.json({ error: "session_id required" }, { status: 400 });
  }

  // Fetch receipt from Cuprice
  const res = await fetch(
    `${CUPRICE_BASE}/api/stripe/receipt?session_id=${sessionId}&shareId=${SHARE_ID}`
  );
  const receipt = await res.json();
  const metadata = receipt.metadata || {};
  let features: string[] = [];

  // Custom plans: reconstruct featureDetails
  let json = "";
  Object.keys(metadata)
    .filter(k => k.startsWith("featureDetails"))
    .sort()
    .forEach(k => json += metadata[k]);

  if (json) {
    features = JSON.parse(json).map((f: { name: string }) => f.name);
  }

  // Regular plans: look up features by planId
  if (!features.length && metadata.planId) {
    const shareRes = await fetch(`${CUPRICE_BASE}/api/share/${SHARE_ID}`);
    const { plans } = await shareRes.json();
    const plan = plans?.find((p: any) => p.id.toString() === metadata.planId);
    if (plan?.features) {
      features = plan.features.filter((f: any) => f.included).map((f: any) => f.name);
    }
  }

  return NextResponse.json({
    planName: receipt.planName,
    features,
    customPlan: receipt.customPlan || false,
  });
}

Payment handler component

Create app/components/payment-handler.tsx:
"use client";
import { useSearchParams, useRouter } from "next/navigation";
import { useEffect } from "react";

export default function PaymentHandler() {
  const params = useSearchParams();
  const router = useRouter();

  useEffect(() => {
    const payment = params.get("payment");
    const sessionId = params.get("session_id");

    if (payment === "success" && sessionId) {
      fetch(`/api/subscription?session_id=${sessionId}`)
        .then(r => r.json())
        .then(data => {
          // Save to your database or localStorage
          localStorage.setItem("plan_active", "true");
          for (const name of data.features) {
            const key = name.toLowerCase().replace(/\s+/g, "_");
            localStorage.setItem(`feature_${key}`, "true");
          }
          router.push("/dashboard");
        });
    }
  }, [params, router]);

  return null;
}

Feature card component

Create app/components/feature-card.tsx:
"use client";
import { useEffect, useState } from "react";

export default function FeatureCard({
  featureKey,
  title,
  description,
}: {
  featureKey: string;
  title: string;
  description: string;
}) {
  const [enabled, setEnabled] = useState(false);

  useEffect(() => {
    const active = localStorage.getItem("plan_active") === "true";
    const bought = localStorage.getItem(`feature_${featureKey}`) === "true";
    setEnabled(active && bought);
  }, [featureKey]);

  return (
    <div className={`p-6 border rounded ${
      enabled ? "border-green-200 bg-white" : "border-gray-200 bg-gray-50 opacity-75"
    }`}>
      <span className={`text-xs font-bold ${enabled ? "text-green-700" : "text-gray-400"}`}>
        {enabled ? "ENABLED" : "LOCKED"}
      </span>
      <h3 className="font-bold mt-2">{title}</h3>
      <p className="text-sm text-gray-500 mt-1">{description}</p>
    </div>
  );
}
This example uses localStorage for simplicity. In production, store subscription data in your database and verify server-side.