fix(api): keep premium through cancellation and refund pix exactly (#1995)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-25 00:57:09 +02:00
1 parent 29e244d4eb
commit 21e806b991
8 files changed
+91 -22

No files matched your search

@@ -128,19 +128,39 @@ function chooseEffectiveSubscription(user: User, subscriptions: Array<Stripe.Sub
return sorted[0]!;
}
function trackMostRecentTerminalSubscription(
current: Stripe.Subscription | null,
candidate: Stripe.Subscription,
): Stripe.Subscription | null {
if (!candidate.ended_at) {
return current;
}
if (!current || (current.ended_at ?? 0) < candidate.ended_at) {
return candidate;
}
return current;
}
async function getEffectiveActiveStripeSubscription(
stripe: Stripe,
user: User,
): Promise<{
subscription: Stripe.Subscription | null;
activeCount: number;
mostRecentTerminalSubscription: Stripe.Subscription | null;
}> {
const subscriptionsById = new Map<string, Stripe.Subscription>();
let mostRecentTerminalSubscription: Stripe.Subscription | null = null;
if (user.stripeSubscriptionId) {
try {
const subscription = await stripe.subscriptions.retrieve(user.stripeSubscriptionId);
if (canProvisionPremiumFromSubscriptionStatus(subscription.status)) {
subscriptionsById.set(subscription.id, subscription);
} else {
mostRecentTerminalSubscription = trackMostRecentTerminalSubscription(
mostRecentTerminalSubscription,
subscription,
);
}
} catch (error) {
Logger.warn(
@@ -162,6 +182,10 @@ async function getEffectiveActiveStripeSubscription(
});
for (const subscription of subscriptions.data) {
if (!canProvisionPremiumFromSubscriptionStatus(subscription.status)) {
mostRecentTerminalSubscription = trackMostRecentTerminalSubscription(
mostRecentTerminalSubscription,
subscription,
);
continue;
}
subscriptionsById.set(subscription.id, subscription);
@@ -182,11 +206,13 @@ async function getEffectiveActiveStripeSubscription(
return {
subscription: null,
activeCount: 0,
mostRecentTerminalSubscription,
};
}
return {
subscription: chooseEffectiveSubscription(user, activeSubscriptions),
activeCount: activeSubscriptions.length,
mostRecentTerminalSubscription,
};
}
@@ -203,15 +229,29 @@ async function reconcileUserPremiumStateFromStripe(params: {userId: UserID; stri
if (!user.stripeSubscriptionId && !user.stripeCustomerId) {
return {status: 'skipped', patchedFields: []};
}
const {subscription, activeCount} = await getEffectiveActiveStripeSubscription(stripe, user);
const {subscription, activeCount, mostRecentTerminalSubscription} = await getEffectiveActiveStripeSubscription(
stripe,
user,
);
if (!subscription) {
const hasStalePremium = user.premiumType === UserPremiumTypes.SUBSCRIPTION;
const hasNonStripePremium = Config.instance.selfHosted || (user.premiumFlags & PremiumFlags.ENABLED_OVERRIDE) !== 0;
if (hasStalePremium && !hasNonStripePremium) {
const effectivePremiumUntil = getEffectivePremiumUntil(user);
const patch: Partial<UserRow> = {};
let effectivePremiumUntil = getEffectivePremiumUntil(user);
if (mostRecentTerminalSubscription?.ended_at && user.premiumUntil) {
const subscriptionEndedAt = new Date(mostRecentTerminalSubscription.ended_at * 1000);
if (subscriptionEndedAt.getTime() < user.premiumUntil.getTime()) {
patch.premium_until = subscriptionEndedAt;
patch.premium_grace_ends_at = subscriptionEndedAt;
effectivePremiumUntil = getEffectivePremiumUntil({
premiumUntil: subscriptionEndedAt,
premiumGiftExtensionEndsAt: user.premiumGiftExtensionEndsAt,
});
}
}
const hasFutureLocalEntitlement = effectivePremiumUntil != null && Date.now() <= effectivePremiumUntil.getTime();
if (hasFutureLocalEntitlement) {
const patch: Partial<UserRow> = {};
if (user.premiumWillCancel !== true) {
patch.premium_will_cancel = true;
}