@@ -27,11 +27,9 @@ interface UsageData {
2727 currentUsage : number
2828 limit : number
2929 /**
30- * Whether the returned `currentUsage`/`limit` represent the user's
31- * individual slice (`'user'`) or the organization's pooled total and cap
32- * (`'organization'`). When `isExceeded` is driven by an org pool check,
33- * the pooled values are surfaced here so downstream error messages are
34- * accurate.
30+ * Whether the returned values are this user's individual slice or the
31+ * organization's pooled total/cap. When an org pool is the blocker,
32+ * the pooled values are surfaced here so error messages reflect it.
3533 */
3634 scope : 'user' | 'organization'
3735 /** Present only when `scope === 'organization'`. */
@@ -47,9 +45,7 @@ export async function checkUsageStatus(
4745 preloadedSubscription ?: HighestPrioritySubscription
4846) : Promise < UsageData > {
4947 try {
50- // If billing is disabled, always return permissive limits
5148 if ( ! isBillingEnabled ) {
52- // Get actual usage from the database for display purposes
5349 const statsRecords = await db . select ( ) . from ( userStats ) . where ( eq ( userStats . userId , userId ) )
5450 const currentUsage =
5551 statsRecords . length > 0
@@ -67,20 +63,14 @@ export async function checkUsageStatus(
6763 }
6864 }
6965
70- // Resolve the highest-priority subscription once so every branch below
71- // agrees on scope. The caller may have already loaded it.
7266 const sub =
7367 preloadedSubscription !== undefined
7468 ? preloadedSubscription
7569 : await getHighestPrioritySubscription ( userId )
7670
77- // Primary limit from user_stats or org (routed by scope).
7871 const limit = await getUserUsageLimit ( userId , sub )
7972 logger . info ( 'Using stored usage limit' , { userId, limit } )
8073
81- // Usage baseline.
82- // Org-scoped: pooled sum across all org members (with pooled refresh).
83- // Personal: user's own currentPeriodCost (with personal refresh).
8474 const subIsOrgScoped = isOrgScopedSubscription ( sub , userId )
8575
8676 let currentUsage = 0
@@ -114,15 +104,14 @@ export async function checkUsageStatus(
114104 periodStart : sub . periodStart ,
115105 periodEnd : sub . periodEnd ?? null ,
116106 planDollars,
117- seats : sub . seats ?? 1 ,
107+ seats : sub . seats || 1 ,
118108 } )
119109 pooled = Math . max ( 0 , pooled - refresh )
120110 }
121111 }
122112 }
123113 currentUsage = pooled
124114 } else {
125- // Personally-scoped: use this user's own row (defensive default 0).
126115 const statsRecords = await db
127116 . select ( )
128117 . from ( userStats )
@@ -161,24 +150,22 @@ export async function checkUsageStatus(
161150 currentUsage = Math . max ( 0 , rawUsage - refresh )
162151 }
163152
164- // Defense-in-depth: even when the user's priority sub is personal, they
165- // may still be a member of an org whose pool has blown its cap (e.g.
166- // billed-account scenarios). Enforce every entitled-org cap they belong
167- // to and override the returned values when one is actually blocking —
168- // that way the error message surfaces the org number, not personal.
153+ // Defense-in-depth: enforce every entitled org cap the user belongs
154+ // to, even when their priority sub is personal. If a secondary org
155+ // pool is blocking, surface its numbers so the error message does
156+ // not quote personal usage while enforcing an org cap.
169157 try {
170158 const memberships = await db
171159 . select ( { organizationId : member . organizationId } )
172160 . from ( member )
173161 . where ( eq ( member . userId , userId ) )
174162
175163 for ( const m of memberships ) {
176- // Skip the org the primary sub is already keyed to; we've already
177- // computed pooled usage against its cap above.
164+ // Already handled above as the primary org.
178165 if ( subIsOrgScoped && sub && sub . referenceId === m . organizationId ) continue
179166
180- // Pull the full org subscription row — refresh math below needs
181- // THAT org's plan/period/seats, not the caller's primary sub.
167+ // Refresh math below needs THIS org's plan/period/seats, not
168+ // the caller's primary sub (which may be a personal Pro) .
182169 const [ orgSub ] = await db
183170 . select ( {
184171 plan : subscription . plan ,
@@ -221,9 +208,6 @@ export async function checkUsageStatus(
221208 }
222209 }
223210
224- // Refresh is driven by the org's OWN subscription period, plan
225- // dollars, and seats — not the caller's primary sub (which may be
226- // a personal Pro in this branch).
227211 if ( isPaid ( orgSub . plan ) && orgSub . periodStart ) {
228212 const planDollars = getPlanTierDollars ( orgSub . plan )
229213 if ( planDollars > 0 ) {
@@ -233,7 +217,7 @@ export async function checkUsageStatus(
233217 periodStart : orgSub . periodStart ,
234218 periodEnd : orgSub . periodEnd ?? null ,
235219 planDollars,
236- seats : orgSub . seats ?? 1 ,
220+ seats : orgSub . seats || 1 ,
237221 } )
238222 pooledUsage = Math . max ( 0 , pooledUsage - orgRefreshDeduction )
239223 }
@@ -295,9 +279,9 @@ export async function checkUsageStatus(
295279 return {
296280 percentUsed : 100 ,
297281 isWarning : false ,
298- isExceeded : true , // Block execution when we can't determine status
282+ isExceeded : true ,
299283 currentUsage : 0 ,
300- limit : 0 , // Zero limit forces blocking
284+ limit : 0 ,
301285 scope : 'user' ,
302286 organizationId : null ,
303287 }
@@ -310,22 +294,19 @@ export async function checkUsageStatus(
310294 */
311295export async function checkAndNotifyUsage ( userId : string ) : Promise < void > {
312296 try {
313- // Skip usage notifications if billing is disabled
314297 if ( ! isBillingEnabled ) {
315298 return
316299 }
317300
318301 const usageData = await checkUsageStatus ( userId )
319302
320303 if ( usageData . isExceeded ) {
321- // User has exceeded their limit
322304 logger . warn ( 'User has exceeded usage limits' , {
323305 userId,
324306 usage : usageData . currentUsage ,
325307 limit : usageData . limit ,
326308 } )
327309
328- // Dispatch event to show a UI notification
329310 if ( typeof window !== 'undefined' ) {
330311 window . dispatchEvent (
331312 new CustomEvent ( 'usage-exceeded' , {
@@ -334,15 +315,13 @@ export async function checkAndNotifyUsage(userId: string): Promise<void> {
334315 )
335316 }
336317 } else if ( usageData . isWarning ) {
337- // User is approaching their limit
338318 logger . info ( 'User approaching usage limits' , {
339319 userId,
340320 usage : usageData . currentUsage ,
341321 limit : usageData . limit ,
342322 percent : usageData . percentUsed ,
343323 } )
344324
345- // Dispatch event to show a UI notification
346325 if ( typeof window !== 'undefined' ) {
347326 window . dispatchEvent (
348327 new CustomEvent ( 'usage-warning' , {
@@ -383,7 +362,6 @@ export async function checkServerSideUsageLimits(
383362
384363 logger . info ( 'Server-side checking usage limits for user' , { userId } )
385364
386- // Check user's own blocked status
387365 const stats = await db
388366 . select ( {
389367 blocked : userStats . billingBlocked ,
@@ -413,14 +391,12 @@ export async function checkServerSideUsageLimits(
413391 }
414392 }
415393
416- // Check if user is in an org where the owner is blocked
417394 const memberships = await db
418395 . select ( { organizationId : member . organizationId } )
419396 . from ( member )
420397 . where ( eq ( member . userId , userId ) )
421398
422399 for ( const m of memberships ) {
423- // Find the owner of this org
424400 const owners = await db
425401 . select ( { userId : member . userId } )
426402 . from ( member )
@@ -479,9 +455,9 @@ export async function checkServerSideUsageLimits(
479455 } )
480456
481457 return {
482- isExceeded : true , // Block execution when we can't determine limits
458+ isExceeded : true ,
483459 currentUsage : 0 ,
484- limit : 0 , // Zero limit forces blocking
460+ limit : 0 ,
485461 message :
486462 error instanceof Error && error . message . includes ( 'No user stats record found' )
487463 ? 'User account not properly initialized. Please contact support.'
0 commit comments