refactor: optimize credit-service imports with lazy loading (#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>
This commit is contained in:
Morgan
2025-11-13 00:40:51 +09:00
committed by GitHub
co-authored by morgan@cal.com <morgan@cal.com> morgan@cal.com <morgan@cal.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent a7cb71726a
commit ad4b5957fc
2 changed files with 34 additions and 13 deletions
@@ -101,6 +101,20 @@ vi.mock("../workflows/lib/reminders/reminderScheduler", () => ({
vi.mock("@calcom/lib/getOrgIdFromMemberOrTeamId", () => ({
default: vi.fn().mockResolvedValue(null),
}));
vi.mock("@calcom/features/ee/billing/stripe-billing-service", () => {
return {
StripeBillingService: vi.fn().mockImplementation(() => ({
getPrice: async (priceId: string) => {
const stripe = (await import("@calcom/features/ee/payments/server/stripe")).default;
return stripe.prices.retrieve(priceId);
},
checkoutSessionIsPaid: vi.fn(),
handleSubscriptionCancel: vi.fn(),
handleSubscriptionCreation: vi.fn(),
handleSubscriptionUpdate: vi.fn(),
})),
};
});
const creditService = new CreditService();
+20 -13
View File
@@ -1,19 +1,11 @@
import type { TFunction } from "i18next";
import dayjs from "@calcom/dayjs";
import {
sendCreditBalanceLimitReachedEmails,
sendCreditBalanceLowWarningEmails,
} from "@calcom/emails/billing-email-service";
import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billing-service";
import { InternalTeamBilling } from "@calcom/features/ee/billing/teams/internal-team-billing";
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
import { cancelScheduledMessagesAndScheduleEmails } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
import { IS_SMS_CREDITS_ENABLED } from "@calcom/lib/constants";
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server/i18n";
import { CreditsRepository } from "@calcom/lib/server/repository/credits";
import { prisma, type PrismaTransaction } from "@calcom/prisma";
import { CreditUsageType, CreditType } from "@calcom/prisma/enums";
@@ -474,9 +466,9 @@ export class CreditService {
const { totalMonthlyCredits } = await this._getAllCreditsForTeam({ teamId, tx });
warningLimit = totalMonthlyCredits * 0.2;
} else if (userId) {
const billingService = new StripeBillingService();
const teamMonthlyPrice = await billingService.getPrice(process.env.STRIPE_TEAM_MONTHLY_PRICE_ID || "");
const pricePerSeat = teamMonthlyPrice.unit_amount ?? 0;
const billing = (await import("@calcom/features/ee/billing")).default;
const teamMonthlyPrice = await billing.getPrice(process.env.STRIPE_TEAM_MONTHLY_PRICE_ID || "");
const pricePerSeat = teamMonthlyPrice?.unit_amount ?? 0;
warningLimit = (pricePerSeat / 2) * 0.2;
}
@@ -495,6 +487,8 @@ export class CreditService {
return null; // user has limit already reached or team has already reached limit this month
}
const { getTranslation } = await import("@calcom/lib/server/i18n");
const teamWithAdmins = creditBalance?.team
? {
...creditBalance.team,
@@ -591,6 +585,10 @@ export class CreditService {
try {
if (result.type === "LIMIT_REACHED") {
const { sendCreditBalanceLimitReachedEmails } = await import(
"@calcom/emails/billing-email-service"
);
const promises: Promise<unknown>[] = [
sendCreditBalanceLimitReachedEmails({
team: result.team,
@@ -602,6 +600,9 @@ export class CreditService {
];
if (!result.creditFor || result.creditFor === CreditUsageType.SMS) {
const { cancelScheduledMessagesAndScheduleEmails } = await import(
"@calcom/features/ee/workflows/lib/reminders/reminderScheduler"
);
promises.push(
cancelScheduledMessagesAndScheduleEmails({ teamId: result.teamId, userId: result.userId }).catch(
(error) => {
@@ -613,6 +614,7 @@ export class CreditService {
await Promise.all(promises);
} else if (result.type === "WARNING") {
const { sendCreditBalanceLowWarningEmails } = await import("@calcom/emails/billing-email-service");
await sendCreditBalanceLowWarningEmails({
balance: result.balance,
team: result.team,
@@ -653,6 +655,7 @@ export class CreditService {
if (!team) return 0;
const { InternalTeamBilling } = await import("@calcom/features/ee/billing/teams/internal-team-billing");
const teamBillingService = new InternalTeamBilling(team);
const subscriptionStatus = await teamBillingService.getSubscriptionStatus();
@@ -668,7 +671,7 @@ export class CreditService {
return activeMembers * creditsPerSeat;
}
const billingService = new StripeBillingService();
const billing = (await import("@calcom/features/ee/billing")).default;
const priceId = process.env.STRIPE_TEAM_MONTHLY_PRICE_ID;
if (!priceId) {
@@ -676,7 +679,11 @@ export class CreditService {
return 0;
}
const monthlyPrice = await billingService.getPrice(priceId);
const monthlyPrice = await billing.getPrice(priceId);
if (!monthlyPrice) {
log.warn("Failed to retrieve monthly price", { teamId, priceId });
return 0;
}
const pricePerSeat = monthlyPrice.unit_amount ?? 0;
const creditsPerSeat = pricePerSeat * 0.5;