Skip to content

Commit 4c3b848

Browse files
committed
add lazy init for stripe client, similar to s3
1 parent 228a923 commit 4c3b848

File tree

3 files changed

+77
-25
lines changed

3 files changed

+77
-25
lines changed

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

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ vi.mock('@/lib/billing/core/usage', () => ({
2828
getUserUsageData: vi.fn(),
2929
}))
3030

31+
vi.mock('../stripe-client', () => ({
32+
getStripeClient: vi.fn().mockReturnValue(null),
33+
requireStripeClient: vi.fn().mockImplementation(() => {
34+
throw new Error(
35+
'Stripe client is not available. Set STRIPE_SECRET_KEY in your environment variables.'
36+
)
37+
}),
38+
hasValidStripeCredentials: vi.fn().mockReturnValue(false),
39+
}))
40+
3141
describe('Billing Core Functions', () => {
3242
beforeEach(() => {
3343
vi.clearAllMocks()
@@ -144,26 +154,12 @@ describe('Billing Core Functions', () => {
144154
})
145155
})
146156

147-
describe('calculateUserOverage', () => {
148-
// Skip these tests for now as they require complex async mocking
149-
it.skip('calculates overage correctly for pro user', async () => {
150-
// This test is skipped due to complex async mocking requirements
151-
expect(true).toBe(true)
152-
})
153-
154-
it.skip('returns zero overage when usage is below base price', async () => {
155-
// This test is skipped due to complex async mocking requirements
156-
expect(true).toBe(true)
157-
})
158-
159-
it.skip('handles free plan users correctly', async () => {
160-
// This test is skipped due to complex async mocking requirements
161-
expect(true).toBe(true)
162-
})
157+
describe('Stripe client integration', () => {
158+
it.concurrent('does not fail when Stripe credentials are not available', async () => {
159+
const result = await getUsersAndOrganizationsForOverageBilling()
163160

164-
it.skip('returns null for non-existent user', async () => {
165-
// This test is skipped due to complex async mocking requirements
166-
expect(true).toBe(true)
161+
expect(result).toHaveProperty('users')
162+
expect(result).toHaveProperty('organizations')
167163
})
168164
})
169165

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import { eq } from 'drizzle-orm'
2-
import Stripe from 'stripe'
3-
import { env } from '@/lib/env'
42
import { createLogger } from '@/lib/logs/console-logger'
53
import { db } from '@/db'
64
import { member, organization, subscription, user, userStats } from '@/db/schema'
5+
import { requireStripeClient } from '../stripe-client'
76
import { resetOrganizationBillingPeriod, resetUserBillingPeriod } from './billing-periods'
87
import { getHighestPrioritySubscription } from './subscription'
98
import { getUserUsageData } from './usage'
109

1110
const logger = createLogger('Billing')
1211

13-
const stripeClient = new Stripe(env.STRIPE_SECRET_KEY || '', {
14-
apiVersion: '2025-02-24.acacia',
15-
})
16-
1712
interface BillingResult {
1813
success: boolean
1914
chargedAmount?: number
@@ -120,6 +115,7 @@ export async function createOverageBillingInvoice(
120115
}
121116

122117
// Create invoice item for overage only
118+
const stripeClient = requireStripeClient()
123119
const invoiceItem = await stripeClient.invoiceItems.create({
124120
customer: customerId,
125121
amount: Math.round(overageAmount * 100), // Convert to cents
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Stripe from 'stripe'
2+
import { env } from '@/lib/env'
3+
import { createLogger } from '@/lib/logs/console-logger'
4+
5+
const logger = createLogger('StripeClient')
6+
7+
// Lazily create a single Stripe client instance
8+
let _stripeClient: Stripe | null = null
9+
10+
/**
11+
* Check if Stripe credentials are valid
12+
*/
13+
export function hasValidStripeCredentials(): boolean {
14+
return !!(
15+
env.STRIPE_SECRET_KEY &&
16+
env.STRIPE_SECRET_KEY.trim() !== '' &&
17+
env.STRIPE_SECRET_KEY !== 'placeholder'
18+
)
19+
}
20+
21+
/**
22+
* Get the Stripe client instance
23+
* @returns Stripe client or null if credentials are not available
24+
*/
25+
export function getStripeClient(): Stripe | null {
26+
if (_stripeClient) return _stripeClient
27+
28+
if (!hasValidStripeCredentials()) {
29+
logger.warn('Stripe credentials not available - Stripe operations will be disabled')
30+
return null
31+
}
32+
33+
try {
34+
_stripeClient = new Stripe(env.STRIPE_SECRET_KEY || '', {
35+
apiVersion: '2025-02-24.acacia',
36+
})
37+
38+
logger.info('Stripe client initialized successfully')
39+
return _stripeClient
40+
} catch (error) {
41+
logger.error('Failed to initialize Stripe client', { error })
42+
return null
43+
}
44+
}
45+
46+
/**
47+
* Get the Stripe client instance, throwing an error if not available
48+
* Use this when Stripe operations are required
49+
*/
50+
export function requireStripeClient(): Stripe {
51+
const client = getStripeClient()
52+
53+
if (!client) {
54+
throw new Error(
55+
'Stripe client is not available. Set STRIPE_SECRET_KEY in your environment variables.'
56+
)
57+
}
58+
59+
return client
60+
}

0 commit comments

Comments
 (0)