So I am using Firebase and Stripe extension for handling subscribers in my SaaS.
The user registers, successfully adding a customerID and stripeID with their customerURL in Firestore, but later when the user purchases a subscription, only the payment intent gets updated in Stripe, nothing else, the Firestore does not get updated either.
Whats wrong with the code here?
// External Imports.
import { getStripePayments } from "@stripe/firestore-stripe-payments";
import { createCheckoutSession } from "@stripe/firestore-stripe-payments";
import { getApp } from "@firebase/app";
import { getFirestore, addDoc, onSnapshot } from "firebase/firestore";
import { getAuth } from "firebase/auth";
const express = require("express");
const bodyParser = require("body-parser");
const stripe = require("stripe")(process.env.STRIPE_SDK_KEY);
const firebaseAdmin = require("firebase-admin");
const firebaseServiceAcc = require("../../firebase.serviceAcc.json");
const db = getFirestore();
const auth = getAuth();
const currentUser = auth.currentUser;
// 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();
const session = await createCheckoutSession(payments, {
price: myPriceId,
});
const createCheckoutSession = async () => {
try {
const docRef = await addDoc(
collection(db, 'subscriptions', currentUser.uid, 'checkout_sessions'),
{
price: 'placeholder',
success_url: window.location.origin,
cancel_url: window.location.origin,
}
);
// Wait for the CheckoutSession to get attached by the extension
onSnapshot(docRef, (snap) => {
const { error, url } = snap.data();
if (error) {
// Show an error to your customer and
// inspect your Cloud Function logs in the Firebase console.
alert(`An error occurred: ${error.message}`);
}
if (url) {
// We have a Stripe Checkout URL, let's redirect.
window.location.assign(url);
}
});
} catch (error) {
console.error("Error creating checkout session:", error);
// Handle error as needed
}
};
// Call the function to create the checkout session
createCheckoutSession();
onCurrentUserSubscriptionUpdate(
payments,
(snapshot) => {
for (const change in snapshot.changes) {
if (change.type === "added") {
console.log(`New subscription added with ID: ${change.subscription.id}`);
}
}
}
);
window.location.assign(session.url);
// 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)
}
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;