Skip to content

Commit ad4b595

Browse files
refactor: optimize credit-service imports with lazy loading (calcom#25091)
* refactor: optimize credit-service imports with lazy loading - Remove top-level imports of heavy modules (reminderScheduler, email services, i18n, billing services) - Implement dynamic imports for modules only when needed: - reminderScheduler: loaded only when SMS credit limit reached - email services: loaded only when sending credit notifications - getTranslation: loaded only when handling low credit balance - InternalTeamBilling: loaded only in getMonthlyCredits method - billing singleton: loaded only when calculating warning limits - Break circular dependency: credit-service → reminderScheduler → ... → credit-service - Update tests to mock StripeBillingService for dynamic imports - All 30 tests passing, no type errors, lint clean This reduces baseline import cost by deferring: - Stripe SDK initialization (loaded twice before) - 557KB+ i18n English translation file - Email template classes - Workflow reminder scheduler Verified with madge: circular dependency successfully resolved Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: add null checks for billing.getPrice() return value Co-Authored-By: morgan@cal.com <morgan@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent a7cb717 commit ad4b595

2 files changed

Lines changed: 34 additions & 13 deletions

File tree

packages/features/ee/billing/credit-service.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ vi.mock("../workflows/lib/reminders/reminderScheduler", () => ({
101101
vi.mock("@calcom/lib/getOrgIdFromMemberOrTeamId", () => ({
102102
default: vi.fn().mockResolvedValue(null),
103103
}));
104+
vi.mock("@calcom/features/ee/billing/stripe-billing-service", () => {
105+
return {
106+
StripeBillingService: vi.fn().mockImplementation(() => ({
107+
getPrice: async (priceId: string) => {
108+
const stripe = (await import("@calcom/features/ee/payments/server/stripe")).default;
109+
return stripe.prices.retrieve(priceId);
110+
},
111+
checkoutSessionIsPaid: vi.fn(),
112+
handleSubscriptionCancel: vi.fn(),
113+
handleSubscriptionCreation: vi.fn(),
114+
handleSubscriptionUpdate: vi.fn(),
115+
})),
116+
};
117+
});
104118

105119
const creditService = new CreditService();
106120

packages/features/ee/billing/credit-service.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
import type { TFunction } from "i18next";
22

33
import dayjs from "@calcom/dayjs";
4-
import {
5-
sendCreditBalanceLimitReachedEmails,
6-
sendCreditBalanceLowWarningEmails,
7-
} from "@calcom/emails/billing-email-service";
8-
import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billing-service";
9-
import { InternalTeamBilling } from "@calcom/features/ee/billing/teams/internal-team-billing";
104
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
11-
import { cancelScheduledMessagesAndScheduleEmails } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
125
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
136
import { IS_SMS_CREDITS_ENABLED } from "@calcom/lib/constants";
147
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
158
import logger from "@calcom/lib/logger";
16-
import { getTranslation } from "@calcom/lib/server/i18n";
179
import { CreditsRepository } from "@calcom/lib/server/repository/credits";
1810
import { prisma, type PrismaTransaction } from "@calcom/prisma";
1911
import { CreditUsageType, CreditType } from "@calcom/prisma/enums";
@@ -474,9 +466,9 @@ export class CreditService {
474466
const { totalMonthlyCredits } = await this._getAllCreditsForTeam({ teamId, tx });
475467
warningLimit = totalMonthlyCredits * 0.2;
476468
} else if (userId) {
477-
const billingService = new StripeBillingService();
478-
const teamMonthlyPrice = await billingService.getPrice(process.env.STRIPE_TEAM_MONTHLY_PRICE_ID || "");
479-
const pricePerSeat = teamMonthlyPrice.unit_amount ?? 0;
469+
const billing = (await import("@calcom/features/ee/billing")).default;
470+
const teamMonthlyPrice = await billing.getPrice(process.env.STRIPE_TEAM_MONTHLY_PRICE_ID || "");
471+
const pricePerSeat = teamMonthlyPrice?.unit_amount ?? 0;
480472
warningLimit = (pricePerSeat / 2) * 0.2;
481473
}
482474

@@ -495,6 +487,8 @@ export class CreditService {
495487
return null; // user has limit already reached or team has already reached limit this month
496488
}
497489

490+
const { getTranslation } = await import("@calcom/lib/server/i18n");
491+
498492
const teamWithAdmins = creditBalance?.team
499493
? {
500494
...creditBalance.team,
@@ -591,6 +585,10 @@ export class CreditService {
591585

592586
try {
593587
if (result.type === "LIMIT_REACHED") {
588+
const { sendCreditBalanceLimitReachedEmails } = await import(
589+
"@calcom/emails/billing-email-service"
590+
);
591+
594592
const promises: Promise<unknown>[] = [
595593
sendCreditBalanceLimitReachedEmails({
596594
team: result.team,
@@ -602,6 +600,9 @@ export class CreditService {
602600
];
603601

604602
if (!result.creditFor || result.creditFor === CreditUsageType.SMS) {
603+
const { cancelScheduledMessagesAndScheduleEmails } = await import(
604+
"@calcom/features/ee/workflows/lib/reminders/reminderScheduler"
605+
);
605606
promises.push(
606607
cancelScheduledMessagesAndScheduleEmails({ teamId: result.teamId, userId: result.userId }).catch(
607608
(error) => {
@@ -613,6 +614,7 @@ export class CreditService {
613614

614615
await Promise.all(promises);
615616
} else if (result.type === "WARNING") {
617+
const { sendCreditBalanceLowWarningEmails } = await import("@calcom/emails/billing-email-service");
616618
await sendCreditBalanceLowWarningEmails({
617619
balance: result.balance,
618620
team: result.team,
@@ -653,6 +655,7 @@ export class CreditService {
653655

654656
if (!team) return 0;
655657

658+
const { InternalTeamBilling } = await import("@calcom/features/ee/billing/teams/internal-team-billing");
656659
const teamBillingService = new InternalTeamBilling(team);
657660
const subscriptionStatus = await teamBillingService.getSubscriptionStatus();
658661

@@ -668,15 +671,19 @@ export class CreditService {
668671
return activeMembers * creditsPerSeat;
669672
}
670673

671-
const billingService = new StripeBillingService();
674+
const billing = (await import("@calcom/features/ee/billing")).default;
672675
const priceId = process.env.STRIPE_TEAM_MONTHLY_PRICE_ID;
673676

674677
if (!priceId) {
675678
log.warn("Monthly price ID not configured", { teamId });
676679
return 0;
677680
}
678681

679-
const monthlyPrice = await billingService.getPrice(priceId);
682+
const monthlyPrice = await billing.getPrice(priceId);
683+
if (!monthlyPrice) {
684+
log.warn("Failed to retrieve monthly price", { teamId, priceId });
685+
return 0;
686+
}
680687
const pricePerSeat = monthlyPrice.unit_amount ?? 0;
681688
const creditsPerSeat = pricePerSeat * 0.5;
682689

0 commit comments

Comments
 (0)