Skip to content

Commit 746ff68

Browse files
fix(sub-deletion): subscription deletion handling for pro vs team/enterprise (#2364)
* fix(subscription): deletion should sync pro limits back to free * consolidate duplicate code
1 parent 75a5b43 commit 746ff68

File tree

1 file changed

+42
-31
lines changed

1 file changed

+42
-31
lines changed

apps/sim/lib/billing/webhooks/subscription.ts

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,34 @@ async function restoreMemberProSubscriptions(organizationId: string): Promise<nu
5050
return restoredCount
5151
}
5252

53+
/**
54+
* Cleanup organization when team/enterprise subscription is deleted.
55+
* - Restores member Pro subscriptions
56+
* - Deletes the organization
57+
* - Syncs usage limits for former members (resets to free or Pro tier)
58+
*/
59+
async function cleanupOrganizationSubscription(organizationId: string): Promise<{
60+
restoredProCount: number
61+
membersSynced: number
62+
}> {
63+
// Get member userIds before deletion (needed for limit syncing after org deletion)
64+
const memberUserIds = await db
65+
.select({ userId: member.userId })
66+
.from(member)
67+
.where(eq(member.organizationId, organizationId))
68+
69+
const restoredProCount = await restoreMemberProSubscriptions(organizationId)
70+
71+
await db.delete(organization).where(eq(organization.id, organizationId))
72+
73+
// Sync usage limits for former members (now free or Pro tier)
74+
for (const m of memberUserIds) {
75+
await syncUsageLimitsFromSubscription(m.userId)
76+
}
77+
78+
return { restoredProCount, membersSynced: memberUserIds.length }
79+
}
80+
5381
/**
5482
* Handle new subscription creation - reset usage if transitioning from free to paid
5583
*/
@@ -137,33 +165,23 @@ export async function handleSubscriptionDeleted(subscription: {
137165
const totalOverage = await calculateSubscriptionOverage(subscription)
138166
const stripe = requireStripeClient()
139167

140-
// Enterprise plans have no overages - reset usage, restore Pro, sync limits, delete org
168+
// Enterprise plans have no overages - reset usage and cleanup org
141169
if (subscription.plan === 'enterprise') {
142-
// Get member userIds before any changes (needed for limit syncing after org deletion)
143-
const memberUserIds = await db
144-
.select({ userId: member.userId })
145-
.from(member)
146-
.where(eq(member.organizationId, subscription.referenceId))
147-
148170
await resetUsageForSubscription({
149171
plan: subscription.plan,
150172
referenceId: subscription.referenceId,
151173
})
152-
const restoredProCount = await restoreMemberProSubscriptions(subscription.referenceId)
153-
154-
await db.delete(organization).where(eq(organization.id, subscription.referenceId))
155174

156-
// Sync usage limits for former members (now free or Pro tier)
157-
for (const m of memberUserIds) {
158-
await syncUsageLimitsFromSubscription(m.userId)
159-
}
175+
const { restoredProCount, membersSynced } = await cleanupOrganizationSubscription(
176+
subscription.referenceId
177+
)
160178

161179
logger.info('Successfully processed enterprise subscription cancellation', {
162180
subscriptionId: subscription.id,
163181
stripeSubscriptionId,
164182
restoredProCount,
165183
organizationDeleted: true,
166-
membersSynced: memberUserIds.length,
184+
membersSynced,
167185
})
168186
return
169187
}
@@ -270,27 +288,19 @@ export async function handleSubscriptionDeleted(subscription: {
270288
referenceId: subscription.referenceId,
271289
})
272290

273-
// For team: restore member Pro subscriptions, sync limits, delete organization
291+
// Plan-specific cleanup after billing
274292
let restoredProCount = 0
275293
let organizationDeleted = false
276294
let membersSynced = 0
277-
if (subscription.plan === 'team') {
278-
// Get member userIds before deletion (needed for limit syncing)
279-
const memberUserIds = await db
280-
.select({ userId: member.userId })
281-
.from(member)
282-
.where(eq(member.organizationId, subscription.referenceId))
283-
284-
restoredProCount = await restoreMemberProSubscriptions(subscription.referenceId)
285295

286-
await db.delete(organization).where(eq(organization.id, subscription.referenceId))
296+
if (subscription.plan === 'team') {
297+
const cleanup = await cleanupOrganizationSubscription(subscription.referenceId)
298+
restoredProCount = cleanup.restoredProCount
299+
membersSynced = cleanup.membersSynced
287300
organizationDeleted = true
288-
289-
// Sync usage limits for former members (now free or Pro tier)
290-
for (const m of memberUserIds) {
291-
await syncUsageLimitsFromSubscription(m.userId)
292-
}
293-
membersSynced = memberUserIds.length
301+
} else if (subscription.plan === 'pro') {
302+
await syncUsageLimitsFromSubscription(subscription.referenceId)
303+
membersSynced = 1
294304
}
295305

296306
// Note: better-auth's Stripe plugin already updates status to 'canceled' before calling this handler
@@ -299,6 +309,7 @@ export async function handleSubscriptionDeleted(subscription: {
299309
logger.info('Successfully processed subscription cancellation', {
300310
subscriptionId: subscription.id,
301311
stripeSubscriptionId,
312+
plan: subscription.plan,
302313
totalOverage,
303314
restoredProCount,
304315
organizationDeleted,

0 commit comments

Comments
 (0)