Skip to content

Commit 334faa1

Browse files
committed
address bugbot comments
1 parent 5ba322f commit 334faa1

File tree

3 files changed

+36
-11
lines changed

3 files changed

+36
-11
lines changed

apps/sim/lib/billing/organizations/membership.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
userStats,
1616
} from '@sim/db/schema'
1717
import { createLogger } from '@sim/logger'
18-
import { and, eq, inArray, sql } from 'drizzle-orm'
18+
import { and, eq, inArray, isNull, ne, or, sql } from 'drizzle-orm'
1919
import { syncUsageLimitsFromSubscription } from '@/lib/billing/core/usage'
2020
import { requireStripeClient } from '@/lib/billing/stripe-client'
2121
import { validateSeatAvailability } from '@/lib/billing/validation/seat-management'
@@ -38,6 +38,10 @@ export async function getOrgMemberIds(organizationId: string): Promise<string[]>
3838

3939
/**
4040
* Block all members of an organization for billing reasons
41+
* Returns the number of members actually blocked
42+
*
43+
* Reason priority: dispute > payment_failed
44+
* A payment_failed block won't overwrite an existing dispute block
4145
*/
4246
export async function blockOrgMembers(
4347
organizationId: string,
@@ -49,17 +53,28 @@ export async function blockOrgMembers(
4953
return 0
5054
}
5155

52-
await db
56+
// Don't overwrite dispute blocks with payment_failed (dispute is higher priority)
57+
const whereClause =
58+
reason === 'payment_failed'
59+
? and(
60+
inArray(userStats.userId, memberIds),
61+
or(ne(userStats.billingBlockedReason, 'dispute'), isNull(userStats.billingBlockedReason))
62+
)
63+
: inArray(userStats.userId, memberIds)
64+
65+
const result = await db
5366
.update(userStats)
5467
.set({ billingBlocked: true, billingBlockedReason: reason })
55-
.where(inArray(userStats.userId, memberIds))
68+
.where(whereClause)
69+
.returning({ userId: userStats.userId })
5670

57-
return memberIds.length
71+
return result.length
5872
}
5973

6074
/**
6175
* Unblock all members of an organization blocked for a specific reason
6276
* Only unblocks members blocked for the specified reason (not other reasons)
77+
* Returns the number of members actually unblocked
6378
*/
6479
export async function unblockOrgMembers(
6580
organizationId: string,
@@ -71,12 +86,13 @@ export async function unblockOrgMembers(
7186
return 0
7287
}
7388

74-
await db
89+
const result = await db
7590
.update(userStats)
7691
.set({ billingBlocked: false, billingBlockedReason: null })
7792
.where(and(inArray(userStats.userId, memberIds), eq(userStats.billingBlockedReason, reason)))
93+
.returning({ userId: userStats.userId })
7894

79-
return memberIds.length
95+
return result.length
8096
}
8197

8298
export interface RestoreProResult {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { db } from '@sim/db'
22
import { subscription, user, userStats } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4-
import { eq } from 'drizzle-orm'
4+
import { and, eq } from 'drizzle-orm'
55
import type Stripe from 'stripe'
66
import { blockOrgMembers, unblockOrgMembers } from '@/lib/billing'
77
import { requireStripeClient } from '@/lib/billing/stripe-client'
@@ -97,7 +97,7 @@ export async function handleDisputeClosed(event: Stripe.Event): Promise<void> {
9797
return
9898
}
9999

100-
// Find and unblock user (Pro plans)
100+
// Find and unblock user (Pro plans) - only if blocked for dispute, not other reasons
101101
const users = await db
102102
.select({ id: user.id })
103103
.from(user)
@@ -108,7 +108,7 @@ export async function handleDisputeClosed(event: Stripe.Event): Promise<void> {
108108
await db
109109
.update(userStats)
110110
.set({ billingBlocked: false, billingBlockedReason: null })
111-
.where(eq(userStats.userId, users[0].id))
111+
.where(and(eq(userStats.userId, users[0].id), eq(userStats.billingBlockedReason, 'dispute')))
112112

113113
logger.info('Unblocked user after dispute resolved in our favor', {
114114
disputeId: dispute.id,

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
userStats,
99
} from '@sim/db/schema'
1010
import { createLogger } from '@sim/logger'
11-
import { and, eq, inArray } from 'drizzle-orm'
11+
import { and, eq, inArray, isNull, ne, or } from 'drizzle-orm'
1212
import type Stripe from 'stripe'
1313
import { getEmailSubject, PaymentFailedEmail, renderCreditPurchaseEmail } from '@/components/emails'
1414
import { calculateSubscriptionOverage } from '@/lib/billing/core/billing'
@@ -607,10 +607,19 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
607607
isOverageInvoice,
608608
})
609609
} else {
610+
// Don't overwrite dispute blocks (dispute > payment_failed priority)
610611
await db
611612
.update(userStats)
612613
.set({ billingBlocked: true, billingBlockedReason: 'payment_failed' })
613-
.where(eq(userStats.userId, sub.referenceId))
614+
.where(
615+
and(
616+
eq(userStats.userId, sub.referenceId),
617+
or(
618+
ne(userStats.billingBlockedReason, 'dispute'),
619+
isNull(userStats.billingBlockedReason)
620+
)
621+
)
622+
)
614623
logger.info('Blocked user due to payment failure', {
615624
userId: sub.referenceId,
616625
isOverageInvoice,

0 commit comments

Comments
 (0)