(
subscription: {
id: string
plan: string | null
referenceId: string
stripeSubscriptionId: string | null
seats?: number | null
periodStart?: Date | null
periodEnd?: Date | null
},
stripeEventId?: string
)
| 278 | * webhook retries from scratch. |
| 279 | */ |
| 280 | export async function handleSubscriptionDeleted( |
| 281 | subscription: { |
| 282 | id: string |
| 283 | plan: string | null |
| 284 | referenceId: string |
| 285 | stripeSubscriptionId: string | null |
| 286 | seats?: number | null |
| 287 | periodStart?: Date | null |
| 288 | periodEnd?: Date | null |
| 289 | }, |
| 290 | stripeEventId?: string |
| 291 | ) { |
| 292 | const stripeSubscriptionId = subscription.stripeSubscriptionId || '' |
| 293 | |
| 294 | logger.info('Processing subscription deletion', { |
| 295 | stripeEventId, |
| 296 | stripeSubscriptionId, |
| 297 | subscriptionId: subscription.id, |
| 298 | }) |
| 299 | |
| 300 | // Fall back to the subscription DB id when we don't have an event id |
| 301 | // (e.g. called outside the Stripe webhook context). Still dedupes a |
| 302 | // single subscription's deletion, just not event-granular. |
| 303 | const idempotencyIdentifier = stripeEventId ?? `sub:${subscription.id}` |
| 304 | |
| 305 | try { |
| 306 | await stripeWebhookIdempotency.executeWithIdempotency( |
| 307 | 'subscription-deleted', |
| 308 | idempotencyIdentifier, |
| 309 | async () => { |
| 310 | const totalOverage = await calculateSubscriptionOverage(subscription) |
| 311 | const stripe = requireStripeClient() |
| 312 | |
| 313 | if (isEnterprise(subscription.plan)) { |
| 314 | await resetUsageForSubscription({ |
| 315 | plan: subscription.plan, |
| 316 | referenceId: subscription.referenceId, |
| 317 | periodStart: subscription.periodStart ?? null, |
| 318 | periodEnd: subscription.periodEnd ?? null, |
| 319 | }) |
| 320 | |
| 321 | const dormantResult = await transitionOrganizationToDormantState( |
| 322 | subscription.referenceId, |
| 323 | subscription.id |
| 324 | ) |
| 325 | |
| 326 | logger.info('Successfully processed enterprise subscription cancellation', { |
| 327 | subscriptionId: subscription.id, |
| 328 | stripeSubscriptionId, |
| 329 | ...dormantResult, |
| 330 | }) |
| 331 | |
| 332 | captureServerEvent(subscription.referenceId, 'subscription_cancelled', { |
| 333 | plan: subscription.plan ?? 'unknown', |
| 334 | reference_id: subscription.referenceId, |
| 335 | }) |
| 336 | |
| 337 | return { totalOverage: 0, kind: 'enterprise' as const } |
no test coverage detected