bookwiz.io / app / api / create-portal-session / route.ts
route.ts
Raw
import { NextResponse } from 'next/server'
import Stripe from 'stripe'
import { createClient } from '@supabase/supabase-js'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2025-05-28.basil',
})

// Create server-side Supabase client with user session (same pattern as other working APIs)
function createServerSupabaseClient(request: Request) {
  const authHeader = request.headers.get('authorization')
  
  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      auth: {
        autoRefreshToken: false,
        persistSession: false
      },
      global: {
        headers: authHeader ? {
          Authorization: authHeader
        } : {}
      }
    }
  )
}

export async function POST(req: Request) {
  try {    
    // Get the authorization header
    const authHeader = req.headers.get('authorization')
    if (!authHeader) {
      return NextResponse.json(
        { error: 'Authorization header required' },
        { status: 401 }
      )
    }

    // Create Supabase client with proper auth context (same as other working APIs)
    const supabase = createServerSupabaseClient(req)

    // Verify the session with Supabase
    const { data: { user }, error: authError } = await supabase.auth.getUser()

    if (authError || !user) {
      return NextResponse.json(
        { error: 'Invalid session' },
        { status: 401 }
      )
    }

    // Get user's subscription to find their Stripe customer ID
    const { data: subscription, error: subscriptionError } = await supabase
      .from('subscriptions')
      .select('id, status, price_id, current_period_start, current_period_end, cancel_at_period_end, stripe_customer_id')
      .eq('user_id', user.id)
      .in('status', ['active', 'trialing', 'past_due']) // Include all manageable statuses
      .order('created_at', { ascending: false })
      .maybeSingle() // Use maybeSingle() instead of single() to handle 0 or 1 rows gracefully

    if (subscriptionError) {
      console.error('Error fetching subscription:', subscriptionError)
      return NextResponse.json(
        { error: 'Error fetching subscription data.' },
        { status: 500 }
      )
    }

    if (!subscription?.stripe_customer_id) {
      return NextResponse.json(
        { error: 'No subscription found. You need an active subscription to manage billing.' },
        { status: 404 }
      )
    }

    // Determine the return URL
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'
    const returnUrl = `${baseUrl}/dashboard/billing`
    
    // Create the portal session
    const portalSession = await stripe.billingPortal.sessions.create({
      customer: subscription.stripe_customer_id,
      return_url: returnUrl,
    })

    return NextResponse.json({ url: portalSession.url })
  } catch (error) {
    console.error('Error creating portal session:', error)
    return NextResponse.json(
      { error: `Error creating portal session: ${error instanceof Error ? error.message : 'Unknown error'}` },
      { status: 500 }
    )
  }
}