Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
cleanup via helper
  • Loading branch information
icecrasher321 committed Nov 26, 2025
commit 027d1f984c7e66d539187eee21a1a48ddccc4fd0
17 changes: 9 additions & 8 deletions apps/sim/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { authorizeSubscriptionReference } from '@/lib/billing/authorization'
import { handleNewUser } from '@/lib/billing/core/usage'
import { syncSubscriptionUsageLimits } from '@/lib/billing/organization'
import { getPlans } from '@/lib/billing/plans'
import { syncSeatsFromStripeQuantity } from '@/lib/billing/validation/seat-management'
import { handleManualEnterpriseSubscription } from '@/lib/billing/webhooks/enterprise'
import {
handleInvoiceFinalized,
Expand Down Expand Up @@ -1682,18 +1683,18 @@ export const auth = betterAuth({
const stripeSubscription = event.data.object as Stripe.Subscription
const quantity = stripeSubscription.items?.data?.[0]?.quantity || 1

// Only update if quantity differs from current seats
if (quantity !== subscription.seats) {
await db
.update(schema.subscription)
.set({ seats: quantity })
.where(eq(schema.subscription.id, subscription.id))
const result = await syncSeatsFromStripeQuantity(
subscription.id,
subscription.seats,
quantity
)

if (result.synced) {
logger.info('[onSubscriptionUpdate] Synced seat count from Stripe', {
subscriptionId: subscription.id,
referenceId: subscription.referenceId,
previousSeats: subscription.seats,
newSeats: quantity,
previousSeats: result.previousSeats,
newSeats: result.newSeats,
})
}
} catch (error) {
Expand Down
47 changes: 47 additions & 0 deletions apps/sim/lib/billing/validation/seat-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,50 @@ export async function getOrganizationSeatAnalytics(organizationId: string) {
return null
}
}

/**
* Sync seat count from Stripe subscription quantity.
* Used by webhook handlers to keep local DB in sync with Stripe.
*/
export async function syncSeatsFromStripeQuantity(
subscriptionId: string,
currentSeats: number | null,
stripeQuantity: number
): Promise<{ synced: boolean; previousSeats: number | null; newSeats: number }> {
const effectiveCurrentSeats = currentSeats ?? 0

// Only update if quantity differs
if (stripeQuantity === effectiveCurrentSeats) {
return {
synced: false,
previousSeats: effectiveCurrentSeats,
newSeats: stripeQuantity,
}
}

try {
await db
.update(subscription)
.set({ seats: stripeQuantity })
.where(eq(subscription.id, subscriptionId))

logger.info('Synced seat count from Stripe', {
subscriptionId,
previousSeats: effectiveCurrentSeats,
newSeats: stripeQuantity,
})

return {
synced: true,
previousSeats: effectiveCurrentSeats,
newSeats: stripeQuantity,
}
} catch (error) {
logger.error('Failed to sync seat count from Stripe', {
subscriptionId,
stripeQuantity,
error,
})
throw error
}
}