Firebase admin issue in Nextjs/Stripe webhook

28 Views Asked by At

I am trying to use stripe webhook in next js , but I get this error every time:

unable to detect a project id in this environment

I Tried in app router than switched to pages router (in my previous projects it was working fine). I tried to initialize using the admin.json and I tried to initialize from a separate file.

The project infos are pulled from the env file successfully.

webhook code :

import * as admin from "firebase-admin";
import { timeConverter } from "@/utils/timeConverter"; // Assuming this utility is defined elsewhere in your project

import Stripe from "stripe";

const key = process.env.STRIPE_TEST_SECRET_KEY || "";

const stripe = new Stripe(key, {
  apiVersion: "2023-10-16",
});

// Initialize Firebase Admin SDK
if (!admin.apps.length) {
  console.log("FIREBASE_PROJECT_ID:", process.env.FIREBASE_PROJECT_ID);
  console.log("FIREBASE_CLIENT_EMAIL:", process.env.FIREBASE_CLIENT_EMAIL);

  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n"),
    }),
  });
}

const db = admin.firestore();

export const config = {
  api: {
    bodyParser: false, // Disable body parsing
  },
};

export default async function webhook(req: any, res: any) {
  if (req.method !== "POST") {
    return res.status(405).send("Method Not Allowed");
  }

  const data: any = await new Promise((resolve, reject) => {
    let rawBody = "";
    req.on("data", (chunk: any) => {
      rawBody += chunk;
    });
    req.on("end", () => {
      resolve(rawBody);
    });
    req.on("error", (err: any) => {
      reject(err);
    });
  });
  const sig = req.headers["stripe-signature"];

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      data,
      sig,
      process.env.STRIPE_WEBHOOK_KEY!
    );
  } catch (err: any) {
    console.error(`Webhook Error: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  const eventType = event.type;
  const session = event.data.object;
  switch (eventType) {
    case "invoice.payment_failed":
      console.log("Invoice payment failed");

      await handleInvoicePaymentFailed(session);
      break;

    case "invoice.paid":
      console.log("Invoice payment successful", session);

      await handleInvoicePaid(session);
      break;

    case "customer.subscription.deleted":
      console.log("Subscription deleted");
      await handleSubscriptionDeleted(session);
      break;

    default:
      console.log(`Unhandled event type ${eventType}`);
  }

  res.json({ received: true });
}

async function handleInvoicePaymentFailed(session: Stripe.Invoice) {
  const userId =
    session.subscription_details &&
    session.subscription_details.metadata &&
    session.subscription_details.metadata.userId;

  if (userId) {
    const usersRef = db.collection("users");
    const snapshot = await usersRef.where("uid", "==", userId).get();

    if (!snapshot.empty) {
      snapshot.forEach(async (doc) => {
        await usersRef.doc(doc.id).update({
          subscriptionStatus: "Not active",
        });
      });
    }
  }
}

async function handleInvoicePaid(session: Stripe.Invoice) {
  console.log({ session });

  const userId =
    session.subscription_details &&
    session.subscription_details.metadata &&
    session.subscription_details.metadata.userId;
  // const userId = "randomId132456798";

  console.log({ userId });

  if (userId) {
    try {
      // const db = admin.firestore();
      const usersRef = db.collection("users");
      console.log(0);
      const snapshot = await usersRef.where("uid", "==", userId).get();
      console.log(0.5, snapshot.size);

      const subscription = await stripe.subscriptions.retrieve(
        session.subscription as string
      ); // Cast to string if necessary

      console.log(1, { subscription });

      const interval = subscription.items.data[0].price.recurring?.interval;
      console.log(2);

      const subscriptionId = subscription.id;

      console.log("subscription from invoice paid", subscription);
      const productId: any = subscription.items.data[0].price.product;
      const product = productId && (await stripe.products.retrieve(productId));
      const productName = product && product.name;
      const subStatus = subscription.status;
      const price =
        subscription.items.data[0].price.unit_amount &&
        subscription.items.data[0].price.unit_amount / 100;

      console.log({ productName });

      if (!snapshot.empty) {
        snapshot.forEach(async (doc) => {
          await usersRef.doc(doc.id).update({
            subscriptionStatus: subStatus,
            subscriptionId: subscriptionId,
            startDate: timeConverter(subscription.current_period_start),
            endDate: timeConverter(subscription.current_period_end),
            customerId: subscription.customer,
            subType: interval,
            productName: productName,

            amount: price,
          });
        });
      }
    } catch (err) {
      console.log("err", err);
    }
  }
}

async function handleSubscriptionDeleted(subscription: any) {
  console.log({ subscription });
  const userId = subscription && subscription.metadata.userId;

  console.log({ userId });
  // Update the Firestore document for the user with the matching userId
  if (userId) {
    const usersRef = db.collection("users");
    const snapshot = await usersRef.where("uid", "==", userId).get();

    if (!snapshot.empty) {
      snapshot.forEach(async (doc) => {
        await usersRef.doc(doc.id).update({
          subscriptionStatus: "deleted",
        });
      });
    }
  }
}
1

There are 1 best solutions below

0
MALIK forz On

Solved : the issue was that firebase was already initialized using firebase-adapter which I was not aware of as this is a client project ,

I removed the admin initialization and just imported firestore from the file that firebase was initilized on

issue was : duplicate initialization