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}`);
}
}