Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -3824,6 +3824,8 @@
"proration_reminder_subject": "Payment Reminder - {{teamName}}",
"proration_reminder_message": "This is a reminder that your invoice of ${{amount}} for team {{teamName}} is still unpaid.",
"proration_reminder_warning": "If this invoice remains unpaid, you will not be able to add new users to your team until payment is received.",
"proration_invoice_text": "An invoice for ${{amount}} has been created for {{seats}} additional seat(s) for your team {{teamName}}.",
"proration_reminder_text": "Reminder: Your invoice of ${{amount}} for team {{teamName}} is still unpaid. Please pay to avoid restrictions on adding new users.",
"current_balance": "Current balance:",
"notification_about_your_booking": "Notification about your booking",
"monthly_credits": "Monthly credits",
Expand Down
37 changes: 30 additions & 7 deletions packages/emails/billing-email-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,21 @@ const eventTypeDisableAttendeeEmail = (metadata?: EventTypeMetadata) => {
return !!metadata?.disableStandardEmails?.all?.attendee;
};

export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEvent) => {
export const sendOrganizerPaymentRefundFailedEmail = async (
calEvent: CalendarEvent
) => {
const emailsToSend: Promise<unknown>[] = [];
emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent })));
emailsToSend.push(
sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent }))
);

if (calEvent.team?.members) {
for (const teamMember of calEvent.team.members) {
emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent, teamMember })));
emailsToSend.push(
sendEmail(
() => new OrganizerPaymentRefundFailedEmail({ calEvent, teamMember })
)
);
}
}

Expand Down Expand Up @@ -75,15 +83,25 @@ export const sendCreditBalanceLowWarningEmails = async (input: {

for (const admin of team.adminAndOwners) {
emailsToSend.push(
sendEmail(() => new CreditBalanceLowWarningEmail({ user: admin, balance, team, creditFor }))
sendEmail(
() =>
new CreditBalanceLowWarningEmail({
user: admin,
balance,
team,
creditFor,
})
)
);
}

await Promise.all(emailsToSend);
}

if (user) {
await sendEmail(() => new CreditBalanceLowWarningEmail({ user, balance, creditFor }));
await sendEmail(
() => new CreditBalanceLowWarningEmail({ user, balance, creditFor })
);
}
};

Expand Down Expand Up @@ -117,14 +135,19 @@ export const sendCreditBalanceLimitReachedEmails = async ({

for (const admin of team.adminAndOwners) {
emailsToSend.push(
sendEmail(() => new CreditBalanceLimitReachedEmail({ user: admin, team, creditFor }))
sendEmail(
() =>
new CreditBalanceLimitReachedEmail({ user: admin, team, creditFor })
)
);
}
await Promise.all(emailsToSend);
}

if (user) {
await sendEmail(() => new CreditBalanceLimitReachedEmail({ user, creditFor }));
await sendEmail(
() => new CreditBalanceLimitReachedEmail({ user, creditFor })
);
}
};

Expand Down
6 changes: 5 additions & 1 deletion packages/emails/templates/proration-invoice-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export default class ProrationInvoiceEmail extends BaseEmail {

protected getTextBody(): string {
const formattedAmount = (this.proration.proratedAmount / 100).toFixed(2);
return `An invoice for $${formattedAmount} has been created for ${this.proration.netSeatIncrease} additional seat(s) for your team ${this.team.name}.`;
return this.user.t("proration_invoice_text", {
amount: formattedAmount,
seats: this.proration.netSeatIncrease,
teamName: this.team.name,
});
}
}
5 changes: 4 additions & 1 deletion packages/emails/templates/proration-reminder-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export default class ProrationReminderEmail extends BaseEmail {

protected getTextBody(): string {
const formattedAmount = (this.proration.proratedAmount / 100).toFixed(2);
return `Reminder: Your invoice of $${formattedAmount} for team ${this.team.name} is still unpaid. Please pay to avoid restrictions on adding new users.`;
return this.user.t("proration_reminder_text", {
amount: formattedAmount,
teamName: this.team.name,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service";
import { ProrationEmailSyncTasker } from "@calcom/features/ee/billing/service/proration/tasker/ProrationEmailSyncTasker";

import { PRORATION_EMAIL_TASKER_DI_TOKENS } from "./tokens";

const thisModule = createModule();
const token = PRORATION_EMAIL_TASKER_DI_TOKENS.PRORATION_EMAIL_SYNC_TASKER;
const moduleToken = PRORATION_EMAIL_TASKER_DI_TOKENS.PRORATION_EMAIL_SYNC_TASKER_MODULE;
const loadModule = bindModuleToClassOnToken({
module: thisModule,
moduleToken,
token,
classs: ProrationEmailSyncTasker,
dep: loggerServiceModule,
});

export const moduleLoader = {
token,
loadModule,
} satisfies ModuleLoader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createContainer } from "@calcom/features/di/di";
import type { ProrationEmailTasker } from "@calcom/features/ee/billing/service/proration/tasker/ProrationEmailTasker";

import { moduleLoader as prorationEmailTaskerModule } from "./ProrationEmailTasker.module";
import { PRORATION_EMAIL_TASKER_DI_TOKENS } from "./tokens";

const container = createContainer();

export function getProrationEmailTasker(): ProrationEmailTasker {
prorationEmailTaskerModule.loadModule(container);
return container.get<ProrationEmailTasker>(PRORATION_EMAIL_TASKER_DI_TOKENS.PRORATION_EMAIL_TASKER);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service";
import { ProrationEmailTasker } from "@calcom/features/ee/billing/service/proration/tasker/ProrationEmailTasker";

import { moduleLoader as prorationEmailSyncTaskerModule } from "./ProrationEmailSyncTasker.module";
import { moduleLoader as prorationEmailTriggerTaskerModule } from "./ProrationEmailTriggerDevTasker.module";
import { PRORATION_EMAIL_TASKER_DI_TOKENS } from "./tokens";

const thisModule = createModule();
const token = PRORATION_EMAIL_TASKER_DI_TOKENS.PRORATION_EMAIL_TASKER;
const moduleToken = PRORATION_EMAIL_TASKER_DI_TOKENS.PRORATION_EMAIL_TASKER_MODULE;
const loadModule = bindModuleToClassOnToken({
module: thisModule,
moduleToken,
token,
classs: ProrationEmailTasker,
depsMap: {
logger: loggerServiceModule,
asyncTasker: prorationEmailTriggerTaskerModule,
syncTasker: prorationEmailSyncTaskerModule,
},
});

export const moduleLoader = {
token,
loadModule,
} satisfies ModuleLoader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service";
import { ProrationEmailTriggerDevTasker } from "@calcom/features/ee/billing/service/proration/tasker/ProrationEmailTriggerDevTasker";

import { PRORATION_EMAIL_TASKER_DI_TOKENS } from "./tokens";

const thisModule = createModule();
const token = PRORATION_EMAIL_TASKER_DI_TOKENS.PRORATION_EMAIL_TRIGGER_TASKER;
const moduleToken = PRORATION_EMAIL_TASKER_DI_TOKENS.PRORATION_EMAIL_TRIGGER_TASKER_MODULE;
const loadModule = bindModuleToClassOnToken({
module: thisModule,
moduleToken,
token,
classs: ProrationEmailTriggerDevTasker,
depsMap: {
logger: loggerServiceModule,
},
});

export const moduleLoader = {
token,
loadModule,
} satisfies ModuleLoader;
9 changes: 9 additions & 0 deletions packages/features/ee/billing/di/tasker/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ export const MONTHLY_PRORATION_TASKER_DI_TOKENS = {
MONTHLY_PRORATION_TRIGGER_TASKER: Symbol("MonthlyProrationTriggerTasker"),
MONTHLY_PRORATION_TRIGGER_TASKER_MODULE: Symbol("MonthlyProrationTriggerTaskerModule"),
};

export const PRORATION_EMAIL_TASKER_DI_TOKENS = {
PRORATION_EMAIL_TASKER: Symbol("ProrationEmailTasker"),
PRORATION_EMAIL_TASKER_MODULE: Symbol("ProrationEmailTaskerModule"),
PRORATION_EMAIL_SYNC_TASKER: Symbol("ProrationEmailSyncTasker"),
PRORATION_EMAIL_SYNC_TASKER_MODULE: Symbol("ProrationEmailSyncTaskerModule"),
PRORATION_EMAIL_TRIGGER_TASKER: Symbol("ProrationEmailTriggerTasker"),
PRORATION_EMAIL_TRIGGER_TASKER_MODULE: Symbol("ProrationEmailTriggerTaskerModule"),
};
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,17 @@ export class MonthlyProrationRepository {
},
});
}

async findForEmail(prorationId: string) {
return await this.prisma.monthlyProration.findUnique({
where: { id: prorationId },
select: {
invoiceId: true,
monthKey: true,
netSeatIncrease: true,
proratedAmount: true,
status: true,
},
});
}
}
Loading
Loading