Skip to content

Commit 8a6eea4

Browse files
committed
block on payment failures even for upgrades
1 parent c11b8db commit 8a6eea4

File tree

2 files changed

+13
-33
lines changed

2 files changed

+13
-33
lines changed

apps/sim/app/api/organizations/[id]/seats/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
161161
quantity: newSeatCount,
162162
},
163163
],
164-
proration_behavior: 'create_prorations', // Stripe's default - charge/credit immediately
164+
proration_behavior: 'always_invoice',
165165
}
166166
)
167167

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

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -469,15 +469,6 @@ export async function handleInvoicePaymentSucceeded(event: Stripe.Event) {
469469
return
470470
}
471471

472-
// Skip proration invoices (e.g. from mid-cycle plan upgrades) — only process cycle renewals
473-
if (invoice.billing_reason && invoice.billing_reason !== 'subscription_cycle') {
474-
logger.info('Skipping non-cycle invoice payment succeeded', {
475-
invoiceId: invoice.id,
476-
billingReason: invoice.billing_reason,
477-
})
478-
return
479-
}
480-
481472
const records = await db
482473
.select()
483474
.from(subscriptionTable)
@@ -527,7 +518,9 @@ export async function handleInvoicePaymentSucceeded(event: Stripe.Event) {
527518
)
528519
}
529520

530-
if (wasBlocked) {
521+
// Only reset usage for cycle renewals — proration invoices (subscription_update) should
522+
// unblock the user but not wipe their accumulated usage mid-cycle.
523+
if (wasBlocked && invoice.billing_reason !== 'subscription_update') {
531524
await resetUsageForSubscription({ plan: sub.plan, referenceId: sub.referenceId })
532525
}
533526
} catch (error) {
@@ -565,20 +558,6 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
565558
return
566559
}
567560

568-
// Skip proration invoices (e.g. from mid-cycle plan upgrades) — don't block users over proration failures.
569-
// Overage invoices (threshold + cycle-end) bypass this guard via !isOverageInvoice and still block.
570-
if (
571-
!isOverageInvoice &&
572-
invoice.billing_reason &&
573-
invoice.billing_reason !== 'subscription_cycle'
574-
) {
575-
logger.info('Skipping non-cycle invoice payment failure', {
576-
invoiceId: invoice.id,
577-
billingReason: invoice.billing_reason,
578-
})
579-
return
580-
}
581-
582561
// Extract and validate customer ID
583562
const customerId = invoice.customer
584563
if (!customerId || typeof customerId !== 'string') {
@@ -607,14 +586,6 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
607586

608587
// Block users after first payment failure
609588
if (attemptCount >= 1) {
610-
logger.error('Payment failure - blocking users', {
611-
invoiceId: invoice.id,
612-
customerId,
613-
attemptCount,
614-
isOverageInvoice,
615-
stripeSubscriptionId,
616-
})
617-
618589
const records = await db
619590
.select()
620591
.from(subscriptionTable)
@@ -623,6 +594,15 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
623594

624595
if (records.length > 0) {
625596
const sub = records[0]
597+
598+
logger.error('Payment failure - blocking users', {
599+
invoiceId: invoice.id,
600+
customerId,
601+
attemptCount,
602+
isOverageInvoice,
603+
stripeSubscriptionId,
604+
})
605+
626606
if (isOrgPlan(sub.plan)) {
627607
const memberCount = await blockOrgMembers(sub.referenceId, 'payment_failed')
628608
logger.info('Blocked team/enterprise members due to payment failure', {

0 commit comments

Comments
 (0)