Skip to content

Commit e71a736

Browse files
authored
fix(billing): increase free tier credits (#862)
* Update free tier to 10 * Lint
1 parent 58e764c commit e71a736

File tree

13 files changed

+52
-34
lines changed

13 files changed

+52
-34
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Skeleton,
1313
} from '@/components/ui'
1414
import { useSession, useSubscription } from '@/lib/auth-client'
15+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
1516
import { createLogger } from '@/lib/logs/console/logger'
1617
import {
1718
BillingSummary,
@@ -227,7 +228,7 @@ export function Subscription({ onOpenChange }: SubscriptionProps) {
227228
subscription.isEnterprise ||
228229
(subscription.isTeam && isTeamAdmin)
229230
}
230-
minimumLimit={usageLimitData?.minimumLimit ?? 5}
231+
minimumLimit={usageLimitData?.minimumLimit ?? DEFAULT_FREE_CREDITS}
231232
/>
232233
)}
233234
</div>

apps/sim/db/schema.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
uuid,
1717
vector,
1818
} from 'drizzle-orm/pg-core'
19+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
1920
import { TAG_SLOTS } from '@/lib/constants/knowledge'
2021

2122
// Custom tsvector type for full-text search
@@ -451,7 +452,9 @@ export const userStats = pgTable('user_stats', {
451452
totalChatExecutions: integer('total_chat_executions').notNull().default(0),
452453
totalTokensUsed: integer('total_tokens_used').notNull().default(0),
453454
totalCost: decimal('total_cost').notNull().default('0'),
454-
currentUsageLimit: decimal('current_usage_limit').notNull().default('5'), // Default $5 for free plan
455+
currentUsageLimit: decimal('current_usage_limit')
456+
.notNull()
457+
.default(DEFAULT_FREE_CREDITS.toString()), // Default $10 for free plan
455458
usageLimitSetBy: text('usage_limit_set_by'), // User ID who set the limit (for team admin tracking)
456459
usageLimitUpdatedAt: timestamp('usage_limit_updated_at').defaultNow(),
457460
// Billing period tracking

apps/sim/hooks/use-subscription-state.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useCallback, useEffect, useState } from 'react'
2+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
23
import type { SubscriptionFeatures } from '@/lib/billing/types'
34
import { createLogger } from '@/lib/logs/console/logger'
45

@@ -89,7 +90,7 @@ export function useSubscriptionState() {
8990

9091
usage: {
9192
current: data?.usage?.current ?? 0,
92-
limit: data?.usage?.limit ?? 5,
93+
limit: data?.usage?.limit ?? DEFAULT_FREE_CREDITS,
9394
percentUsed: data?.usage?.percentUsed ?? 0,
9495
isWarning: data?.usage?.isWarning ?? false,
9596
isExceeded: data?.usage?.isExceeded ?? false,
@@ -214,9 +215,9 @@ export function useUsageLimit() {
214215
}
215216

216217
return {
217-
currentLimit: data?.currentLimit ?? 5,
218+
currentLimit: data?.currentLimit ?? DEFAULT_FREE_CREDITS,
218219
canEdit: data?.canEdit ?? false,
219-
minimumLimit: data?.minimumLimit ?? 5,
220+
minimumLimit: data?.minimumLimit ?? DEFAULT_FREE_CREDITS,
220221
plan: data?.plan ?? 'free',
221222
setBy: data?.setBy,
222223
updatedAt: data?.updatedAt ? new Date(data.updatedAt) : null,

apps/sim/lib/auth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
renderPasswordResetEmail,
2121
} from '@/components/emails/render-email'
2222
import { getBaseURL } from '@/lib/auth-client'
23+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
2324
import { env, isTruthy } from '@/lib/env'
2425
import { isProd } from '@/lib/environment'
2526
import { createLogger } from '@/lib/logs/console/logger'
@@ -1080,7 +1081,7 @@ export const auth = betterAuth({
10801081
name: 'free',
10811082
priceId: env.STRIPE_FREE_PRICE_ID || '',
10821083
limits: {
1083-
cost: env.FREE_TIER_COST_LIMIT ?? 5,
1084+
cost: env.FREE_TIER_COST_LIMIT ?? DEFAULT_FREE_CREDITS,
10841085
sharingEnabled: 0,
10851086
multiplayerEnabled: 0,
10861087
workspaceCollaborationEnabled: 0,

apps/sim/lib/billing/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
* Billing and cost constants shared between client and server code
33
*/
44

5+
/**
6+
* Default free credits (in dollars) for new users
7+
*/
8+
export const DEFAULT_FREE_CREDITS = 10
9+
510
/**
611
* Base charge applied to every workflow execution
712
* This charge is applied regardless of whether the workflow uses AI models

apps/sim/lib/billing/core/billing.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { and, eq } from 'drizzle-orm'
2+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
23
import {
34
resetOrganizationBillingPeriod,
45
resetUserBillingPeriod,
@@ -917,7 +918,7 @@ function getDefaultBillingSummary(type: 'individual' | 'organization') {
917918
currentUsage: 0,
918919
overageAmount: 0,
919920
totalProjected: 0,
920-
usageLimit: 5,
921+
usageLimit: DEFAULT_FREE_CREDITS,
921922
percentUsed: 0,
922923
isWarning: false,
923924
isExceeded: false,
@@ -935,7 +936,7 @@ function getDefaultBillingSummary(type: 'individual' | 'organization') {
935936
// Usage details
936937
usage: {
937938
current: 0,
938-
limit: 5,
939+
limit: DEFAULT_FREE_CREDITS,
939940
percentUsed: 0,
940941
isWarning: false,
941942
isExceeded: false,

apps/sim/lib/billing/core/organization-billing.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { and, eq } from 'drizzle-orm'
2+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
23
import { getPlanPricing } from '@/lib/billing/core/billing'
34
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
45
import { createLogger } from '@/lib/logs/console/logger'
@@ -87,7 +88,7 @@ export async function getOrganizationBillingData(
8788
// Process member data
8889
const members: MemberUsageData[] = membersWithUsage.map((memberRecord) => {
8990
const currentUsage = Number(memberRecord.currentPeriodCost || 0)
90-
const usageLimit = Number(memberRecord.currentUsageLimit || 5)
91+
const usageLimit = Number(memberRecord.currentUsageLimit || DEFAULT_FREE_CREDITS)
9192
const percentUsed = usageLimit > 0 ? (currentUsage / usageLimit) * 100 : 0
9293

9394
return {
@@ -197,13 +198,14 @@ export async function updateMemberUsageLimit(
197198

198199
// Validate minimum limit based on plan
199200
const planLimits = {
200-
free: 5,
201+
free: DEFAULT_FREE_CREDITS,
201202
pro: 20,
202203
team: 40,
203204
enterprise: 100, // Default, can be overridden by metadata
204205
}
205206

206-
let minimumLimit = planLimits[subscription.plan as keyof typeof planLimits] || 5
207+
let minimumLimit =
208+
planLimits[subscription.plan as keyof typeof planLimits] || DEFAULT_FREE_CREDITS
207209

208210
// For enterprise, check metadata for custom limits
209211
if (subscription.plan === 'enterprise' && subscription.metadata) {

apps/sim/lib/billing/core/subscription.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { and, eq, inArray } from 'drizzle-orm'
22
import { client } from '@/lib/auth-client'
3+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
34
import {
45
calculateDefaultUsageLimit,
56
checkEnterprisePlan,
@@ -156,7 +157,7 @@ export async function hasExceededCostLimit(userId: string): Promise<boolean> {
156157
const subscription = await getHighestPrioritySubscription(userId)
157158

158159
// Calculate usage limit
159-
let limit = 5 // Default free tier limit
160+
let limit = DEFAULT_FREE_CREDITS // Default free tier limit
160161
if (subscription) {
161162
limit = calculateDefaultUsageLimit(subscription)
162163
logger.info('Using subscription-based limit', {
@@ -338,7 +339,7 @@ export async function getUserSubscriptionState(userId: string): Promise<UserSubs
338339
// Check cost limit using already-fetched user stats
339340
let hasExceededLimit = false
340341
if (isProd && statsRecords.length > 0) {
341-
let limit = 5 // Default free tier limit
342+
let limit = DEFAULT_FREE_CREDITS // Default free tier limit
342343
if (subscription) {
343344
limit = calculateDefaultUsageLimit(subscription)
344345
}

apps/sim/lib/billing/core/usage.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { and, eq } from 'drizzle-orm'
2+
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
23
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
34
import { calculateDefaultUsageLimit, canEditUsageLimit } from '@/lib/billing/subscriptions/utils'
45
import type { BillingData, UsageData, UsageLimitInfo } from '@/lib/billing/types'
@@ -29,7 +30,7 @@ export async function getUserUsageData(userId: string): Promise<UsageData> {
2930
await initializeUserUsageLimit(userId)
3031
return {
3132
currentUsage: 0,
32-
limit: 5,
33+
limit: DEFAULT_FREE_CREDITS,
3334
percentUsed: 0,
3435
isWarning: false,
3536
isExceeded: false,
@@ -98,7 +99,7 @@ export async function getUserUsageLimitInfo(userId: string): Promise<UsageLimitI
9899
let minimumLimit: number
99100
if (!subscription || subscription.status !== 'active') {
100101
// Free plan users
101-
minimumLimit = 5
102+
minimumLimit = DEFAULT_FREE_CREDITS
102103
} else if (subscription.plan === 'pro') {
103104
// Pro plan users: $20 minimum
104105
minimumLimit = 20
@@ -131,9 +132,9 @@ export async function getUserUsageLimitInfo(userId: string): Promise<UsageLimitI
131132
if (userStatsRecord.length === 0) {
132133
await initializeUserUsageLimit(userId)
133134
return {
134-
currentLimit: 5,
135+
currentLimit: DEFAULT_FREE_CREDITS,
135136
canEdit: false,
136-
minimumLimit: 5,
137+
minimumLimit: DEFAULT_FREE_CREDITS,
137138
plan: 'free',
138139
setBy: null,
139140
updatedAt: null,
@@ -171,16 +172,16 @@ export async function initializeUserUsageLimit(userId: string): Promise<void> {
171172
return // User already has usage stats, don't override
172173
}
173174

174-
// Create initial usage stats with default $5 limit
175+
// Create initial usage stats with default free credits limit
175176
await db.insert(userStats).values({
176177
id: crypto.randomUUID(),
177178
userId,
178-
currentUsageLimit: '5', // Default $5 for new users
179+
currentUsageLimit: DEFAULT_FREE_CREDITS.toString(), // Default free credits for new users
179180
usageLimitUpdatedAt: new Date(),
180181
billingPeriodStart: new Date(), // Start billing period immediately
181182
})
182183

183-
logger.info('Initialized usage limit for new user', { userId, limit: 5 })
184+
logger.info('Initialized usage limit for new user', { userId, limit: DEFAULT_FREE_CREDITS })
184185
} catch (error) {
185186
logger.error('Failed to initialize usage limit', { userId, error })
186187
throw error
@@ -233,7 +234,7 @@ export async function updateUserUsageLimit(
233234

234235
if (!subscription || subscription.status !== 'active') {
235236
// Free plan users (shouldn't reach here due to canEditUsageLimit check above)
236-
minimumLimit = 5
237+
minimumLimit = DEFAULT_FREE_CREDITS
237238
} else if (subscription.plan === 'pro') {
238239
// Pro plan users: $20 minimum
239240
minimumLimit = 20
@@ -311,7 +312,7 @@ export async function getUserUsageLimit(userId: string): Promise<number> {
311312
if (userStatsQuery.length === 0) {
312313
// User doesn't have stats yet, initialize and return default
313314
await initializeUserUsageLimit(userId)
314-
return 5 // Default free plan limit
315+
return DEFAULT_FREE_CREDITS // Default free plan limit
315316
}
316317

317318
return Number.parseFloat(userStatsQuery[0].currentUsageLimit)
@@ -380,16 +381,16 @@ export async function syncUsageLimitsFromSubscription(userId: string): Promise<v
380381

381382
// Only update if subscription is free plan or if current limit is below new minimum
382383
if (!subscription || subscription.status !== 'active') {
383-
// User downgraded to free plan - cap at $5
384+
// User downgraded to free plan - cap at default free credits
384385
await db
385386
.update(userStats)
386387
.set({
387-
currentUsageLimit: '5',
388+
currentUsageLimit: DEFAULT_FREE_CREDITS.toString(),
388389
usageLimitUpdatedAt: new Date(),
389390
})
390391
.where(eq(userStats.userId, userId))
391392

392-
logger.info('Synced usage limit to free plan', { userId, limit: 5 })
393+
logger.info('Synced usage limit to free plan', { userId, limit: DEFAULT_FREE_CREDITS })
393394
} else if (currentLimit < defaultLimit) {
394395
// User upgraded and current limit is below new minimum - raise to minimum
395396
await db
@@ -451,7 +452,7 @@ export async function getTeamUsageLimits(organizationId: string): Promise<
451452
userId: memberData.userId,
452453
userName: memberData.userName,
453454
userEmail: memberData.userEmail,
454-
currentLimit: Number.parseFloat(memberData.currentLimit || '5'),
455+
currentLimit: Number.parseFloat(memberData.currentLimit || DEFAULT_FREE_CREDITS.toString()),
455456
currentUsage: Number.parseFloat(memberData.currentPeriodCost || '0'),
456457
totalCost: Number.parseFloat(memberData.totalCost || '0'),
457458
lastActive: memberData.lastActive,

apps/sim/lib/billing/subscriptions/utils.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { calculateDefaultUsageLimit, checkEnterprisePlan } from '@/lib/billing/s
33

44
vi.mock('@/lib/env', () => ({
55
env: {
6-
FREE_TIER_COST_LIMIT: 5,
6+
FREE_TIER_COST_LIMIT: 10,
77
PRO_TIER_COST_LIMIT: 20,
88
TEAM_TIER_COST_LIMIT: 40,
99
ENTERPRISE_TIER_COST_LIMIT: 200,
@@ -27,15 +27,15 @@ describe('Subscription Utilities', () => {
2727

2828
describe('calculateDefaultUsageLimit', () => {
2929
it.concurrent('returns free-tier limit when subscription is null', () => {
30-
expect(calculateDefaultUsageLimit(null)).toBe(5)
30+
expect(calculateDefaultUsageLimit(null)).toBe(10)
3131
})
3232

3333
it.concurrent('returns free-tier limit when subscription is undefined', () => {
34-
expect(calculateDefaultUsageLimit(undefined)).toBe(5)
34+
expect(calculateDefaultUsageLimit(undefined)).toBe(10)
3535
})
3636

3737
it.concurrent('returns free-tier limit when subscription is not active', () => {
38-
expect(calculateDefaultUsageLimit({ plan: 'pro', status: 'canceled', seats: 1 })).toBe(5)
38+
expect(calculateDefaultUsageLimit({ plan: 'pro', status: 'canceled', seats: 1 })).toBe(10)
3939
})
4040

4141
it.concurrent('returns pro limit for active pro plan', () => {

0 commit comments

Comments
 (0)