firebase / extensions

Source code for official Firebase extensions
https://firebase.google.com/products/extensions
Apache License 2.0
882 stars 372 forks source link

Issues with Stripe <-> Firebase integration #1984

Closed Andre1992 closed 3 months ago

Andre1992 commented 3 months ago

Hi,

I have succesfully done all the steps required for the API, SDK, Webhooks, Firebase Stripe Extension, Firestore Rules, everything to work as it should be except this.

When a user register in my web app, a stripeID and stripe customerID (URL to Stripe dashboard for the customer) and metadata firebase ID is created in Firestore which is fine.

But later when the same user subscribes through my stripe product inside my app, firestore does not update the user and a new customer is created with a new customerID in Stripe after checkout session is completed.

Now I have heard multiple reasons for this, that it could be wrong with the firebase extension, wrong with the they create in dashboard, that my back-end doesnt trigger correctly etc.

But so the logs confirm that the problem is that the checkout integration is not connected with the Firestore.

This is such a high priority for me, I would compensate alot for help here.

This is my code:

// External Imports.
const express = require("express");
const bodyParser = require("body-parser");
const stripe = require("stripe")(process.env.STRIPE_SDK_KEY);
const firebaseAdmin = require("firebase-admin");

// Local Imports
const firebaseServiceAcc = require("../../firebase.serviceAcc.json");

// Configuring Firebasebase Admin and Firestore
firebaseAdmin.initializeApp({
  credential: firebaseAdmin.credential.cert(firebaseServiceAcc),
});
const firestore = firebaseAdmin.firestore();

// Configuring the router for this file.
const router = express.Router();

// Handlers.

// This function handles all of stripe webhook calls.
const handleWebhook = async function (req, res) {
  // Getting request headers and body.
  const sig = req.headers["stripe-signature"];
  const payload = req.body;

  if (payload.type === "checkout.session.completed") {
    const event = stripe.webhooks.constructEvent(
      payload,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
    const session = event.data.object;
    const userId = session.customer;
    console.log(userId)
  }

  // Declaring an event variable.

  // try {
  //   const event = stripe.webhooks.constructEvent(
  //     payload,
  //     sig,
  //     process.env.STRIPE_WEBHOOK_SECRET
  //   );
  //   const session = event.data.object;
  //   const userId = session.customer;
  //   // const subscriptions = firestore.collection("subscriptions");
  //   // const userRef = firestore.collection("users").doc(userId);
  //   // const user = await userRef.get();

  //   // Performing an action according to the event type.
  //   switch (event.type) {
  //     // case "invoice.payment_succeeded":
  //     //   // If the user who just paid, doesn't exists in firestore.
  //     //   if (!user.exists) {
  //     //     console.error(
  //     //       `User not found for invoice payment succeeded event. User ID: ${userId}`
  //     //     );

  //     //     return res.json({
  //     //       success: false,
  //     //       message: "User not found for invoice payment succeeded event.",
  //     //     });
  //     //   }

  //       // console.log("Successful Invoice Payment");

  //       // break;

  //     case "checkout.session.completed":
  //       // Create or update the user document in firestore with the subscription status
  //       // const userSubscription = await subscriptions
  //       //   .where("uid", "==", userId)
  //       //   .get();

  //       let success = true;
  //       let message = "SUCESSFUL";

  //       // Adding a new subscription record.
  //       // if (userSubscription.empty) {
  //       //   await subscriptions.add({
  //       //     uid: userId,
  //       //     premiumUser: true,
  //       //   });

  //       //   success = true;
  //       //   message = `Subscription record created for user: ${userId}`;
  //       // }

  //       // Updating the subscription record.
  //       // else {
  //       //   const userSubsDocId = await userSubscription.docs[0].data().id;
  //       //   await subscriptions.doc(userSubsDocId).update({
  //       //     premiumUser: true,
  //       //   });

  //       //   success = true;
  //       //   message = `Subscription record updated for user: ${userId}`;
  //       // }

  //       // Retrieve additional information from the Stripe event
  //       const stripeCustomerId = session.customer;
  //       const stripeSubscriptionId = session.subscription;

  //       // Only update Firestore with Stripe-related fields if they exist
  //       const updateFields = {};

  //       if (stripeCustomerId) {
  //         updateFields.stripeCustomerID = stripeCustomerId;
  //       }

  //       if (stripeSubscriptionId) {
  //         updateFields.stripeSubscriptionID = stripeSubscriptionId;
  //       }

  //       // if (Object.keys(updateFields).length > 0) {
  //       //   const updatedUserSubscription = await subscriptions
  //       //     .where("uid", "==", userId)
  //       //     .get();
  //       //   const updatedUserSubsDocId =
  //       //     await updatedUserSubscription.docs[0].data().id;

  //       //   await subscriptions.doc(updatedUserSubsDocId).update(updateFields);
  //       // }
  //       console.log(session, userId, stripeCustomerId, stripeSubscriptionId)
  //       console.log(`Subscription updated for user: ${userId}`);

  //       return res.json({ success, message });

  //     case "customer.subscription.updated":
  //       // Handle subscription updated event
  //       break;

  //     case "customer.subscription.created":
  //       // Handle subscription created event
  //       break;

  //     default:
  //       console.log(`Unhandled event type: ${event.type}`);
  //   }
  // } catch (err) {
  //   console.error("Webhook error:", err.message);
  //   return res.status(400).send(`Webhook Error: ${err.message}`);
  // }

  // Return a response to acknowledge receipt of the event
  res.json({ received: true });
};

// This function handles /webhook/payment-intent-succeeded endpoint
const handlePaymentIntentSucceed = async function (req, res) {
  // Getting request headers and body.
  const sig = req.headers["stripe-signature"];
  const payload = req.body;
  const { id } = payload;

  const findUserBySubscriptionId = async (subscriptionId) => {
    try {
      const userSnapshot = await firestore
        .collection("users")
        .where("stripeSubscriptionID", "==", subscriptionId)
        .get();

      // User not found
      if (userSnapshot.empty) {
        return null;
      }

      // Return the user data
      return userSnapshot.docs[0].data();
    } catch (error) {
      console.error("Error finding user by subscription:", error);
      throw error;
    }
  };

  try {
    const event = stripe.webhooks.constructEvent(
      payload,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
    const user = await findUserBySubscriptionId(id);

    if (!user) {
      console.error(`User not found for payment_intent ${id}`);

      return res.json({
        success: true,
        message: "User not found, but event acknowledged.",
      });
    }

    switch (event.type) {
      case "payment_intent.succeeded":
        const paymentIntentSucceeded = event.data.object;

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

    response.send();

    res.json({ success: true });
  } catch (error) {
    console.error("Error handling payment_intent.succeeded:", error);
    res.status(500).json({ error: "Internal Server Error" });
  }
};

// This function creates a checkout session for a button click event.
const handleBuyButtonClick = async function (req, res) {
  try {
    const { priceId } = req.body;
    const checkoutSession = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      line_items: [
        {
          // price: priceId,
          price: priceId
          quantity: 1,
        },
      ],
      mode: "payment",
      success_url: `${process.env.CLIENT_URL}/pages/verify-success.html`,
      cancel_url: `${process.env.CLIENT_URL}/pages/verify-cancel.html`,
    });

    res.json({ sessionId: checkoutSession.id });
  } catch (error) {
    console.error("Error handling Buy Button click:", error);
    res.status(500).json({ error: "Internal Server Error" });
  }
};

// Endpoints.

// Stripe webhook endpoint.
router.post(
  "/webhook",
  bodyParser.raw({ type: "application/json" }),
  handleWebhook
);

// Endpoint to specifically handle, payment success.
router.post("/webhook/payment-intent-succeeded", handlePaymentIntentSucceed);

// Buy Button Click Endpoint.
router.post("/create-checkout-session", handleBuyButtonClick);

// Exporting the router.
module.exports = router;
pr-Mais commented 3 months ago

Hi @Andre1992, the extension's repo is https://github.com/invertase/stripe-firebase-extensions, can you please open your issue there.