diff --git a/lib/accounts/__tests__/createAccountHandler.test.ts b/lib/accounts/__tests__/createAccountHandler.test.ts index 0ad4d2c7b..e4a3b6d46 100644 --- a/lib/accounts/__tests__/createAccountHandler.test.ts +++ b/lib/accounts/__tests__/createAccountHandler.test.ts @@ -7,7 +7,7 @@ const mockGetAccountWithDetails = vi.fn(); const mockInsertAccount = vi.fn(); const mockInsertAccountEmail = vi.fn(); const mockInsertAccountWallet = vi.fn(); -const mockInsertCreditsUsage = vi.fn(); +const mockInitializeAccountCredits = vi.fn(); const mockAssignAccountToOrg = vi.fn(); vi.mock("@/lib/supabase/account_emails/selectAccountByEmail", () => ({ @@ -34,8 +34,8 @@ vi.mock("@/lib/supabase/account_wallets/insertAccountWallet", () => ({ insertAccountWallet: (...args: unknown[]) => mockInsertAccountWallet(...args), })); -vi.mock("@/lib/supabase/credits_usage/insertCreditsUsage", () => ({ - insertCreditsUsage: (...args: unknown[]) => mockInsertCreditsUsage(...args), +vi.mock("@/lib/credits/initializeAccountCredits", () => ({ + initializeAccountCredits: (...args: unknown[]) => mockInitializeAccountCredits(...args), })); vi.mock("@/lib/organizations/assignAccountToOrg", () => ({ @@ -128,7 +128,7 @@ describe("createAccountHandler", () => { expect(mockInsertAccountEmail).toHaveBeenCalledWith("account-789", "new@example.com"); expect(mockAssignAccountToOrg).toHaveBeenCalledWith("account-789", "new@example.com"); expect(mockInsertAccountWallet).toHaveBeenCalledWith("account-789", "0xdef"); - expect(mockInsertCreditsUsage).toHaveBeenCalledWith("account-789"); + expect(mockInitializeAccountCredits).toHaveBeenCalledWith("account-789"); }); it("returns 400 when account creation fails", async () => { diff --git a/lib/accounts/createAccountHandler.ts b/lib/accounts/createAccountHandler.ts index 6f7578de6..e93c5fcc5 100644 --- a/lib/accounts/createAccountHandler.ts +++ b/lib/accounts/createAccountHandler.ts @@ -6,7 +6,7 @@ import { getAccountWithDetails } from "@/lib/supabase/accounts/getAccountWithDet import { insertAccount } from "@/lib/supabase/accounts/insertAccount"; import { insertAccountEmail } from "@/lib/supabase/account_emails/insertAccountEmail"; import { insertAccountWallet } from "@/lib/supabase/account_wallets/insertAccountWallet"; -import { insertCreditsUsage } from "@/lib/supabase/credits_usage/insertCreditsUsage"; +import { initializeAccountCredits } from "@/lib/credits/initializeAccountCredits"; import { assignAccountToOrg } from "@/lib/organizations/assignAccountToOrg"; import type { CreateAccountBody } from "./validateCreateAccountBody"; @@ -91,7 +91,7 @@ export async function createAccountHandler(body: CreateAccountBody): Promise ({ insertAccountEmail: vi.fn(() => ({ id: "ae_1" })), })); -vi.mock("@/lib/supabase/credits_usage/insertCreditsUsage", () => ({ - insertCreditsUsage: vi.fn(() => ({ id: "cu_1" })), +vi.mock("@/lib/credits/initializeAccountCredits", () => ({ + initializeAccountCredits: vi.fn(() => ({ id: "cu_1" })), })); vi.mock("@/lib/keys/generateApiKey", () => ({ diff --git a/lib/agents/__tests__/createAccountWithEmail.test.ts b/lib/agents/__tests__/createAccountWithEmail.test.ts index 5dba14dc4..425faec56 100644 --- a/lib/agents/__tests__/createAccountWithEmail.test.ts +++ b/lib/agents/__tests__/createAccountWithEmail.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { createAccountWithEmail } from "@/lib/agents/createAccountWithEmail"; import { insertAccount } from "@/lib/supabase/accounts/insertAccount"; import { insertAccountEmail } from "@/lib/supabase/account_emails/insertAccountEmail"; -import { insertCreditsUsage } from "@/lib/supabase/credits_usage/insertCreditsUsage"; +import { initializeAccountCredits } from "@/lib/credits/initializeAccountCredits"; vi.mock("@/lib/supabase/accounts/insertAccount", () => ({ insertAccount: vi.fn(), @@ -12,8 +12,8 @@ vi.mock("@/lib/supabase/account_emails/insertAccountEmail", () => ({ insertAccountEmail: vi.fn(() => ({ id: "ae_1" })), })); -vi.mock("@/lib/supabase/credits_usage/insertCreditsUsage", () => ({ - insertCreditsUsage: vi.fn(() => ({ id: "cu_1" })), +vi.mock("@/lib/credits/initializeAccountCredits", () => ({ + initializeAccountCredits: vi.fn(() => ({ id: "cu_1" })), })); describe("createAccountWithEmail", () => { @@ -25,9 +25,9 @@ describe("createAccountWithEmail", () => { vi.mocked(insertAccountEmail).mockResolvedValue({ id: "ae_1", } as unknown as Awaited>); - vi.mocked(insertCreditsUsage).mockResolvedValue({ + vi.mocked(initializeAccountCredits).mockResolvedValue({ id: "cu_1", - } as unknown as Awaited>); + } as unknown as Awaited>); }); it("creates the account, inserts the email link and credits row, and returns the new account id", async () => { @@ -36,7 +36,7 @@ describe("createAccountWithEmail", () => { expect(result).toBe("acc_new"); expect(insertAccount).toHaveBeenCalledOnce(); expect(insertAccountEmail).toHaveBeenCalledWith("acc_new", "user@example.com"); - expect(insertCreditsUsage).toHaveBeenCalledWith("acc_new"); + expect(initializeAccountCredits).toHaveBeenCalledWith("acc_new"); }); it("throws when insertAccountEmail returns null so the caller cannot end up with an emailless account", async () => { @@ -47,11 +47,13 @@ describe("createAccountWithEmail", () => { await expect(createAccountWithEmail("user@example.com")).rejects.toThrow(/insertAccountEmail/); }); - it("throws when insertCreditsUsage returns null", async () => { - vi.mocked(insertCreditsUsage).mockResolvedValueOnce( - null as unknown as Awaited>, + it("throws when initializeAccountCredits returns null", async () => { + vi.mocked(initializeAccountCredits).mockResolvedValueOnce( + null as unknown as Awaited>, ); - await expect(createAccountWithEmail("user@example.com")).rejects.toThrow(/insertCreditsUsage/); + await expect(createAccountWithEmail("user@example.com")).rejects.toThrow( + /initializeAccountCredits/, + ); }); }); diff --git a/lib/agents/createAccountWithEmail.ts b/lib/agents/createAccountWithEmail.ts index 2ae486d78..666d60808 100644 --- a/lib/agents/createAccountWithEmail.ts +++ b/lib/agents/createAccountWithEmail.ts @@ -1,6 +1,6 @@ import { insertAccount } from "@/lib/supabase/accounts/insertAccount"; import { insertAccountEmail } from "@/lib/supabase/account_emails/insertAccountEmail"; -import { insertCreditsUsage } from "@/lib/supabase/credits_usage/insertCreditsUsage"; +import { initializeAccountCredits } from "@/lib/credits/initializeAccountCredits"; /** * Creates a new account row and wires up its email link and credits usage @@ -27,9 +27,9 @@ export async function createAccountWithEmail(email: string): Promise { throw new Error("createAccountWithEmail: insertAccountEmail returned null"); } - const credits = await insertCreditsUsage(account.id); + const credits = await initializeAccountCredits(account.id); if (!credits) { - throw new Error("createAccountWithEmail: insertCreditsUsage returned null"); + throw new Error("createAccountWithEmail: initializeAccountCredits returned null"); } return account.id; diff --git a/lib/credits/__tests__/checkAndResetCredits.test.ts b/lib/credits/__tests__/checkAndResetCredits.test.ts index 6d8e66be6..c47e88060 100644 --- a/lib/credits/__tests__/checkAndResetCredits.test.ts +++ b/lib/credits/__tests__/checkAndResetCredits.test.ts @@ -3,8 +3,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { checkAndResetCredits } from "@/lib/credits/checkAndResetCredits"; import { selectCreditsUsage } from "@/lib/supabase/credits_usage/selectCreditsUsage"; import { updateCreditsUsage } from "@/lib/supabase/credits_usage/updateCreditsUsage"; -import { getActiveSubscriptionDetails } from "@/lib/stripe/getActiveSubscriptionDetails"; -import { getOrgSubscription } from "@/lib/stripe/getOrgSubscription"; +import { getAccountSubscriptionState } from "@/lib/credits/getAccountSubscriptionState"; import { DEFAULT_CREDITS, PRO_CREDITS } from "@/lib/credits/const"; vi.mock("@/lib/supabase/credits_usage/selectCreditsUsage", () => ({ @@ -15,16 +14,32 @@ vi.mock("@/lib/supabase/credits_usage/updateCreditsUsage", () => ({ updateCreditsUsage: vi.fn(), })); -vi.mock("@/lib/stripe/getActiveSubscriptionDetails", () => ({ - getActiveSubscriptionDetails: vi.fn(), -})); - -vi.mock("@/lib/stripe/getOrgSubscription", () => ({ - getOrgSubscription: vi.fn(), +vi.mock("@/lib/credits/getAccountSubscriptionState", () => ({ + getAccountSubscriptionState: vi.fn(), })); const ACCOUNT = "123e4567-e89b-12d3-a456-426614174000"; +const freeState = { isPro: false, activeSubscription: null }; +const proStateFromAccount = { + isPro: true, + activeSubscription: { + id: "sub_1", + status: "active", + canceled_at: null, + current_period_start: Math.floor(new Date("2026-04-15T00:00:00.000Z").getTime() / 1000), + } as never, +}; +const proStateFromOrgNewlySubscribed = { + isPro: true, + activeSubscription: { + id: "sub_org", + status: "active", + canceled_at: null, + current_period_start: Math.floor(new Date("2026-05-08T00:00:00.000Z").getTime() / 1000), + } as never, +}; + const baseRow = ( overrides: Partial<{ remaining_credits: number; timestamp: string | null }> = {}, ) => ({ @@ -44,8 +59,7 @@ describe("checkAndResetCredits", () => { it("returns { creditsUsage: null, isPro: false } when no credits row exists", async () => { vi.mocked(selectCreditsUsage).mockResolvedValue([]); - vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(null); - vi.mocked(getOrgSubscription).mockResolvedValue(null); + vi.mocked(getAccountSubscriptionState).mockResolvedValue(freeState); const result = await checkAndResetCredits(ACCOUNT); @@ -56,8 +70,7 @@ describe("checkAndResetCredits", () => { it("returns the row unchanged when it has no timestamp (never refilled)", async () => { const row = baseRow({ timestamp: null, remaining_credits: 200 }); vi.mocked(selectCreditsUsage).mockResolvedValue([row]); - vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(null); - vi.mocked(getOrgSubscription).mockResolvedValue(null); + vi.mocked(getAccountSubscriptionState).mockResolvedValue(freeState); const result = await checkAndResetCredits(ACCOUNT); @@ -68,8 +81,7 @@ describe("checkAndResetCredits", () => { it("returns the row unchanged when last refill was within the past month and no new sub", async () => { const row = baseRow({ timestamp: "2026-05-01T00:00:00.000Z", remaining_credits: 150 }); vi.mocked(selectCreditsUsage).mockResolvedValue([row]); - vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(null); - vi.mocked(getOrgSubscription).mockResolvedValue(null); + vi.mocked(getAccountSubscriptionState).mockResolvedValue(freeState); const result = await checkAndResetCredits(ACCOUNT); @@ -86,8 +98,7 @@ describe("checkAndResetCredits", () => { }; vi.mocked(selectCreditsUsage).mockResolvedValue([row]); vi.mocked(updateCreditsUsage).mockResolvedValue(refilled); - vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(null); - vi.mocked(getOrgSubscription).mockResolvedValue(null); + vi.mocked(getAccountSubscriptionState).mockResolvedValue(freeState); const result = await checkAndResetCredits(ACCOUNT); @@ -110,13 +121,7 @@ describe("checkAndResetCredits", () => { }; vi.mocked(selectCreditsUsage).mockResolvedValue([row]); vi.mocked(updateCreditsUsage).mockResolvedValue(refilled); - vi.mocked(getActiveSubscriptionDetails).mockResolvedValue({ - id: "sub_1", - status: "active", - canceled_at: null, - current_period_start: Math.floor(new Date("2026-04-15T00:00:00.000Z").getTime() / 1000), - } as never); - vi.mocked(getOrgSubscription).mockResolvedValue(null); + vi.mocked(getAccountSubscriptionState).mockResolvedValue(proStateFromAccount); const result = await checkAndResetCredits(ACCOUNT); @@ -139,13 +144,7 @@ describe("checkAndResetCredits", () => { }; vi.mocked(selectCreditsUsage).mockResolvedValue([row]); vi.mocked(updateCreditsUsage).mockResolvedValue(refilled); - vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(null); - vi.mocked(getOrgSubscription).mockResolvedValue({ - id: "sub_org", - status: "active", - canceled_at: null, - current_period_start: Math.floor(new Date("2026-05-08T00:00:00.000Z").getTime() / 1000), - } as never); + vi.mocked(getAccountSubscriptionState).mockResolvedValue(proStateFromOrgNewlySubscribed); const result = await checkAndResetCredits(ACCOUNT); @@ -157,13 +156,7 @@ describe("checkAndResetCredits", () => { it("reports isPro=true without refilling when sub is active but neither refill trigger fires", async () => { const row = baseRow({ timestamp: "2026-05-01T00:00:00.000Z", remaining_credits: 800 }); vi.mocked(selectCreditsUsage).mockResolvedValue([row]); - vi.mocked(getActiveSubscriptionDetails).mockResolvedValue({ - id: "sub_1", - status: "active", - canceled_at: null, - current_period_start: Math.floor(new Date("2026-04-15T00:00:00.000Z").getTime() / 1000), - } as never); - vi.mocked(getOrgSubscription).mockResolvedValue(null); + vi.mocked(getAccountSubscriptionState).mockResolvedValue(proStateFromAccount); const result = await checkAndResetCredits(ACCOUNT); diff --git a/lib/credits/__tests__/getAccountSubscriptionState.test.ts b/lib/credits/__tests__/getAccountSubscriptionState.test.ts new file mode 100644 index 000000000..5c9e0d45b --- /dev/null +++ b/lib/credits/__tests__/getAccountSubscriptionState.test.ts @@ -0,0 +1,72 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { getAccountSubscriptionState } from "@/lib/credits/getAccountSubscriptionState"; +import { getActiveSubscriptionDetails } from "@/lib/stripe/getActiveSubscriptionDetails"; +import { getOrgSubscription } from "@/lib/stripe/getOrgSubscription"; + +vi.mock("@/lib/stripe/getActiveSubscriptionDetails", () => ({ + getActiveSubscriptionDetails: vi.fn(), +})); + +vi.mock("@/lib/stripe/getOrgSubscription", () => ({ + getOrgSubscription: vi.fn(), +})); + +const ACCOUNT = "123e4567-e89b-12d3-a456-426614174000"; + +describe("getAccountSubscriptionState", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns isPro=false / activeSubscription=null when neither subscription is active", async () => { + vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(null); + vi.mocked(getOrgSubscription).mockResolvedValue(null); + + const result = await getAccountSubscriptionState(ACCOUNT); + + expect(result).toEqual({ isPro: false, activeSubscription: null }); + }); + + it("returns isPro=true and prefers the account subscription when both are active", async () => { + const accountSub = { + id: "sub_account", + status: "active", + canceled_at: null, + } as never; + const orgSub = { id: "sub_org", status: "active", canceled_at: null } as never; + vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(accountSub); + vi.mocked(getOrgSubscription).mockResolvedValue(orgSub); + + const result = await getAccountSubscriptionState(ACCOUNT); + + expect(result).toEqual({ isPro: true, activeSubscription: accountSub }); + }); + + it("falls back to the org subscription when only it is active", async () => { + const orgSub = { + id: "sub_org", + status: "trialing", + canceled_at: null, + } as never; + vi.mocked(getActiveSubscriptionDetails).mockResolvedValue(null); + vi.mocked(getOrgSubscription).mockResolvedValue(orgSub); + + const result = await getAccountSubscriptionState(ACCOUNT); + + expect(result).toEqual({ isPro: true, activeSubscription: orgSub }); + }); + + it("returns isPro=false when the account subscription exists but is canceled trialing", async () => { + vi.mocked(getActiveSubscriptionDetails).mockResolvedValue({ + id: "sub_account", + status: "trialing", + canceled_at: 1700000000, + } as never); + vi.mocked(getOrgSubscription).mockResolvedValue(null); + + const result = await getAccountSubscriptionState(ACCOUNT); + + expect(result).toEqual({ isPro: false, activeSubscription: null }); + }); +}); diff --git a/lib/credits/__tests__/initializeAccountCredits.test.ts b/lib/credits/__tests__/initializeAccountCredits.test.ts new file mode 100644 index 000000000..220fc8608 --- /dev/null +++ b/lib/credits/__tests__/initializeAccountCredits.test.ts @@ -0,0 +1,76 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { initializeAccountCredits } from "@/lib/credits/initializeAccountCredits"; +import { insertCreditsUsage } from "@/lib/supabase/credits_usage/insertCreditsUsage"; +import { getAccountSubscriptionState } from "@/lib/credits/getAccountSubscriptionState"; +import { DEFAULT_CREDITS, PRO_CREDITS } from "@/lib/credits/const"; + +vi.mock("@/lib/supabase/credits_usage/insertCreditsUsage", () => ({ + insertCreditsUsage: vi.fn(), +})); + +vi.mock("@/lib/credits/getAccountSubscriptionState", () => ({ + getAccountSubscriptionState: vi.fn(), +})); + +const ACCOUNT = "123e4567-e89b-12d3-a456-426614174000"; + +describe("initializeAccountCredits", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("seeds DEFAULT_CREDITS for a free-tier account", async () => { + vi.mocked(getAccountSubscriptionState).mockResolvedValue({ + isPro: false, + activeSubscription: null, + }); + const inserted = { + id: 1, + account_id: ACCOUNT, + remaining_credits: DEFAULT_CREDITS, + timestamp: null, + }; + vi.mocked(insertCreditsUsage).mockResolvedValue(inserted); + + const result = await initializeAccountCredits(ACCOUNT); + + expect(insertCreditsUsage).toHaveBeenCalledWith(ACCOUNT, DEFAULT_CREDITS); + expect(result).toEqual(inserted); + }); + + it("seeds PRO_CREDITS when the account already has an active subscription", async () => { + vi.mocked(getAccountSubscriptionState).mockResolvedValue({ + isPro: true, + activeSubscription: { + id: "sub_1", + status: "active", + canceled_at: null, + } as never, + }); + const inserted = { + id: 2, + account_id: ACCOUNT, + remaining_credits: PRO_CREDITS, + timestamp: null, + }; + vi.mocked(insertCreditsUsage).mockResolvedValue(inserted); + + const result = await initializeAccountCredits(ACCOUNT); + + expect(insertCreditsUsage).toHaveBeenCalledWith(ACCOUNT, PRO_CREDITS); + expect(result).toEqual(inserted); + }); + + it("returns null when the underlying insert fails", async () => { + vi.mocked(getAccountSubscriptionState).mockResolvedValue({ + isPro: false, + activeSubscription: null, + }); + vi.mocked(insertCreditsUsage).mockResolvedValue(null); + + const result = await initializeAccountCredits(ACCOUNT); + + expect(result).toBeNull(); + }); +}); diff --git a/lib/credits/checkAndResetCredits.ts b/lib/credits/checkAndResetCredits.ts index b455d1877..fa61726cb 100644 --- a/lib/credits/checkAndResetCredits.ts +++ b/lib/credits/checkAndResetCredits.ts @@ -3,9 +3,7 @@ import { type CreditsUsage, } from "@/lib/supabase/credits_usage/selectCreditsUsage"; import { updateCreditsUsage } from "@/lib/supabase/credits_usage/updateCreditsUsage"; -import isActiveSubscription from "@/lib/stripe/isActiveSubscription"; -import { getActiveSubscriptionDetails } from "@/lib/stripe/getActiveSubscriptionDetails"; -import { getOrgSubscription } from "@/lib/stripe/getOrgSubscription"; +import { getAccountSubscriptionState } from "@/lib/credits/getAccountSubscriptionState"; import { DEFAULT_CREDITS, PRO_CREDITS } from "@/lib/credits/const"; export interface CheckAndResetCreditsResult { @@ -21,16 +19,11 @@ export interface CheckAndResetCreditsResult { * Also returns `isPro` so callers don't need to repeat the subscription lookup. */ export async function checkAndResetCredits(accountId: string): Promise { - const [rows, accountSub, orgSub] = await Promise.all([ + const [rows, { isPro, activeSubscription }] = await Promise.all([ selectCreditsUsage({ account_id: accountId }), - getActiveSubscriptionDetails(accountId), - getOrgSubscription(accountId), + getAccountSubscriptionState(accountId), ]); - const hasAccountSub = isActiveSubscription(accountSub); - const hasOrgSub = isActiveSubscription(orgSub); - const isPro = hasAccountSub || hasOrgSub; - if (!rows || rows.length === 0) { return { creditsUsage: null, isPro }; } @@ -45,8 +38,8 @@ export async function checkAndResetCredits(accountId: string): Promise { + const [accountSub, orgSub] = await Promise.all([ + getActiveSubscriptionDetails(accountId), + getOrgSubscription(accountId), + ]); + const hasAccountSub = isActiveSubscription(accountSub); + const hasOrgSub = isActiveSubscription(orgSub); + return { + isPro: hasAccountSub || hasOrgSub, + activeSubscription: hasAccountSub ? accountSub : hasOrgSub ? orgSub : null, + }; +} diff --git a/lib/credits/initializeAccountCredits.ts b/lib/credits/initializeAccountCredits.ts new file mode 100644 index 000000000..30137f103 --- /dev/null +++ b/lib/credits/initializeAccountCredits.ts @@ -0,0 +1,19 @@ +import type { Tables } from "@/types/database.types"; +import { insertCreditsUsage } from "@/lib/supabase/credits_usage/insertCreditsUsage"; +import { getAccountSubscriptionState } from "@/lib/credits/getAccountSubscriptionState"; +import { DEFAULT_CREDITS, PRO_CREDITS } from "@/lib/credits/const"; + +/** + * Seeds a brand-new `credits_usage` row for an account with the plan-aware + * starting balance: `PRO_CREDITS` if the account (or an org they belong to) + * already has an active Stripe subscription, otherwise `DEFAULT_CREDITS`. + * + * Use this from any account-creation path. Do not call `insertCreditsUsage` + * directly with a hard-coded number — let this function pick the right value. + */ +export async function initializeAccountCredits( + accountId: string, +): Promise | null> { + const { isPro } = await getAccountSubscriptionState(accountId); + return insertCreditsUsage(accountId, isPro ? PRO_CREDITS : DEFAULT_CREDITS); +} diff --git a/lib/supabase/credits_usage/insertCreditsUsage.ts b/lib/supabase/credits_usage/insertCreditsUsage.ts index 78df4956c..a2876e914 100644 --- a/lib/supabase/credits_usage/insertCreditsUsage.ts +++ b/lib/supabase/credits_usage/insertCreditsUsage.ts @@ -1,20 +1,20 @@ import supabase from "../serverClient"; import type { Tables } from "@/types/database.types"; -/** Default credits for free tier accounts */ -const DEFAULT_CREDITS = 25; - /** - * Inserts a new credits_usage record for an account. - * Initializes with default credits. + * Inserts a new credits_usage record for an account at the supplied balance. + * + * This is the low-level DB op. Callers should not invoke it directly with a + * hard-coded number — go through `lib/credits/initializeAccountCredits` so the + * plan-aware DEFAULT_CREDITS / PRO_CREDITS choice stays in one place. * * @param accountId - The account ID to initialize credits for - * @param remainingCredits - Optional override for initial credits (defaults to 25) + * @param remainingCredits - Initial balance (caller decides — no default) * @returns The inserted credits_usage record, or null if failed */ export async function insertCreditsUsage( accountId: string, - remainingCredits: number = DEFAULT_CREDITS, + remainingCredits: number, ): Promise | null> { const { data, error } = await supabase .from("credits_usage")