@@ -3,95 +3,27 @@ import {
33 member ,
44 organization as organizationTable ,
55 subscription as subscriptionTable ,
6- userStats ,
7- workspace as workspaceTable ,
86} from '@sim/db/schema'
97import { createLogger } from '@sim/logger'
108import { isOrgAdminRole } from '@sim/platform-authz/workspace'
11- import { and , asc , desc , eq , isNull } from 'drizzle-orm'
9+ import { and , desc , eq } from 'drizzle-orm'
1210import { type NextRequest , NextResponse } from 'next/server'
1311import { getBillingContract } from '@/lib/api/contracts/subscription'
1412import { parseRequest } from '@/lib/api/server'
1513import { getSession } from '@/lib/auth'
1614import { getOrganizationSubscription , getPersonalBillingSummary } from '@/lib/billing/core/billing'
1715import { getOrganizationBillingData } from '@/lib/billing/core/organization'
16+ import {
17+ getOrganizationBillingBlockState ,
18+ getUpgradeWorkspaceId ,
19+ } from '@/lib/billing/core/payer-context'
1820import { resolveBillingInterval } from '@/lib/billing/core/subscription'
1921import { getCreditBalanceForEntity } from '@/lib/billing/credits/balance'
2022import { isPaid } from '@/lib/billing/plan-helpers'
2123import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
2224
2325const logger = createLogger ( 'UnifiedBillingAPI' )
2426
25- interface BillingBlockState {
26- billingBlocked : boolean
27- billingBlockedReason : 'payment_failed' | 'dispute' | null
28- blockedByOrgOwner : boolean
29- }
30-
31- /**
32- * Finds an active workspace whose host billing identity is the requested payer.
33- */
34- async function getUpgradeWorkspaceId (
35- target : { type : 'user' ; id : string } | { type : 'organization' ; id : string }
36- ) : Promise < string | null > {
37- const targetPredicate =
38- target . type === 'organization'
39- ? eq ( workspaceTable . organizationId , target . id )
40- : and (
41- eq ( workspaceTable . ownerId , target . id ) ,
42- eq ( workspaceTable . billedAccountUserId , target . id ) ,
43- isNull ( workspaceTable . organizationId )
44- )
45-
46- const [ workspace ] = await dbReplica
47- . select ( { id : workspaceTable . id } )
48- . from ( workspaceTable )
49- . where ( and ( targetPredicate , isNull ( workspaceTable . archivedAt ) ) )
50- . orderBy ( asc ( workspaceTable . createdAt ) , asc ( workspaceTable . id ) )
51- . limit ( 1 )
52-
53- return workspace ?. id ?? null
54- }
55-
56- /**
57- * Reads the exact organization's payer block from its owner, without allowing
58- * the viewer's personal status or another organization membership to leak in.
59- */
60- async function getOrganizationBillingBlockState (
61- organizationId : string ,
62- viewerUserId : string
63- ) : Promise < BillingBlockState > {
64- const [ owner ] = await dbReplica
65- . select ( { userId : member . userId } )
66- . from ( member )
67- . where ( and ( eq ( member . organizationId , organizationId ) , eq ( member . role , 'owner' ) ) )
68- . limit ( 1 )
69-
70- if ( ! owner ) {
71- return {
72- billingBlocked : false ,
73- billingBlockedReason : null ,
74- blockedByOrgOwner : false ,
75- }
76- }
77-
78- const [ stats ] = await dbReplica
79- . select ( {
80- billingBlocked : userStats . billingBlocked ,
81- billingBlockedReason : userStats . billingBlockedReason ,
82- } )
83- . from ( userStats )
84- . where ( eq ( userStats . userId , owner . userId ) )
85- . limit ( 1 )
86-
87- const billingBlocked = Boolean ( stats ?. billingBlocked )
88- return {
89- billingBlocked,
90- billingBlockedReason : billingBlocked ? ( stats ?. billingBlockedReason ?? null ) : null ,
91- blockedByOrgOwner : billingBlocked && owner . userId !== viewerUserId ,
92- }
93- }
94-
9527/**
9628 * Unified Billing Endpoint
9729 */
0 commit comments