vercel / nextjs-subscription-payments

Clone, deploy, and fully customize a SaaS subscription application with Next.js.
https://subscription-payments.vercel.app/
MIT License
6.39k stars 1.3k forks source link

Unable to make requests to Supabase from Route Handler #264

Closed commandcenterio closed 10 months ago

commandcenterio commented 10 months ago

Hello, I am facing an odd issue that is preventing us from releasing our application.

Problem

Using the app/api/webhooks/route.ts I am able to successfully capture the data from Stripe. The issue comes after I create a new value based on the data from stripe, I take that new value and try to insert it in a table in Supabase. I have used createClient from '@supabase/supabase-js' to create a client using my service role key (i verified it is present in my env vars). I have logged out the class in prod and confirmed it is initialized w/ the correct keys.

When I make a call to get data from a table in supabase the following happens:

Our current code works perfectly fine locally, these issues are only present in production. This is our last blocker before we release. Any help would be greatly appreciated.

My route.ts:

export async function POST(req: Request) {
  const body = await req.text();
  const sig = headers().get('Stripe-Signature') as string;
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
  let event: Stripe.Event;

  try {
    if (!sig || !webhookSecret) return;
    event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
  } catch (err: any) {
    console.error(`❌ Error message: ${err.message}`);
    return new Response(`Webhook Error: ${err.message}`, { status: 400 });
  }

  // Note: supabaseAdmin uses the SERVICE_ROLE_KEY which you must only use in a secure server-side context
  // as it has admin privileges and overwrites RLS policies!
  const supabaseAdmin = createClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL || '',
    process.env.SUPABASE_SERVICE_ROLE_KEY || '',
    {
      auth: {
        persistSession: false,
        autoRefreshToken: false,
        detectSessionInUrl: false
      }
    }
  );

  if (relevantEvents.has(event.type)) {
    try {
      switch (event.type) {
        case 'invoice.payment_succeeded':
          const checkoutSession = event.data.object as Stripe.Checkout.Session;
          console.log('checkoutSession -> Customer ID: ', checkoutSession.customer);
          const invoice = event.data.object as Stripe.Invoice;
          const invoiceBillingReason = invoice.billing_reason;
          console.log('invoiceBillingReason: ', invoiceBillingReason);
          if (invoiceBillingReason === 'subscription_create' || invoiceBillingReason === 'subscription_cycle') {
            await updateSupportHours(invoice, checkoutSession, supabaseAdmin);
          }
          break;
        default:
          throw new Error('Unhandled relevant event!');
      }
    } catch (error) {
      console.error(error);
      return new Response(
        'Webhook handler failed. View your nextjs function logs.',
        {
          status: 400
        }
      );
    }
  }
  return new Response(JSON.stringify({ received: true }));
}

the error happens from:

const { data, error } = await supabaseAdmin
            .from('projects')
            .select()

which is called in updateSupportHours

Here is the updateSupportHours():

const updateSupportHours = async (
  invoice: Stripe.Invoice,
  checkoutSession: Stripe.Checkout.Session,
  supabaseAdmin: any
) => {
  console.log('hit updateSupportHours...');
  const lineItems = invoice.lines.data;
  let newHours = 0;
  lineItems.forEach( async (
    item: Stripe.InvoiceLineItem,
    index: number,
    array: Stripe.InvoiceLineItem[]
  ) => {
    const priceMetaData = item?.price?.metadata;
    console.log('priceMetaData: ', priceMetaData);
    if (priceMetaData && priceMetaData?.support === 'true') {
      // add all the support hours from the line items
      if (priceMetaData?.supportHours && Number(priceMetaData?.supportHours) > 0) {
        newHours += Number(priceMetaData?.supportHours as string);
        console.log('newHours: ', newHours);
      } else {
        console.log('No support hours found for product');
      }

      if (newHours > 0) {
        // const adjusted = await adjustProjectSupportHoursByCustomerId(
        //   checkoutSession.customer as string,
        //   `-${newHours}`
        // );
        try {
          console.log('getting project info for customer: ', checkoutSession.customer);
          const { data, error } = await supabaseAdmin
            .from('projects')
            .select()
            // .eq('stripe_customer_id', checkoutSession.customer)
            // .eq('active', true)
            // .single();

          console.log('project: ', data);
          console.log('project error: ', error);

          // if (projectError) {
          //   console.error('Error:', projectError);
          //   return false;
          // }
          console.log('updating project hours...');

          // const adjusted = await updateProjectHours(data?.id, `-${newHours}`, supabaseAdmin);
          // console.log('adjusted: ', adjusted);

          // if (!adjusted) {
          //   throw new Error('Support hours not adjusted for customer: ' + checkoutSession.customer);
          // }
          console.log('adjusted hours!');
        } catch (error) {
          console.error('Error adjusting support hours:', error);
          throw error;
        }
      } else {
        console.log('No support hours to adjust');
      }
    }
  });
};
arthureberledev commented 10 months ago

I don't know if that's really the issue but I've heard bad things about using forEach with async/await: you can read more here. Maybe it helps to use an alternative.

commandcenterio commented 10 months ago

@arthureberledev this indeed was the issue. everything works as expected now in prod. what an oversight on part. I really appreciate the suggestion here, I'd love to buy you a coffee or something. this was bugging me for weeks

arthureberledev commented 10 months ago

@arthureberledev this indeed was the issue. everything works as expected now in prod. what an oversight on part. I really appreciate the suggestion here, I'd love to buy you a coffee or something. this was bugging me for weeks

Great to hear that! I have a buymeacoffee link but really, you don't have to buy me anything :D