2626import { db } from '@sim/db'
2727import { organization , subscription , user , userStats } from '@sim/db/schema'
2828import { createLogger } from '@sim/logger'
29- import { and , eq , sql } from 'drizzle-orm'
29+ import { and , eq } from 'drizzle-orm'
3030import { nanoid } from 'nanoid'
31- import { getPlanPricing } from '@/lib/billing/core/billing'
3231import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
32+ import { addCredits } from '@/lib/billing/credits/balance'
33+ import { setUsageLimitForCredits } from '@/lib/billing/credits/purchase'
34+ import { getEffectiveSeats } from '@/lib/billing/subscriptions/utils'
3335import { withAdminAuth } from '@/app/api/v1/admin/middleware'
3436import {
3537 badRequestResponse ,
@@ -85,15 +87,20 @@ export const POST = withAdminAuth(async (request) => {
8587
8688 const userSubscription = await getHighestPrioritySubscription ( resolvedUserId )
8789
90+ if ( ! userSubscription || ! [ 'pro' , 'team' , 'enterprise' ] . includes ( userSubscription . plan ) ) {
91+ return badRequestResponse (
92+ 'User must have an active Pro, Team, or Enterprise subscription to receive credits'
93+ )
94+ }
95+
8896 let entityType : 'user' | 'organization'
8997 let entityId : string
90- let plan : string
98+ const plan = userSubscription . plan
9199 let seats : number | null = null
92100
93- if ( userSubscription ?. plan === 'team' || userSubscription ?. plan === 'enterprise' ) {
101+ if ( plan === 'team' || plan === 'enterprise' ) {
94102 entityType = 'organization'
95103 entityId = userSubscription . referenceId
96- plan = userSubscription . plan
97104
98105 const [ orgExists ] = await db
99106 . select ( { id : organization . id } )
@@ -106,106 +113,67 @@ export const POST = withAdminAuth(async (request) => {
106113 }
107114
108115 const [ subData ] = await db
109- . select ( { seats : subscription . seats } )
116+ . select ( )
110117 . from ( subscription )
111118 . where ( and ( eq ( subscription . referenceId , entityId ) , eq ( subscription . status , 'active' ) ) )
112119 . limit ( 1 )
113120
114- seats = subData ?. seats ?? null
115- } else if ( userSubscription ?. plan === 'pro' ) {
121+ seats = getEffectiveSeats ( subData )
122+ } else {
116123 entityType = 'user'
117124 entityId = resolvedUserId
118- plan = 'pro'
119- } else {
120- return badRequestResponse (
121- 'User must have an active Pro or Team subscription to receive credits'
122- )
123- }
124125
125- const { basePrice } = getPlanPricing ( plan )
126-
127- const result = await db . transaction ( async ( tx ) => {
128- let newCreditBalance : number
129- let newUsageLimit : number
130-
131- if ( entityType === 'organization' ) {
132- await tx
133- . update ( organization )
134- . set ( { creditBalance : sql `${ organization . creditBalance } + ${ amount } ` } )
135- . where ( eq ( organization . id , entityId ) )
136-
137- const [ orgData ] = await tx
138- . select ( {
139- creditBalance : organization . creditBalance ,
140- orgUsageLimit : organization . orgUsageLimit ,
141- } )
142- . from ( organization )
143- . where ( eq ( organization . id , entityId ) )
144- . limit ( 1 )
145-
146- newCreditBalance = Number . parseFloat ( orgData ?. creditBalance || '0' )
147- const currentLimit = Number . parseFloat ( orgData ?. orgUsageLimit || '0' )
148- const planBase = Number ( basePrice ) * ( seats || 1 )
149- const calculatedLimit = planBase + newCreditBalance
150-
151- if ( calculatedLimit > currentLimit ) {
152- await tx
153- . update ( organization )
154- . set ( { orgUsageLimit : calculatedLimit . toString ( ) } )
155- . where ( eq ( organization . id , entityId ) )
156- newUsageLimit = calculatedLimit
157- } else {
158- newUsageLimit = currentLimit
159- }
160- } else {
161- const [ existingStats ] = await tx
162- . select ( { id : userStats . id } )
163- . from ( userStats )
164- . where ( eq ( userStats . userId , entityId ) )
165- . limit ( 1 )
166-
167- if ( ! existingStats ) {
168- await tx . insert ( userStats ) . values ( {
169- id : nanoid ( ) ,
170- userId : entityId ,
171- creditBalance : amount . toString ( ) ,
172- } )
173- } else {
174- await tx
175- . update ( userStats )
176- . set ( { creditBalance : sql `${ userStats . creditBalance } + ${ amount } ` } )
177- . where ( eq ( userStats . userId , entityId ) )
178- }
179-
180- const [ stats ] = await tx
181- . select ( {
182- creditBalance : userStats . creditBalance ,
183- currentUsageLimit : userStats . currentUsageLimit ,
184- } )
185- . from ( userStats )
186- . where ( eq ( userStats . userId , entityId ) )
187- . limit ( 1 )
188-
189- newCreditBalance = Number . parseFloat ( stats ?. creditBalance || '0' )
190- const currentLimit = Number . parseFloat ( stats ?. currentUsageLimit || '0' )
191- const planBase = Number ( basePrice )
192- const calculatedLimit = planBase + newCreditBalance
193-
194- if ( calculatedLimit > currentLimit ) {
195- await tx
196- . update ( userStats )
197- . set ( { currentUsageLimit : calculatedLimit . toString ( ) } )
198- . where ( eq ( userStats . userId , entityId ) )
199- newUsageLimit = calculatedLimit
200- } else {
201- newUsageLimit = currentLimit
202- }
126+ const [ existingStats ] = await db
127+ . select ( { id : userStats . id } )
128+ . from ( userStats )
129+ . where ( eq ( userStats . userId , entityId ) )
130+ . limit ( 1 )
131+
132+ if ( ! existingStats ) {
133+ await db . insert ( userStats ) . values ( {
134+ id : nanoid ( ) ,
135+ userId : entityId ,
136+ } )
203137 }
138+ }
204139
205- return { newCreditBalance, newUsageLimit }
206- } )
140+ await addCredits ( entityType , entityId , amount )
207141
208- const { newCreditBalance, newUsageLimit } = result
142+ let newCreditBalance : number
143+ if ( entityType === 'organization' ) {
144+ const [ orgData ] = await db
145+ . select ( { creditBalance : organization . creditBalance } )
146+ . from ( organization )
147+ . where ( eq ( organization . id , entityId ) )
148+ . limit ( 1 )
149+ newCreditBalance = Number . parseFloat ( orgData ?. creditBalance || '0' )
150+ } else {
151+ const [ stats ] = await db
152+ . select ( { creditBalance : userStats . creditBalance } )
153+ . from ( userStats )
154+ . where ( eq ( userStats . userId , entityId ) )
155+ . limit ( 1 )
156+ newCreditBalance = Number . parseFloat ( stats ?. creditBalance || '0' )
157+ }
158+
159+ await setUsageLimitForCredits ( entityType , entityId , plan , seats , newCreditBalance )
160+
161+ let newUsageLimit : number
162+ if ( entityType === 'organization' ) {
163+ const [ orgData ] = await db
164+ . select ( { orgUsageLimit : organization . orgUsageLimit } )
165+ . from ( organization )
166+ . where ( eq ( organization . id , entityId ) )
167+ . limit ( 1 )
168+ newUsageLimit = Number . parseFloat ( orgData ?. orgUsageLimit || '0' )
169+ } else {
170+ const [ stats ] = await db
171+ . select ( { currentUsageLimit : userStats . currentUsageLimit } )
172+ . from ( userStats )
173+ . where ( eq ( userStats . userId , entityId ) )
174+ . limit ( 1 )
175+ newUsageLimit = Number . parseFloat ( stats ?. currentUsageLimit || '0' )
176+ }
209177
210178 logger . info ( 'Admin API: Issued credits' , {
211179 resolvedUserId,
0 commit comments