diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx index b5ef365263..3c47e7f55c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx @@ -12,6 +12,7 @@ import { Skeleton, } from '@/components/ui' import { useSession, useSubscription } from '@/lib/auth-client' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { createLogger } from '@/lib/logs/console/logger' import { BillingSummary, @@ -227,7 +228,7 @@ export function Subscription({ onOpenChange }: SubscriptionProps) { subscription.isEnterprise || (subscription.isTeam && isTeamAdmin) } - minimumLimit={usageLimitData?.minimumLimit ?? 5} + minimumLimit={usageLimitData?.minimumLimit ?? DEFAULT_FREE_CREDITS} /> )} diff --git a/apps/sim/db/schema.ts b/apps/sim/db/schema.ts index c77d55cd8a..6b6c234268 100644 --- a/apps/sim/db/schema.ts +++ b/apps/sim/db/schema.ts @@ -16,6 +16,7 @@ import { uuid, vector, } from 'drizzle-orm/pg-core' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { TAG_SLOTS } from '@/lib/constants/knowledge' // Custom tsvector type for full-text search @@ -451,7 +452,9 @@ export const userStats = pgTable('user_stats', { totalChatExecutions: integer('total_chat_executions').notNull().default(0), totalTokensUsed: integer('total_tokens_used').notNull().default(0), totalCost: decimal('total_cost').notNull().default('0'), - currentUsageLimit: decimal('current_usage_limit').notNull().default('5'), // Default $5 for free plan + currentUsageLimit: decimal('current_usage_limit') + .notNull() + .default(DEFAULT_FREE_CREDITS.toString()), // Default $10 for free plan usageLimitSetBy: text('usage_limit_set_by'), // User ID who set the limit (for team admin tracking) usageLimitUpdatedAt: timestamp('usage_limit_updated_at').defaultNow(), // Billing period tracking diff --git a/apps/sim/hooks/use-subscription-state.ts b/apps/sim/hooks/use-subscription-state.ts index 873fd4825e..93dffb0045 100644 --- a/apps/sim/hooks/use-subscription-state.ts +++ b/apps/sim/hooks/use-subscription-state.ts @@ -1,4 +1,5 @@ import { useCallback, useEffect, useState } from 'react' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import type { SubscriptionFeatures } from '@/lib/billing/types' import { createLogger } from '@/lib/logs/console/logger' @@ -89,7 +90,7 @@ export function useSubscriptionState() { usage: { current: data?.usage?.current ?? 0, - limit: data?.usage?.limit ?? 5, + limit: data?.usage?.limit ?? DEFAULT_FREE_CREDITS, percentUsed: data?.usage?.percentUsed ?? 0, isWarning: data?.usage?.isWarning ?? false, isExceeded: data?.usage?.isExceeded ?? false, @@ -214,9 +215,9 @@ export function useUsageLimit() { } return { - currentLimit: data?.currentLimit ?? 5, + currentLimit: data?.currentLimit ?? DEFAULT_FREE_CREDITS, canEdit: data?.canEdit ?? false, - minimumLimit: data?.minimumLimit ?? 5, + minimumLimit: data?.minimumLimit ?? DEFAULT_FREE_CREDITS, plan: data?.plan ?? 'free', setBy: data?.setBy, updatedAt: data?.updatedAt ? new Date(data.updatedAt) : null, diff --git a/apps/sim/lib/auth.ts b/apps/sim/lib/auth.ts index 20ffbeb2dc..14760e7168 100644 --- a/apps/sim/lib/auth.ts +++ b/apps/sim/lib/auth.ts @@ -20,6 +20,7 @@ import { renderPasswordResetEmail, } from '@/components/emails/render-email' import { getBaseURL } from '@/lib/auth-client' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { env, isTruthy } from '@/lib/env' import { isProd } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' @@ -1080,7 +1081,7 @@ export const auth = betterAuth({ name: 'free', priceId: env.STRIPE_FREE_PRICE_ID || '', limits: { - cost: env.FREE_TIER_COST_LIMIT ?? 5, + cost: env.FREE_TIER_COST_LIMIT ?? DEFAULT_FREE_CREDITS, sharingEnabled: 0, multiplayerEnabled: 0, workspaceCollaborationEnabled: 0, diff --git a/apps/sim/lib/billing/constants.ts b/apps/sim/lib/billing/constants.ts index 33c3e8b3c1..cc9c1b50ab 100644 --- a/apps/sim/lib/billing/constants.ts +++ b/apps/sim/lib/billing/constants.ts @@ -2,6 +2,11 @@ * Billing and cost constants shared between client and server code */ +/** + * Default free credits (in dollars) for new users + */ +export const DEFAULT_FREE_CREDITS = 10 + /** * Base charge applied to every workflow execution * This charge is applied regardless of whether the workflow uses AI models diff --git a/apps/sim/lib/billing/core/billing.ts b/apps/sim/lib/billing/core/billing.ts index 4de81e0d30..728575658b 100644 --- a/apps/sim/lib/billing/core/billing.ts +++ b/apps/sim/lib/billing/core/billing.ts @@ -1,4 +1,5 @@ import { and, eq } from 'drizzle-orm' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { resetOrganizationBillingPeriod, resetUserBillingPeriod, @@ -917,7 +918,7 @@ function getDefaultBillingSummary(type: 'individual' | 'organization') { currentUsage: 0, overageAmount: 0, totalProjected: 0, - usageLimit: 5, + usageLimit: DEFAULT_FREE_CREDITS, percentUsed: 0, isWarning: false, isExceeded: false, @@ -935,7 +936,7 @@ function getDefaultBillingSummary(type: 'individual' | 'organization') { // Usage details usage: { current: 0, - limit: 5, + limit: DEFAULT_FREE_CREDITS, percentUsed: 0, isWarning: false, isExceeded: false, diff --git a/apps/sim/lib/billing/core/organization-billing.ts b/apps/sim/lib/billing/core/organization-billing.ts index 5f10877365..d47c4d9559 100644 --- a/apps/sim/lib/billing/core/organization-billing.ts +++ b/apps/sim/lib/billing/core/organization-billing.ts @@ -1,4 +1,5 @@ import { and, eq } from 'drizzle-orm' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { getPlanPricing } from '@/lib/billing/core/billing' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { createLogger } from '@/lib/logs/console/logger' @@ -87,7 +88,7 @@ export async function getOrganizationBillingData( // Process member data const members: MemberUsageData[] = membersWithUsage.map((memberRecord) => { const currentUsage = Number(memberRecord.currentPeriodCost || 0) - const usageLimit = Number(memberRecord.currentUsageLimit || 5) + const usageLimit = Number(memberRecord.currentUsageLimit || DEFAULT_FREE_CREDITS) const percentUsed = usageLimit > 0 ? (currentUsage / usageLimit) * 100 : 0 return { @@ -197,13 +198,14 @@ export async function updateMemberUsageLimit( // Validate minimum limit based on plan const planLimits = { - free: 5, + free: DEFAULT_FREE_CREDITS, pro: 20, team: 40, enterprise: 100, // Default, can be overridden by metadata } - let minimumLimit = planLimits[subscription.plan as keyof typeof planLimits] || 5 + let minimumLimit = + planLimits[subscription.plan as keyof typeof planLimits] || DEFAULT_FREE_CREDITS // For enterprise, check metadata for custom limits if (subscription.plan === 'enterprise' && subscription.metadata) { diff --git a/apps/sim/lib/billing/core/subscription.ts b/apps/sim/lib/billing/core/subscription.ts index f1bfa10065..369013031d 100644 --- a/apps/sim/lib/billing/core/subscription.ts +++ b/apps/sim/lib/billing/core/subscription.ts @@ -1,5 +1,6 @@ import { and, eq, inArray } from 'drizzle-orm' import { client } from '@/lib/auth-client' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { calculateDefaultUsageLimit, checkEnterprisePlan, @@ -156,7 +157,7 @@ export async function hasExceededCostLimit(userId: string): Promise { const subscription = await getHighestPrioritySubscription(userId) // Calculate usage limit - let limit = 5 // Default free tier limit + let limit = DEFAULT_FREE_CREDITS // Default free tier limit if (subscription) { limit = calculateDefaultUsageLimit(subscription) logger.info('Using subscription-based limit', { @@ -338,7 +339,7 @@ export async function getUserSubscriptionState(userId: string): Promise 0) { - let limit = 5 // Default free tier limit + let limit = DEFAULT_FREE_CREDITS // Default free tier limit if (subscription) { limit = calculateDefaultUsageLimit(subscription) } diff --git a/apps/sim/lib/billing/core/usage.ts b/apps/sim/lib/billing/core/usage.ts index 25e11e9869..657aa2d7f3 100644 --- a/apps/sim/lib/billing/core/usage.ts +++ b/apps/sim/lib/billing/core/usage.ts @@ -1,4 +1,5 @@ import { and, eq } from 'drizzle-orm' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { calculateDefaultUsageLimit, canEditUsageLimit } from '@/lib/billing/subscriptions/utils' import type { BillingData, UsageData, UsageLimitInfo } from '@/lib/billing/types' @@ -29,7 +30,7 @@ export async function getUserUsageData(userId: string): Promise { await initializeUserUsageLimit(userId) return { currentUsage: 0, - limit: 5, + limit: DEFAULT_FREE_CREDITS, percentUsed: 0, isWarning: false, isExceeded: false, @@ -98,7 +99,7 @@ export async function getUserUsageLimitInfo(userId: string): Promise { return // User already has usage stats, don't override } - // Create initial usage stats with default $5 limit + // Create initial usage stats with default free credits limit await db.insert(userStats).values({ id: crypto.randomUUID(), userId, - currentUsageLimit: '5', // Default $5 for new users + currentUsageLimit: DEFAULT_FREE_CREDITS.toString(), // Default free credits for new users usageLimitUpdatedAt: new Date(), billingPeriodStart: new Date(), // Start billing period immediately }) - logger.info('Initialized usage limit for new user', { userId, limit: 5 }) + logger.info('Initialized usage limit for new user', { userId, limit: DEFAULT_FREE_CREDITS }) } catch (error) { logger.error('Failed to initialize usage limit', { userId, error }) throw error @@ -233,7 +234,7 @@ export async function updateUserUsageLimit( if (!subscription || subscription.status !== 'active') { // Free plan users (shouldn't reach here due to canEditUsageLimit check above) - minimumLimit = 5 + minimumLimit = DEFAULT_FREE_CREDITS } else if (subscription.plan === 'pro') { // Pro plan users: $20 minimum minimumLimit = 20 @@ -311,7 +312,7 @@ export async function getUserUsageLimit(userId: string): Promise { if (userStatsQuery.length === 0) { // User doesn't have stats yet, initialize and return default await initializeUserUsageLimit(userId) - return 5 // Default free plan limit + return DEFAULT_FREE_CREDITS // Default free plan limit } return Number.parseFloat(userStatsQuery[0].currentUsageLimit) @@ -380,16 +381,16 @@ export async function syncUsageLimitsFromSubscription(userId: string): Promise ({ env: { - FREE_TIER_COST_LIMIT: 5, + FREE_TIER_COST_LIMIT: 10, PRO_TIER_COST_LIMIT: 20, TEAM_TIER_COST_LIMIT: 40, ENTERPRISE_TIER_COST_LIMIT: 200, @@ -27,15 +27,15 @@ describe('Subscription Utilities', () => { describe('calculateDefaultUsageLimit', () => { it.concurrent('returns free-tier limit when subscription is null', () => { - expect(calculateDefaultUsageLimit(null)).toBe(5) + expect(calculateDefaultUsageLimit(null)).toBe(10) }) it.concurrent('returns free-tier limit when subscription is undefined', () => { - expect(calculateDefaultUsageLimit(undefined)).toBe(5) + expect(calculateDefaultUsageLimit(undefined)).toBe(10) }) it.concurrent('returns free-tier limit when subscription is not active', () => { - expect(calculateDefaultUsageLimit({ plan: 'pro', status: 'canceled', seats: 1 })).toBe(5) + expect(calculateDefaultUsageLimit({ plan: 'pro', status: 'canceled', seats: 1 })).toBe(10) }) it.concurrent('returns pro limit for active pro plan', () => { diff --git a/apps/sim/lib/billing/subscriptions/utils.ts b/apps/sim/lib/billing/subscriptions/utils.ts index 2143f5499d..f33f68d256 100644 --- a/apps/sim/lib/billing/subscriptions/utils.ts +++ b/apps/sim/lib/billing/subscriptions/utils.ts @@ -1,3 +1,4 @@ +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { env } from '@/lib/env' export function checkEnterprisePlan(subscription: any): boolean { @@ -20,7 +21,7 @@ export function checkTeamPlan(subscription: any): boolean { */ export function calculateDefaultUsageLimit(subscription: any): number { if (!subscription || subscription.status !== 'active') { - return env.FREE_TIER_COST_LIMIT || 0 + return env.FREE_TIER_COST_LIMIT || DEFAULT_FREE_CREDITS } const seats = subscription.seats || 1 @@ -45,7 +46,7 @@ export function calculateDefaultUsageLimit(subscription: any): number { return seats * (env.ENTERPRISE_TIER_COST_LIMIT || 0) } - return env.FREE_TIER_COST_LIMIT || 0 + return env.FREE_TIER_COST_LIMIT || DEFAULT_FREE_CREDITS } /** diff --git a/apps/sim/scripts/test-billing-suite.ts b/apps/sim/scripts/test-billing-suite.ts index 763016d475..78fce1738b 100644 --- a/apps/sim/scripts/test-billing-suite.ts +++ b/apps/sim/scripts/test-billing-suite.ts @@ -56,7 +56,7 @@ async function runBillingTestSuite(): Promise { logger.info('\nšŸ“‹ Creating test users...') // Free user (no overage billing) - const freeUser = await createTestUser('free', 5) // $5 usage on free plan + const freeUser = await createTestUser('free', 10) // $10 usage on free plan results.users.push(freeUser) // Pro user with no overage diff --git a/apps/sim/stores/subscription/store.ts b/apps/sim/stores/subscription/store.ts index aebb53b8d0..7620327eba 100644 --- a/apps/sim/stores/subscription/store.ts +++ b/apps/sim/stores/subscription/store.ts @@ -1,5 +1,6 @@ import { create } from 'zustand' import { devtools } from 'zustand/middleware' +import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' import { createLogger } from '@/lib/logs/console/logger' import type { BillingStatus, @@ -22,7 +23,7 @@ const defaultFeatures: SubscriptionFeatures = { const defaultUsage: UsageData = { current: 0, - limit: 5, + limit: DEFAULT_FREE_CREDITS, percentUsed: 0, isWarning: false, isExceeded: false,