2024-06-05 13:20:26 -07:00
import { NotificationType } from "../../types" ;
2024-04-21 10:36:48 -07:00
import { withAuth } from "../../lib/withAuth" ;
2024-06-05 13:20:26 -07:00
import { sendNotification } from "../notification/email_notification" ;
2024-04-15 17:01:47 -04:00
import { supabase_service } from "../supabase" ;
2024-07-23 17:30:46 -03:00
import { Logger } from "../../lib/logger" ;
2024-09-04 15:19:45 -03:00
import * as Sentry from "@sentry/node" ;
2024-09-25 19:25:18 +02:00
import { AuthCreditUsageChunk } from "../../controllers/v1/types" ;
2024-09-25 21:37:01 +02:00
import { getACUC , setCachedACUC } from "../../controllers/auth" ;
2024-07-30 18:59:35 -04:00
2024-08-12 13:37:47 -04:00
const FREE_CREDITS = 500 ;
2024-07-30 18:59:35 -04:00
2024-09-25 20:57:45 +02:00
/**
* If you do not know the subscription_id in the current context, pass subscription_id as undefined.
*/
export async function billTeam ( team_id : string , subscription_id : string | null | undefined , credits : number ) {
return withAuth ( supaBillTeam ) ( team_id , subscription_id , credits ) ;
2024-04-21 10:36:48 -07:00
}
2024-09-25 20:57:45 +02:00
export async function supaBillTeam ( team_id : string , subscription_id : string , credits : number ) {
2024-04-15 17:01:47 -04:00
if ( team_id === "preview" ) {
return { success : true , message : "Preview team, no credits used" } ;
}
2024-07-23 17:30:46 -03:00
Logger . info ( ` Billing team ${ team_id } for ${ credits } credits ` ) ;
2024-09-03 21:02:41 -03:00
2024-09-25 21:37:01 +02:00
const { data , error } =
2024-09-25 20:57:45 +02:00
await supabase_service . rpc ( "bill_team" , { _team_id : team_id , sub_id : subscription_id ? ? null , fetch_subscription : subscription_id === undefined , credits } ) ;
if ( error ) {
Sentry . captureException ( error ) ;
Logger . error ( "Failed to bill team: " + JSON . stringify ( error ) ) ;
2024-09-25 21:37:01 +02:00
return ;
2024-04-25 10:05:53 -03:00
}
2024-09-25 21:37:01 +02:00
( async ( ) = > {
for ( const apiKey of ( data ? ? [ ] ) . map ( x = > x . api_key ) ) {
2024-09-25 22:15:02 +02:00
await setCachedACUC ( apiKey , acuc = > ( acuc ? {
. . . acuc ,
credits_used : acuc.credits_used + credits ,
adjusted_credits_used : acuc.adjusted_credits_used + credits ,
remaining_credits : acuc.remaining_credits - credits ,
} : null ) ) ;
2024-09-25 21:37:01 +02:00
}
} ) ( ) ;
2024-04-15 17:01:47 -04:00
}
2024-09-25 19:25:18 +02:00
export async function checkTeamCredits ( chunk : AuthCreditUsageChunk , team_id : string , credits : number ) {
return withAuth ( supaCheckTeamCredits ) ( chunk , team_id , credits ) ;
2024-04-21 10:36:48 -07:00
}
2024-08-20 14:16:54 -03:00
2024-04-21 10:36:48 -07:00
// if team has enough credits for the operation, return true, else return false
2024-09-25 19:25:18 +02:00
export async function supaCheckTeamCredits ( chunk : AuthCreditUsageChunk , team_id : string , credits : number ) {
// WARNING: chunk will be null if team_id is preview -- do not perform operations on it under ANY circumstances - mogery
2024-04-15 17:01:47 -04:00
if ( team_id === "preview" ) {
2024-08-20 14:16:54 -03:00
return { success : true , message : "Preview team, no credits used" , remainingCredits : Infinity } ;
2024-04-15 17:01:47 -04:00
}
2024-09-25 19:25:18 +02:00
const creditsWillBeUsed = chunk . adjusted_credits_used + credits ;
2024-09-07 13:42:45 -03:00
// Removal of + credits
2024-09-25 19:25:18 +02:00
const creditUsagePercentage = creditsWillBeUsed / chunk . price_credits ;
2024-06-05 13:20:26 -07:00
2024-04-25 10:05:53 -03:00
// Compare the adjusted total credits used with the credits allowed by the plan
2024-09-25 21:44:05 +02:00
if ( creditsWillBeUsed > chunk . price_credits ) {
2024-09-25 19:25:18 +02:00
sendNotification (
2024-09-07 13:42:45 -03:00
team_id ,
NotificationType . LIMIT_REACHED ,
2024-09-25 19:25:18 +02:00
chunk . sub_current_period_start ,
chunk . sub_current_period_end
2024-09-07 13:42:45 -03:00
) ;
2024-09-26 16:07:15 -04:00
return { success : false , message : "Insufficient credits. For more credits, you can upgrade your plan at https://firecrawl.dev/pricing." , remainingCredits : chunk.remaining_credits , chunk } ;
2024-09-07 13:42:45 -03:00
} else if ( creditUsagePercentage >= 0.8 && creditUsagePercentage < 1 ) {
2024-06-05 13:20:26 -07:00
// Send email notification for approaching credit limit
2024-09-25 19:25:18 +02:00
sendNotification (
2024-09-07 13:42:45 -03:00
team_id ,
NotificationType . APPROACHING_LIMIT ,
2024-09-25 19:25:18 +02:00
chunk . sub_current_period_start ,
chunk . sub_current_period_end
2024-09-07 13:42:45 -03:00
) ;
2024-04-15 17:01:47 -04:00
}
2024-09-25 20:37:35 +02:00
return { success : true , message : "Sufficient credits available" , remainingCredits : chunk.remaining_credits , chunk } ;
2024-04-15 17:01:47 -04:00
}
// Count the total credits used by a team within the current billing period and return the remaining credits.
export async function countCreditsAndRemainingForCurrentBillingPeriod (
team_id : string
) {
// 1. Retrieve the team's active subscription based on the team_id.
const { data : subscription , error : subscriptionError } =
await supabase_service
. from ( "subscriptions" )
. select ( "id, price_id, current_period_start, current_period_end" )
. eq ( "team_id" , team_id )
. single ( ) ;
2024-04-26 11:42:49 -03:00
const { data : coupons } = await supabase_service
. from ( "coupons" )
. select ( "credits" )
. eq ( "team_id" , team_id )
. eq ( "status" , "active" ) ;
2024-04-25 10:05:53 -03:00
2024-04-26 11:42:49 -03:00
let couponCredits = 0 ;
if ( coupons && coupons . length > 0 ) {
2024-06-05 13:22:03 -07:00
couponCredits = coupons . reduce (
( total , coupon ) = > total + coupon . credits ,
0
) ;
2024-04-26 11:42:49 -03:00
}
2024-04-15 17:01:47 -04:00
2024-04-26 11:42:49 -03:00
if ( subscriptionError || ! subscription ) {
2024-04-15 17:01:47 -04:00
// Free
const { data : creditUsages , error : creditUsageError } =
await supabase_service
. from ( "credit_usage" )
. select ( "credits_used" )
. is ( "subscription_id" , null )
. eq ( "team_id" , team_id ) ;
if ( creditUsageError || ! creditUsages ) {
2024-06-05 13:22:03 -07:00
throw new Error (
` Failed to retrieve credit usage for team_id: ${ team_id } `
) ;
2024-04-15 17:01:47 -04:00
}
const totalCreditsUsed = creditUsages . reduce (
( acc , usage ) = > acc + usage . credits_used ,
0
) ;
2024-04-26 11:42:49 -03:00
const remainingCredits = FREE_CREDITS + couponCredits - totalCreditsUsed ;
2024-06-05 13:22:03 -07:00
return {
totalCreditsUsed : totalCreditsUsed ,
remainingCredits ,
totalCredits : FREE_CREDITS + couponCredits ,
} ;
2024-04-15 17:01:47 -04:00
}
const { data : creditUsages , error : creditUsageError } = await supabase_service
2024-04-26 11:42:49 -03:00
. from ( "credit_usage" )
. select ( "credits_used" )
. eq ( "subscription_id" , subscription . id )
. gte ( "created_at" , subscription . current_period_start )
. lte ( "created_at" , subscription . current_period_end ) ;
2024-04-15 17:01:47 -04:00
if ( creditUsageError || ! creditUsages ) {
2024-06-05 13:22:03 -07:00
throw new Error (
` Failed to retrieve credit usage for subscription_id: ${ subscription . id } `
) ;
2024-04-15 17:01:47 -04:00
}
2024-06-05 13:22:03 -07:00
const totalCreditsUsed = creditUsages . reduce (
( acc , usage ) = > acc + usage . credits_used ,
0
) ;
2024-04-15 17:01:47 -04:00
2024-04-25 10:05:53 -03:00
const { data : price , error : priceError } = await supabase_service
2024-04-26 11:42:49 -03:00
. from ( "prices" )
. select ( "credits" )
. eq ( "id" , subscription . price_id )
. single ( ) ;
2024-04-25 10:05:53 -03:00
if ( priceError || ! price ) {
2024-06-05 13:22:03 -07:00
throw new Error (
` Failed to retrieve price for price_id: ${ subscription . price_id } `
) ;
2024-04-25 10:05:53 -03:00
}
2024-04-26 11:42:49 -03:00
const remainingCredits = price . credits + couponCredits - totalCreditsUsed ;
2024-04-25 10:05:53 -03:00
return {
2024-04-26 11:42:49 -03:00
totalCreditsUsed ,
remainingCredits ,
2024-06-05 13:22:03 -07:00
totalCredits : price.credits ,
2024-04-25 10:05:53 -03:00
} ;
2024-04-26 11:42:49 -03:00
}