diff --git a/apps/api/src/controllers/auth.ts b/apps/api/src/controllers/auth.ts index fe7baa62..96f94a99 100644 --- a/apps/api/src/controllers/auth.ts +++ b/apps/api/src/controllers/auth.ts @@ -37,7 +37,7 @@ function normalizedApiIsUuid(potentialUuid: string): boolean { return validate(potentialUuid); } -async function setCachedACUC(api_key: string, acuc: AuthCreditUsageChunk) { +export async function setCachedACUC(api_key: string, acuc: AuthCreditUsageChunk) { const cacheKeyACUC = `acuc_${api_key}`; const redLockKey = `lock_${cacheKeyACUC}`; const lockTTL = 10000; // 10 seconds @@ -58,14 +58,14 @@ async function setCachedACUC(api_key: string, acuc: AuthCreditUsageChunk) { } } -async function getACUC(api_key: string): Promise { +export async function getACUC(api_key: string, cacheOnly = false): Promise { const cacheKeyACUC = `acuc_${api_key}`; const cachedACUC = await getValue(cacheKeyACUC); if (cachedACUC !== null) { return JSON.parse(cachedACUC); - } else { + } else if (!cacheOnly) { const { data, error } = await supabase_service.rpc("auth_credit_usage_chunk", { input_key: api_key }); @@ -85,6 +85,8 @@ async function getACUC(api_key: string): Promise { } return chunk; + } else { + return null; } } diff --git a/apps/api/src/controllers/v1/types.ts b/apps/api/src/controllers/v1/types.ts index 5e99fdab..441980b1 100644 --- a/apps/api/src/controllers/v1/types.ts +++ b/apps/api/src/controllers/v1/types.ts @@ -324,7 +324,7 @@ export type AuthCreditUsageChunk = { price_id: string | null; price_credits: number; // credit limit with assoicated price, or free_credits (500) if free plan credits_used: number; - coupon_credits: number; + coupon_credits: number; // do not rely on this number to be up to date after calling a billTeam coupons: any[]; adjusted_credits_used: number; // credits this period minus coupons used remaining_credits: number; diff --git a/apps/api/src/services/billing/credit_billing.ts b/apps/api/src/services/billing/credit_billing.ts index 9d0d1f09..6fa34906 100644 --- a/apps/api/src/services/billing/credit_billing.ts +++ b/apps/api/src/services/billing/credit_billing.ts @@ -5,6 +5,7 @@ import { supabase_service } from "../supabase"; import { Logger } from "../../lib/logger"; import * as Sentry from "@sentry/node"; import { AuthCreditUsageChunk } from "../../controllers/v1/types"; +import { getACUC, setCachedACUC } from "../../controllers/auth"; const FREE_CREDITS = 500; @@ -20,13 +21,29 @@ export async function supaBillTeam(team_id: string, subscription_id: string, cre } Logger.info(`Billing team ${team_id} for ${credits} credits`); - const { error } = + const { data, error } = 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)); + return; } + + (async () => { + for (const apiKey of (data ?? []).map(x => x.api_key)) { + const acuc = await getACUC(apiKey, true); + + if (acuc !== null) { + await setCachedACUC(apiKey, { + ...acuc, + credits_used: acuc.credits_used + credits, + adjusted_credits_used: acuc.adjusted_credits_used + credits, + remaining_credits: acuc.remaining_credits - credits, + }); + } + } + })(); } export async function checkTeamCredits(chunk: AuthCreditUsageChunk, team_id: string, credits: number) {