I am using Stripe payments and firebase cloud functions to create a checkout session. I am using a prebuilt checkout. The function I've created listens to the checkout.session.completed webhook endpoint. Everything works fine except the same event fires multiple times at different times. I've spoken with Stripe's support team and they told me that this is expected behavior and I can prevent this by logging the events I’ve processed, and then not processing already-logged events. To be honest I have no idea what this means and how I should do that. They provided me with this link. The problem I have is that I am writing the same document 3-4 times instead of only once. All I want to know is how do I handle the duplication. I have no idea where to start.
I finished the payment checkout payment successfully at 4:01:48PM, the other calls I haven't done anything to trigger them. Didn't touch a thing.
Here is proff that the payment was done only once at 4:01PM.

And these are logs from firebase cloud functions.

This is the code I am using for the functions. I must admit the async/await calls might be wrong but I don't know any better.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.createStripeCheckout = functions
.region("europe-west3")
.https.onCall(async (data, context) => {
const lineItems = [];
const bar = new Promise((resolve, reject) => {
data.items.forEach((prodData, index, array) => {
const productID = prodData.productID;
const size = prodData.size;
const color = prodData.color;
const quantity = prodData.quantity;
let collec;
let name;
let price;
let type;
let description;
const image = [];
if (productID.charAt(0) == "0") {
collec = "women";
} else {
collec = "men";
}
admin
.firestore()
.collection(collec)
.doc(productID)
.get()
.then((qdata) => {
name = qdata.data().name;
price = qdata.data().price * 100;
if (productID.charAt(0) == "0") {
type = "Women's " + qdata.data().type;
} else {
type = "Men's " + qdata.data().type;
}
description = type + " - " + size + " - " + color + " " +
"Product ID: " + productID;
image.push("https://timedshop.b-cdn.net/"+productID+"01.jpg");
const obj = {
price_data: {
currency: "eur",
product_data: {
name: name,
description: description,
images: image,
},
unit_amount: price,
},
adjustable_quantity: {
enabled: true,
maximum: 5,
minimum: 1,
},
quantity: quantity,
};
lineItems.push(obj);
if (index === array.length -1) resolve();
});
});
});
let session;
await bar.then(async () => {
const stripe = require("stripe")(functions.config().stripe.secret_key);
session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
shipping_rates: ["<<key hidden by me>>"],
shipping_address_collection: {
allowed_countries: ["RO"],
},
mode: "payment",
success_url: "http://localhost:5500/success",
cancel_url: "http://localhost:5500/cancel",
line_items: lineItems,
metadata: {"userID": data.userID},
phone_number_collection: {
enabled: true,
},
});
});
return {
id: session.id,
};
});
exports.stripeWebhook = functions
.region("europe-west3")
.https.onRequest(async (req, res) => {
const getItems = (checkoutSessionID) => {
return new Promise((resolve, reject) => {
stripe.checkout.sessions.listLineItems(
checkoutSessionID,
(err, lineItems) => {
if (err) {
return reject(err);
}
resolve(lineItems);
},
);
});
};
let event;
let stripe;
try {
stripe = require("stripe")(functions.config().stripe.secret_key);
const whSec = functions.config().stripe.payments_webhook_secret;
event = stripe.webhooks.constructEvent(
req.rawBody,
req.headers["stripe-signature"],
whSec,
);
} catch (err) {
console.error("Webhook signature verification failed.");
return res.sendStatus(400);
}
if (event.type === "checkout.session.completed") {
const dataObject = event.data.object;
const items = await getItems(dataObject.id);
let product;
const products = [];
for (let i=0; i<items.data.length; i++) {
product = await stripe.products.retrieve(items.data[i].price.product);
const obj = {
name: product.name,
description: product.description,
images: product.images,
};
products.push(obj);
}
await admin.firestore().collection("orders").doc().set({
checkoutSessionId: dataObject.id,
paymentStatus: dataObject.payment_status,
shippingInfo: dataObject.shipping,
currency: dataObject.currency,
amountTotal: dataObject.amount_total,
email: dataObject.customer_details.email,
phone: dataObject.customer_details.phone,
items: items,
desc_img: products,
}).then(()=>{
return res.end();
})
.catch((error)=>{
console.error(error);
return res.sendStatus(400);
});
} else {
return res.sendStatus(400);
}
res.end();
});
And this is how I call the function from my front-end:
function callFunction(items, userID) {
const createStripeCheckout = firebase
.app()
.functions("europe-west3")
.httpsCallable("createStripeCheckout");
const stripe = Stripe(
"<<key hidden by me>>"
);
console.log(items.length);
createStripeCheckout({ items: items, userID: userID }).then((response) => {
console.log(response);
const sessionId = response.data.id;
stripe.redirectToCheckout({
sessionId: sessionId,
});
});
}
