feat: fixes and formatter

This commit is contained in:
2026-01-12 16:01:32 +01:00
parent 85c6084351
commit 11e632acff
353 changed files with 23315 additions and 27361 deletions
+221 -280
View File
@@ -1,9 +1,9 @@
const { onRequest } = require("firebase-functions/v2/https");
const { HttpsError } = require("firebase-functions/https");
const { onRequest } = require('firebase-functions/v2/https')
const { HttpsError } = require('firebase-functions/https')
const { ORDER_TYPES, createOrderDocument } = require("../helpers/orders");
const { getStripeClient } = require("../../helpers/stripe");
const { REGION } = require("./config");
const { ORDER_TYPES, createOrderDocument } = require('../helpers/orders')
const { getStripeClient } = require('../../helpers/stripe')
const { REGION } = require('./config')
const {
paymentsCollection,
resolveStripeWebhookSecret,
@@ -17,25 +17,21 @@ const {
buildSubscriptionPayload,
resolveUserContext,
upsertPaymentDocument,
} = require("./shared");
const { PREMIUM_SUBSCRIPTION_STATUSES } = require("./constants");
} = require('./shared')
const { PREMIUM_SUBSCRIPTION_STATUSES } = require('./constants')
const handleCheckoutSessionCompleted = async (
session,
event,
{ stripe } = {},
) => {
if (!session || typeof session !== "object") {
return;
const handleCheckoutSessionCompleted = async (session, event, { stripe } = {}) => {
if (!session || typeof session !== 'object') {
return
}
const firebaseUid = extractFirebaseUid(session.metadata);
const firebaseUid = extractFirebaseUid(session.metadata)
const paymentDocRef = await upsertPaymentDocument(session.id, {
userId: firebaseUid || null,
customerId: session.customer || null,
subscriptionId: session.subscription || null,
invoiceId: session.invoice || null,
status: session.status || "completed",
status: session.status || 'completed',
paymentStatus: session.payment_status || null,
mode: session.mode || null,
amountSubtotal: session.amount_subtotal ?? null,
@@ -44,109 +40,103 @@ const handleCheckoutSessionCompleted = async (
metadata: session.metadata || {},
completedAt: toFirestoreTimestamp(session.created),
expiresAt: toFirestoreTimestamp(session.expires_at),
paymentIntentId:
typeof session.payment_intent === "string"
? session.payment_intent
: null,
paymentIntentId: typeof session.payment_intent === 'string' ? session.payment_intent : null,
lastEventType: event?.type || null,
lastEventId: event?.id || null,
lastEventAt: getServerTimestamp(),
});
})
const { uid, userRef } = await resolveUserContext({
metadata: session.metadata,
customerId: session.customer,
});
})
if (userRef) {
const lastEvent = buildEventSnapshot(event?.type, session.id);
const lastEvent = buildEventSnapshot(event?.type, session.id)
if (firebaseUid) {
lastEvent.uid = firebaseUid;
lastEvent.uid = firebaseUid
}
const userUpdate = {
lastStripeWebhookEvent: lastEvent,
};
}
if (session.customer) {
userUpdate.stripeCustomerId = session.customer;
userUpdate.stripeCustomerId = session.customer
}
if (session.metadata?.subscriptionLevel) {
userUpdate.premiumLevel = session.metadata.subscriptionLevel;
userUpdate.premiumLevel = session.metadata.subscriptionLevel
}
if (session.metadata?.subscriptionBillingPeriod) {
userUpdate.premiumBillingPeriod =
session.metadata.subscriptionBillingPeriod;
userUpdate.premiumBillingPeriod = session.metadata.subscriptionBillingPeriod
}
await userRef.set(userUpdate, { merge: true });
await userRef.set(userUpdate, { merge: true })
}
if (
stripe &&
session.mode === "subscription" &&
typeof session.subscription === "string" &&
session.mode === 'subscription' &&
typeof session.subscription === 'string' &&
session.subscription
) {
try {
const subscription = await stripe.subscriptions.retrieve(
session.subscription,
{ expand: ["items.data.price.product"] },
);
const subscription = await stripe.subscriptions.retrieve(session.subscription, {
expand: ['items.data.price.product'],
})
if (subscription) {
await handleCustomerSubscriptionEvent(subscription, event, { stripe });
await handleCustomerSubscriptionEvent(subscription, event, { stripe })
}
} catch (error) {
console.error(
"[subscription-handleCheckoutSessionCompleted] Unable to sync subscription",
'[subscription-handleCheckoutSessionCompleted] Unable to sync subscription',
session.subscription,
error,
);
error
)
}
}
if (
session.mode === "payment" &&
(session.payment_status === "paid" ||
session.payment_status === "no_payment_required") &&
session.metadata?.purchaseType === "COIN_PACK" &&
session.mode === 'payment' &&
(session.payment_status === 'paid' || session.payment_status === 'no_payment_required') &&
session.metadata?.purchaseType === 'COIN_PACK' &&
userRef
) {
const coinAmountRaw = Number(session.metadata?.coinAmount || 0);
const coinAmount = Number.isFinite(coinAmountRaw) ? coinAmountRaw : 0;
const coinAmountRaw = Number(session.metadata?.coinAmount || 0)
const coinAmount = Number.isFinite(coinAmountRaw) ? coinAmountRaw : 0
if (coinAmount > 0 && paymentDocRef) {
let paymentSnapshot = null;
let paymentSnapshot = null
try {
paymentSnapshot = await paymentDocRef.get();
paymentSnapshot = await paymentDocRef.get()
} catch (error) {
console.warn(
"[subscription-handleCheckoutSessionCompleted] Unable to read payment doc",
'[subscription-handleCheckoutSessionCompleted] Unable to read payment doc',
session.id,
error?.message || error,
);
error?.message || error
)
}
const alreadyGranted = Boolean(
paymentSnapshot?.exists && paymentSnapshot.data()?.coinPackGrantedAt,
);
paymentSnapshot?.exists && paymentSnapshot.data()?.coinPackGrantedAt
)
if (!alreadyGranted) {
const targetUserId = uid || firebaseUid || userRef.id;
const targetUserId = uid || firebaseUid || userRef.id
await createOrderDocument({
userId: targetUserId,
type: ORDER_TYPES.COINS,
amount: coinAmount,
metadata: {
source: "STRIPE_CHECKOUT",
source: 'STRIPE_CHECKOUT',
paymentId: session.id || null,
coinPackKey: session.metadata?.coinPackKey || null,
},
orderId: `stripe_${session.id}`,
});
})
await paymentDocRef.set(
{
@@ -154,25 +144,21 @@ const handleCheckoutSessionCompleted = async (
coinPackGrantedAmount: coinAmount,
coinPackGrantedKey: session.metadata?.coinPackKey || null,
},
{ merge: true },
);
{ merge: true }
)
}
}
}
};
}
const handleCustomerSubscriptionEvent = async (
subscription,
event,
{ stripe } = {},
) => {
if (!subscription || typeof subscription !== "object") {
return;
const handleCustomerSubscriptionEvent = async (subscription, event, { stripe } = {}) => {
if (!subscription || typeof subscription !== 'object') {
return
}
const subscriptionPayload = buildSubscriptionPayload(subscription);
const subscriptionPayload = buildSubscriptionPayload(subscription)
if (!subscriptionPayload) {
return;
return
}
const {
@@ -182,36 +168,36 @@ const handleCustomerSubscriptionEvent = async (
} = await resolveUserContext({
metadata: subscription.metadata,
customerId: subscription.customer,
});
})
let userData = resolvedUserData || null;
let userData = resolvedUserData || null
if (!userData && userRef) {
try {
const snapshot = await userRef.get();
userData = snapshot.exists ? snapshot.data() || null : null;
const snapshot = await userRef.get()
userData = snapshot.exists ? snapshot.data() || null : null
} catch (error) {
console.warn(
"[subscription-handleCustomerSubscriptionEvent] Unable to read user",
'[subscription-handleCustomerSubscriptionEvent] Unable to read user',
subscription.customer,
error?.message || error,
);
error?.message || error
)
}
}
let resolvedCustomerId = subscriptionPayload?.customerId || null;
let resolvedCustomerId = subscriptionPayload?.customerId || null
if (!resolvedCustomerId && subscription.customer) {
resolvedCustomerId = subscription.customer;
resolvedCustomerId = subscription.customer
}
const fallbackUid = extractFirebaseUid(subscription.metadata);
const resolvedUid = uid || fallbackUid || null;
const fallbackUid = extractFirebaseUid(subscription.metadata)
const resolvedUid = uid || fallbackUid || null
await upsertPaymentDocument(subscription.id, {
userId: resolvedUid,
customerId: resolvedCustomerId,
subscriptionId: subscription.id || null,
status: subscription.status || null,
mode: "subscription",
mode: 'subscription',
priceId: subscriptionPayload?.priceId || null,
productId: subscriptionPayload?.productId || null,
cancelAtPeriodEnd: subscriptionPayload?.cancelAtPeriodEnd ?? null,
@@ -224,76 +210,66 @@ const handleCustomerSubscriptionEvent = async (
lastEventType: event?.type || null,
lastEventId: event?.id || null,
lastEventAt: getServerTimestamp(),
});
})
if (!userRef) {
console.warn(
"[subscription-handleCustomerSubscriptionEvent] User not resolved",
{
subscriptionId: subscription?.id || null,
customerId: subscription?.customer || null,
metadataKeys: Object.keys(subscription?.metadata || {}),
eventType: event?.type || null,
},
);
return;
console.warn('[subscription-handleCustomerSubscriptionEvent] User not resolved', {
subscriptionId: subscription?.id || null,
customerId: subscription?.customer || null,
metadataKeys: Object.keys(subscription?.metadata || {}),
eventType: event?.type || null,
})
return
}
const lastEvent = buildEventSnapshot(event?.type, subscription.id);
const lastEvent = buildEventSnapshot(event?.type, subscription.id)
if (resolvedUid) {
lastEvent.uid = resolvedUid;
lastEvent.uid = resolvedUid
}
const priceMeta = getSubscriptionMetaFromPrice(subscriptionPayload?.priceId);
const metadataLevel = subscription.metadata?.subscriptionLevel || null;
const metadataPeriod =
subscription.metadata?.subscriptionBillingPeriod || null;
const resolvedLevel = metadataLevel || priceMeta?.level || null;
const resolvedPeriod = metadataPeriod || priceMeta?.billingPeriod || null;
const priceMeta = getSubscriptionMetaFromPrice(subscriptionPayload?.priceId)
const metadataLevel = subscription.metadata?.subscriptionLevel || null
const metadataPeriod = subscription.metadata?.subscriptionBillingPeriod || null
const resolvedLevel = metadataLevel || priceMeta?.level || null
const resolvedPeriod = metadataPeriod || priceMeta?.billingPeriod || null
const isPremium = subscription.status
? PREMIUM_SUBSCRIPTION_STATUSES.has(subscription.status)
: false;
: false
const primaryItem = Array.isArray(subscriptionPayload?.items)
? subscriptionPayload.items[0]
: null;
: null
let coinsPerMonth = null;
let coinsPerMonth = null
const productMetadata =
primaryItem?.price &&
typeof primaryItem.price === "object" &&
typeof primaryItem.price === 'object' &&
primaryItem.price.product &&
typeof primaryItem.price.product === "object"
typeof primaryItem.price.product === 'object'
? primaryItem.price.product.metadata
: null;
: null
coinsPerMonth = parseCoinsPerMonth(productMetadata || {});
coinsPerMonth = parseCoinsPerMonth(productMetadata || {})
if (coinsPerMonth === null && stripe && primaryItem?.price?.id) {
try {
const priceWithProduct = await stripe.prices.retrieve(
primaryItem.price.id,
{ expand: ["product"] },
);
coinsPerMonth = parseCoinsPerMonth(
priceWithProduct?.product?.metadata || {},
);
const priceWithProduct = await stripe.prices.retrieve(primaryItem.price.id, {
expand: ['product'],
})
coinsPerMonth = parseCoinsPerMonth(priceWithProduct?.product?.metadata || {})
} catch (error) {
console.warn(
"[subscription-handleCustomerSubscriptionEvent] Unable to retrieve price product metadata",
'[subscription-handleCustomerSubscriptionEvent] Unable to retrieve price product metadata',
primaryItem.price.id,
error?.message || error,
);
error?.message || error
)
}
}
let subscriptionNextGrantAt = null;
let subscriptionNextGrantAt = null
if (coinsPerMonth !== null && subscriptionPayload?.currentPeriodEnd) {
subscriptionNextGrantAt = computeNextGrantTimestamp(
subscriptionPayload.currentPeriodEnd,
1,
);
subscriptionNextGrantAt = computeNextGrantTimestamp(subscriptionPayload.currentPeriodEnd, 1)
}
const userUpdate = {
@@ -313,53 +289,46 @@ const handleCustomerSubscriptionEvent = async (
premiumLevel: isPremium ? resolvedLevel : null,
premiumBillingPeriod: isPremium ? resolvedPeriod : null,
subscriptionNextGrantAt,
};
}
if (!isPremium) {
userUpdate.subscriptionNextGrantAt = null;
userUpdate.subscriptionNextGrantAt = null
}
await userRef.set(userUpdate, { merge: true });
};
await userRef.set(userUpdate, { merge: true })
}
const handleInvoiceEvent = async (invoice, event, { stripe } = {}) => {
if (!invoice || typeof invoice !== "object") {
return;
if (!invoice || typeof invoice !== 'object') {
return
}
const firebaseUid = extractFirebaseUid(invoice.metadata);
const eventType = event?.type || null;
const paymentDocRef = paymentsCollection.doc(invoice.id);
const firebaseUid = extractFirebaseUid(invoice.metadata)
const eventType = event?.type || null
const paymentDocRef = paymentsCollection.doc(invoice.id)
let resolvedSubscriptionId =
typeof invoice.subscription === "string" && invoice.subscription
? invoice.subscription
: null;
typeof invoice.subscription === 'string' && invoice.subscription ? invoice.subscription : null
if (!resolvedSubscriptionId) {
const lineSubscriptionId = Array.isArray(invoice?.lines?.data)
? invoice.lines.data
.map((line) =>
typeof line?.subscription === "string" && line.subscription
? line.subscription
: null,
typeof line?.subscription === 'string' && line.subscription ? line.subscription : null
)
.find((value) => value)
: null;
: null
if (lineSubscriptionId) {
resolvedSubscriptionId = lineSubscriptionId;
console.log(
"[subscription-handleInvoiceEvent] Subscription resolved from invoice line",
{
invoiceId: invoice?.id || null,
subscriptionId: resolvedSubscriptionId,
},
);
resolvedSubscriptionId = lineSubscriptionId
console.log('[subscription-handleInvoiceEvent] Subscription resolved from invoice line', {
invoiceId: invoice?.id || null,
subscriptionId: resolvedSubscriptionId,
})
}
}
console.log("[subscription-handleInvoiceEvent] Received invoice webhook", {
console.log('[subscription-handleInvoiceEvent] Received invoice webhook', {
eventType,
invoiceId: invoice?.id || null,
subscriptionId: invoice?.subscription || null,
@@ -368,17 +337,14 @@ const handleInvoiceEvent = async (invoice, event, { stripe } = {}) => {
status: invoice?.status || null,
billingReason: invoice?.billing_reason || null,
attemptCount: invoice?.attempt_count ?? null,
});
})
await upsertPaymentDocument(invoice.id, {
userId: firebaseUid || null,
customerId: invoice.customer || null,
subscriptionId: resolvedSubscriptionId,
status: invoice.status || null,
paymentStatus:
eventType === "invoice.payment_failed"
? "failed"
: invoice.status || null,
paymentStatus: eventType === 'invoice.payment_failed' ? 'failed' : invoice.status || null,
amountDue: invoice.amount_due ?? null,
amountPaid: invoice.amount_paid ?? null,
amountRemaining: invoice.amount_remaining ?? null,
@@ -394,8 +360,8 @@ const handleInvoiceEvent = async (invoice, event, { stripe } = {}) => {
lastEventType: eventType,
lastEventId: event?.id || null,
lastEventAt: getServerTimestamp(),
mode: "invoice",
});
mode: 'invoice',
})
const {
uid,
@@ -404,124 +370,109 @@ const handleInvoiceEvent = async (invoice, event, { stripe } = {}) => {
} = await resolveUserContext({
metadata: invoice.metadata,
customerId: invoice.customer,
});
})
if (!userRef) {
console.warn(
"[subscription-handleInvoiceEvent] User context not resolved",
{
invoiceId: invoice?.id || null,
customerId: invoice?.customer || null,
firebaseUid: firebaseUid || null,
metadataKeys: Object.keys(invoice?.metadata || {}),
},
);
return;
console.warn('[subscription-handleInvoiceEvent] User context not resolved', {
invoiceId: invoice?.id || null,
customerId: invoice?.customer || null,
firebaseUid: firebaseUid || null,
metadataKeys: Object.keys(invoice?.metadata || {}),
})
return
}
let userData = resolvedUserData || null;
let userData = resolvedUserData || null
if (!userData) {
try {
const snapshot = await userRef.get();
userData = snapshot.exists ? snapshot.data() || null : null;
const snapshot = await userRef.get()
userData = snapshot.exists ? snapshot.data() || null : null
} catch (error) {
console.warn(
"[subscription-handleInvoiceEvent] Unable to read user",
'[subscription-handleInvoiceEvent] Unable to read user',
invoice.customer,
error?.message || error,
);
error?.message || error
)
}
}
const lastEvent = buildEventSnapshot(eventType, invoice.id);
const lastEvent = buildEventSnapshot(eventType, invoice.id)
if (firebaseUid) {
lastEvent.uid = firebaseUid;
lastEvent.uid = firebaseUid
}
const userUpdate = {
lastStripeWebhookEvent: lastEvent,
};
}
const billingReason = invoice.billing_reason || null;
const isInvoicePaid = invoice.status === "paid";
if (
billingReason === "subscription_create" ||
billingReason === "subscription_cycle"
) {
const billingReason = invoice.billing_reason || null
const isInvoicePaid = invoice.status === 'paid'
if (billingReason === 'subscription_create' || billingReason === 'subscription_cycle') {
if (isInvoicePaid && invoice.subscription) {
userUpdate.subscriptionLastInvoiceAt = getServerTimestamp();
userUpdate.subscriptionLastInvoiceAt = getServerTimestamp()
}
}
await userRef.set(userUpdate, { merge: true });
await userRef.set(userUpdate, { merge: true })
const subscriptionLine = Array.isArray(invoice?.lines?.data)
? invoice.lines.data.find(
(line) =>
line &&
typeof line === "object" &&
(line.type === "subscription" || line.price),
(line) => line && typeof line === 'object' && (line.type === 'subscription' || line.price)
)
: null;
: null
const priceId =
typeof subscriptionLine?.price?.id === "string"
typeof subscriptionLine?.price?.id === 'string'
? subscriptionLine.price.id
: typeof subscriptionLine?.price === "string"
: typeof subscriptionLine?.price === 'string'
? subscriptionLine.price
: null;
: null
const priceMeta = getSubscriptionMetaFromPrice(priceId) || {};
const priceMeta = getSubscriptionMetaFromPrice(priceId) || {}
const billingPeriod =
priceMeta.billingPeriod ||
(subscriptionLine?.price?.recurring?.interval === "year"
? "annual"
: subscriptionLine?.price?.recurring?.interval === "month"
? "monthly"
: null);
(subscriptionLine?.price?.recurring?.interval === 'year'
? 'annual'
: subscriptionLine?.price?.recurring?.interval === 'month'
? 'monthly'
: null)
const coinsPerMonth =
priceMeta.coinsPerMonth ??
parseCoinsPerMonth(subscriptionLine?.price?.product?.metadata || {}) ??
null;
null
const coinsToGrant =
billingPeriod === "annual" && typeof coinsPerMonth === "number"
? coinsPerMonth * 12
: null;
billingPeriod === 'annual' && typeof coinsPerMonth === 'number' ? coinsPerMonth * 12 : null
const targetSubscriptionId =
resolvedSubscriptionId ||
(typeof invoice.subscription === "string" ? invoice.subscription : null) ||
(typeof subscriptionLine?.subscription === "string"
? subscriptionLine.subscription
: null);
(typeof invoice.subscription === 'string' ? invoice.subscription : null) ||
(typeof subscriptionLine?.subscription === 'string' ? subscriptionLine.subscription : null)
const shouldGrantUpfront =
isInvoicePaid &&
coinsToGrant &&
(billingReason === "subscription_create" ||
billingReason === "subscription_cycle");
(billingReason === 'subscription_create' || billingReason === 'subscription_cycle')
if (shouldGrantUpfront && targetSubscriptionId) {
let grantSnapshot = null;
let grantSnapshot = null
try {
grantSnapshot = await paymentDocRef.get();
grantSnapshot = await paymentDocRef.get()
} catch (error) {
console.warn(
"[subscription-handleInvoiceEvent] Unable to read payment doc before grant",
'[subscription-handleInvoiceEvent] Unable to read payment doc before grant',
invoice?.id || null,
error?.message || error,
);
error?.message || error
)
}
const alreadyGranted =
grantSnapshot?.exists &&
Boolean(grantSnapshot.data()?.subscriptionCoinsGrantedAt);
grantSnapshot?.exists && Boolean(grantSnapshot.data()?.subscriptionCoinsGrantedAt)
if (!alreadyGranted) {
const targetUserId = uid || firebaseUid || userRef.id;
const orderId = `subscription_${targetSubscriptionId}_invoice_${invoice.id}`;
const targetUserId = uid || firebaseUid || userRef.id
const orderId = `subscription_${targetSubscriptionId}_invoice_${invoice.id}`
try {
const { orderId: processedOrderId } = await createOrderDocument({
@@ -529,139 +480,129 @@ const handleInvoiceEvent = async (invoice, event, { stripe } = {}) => {
type: ORDER_TYPES.SUBSCRIPTION,
amount: coinsToGrant,
metadata: {
source: "STRIPE_INVOICE",
source: 'STRIPE_INVOICE',
billingPeriod,
coinsPerMonth,
invoiceId: invoice.id || null,
subscriptionId: targetSubscriptionId,
grantStrategy: "upfront",
grantStrategy: 'upfront',
},
orderId,
});
})
await paymentDocRef.set(
{
subscriptionCoinsGrantedAt: getServerTimestamp(),
subscriptionCoinsGrantAmount: coinsToGrant,
subscriptionCoinsGrantOrderId: processedOrderId,
subscriptionCoinsGrantSource: "invoice_upfront",
subscriptionCoinsGrantSource: 'invoice_upfront',
},
{ merge: true },
);
{ merge: true }
)
await userRef.set(
{
subscriptionLastGrantAt: getServerTimestamp(),
subscriptionLastGrantAmount: coinsToGrant,
subscriptionLastGrantOrderId: processedOrderId,
subscriptionLastGrantSource: "invoice_upfront",
subscriptionLastGrantSource: 'invoice_upfront',
subscriptionNextGrantAt: null,
subscriptionGrantInterval: null,
subscriptionGrantStrategy: "upfront",
subscriptionGrantStrategy: 'upfront',
subscriptionCoinsPerMonth: coinsPerMonth,
},
{ merge: true },
);
{ merge: true }
)
} catch (error) {
console.error(
"[subscription-handleInvoiceEvent] Unable to grant upfront subscription coins",
'[subscription-handleInvoiceEvent] Unable to grant upfront subscription coins',
{
invoiceId: invoice?.id || null,
subscriptionId: targetSubscriptionId,
error: error?.message || error,
},
);
}
)
}
}
}
};
}
const handleStripeWebhookEvent = async ({ event, stripe }) => {
if (!event || typeof event !== "object") {
return;
if (!event || typeof event !== 'object') {
return
}
const eventType = event.type;
const eventType = event.type
switch (eventType) {
case "checkout.session.completed":
case 'checkout.session.completed':
await handleCheckoutSessionCompleted(event.data?.object, event, {
stripe,
});
break;
case "customer.subscription.created":
case "customer.subscription.updated":
case "customer.subscription.deleted":
})
break
case 'customer.subscription.created':
case 'customer.subscription.updated':
case 'customer.subscription.deleted':
await handleCustomerSubscriptionEvent(event.data?.object, event, {
stripe,
});
break;
case "invoice.payment_succeeded":
case "invoice.payment_failed":
case "invoice.finalized":
await handleInvoiceEvent(event.data?.object, event, { stripe });
break;
})
break
case 'invoice.payment_succeeded':
case 'invoice.payment_failed':
case 'invoice.finalized':
await handleInvoiceEvent(event.data?.object, event, { stripe })
break
default:
console.log(
"[subscription-handleStripeWebhookEvent] Unhandled event type",
eventType,
);
console.log('[subscription-handleStripeWebhookEvent] Unhandled event type', eventType)
}
};
}
const handleStripeWebhook = onRequest({ region: REGION }, async (req, res) => {
if (req.method !== "POST") {
res.status(405).send("Method Not Allowed");
return;
if (req.method !== 'POST') {
res.status(405).send('Method Not Allowed')
return
}
const signature = req.headers["stripe-signature"];
const signature = req.headers['stripe-signature']
if (!signature) {
res.status(400).send("Missing Stripe signature");
return;
res.status(400).send('Missing Stripe signature')
return
}
let rawBody = req.rawBody;
let rawBody = req.rawBody
if (!rawBody && req.body) {
rawBody = Buffer.from(JSON.stringify(req.body));
rawBody = Buffer.from(JSON.stringify(req.body))
}
if (!rawBody) {
res.status(400).send("Missing request body");
return;
res.status(400).send('Missing request body')
return
}
let stripe = null;
let stripe = null
try {
stripe = getStripeClient();
stripe = getStripeClient()
} catch (error) {
console.error(
"[subscription-handleStripeWebhook] Stripe client error",
error,
);
res.status(500).send("Client Stripe indisponible");
return;
console.error('[subscription-handleStripeWebhook] Stripe client error', error)
res.status(500).send('Client Stripe indisponible')
return
}
try {
const event = stripe.webhooks.constructEvent(
rawBody,
signature,
resolveStripeWebhookSecret(),
);
const event = stripe.webhooks.constructEvent(rawBody, signature, resolveStripeWebhookSecret())
await handleStripeWebhookEvent({ event, stripe });
await handleStripeWebhookEvent({ event, stripe })
res.status(200).send({ received: true });
res.status(200).send({ received: true })
} catch (error) {
console.error("[subscription-handleStripeWebhook] error", error);
console.error('[subscription-handleStripeWebhook] error', error)
if (error instanceof HttpsError) {
res.status(400).send(error.message);
return;
res.status(400).send(error.message)
return
}
res.status(500).send("Erreur lors du traitement du webhook");
res.status(500).send('Erreur lors du traitement du webhook')
}
});
})
module.exports = {
handleStripeWebhook,
};
}