@@ -503,22 +503,37 @@ export async function handleInvoicePaymentSucceeded(event: Stripe.Event) {
503503 wasBlocked = row . length > 0 ? ! ! row [ 0 ] . blocked : false
504504 }
505505
506- if ( isOrgPlan ( sub . plan ) ) {
507- await unblockOrgMembers ( sub . referenceId , 'payment_failed' )
508- } else {
509- // Only unblock users blocked for payment_failed, not disputes
510- await db
511- . update ( userStats )
512- . set ( { billingBlocked : false , billingBlockedReason : null } )
513- . where (
514- and (
515- eq ( userStats . userId , sub . referenceId ) ,
516- eq ( userStats . billingBlockedReason , 'payment_failed' )
506+ // For proration invoices (mid-cycle upgrades/seat changes), only unblock if real money
507+ // was collected. A $0 credit invoice from a downgrade should not unblock a user who
508+ // was blocked for a different failed payment.
509+ const isProrationInvoice = invoice . billing_reason === 'subscription_update'
510+ const shouldUnblock = ! isProrationInvoice || ( invoice . amount_paid ?? 0 ) > 0
511+
512+ if ( shouldUnblock ) {
513+ if ( isOrgPlan ( sub . plan ) ) {
514+ await unblockOrgMembers ( sub . referenceId , 'payment_failed' )
515+ } else {
516+ await db
517+ . update ( userStats )
518+ . set ( { billingBlocked : false , billingBlockedReason : null } )
519+ . where (
520+ and (
521+ eq ( userStats . userId , sub . referenceId ) ,
522+ eq ( userStats . billingBlockedReason , 'payment_failed' )
523+ )
517524 )
518- )
525+ }
526+ } else {
527+ logger . info ( 'Skipping unblock for zero-amount proration invoice' , {
528+ invoiceId : invoice . id ,
529+ billingReason : invoice . billing_reason ,
530+ amountPaid : invoice . amount_paid ,
531+ } )
519532 }
520533
521- if ( wasBlocked ) {
534+ // Only reset usage for cycle renewals — proration invoices should not wipe
535+ // accumulated usage mid-cycle.
536+ if ( wasBlocked && ! isProrationInvoice ) {
522537 await resetUsageForSubscription ( { plan : sub . plan , referenceId : sub . referenceId } )
523538 }
524539 } catch ( error ) {
@@ -584,14 +599,6 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
584599
585600 // Block users after first payment failure
586601 if ( attemptCount >= 1 ) {
587- logger . error ( 'Payment failure - blocking users' , {
588- invoiceId : invoice . id ,
589- customerId,
590- attemptCount,
591- isOverageInvoice,
592- stripeSubscriptionId,
593- } )
594-
595602 const records = await db
596603 . select ( )
597604 . from ( subscriptionTable )
@@ -600,6 +607,15 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
600607
601608 if ( records . length > 0 ) {
602609 const sub = records [ 0 ]
610+
611+ logger . error ( 'Payment failure - blocking users' , {
612+ invoiceId : invoice . id ,
613+ customerId,
614+ attemptCount,
615+ isOverageInvoice,
616+ stripeSubscriptionId,
617+ } )
618+
603619 if ( isOrgPlan ( sub . plan ) ) {
604620 const memberCount = await blockOrgMembers ( sub . referenceId , 'payment_failed' )
605621 logger . info ( 'Blocked team/enterprise members due to payment failure' , {
0 commit comments