export async function onSubscriptionUpdated()

in packages/stripe/src/hooks.ts [91:199]


export async function onSubscriptionUpdated(
	ctx: GenericEndpointContext,
	options: StripeOptions,
	event: Stripe.Event,
) {
	try {
		if (!options.subscription?.enabled) {
			return;
		}
		const subscriptionUpdated = event.data.object as Stripe.Subscription;
		const priceId = subscriptionUpdated.items.data[0].price.id;
		const plan = await getPlanByPriceId(options, priceId);

		const subscriptionId = subscriptionUpdated.metadata?.subscriptionId;
		const customerId = subscriptionUpdated.customer?.toString();
		let subscription = await ctx.context.adapter.findOne<Subscription>({
			model: "subscription",
			where: subscriptionId
				? [{ field: "id", value: subscriptionId }]
				: [{ field: "stripeSubscriptionId", value: subscriptionUpdated.id }],
		});
		if (!subscription) {
			const subs = await ctx.context.adapter.findMany<Subscription>({
				model: "subscription",
				where: [{ field: "stripeCustomerId", value: customerId }],
			});
			if (subs.length > 1) {
				const activeSub = subs.find(
					(sub: Subscription) =>
						sub.status === "active" || sub.status === "trialing",
				);
				if (!activeSub) {
					logger.warn(
						`Stripe webhook error: Multiple subscriptions found for customerId: ${customerId} and no active subscription is found`,
					);
					return;
				}
				subscription = activeSub;
			} else {
				subscription = subs[0];
			}
		}

		const seats = subscriptionUpdated.items.data[0].quantity;
		await ctx.context.adapter.update({
			model: "subscription",
			update: {
				...(plan
					? {
							plan: plan.name.toLowerCase(),
							limits: plan.limits,
						}
					: {}),
				updatedAt: new Date(),
				status: subscriptionUpdated.status,
				periodStart: new Date(
					subscriptionUpdated.items.data[0].current_period_start * 1000,
				),
				periodEnd: new Date(
					subscriptionUpdated.items.data[0].current_period_end * 1000,
				),
				cancelAtPeriodEnd: subscriptionUpdated.cancel_at_period_end,
				seats,
				stripeSubscriptionId: subscriptionUpdated.id,
			},
			where: [
				{
					field: "id",
					value: subscription.id,
				},
			],
		});
		const subscriptionCanceled =
			subscriptionUpdated.status === "active" &&
			subscriptionUpdated.cancel_at_period_end &&
			!subscription.cancelAtPeriodEnd; //if this is true, it means the subscription was canceled before the event was triggered
		if (subscriptionCanceled) {
			await options.subscription.onSubscriptionCancel?.({
				subscription,
				cancellationDetails:
					subscriptionUpdated.cancellation_details || undefined,
				stripeSubscription: subscriptionUpdated,
				event,
			});
		}
		await options.subscription.onSubscriptionUpdate?.({
			event,
			subscription,
		});
		if (plan) {
			if (
				subscriptionUpdated.status === "active" &&
				subscription.status === "trialing" &&
				plan.freeTrial?.onTrialEnd
			) {
				await plan.freeTrial.onTrialEnd({ subscription }, ctx.request);
			}
			if (
				subscriptionUpdated.status === "incomplete_expired" &&
				subscription.status === "trialing" &&
				plan.freeTrial?.onTrialExpired
			) {
				await plan.freeTrial.onTrialExpired(subscription, ctx.request);
			}
		}
	} catch (error: any) {
		logger.error(`Stripe webhook failed. Error: ${error}`);
	}
}