Files
musicland/functions/src/subscription/management.js
T
2026-08-25 15:55:11 +02:00

190 lines
5.8 KiB
JavaScript

const { HttpsError, onCall } = require('firebase-functions/https')
const { getStripeClient, mapStripeErrorToHttps } = require('../../helpers/stripe')
const { STRIPE_SECRET_KEY } = require('../../config/secrets')
const { REGION } = require('./config')
const { CANCELABLE_SUBSCRIPTION_STATUSES, ACTIVE_SUBSCRIPTION_STATUSES } = require('./constants')
const { refsList, formatSubscriptionForClient, resolveUserContext } = require('./shared')
const STRIPE_FUNCTION_OPTIONS = {
region: REGION,
cors: true,
invoker: 'public',
secrets: [STRIPE_SECRET_KEY],
}
const cancelActiveSubscription = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try {
const uid = request?.auth?.uid
if (!uid) {
throw new HttpsError('unauthenticated', 'Connecte-toi pour gérer ton abonnement.')
}
const stripe = getStripeClient()
const userRef = refsList?.users?.doc(uid) || null
const snapshot = userRef ? await userRef.get() : null
const userData = snapshot?.exists ? snapshot.data() || {} : {}
const inputSubscriptionId =
typeof request?.data?.subscriptionId === 'string' ? request.data.subscriptionId.trim() : ''
let subscriptionId =
inputSubscriptionId ||
userData?.stripeSubscription?.id ||
userData?.stripeSubscription?.subscriptionId ||
null
const customerId = userData?.stripeCustomerId || null
if (!subscriptionId && customerId) {
try {
const response = await stripe.subscriptions.list({
customer: customerId,
status: 'all',
limit: 5,
})
const { data: subscriptionList = [] } = response || {}
const activeSubscription = subscriptionList.find(
(candidate) => candidate?.status && CANCELABLE_SUBSCRIPTION_STATUSES.has(candidate.status)
)
if (activeSubscription?.id) {
subscriptionId = activeSubscription.id
}
} catch (error) {
console.warn(
'[subscription-cancelActiveSubscription] Unable to list subscriptions',
customerId,
error?.message || error
)
}
}
if (!subscriptionId) {
throw new HttpsError('failed-precondition', 'Aucun abonnement actif à annuler.')
}
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
if (!subscription) {
throw new HttpsError('not-found', 'Abonnement introuvable côté Stripe.')
}
if (subscription.status === 'canceled') {
return {
subscriptionId: subscription.id,
status: subscription.status,
cancelAtPeriodEnd: subscription.cancel_at_period_end === true,
currentPeriodEnd: subscription.current_period_end || null,
alreadyCanceled: true,
}
}
if (subscription.cancel_at_period_end === true) {
return {
subscriptionId: subscription.id,
status: subscription.status,
cancelAtPeriodEnd: true,
currentPeriodEnd: subscription.current_period_end || null,
alreadyCanceled: false,
}
}
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
cancel_at_period_end: true,
})
return {
subscriptionId: updatedSubscription.id,
status: updatedSubscription.status,
cancelAtPeriodEnd: updatedSubscription.cancel_at_period_end === true,
currentPeriodEnd: updatedSubscription.current_period_end || null,
alreadyCanceled: false,
}
} catch (error) {
console.error('[subscription-cancelActiveSubscription] error', error)
if (error instanceof HttpsError) {
throw error
}
throw mapStripeErrorToHttps(error, "Impossible d'annuler l'abonnement Stripe.")
}
})
const getActiveSubscription = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try {
const uid = request?.auth?.uid
if (!uid) {
throw new HttpsError('unauthenticated', 'Connecte-toi pour récupérer ton abonnement.')
}
const stripe = getStripeClient()
const {
uid: resolvedUid,
userRef,
userData,
} = await resolveUserContext({
metadata: request?.data?.metadata || {},
customerId: null,
})
const lookupUid = resolvedUid || uid
const lookupRef = userRef || refsList?.users?.doc(lookupUid) || null
const snapshot = lookupRef ? await lookupRef.get() : null
const data = snapshot?.exists ? snapshot.data() || {} : userData || {}
const subscriptionId =
data?.stripeSubscription?.id || data?.stripeSubscription?.subscriptionId || null
const customerId = data?.stripeCustomerId || null
if (!subscriptionId && !customerId) {
return {
subscription: null,
customerId: null,
}
}
if (subscriptionId) {
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
expand: ['items.data.price'],
})
if (subscription) {
return {
subscription: formatSubscriptionForClient(subscription),
customerId: subscription.customer || customerId || null,
}
}
}
if (customerId) {
const response = await stripe.subscriptions.list({
customer: customerId,
status: 'all',
limit: 5,
expand: ['data.items.data.price'],
})
const [subscription] = response?.data || []
if (subscription && ACTIVE_SUBSCRIPTION_STATUSES.has(subscription.status)) {
return {
subscription: formatSubscriptionForClient(subscription),
customerId,
}
}
}
return {
subscription: null,
customerId,
}
} catch (error) {
console.error('[subscription-getActiveSubscription] error', error)
if (error instanceof HttpsError) {
throw error
}
throw mapStripeErrorToHttps(error, "Impossible de récupérer l'abonnement Stripe.")
}
})
module.exports = {
cancelActiveSubscription,
getActiveSubscription,
}