feat: add PlatformOrganizationBillingTasker with sync and trigger.dev versions (#26803)
* feat: add PlatformOrganizationBillingTasker with sync and trigger.dev versions - Create PlatformOrganizationBillingTasker extending Tasker base class - Add sync tasker (PlatformOrganizationBillingSyncTasker) for synchronous execution - Add trigger.dev tasker (PlatformOrganizationBillingTriggerTasker) for async execution - Create trigger.dev task for incrementing subscription usage - Add PlatformOrganizationBillingTaskService with business logic - Add PlatformOrganizationBillingRepository for data access - Add createSubscriptionUsageRecord method to StripeBillingService - Follow BookingEmailAndSmsTasker pattern for consistency Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: move repository methods per PR feedback - Move findPlatformOrgByUserId to OrganizationRepository - Create new PlatformBillingRepository for billing queries - Remove PlatformOrganizationBillingRepository (consolidated) - Update task service and trigger.dev task to use new repositories Co-Authored-By: morgan@cal.com <morgan@cal.com> * feat: add DI using @evyweb/ioctopus for platform billing tasker - Create di/tasker/ directory following BookingEmailAndSmsTasker pattern - Add tokens.ts with DI tokens for all billing tasker classes - Add module files for PlatformBillingRepository, TaskService, SyncTasker, TriggerTasker, Tasker - Add container files with getter functions - Update trigger.dev task to use DI container instead of manual instantiation Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: add select statement to PlatformBillingRepository.findByTeamId Only select subscriptionId field since that's the only field used by the task service Co-Authored-By: morgan@cal.com <morgan@cal.com> * feat: add NestJS DI modules for platform billing tasker in API v2 - Add platform billing tasker exports to platform-libraries/organizations.ts - Export IBillingProviderService type from platform-libraries - Create StripeBillingProviderService wrapper implementing IBillingProviderService - Create PrismaPlatformBillingRepository extending PlatformBillingRepository - Create NestJS service wrappers for all platform billing tasker classes - Create PlatformBillingTaskerModule with all providers and exports - Update PlatformOrganizationBillingTaskService to use Pick<IBillingProviderService> Co-Authored-By: morgan@cal.com <morgan@cal.com> * feat: handle cancel and reschedule usage * fix: restore IsUserInBillingOrg to billing module providers Co-Authored-By: morgan@cal.com <morgan@cal.com> * chore: add idempotency key * fix: just log error] * fix: logger and typo * fix: address Cubic AI review feedback (high confidence issues) - Fix grammar error: 'Delayed task are' -> 'Delayed tasks are' in PlatformOrganizationBillingSyncTasker.ts (3 occurrences) - Re-throw error after logging in increment-usage.ts to enable trigger.dev retry mechanism Co-Authored-By: unknown <> * fix: remove duplicate code and use DI instead * fix: remove orThrow on find first in findPlatformOrgByUserId * refactor: prevent usage increment task from re-throwing errors --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
unknown
Devin AI
parent
38a7fcfedd
commit
c60addc528
@@ -47,6 +47,8 @@ const loadConfig = (): AppConfig => {
|
||||
e2e: getEnv("IS_E2E", "false") === "true",
|
||||
enableSlotsWorkers: getEnv("ENABLE_SLOTS_WORKERS", "true") === "true",
|
||||
slotsWorkerPoolSize: Number(getEnv("SLOTS_WORKER_POOL_SIZE", "4")),
|
||||
vercel: getEnv("VERCEL", "false") === "true",
|
||||
enableAsyncTasker: getEnv("ENABLE_ASYNC_TASKER", "false") === "true",
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -35,4 +35,6 @@ export type AppConfig = {
|
||||
e2e: boolean;
|
||||
enableSlotsWorkers: boolean;
|
||||
slotsWorkerPoolSize: number;
|
||||
vercel: boolean;
|
||||
enableAsyncTasker: boolean;
|
||||
};
|
||||
|
||||
@@ -41,6 +41,8 @@ export type Environment = {
|
||||
ENABLE_SLOTS_WORKERS: string;
|
||||
SLOTS_WORKER_POOL_SIZE: string;
|
||||
USE_POOL: string;
|
||||
VERCEL: string;
|
||||
ENABLE_ASYNC_TASKER: string;
|
||||
};
|
||||
|
||||
export const getEnv = <K extends keyof Environment>(key: K, fallback?: Environment[K]): Environment[K] => {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Module, Scope } from "@nestjs/common";
|
||||
import { Logger } from "@/lib/logger.bridge";
|
||||
import { PrismaPlatformBillingRepository } from "@/lib/repositories/prisma-platform-billing.repository";
|
||||
import { StripeBillingProviderService } from "@/lib/services/stripe-billing-provider.service";
|
||||
import { PlatformBillingSyncTaskerService } from "@/lib/services/tasker/platform-billing-sync-tasker.service";
|
||||
import { PlatformBillingTaskService } from "@/lib/services/tasker/platform-billing-task.service";
|
||||
import { PlatformBillingTasker } from "@/lib/services/tasker/platform-billing-tasker.service";
|
||||
import { PlatformBillingTriggerTaskerService } from "@/lib/services/tasker/platform-billing-trigger-tasker.service";
|
||||
import { OrganizationsModule } from "@/modules/organizations/organizations.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, StripeModule, OrganizationsModule],
|
||||
providers: [
|
||||
PrismaPlatformBillingRepository,
|
||||
StripeBillingProviderService,
|
||||
{
|
||||
provide: Logger,
|
||||
useFactory: () => {
|
||||
return new Logger();
|
||||
},
|
||||
scope: Scope.TRANSIENT,
|
||||
},
|
||||
PlatformBillingTaskService,
|
||||
PlatformBillingSyncTaskerService,
|
||||
PlatformBillingTriggerTaskerService,
|
||||
PlatformBillingTasker,
|
||||
],
|
||||
exports: [PlatformBillingTasker],
|
||||
})
|
||||
export class PlatformBillingTaskerModule {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { PlatformBillingRepository } from "@calcom/platform-libraries/organizations";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaPlatformBillingRepository extends PlatformBillingRepository {
|
||||
constructor(dbWrite: PrismaWriteService) {
|
||||
super(dbWrite.prisma as unknown as PrismaClient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { StripeBillingService } from "@calcom/platform-libraries";
|
||||
import type { IBillingProviderService } from "@calcom/platform-libraries/organizations";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
|
||||
@Injectable()
|
||||
export class StripeBillingProviderService
|
||||
extends StripeBillingService
|
||||
implements Pick<IBillingProviderService, "createSubscriptionUsageRecord">
|
||||
{
|
||||
constructor(readonly stripeService: StripeService) {
|
||||
super(stripeService.getStripe());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Logger } from "@/lib/logger.bridge";
|
||||
import { PlatformBillingTaskService } from "@/lib/services/tasker/platform-billing-task.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { PlatformOrganizationBillingSyncTasker as BasePlatformOrganizationBillingSyncTasker } from "@calcom/platform-libraries/organizations";
|
||||
|
||||
@Injectable()
|
||||
export class PlatformBillingSyncTaskerService extends BasePlatformOrganizationBillingSyncTasker {
|
||||
constructor(billingTaskService: PlatformBillingTaskService, logger: Logger) {
|
||||
super({
|
||||
logger,
|
||||
billingTaskService,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Logger } from "@/lib/logger.bridge";
|
||||
import { PrismaPlatformBillingRepository } from "@/lib/repositories/prisma-platform-billing.repository";
|
||||
import { StripeBillingProviderService } from "@/lib/services/stripe-billing-provider.service";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { PlatformOrganizationBillingTaskService as BasePlatformOrganizationBillingTaskService } from "@calcom/platform-libraries/organizations";
|
||||
|
||||
@Injectable()
|
||||
export class PlatformBillingTaskService extends BasePlatformOrganizationBillingTaskService {
|
||||
constructor(
|
||||
organizationRepository: OrganizationsRepository,
|
||||
platformBillingRepository: PrismaPlatformBillingRepository,
|
||||
billingProviderService: StripeBillingProviderService,
|
||||
logger: Logger
|
||||
) {
|
||||
super({
|
||||
logger,
|
||||
organizationRepository,
|
||||
platformBillingRepository,
|
||||
billingProviderService,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { PlatformOrganizationBillingTasker as BasePlatformOrganizationBillingTasker } from "@calcom/platform-libraries/organizations";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Logger } from "@/lib/logger.bridge";
|
||||
import { PlatformBillingSyncTaskerService } from "@/lib/services/tasker/platform-billing-sync-tasker.service";
|
||||
import { PlatformBillingTriggerTaskerService } from "@/lib/services/tasker/platform-billing-trigger-tasker.service";
|
||||
|
||||
@Injectable()
|
||||
export class PlatformBillingTasker extends BasePlatformOrganizationBillingTasker {
|
||||
constructor(
|
||||
syncTasker: PlatformBillingSyncTaskerService,
|
||||
asyncTasker: PlatformBillingTriggerTaskerService,
|
||||
logger: Logger
|
||||
) {
|
||||
super({
|
||||
logger,
|
||||
asyncTasker: asyncTasker,
|
||||
syncTasker: syncTasker,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Logger } from "@/lib/logger.bridge";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { PlatformOrganizationBillingTriggerTasker as BasePlatformOrganizationBillingTriggerTasker } from "@calcom/platform-libraries/organizations";
|
||||
|
||||
@Injectable()
|
||||
export class PlatformBillingTriggerTaskerService extends BasePlatformOrganizationBillingTriggerTasker {
|
||||
constructor(logger: Logger) {
|
||||
super({
|
||||
logger,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { BullModule } from "@nestjs/bull";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/repositories/bookings.repository";
|
||||
import { PlatformBillingTaskerModule } from "@/lib/modules/platform-billing-tasker.module";
|
||||
import { BillingProcessor } from "@/modules/billing/billing.processor";
|
||||
import { BillingRepository } from "@/modules/billing/billing.repository";
|
||||
import { BillingController } from "@/modules/billing/controllers/billing.controller";
|
||||
import { IsUserInBillingOrg } from "@/modules/billing/guards/is-user-in-billing-org";
|
||||
import { BillingServiceCachingProxy } from "@/modules/billing/services/billing-service-caching-proxy";
|
||||
import { BillingConfigService } from "@/modules/billing/services/billing.config.service";
|
||||
import { BillingService } from "@/modules/billing/services/billing.service";
|
||||
import { BillingServiceCachingProxy } from "@/modules/billing/services/billing-service-caching-proxy";
|
||||
import { ManagedOrganizationsBillingService } from "@/modules/billing/services/managed-organizations.billing.service";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
@@ -14,8 +17,6 @@ import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { BullModule } from "@nestjs/bull";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -32,6 +33,7 @@ import { Module } from "@nestjs/common";
|
||||
},
|
||||
}),
|
||||
UsersModule,
|
||||
PlatformBillingTaskerModule,
|
||||
],
|
||||
providers: [
|
||||
BillingConfigService,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { PlatformBilling, Team } from "@calcom/prisma/client";
|
||||
export type BillingData = {
|
||||
team: (Team & { platformBilling: PlatformBilling | null }) | null;
|
||||
status: "valid" | "no_subscription" | "no_billing";
|
||||
plan: string;
|
||||
plan: PlatformPlan | "none";
|
||||
};
|
||||
|
||||
export interface IBillingService {
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import { AppConfig } from "@/config/type";
|
||||
import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/repositories/bookings.repository";
|
||||
import { BILLING_QUEUE, INCREMENT_JOB, IncrementJobDataType } from "@/modules/billing/billing.processor";
|
||||
import { BillingRepository } from "@/modules/billing/billing.repository";
|
||||
import { IBillingService } from "@/modules/billing/interfaces/billing-service.interface";
|
||||
import { BillingConfigService } from "@/modules/billing/services/billing.config.service";
|
||||
import { PlatformPlan } from "@/modules/billing/types";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { InjectQueue } from "@nestjs/bull";
|
||||
import {
|
||||
BadRequestException,
|
||||
@@ -22,6 +11,16 @@ import { ConfigService } from "@nestjs/config";
|
||||
import { Queue } from "bull";
|
||||
import { DateTime } from "luxon";
|
||||
import Stripe from "stripe";
|
||||
import { AppConfig } from "@/config/type";
|
||||
import { PlatformBillingTasker } from "@/lib/services/tasker/platform-billing-tasker.service";
|
||||
import { BILLING_QUEUE, INCREMENT_JOB, IncrementJobDataType } from "@/modules/billing/billing.processor";
|
||||
import { BillingRepository } from "@/modules/billing/billing.repository";
|
||||
import { BillingData, IBillingService } from "@/modules/billing/interfaces/billing-service.interface";
|
||||
import { BillingConfigService } from "@/modules/billing/services/billing.config.service";
|
||||
import { PlatformPlan } from "@/modules/billing/types";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
|
||||
@Injectable()
|
||||
export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
@@ -35,14 +34,13 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
private readonly configService: ConfigService<AppConfig>,
|
||||
private readonly billingConfigService: BillingConfigService,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly oAuthClientRepository: OAuthClientRepository,
|
||||
private readonly bookingsRepository: BookingsRepository_2024_08_13,
|
||||
private readonly platformBillingTasker: PlatformBillingTasker,
|
||||
@InjectQueue(BILLING_QUEUE) private readonly billingQueue: Queue
|
||||
) {
|
||||
this.webAppUrl = this.configService.get("app.baseUrl", { infer: true }) ?? "https://app.cal.com";
|
||||
}
|
||||
|
||||
async getBillingData(teamId: number) {
|
||||
async getBillingData(teamId: number): Promise<BillingData> {
|
||||
const teamWithBilling = await this.teamsRepository.findByIdIncludeBilling(teamId);
|
||||
|
||||
if (teamWithBilling?.platformBilling) {
|
||||
@@ -52,7 +50,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
return {
|
||||
team: teamWithBilling,
|
||||
status: "valid" as const,
|
||||
plan: teamWithBilling.platformBilling.plan,
|
||||
plan: teamWithBilling.platformBilling.plan as PlatformPlan,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -62,7 +60,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
|
||||
async createTeamBilling(teamId: number): Promise<string> {
|
||||
const teamWithBilling = await this.teamsRepository.findByIdIncludeBilling(teamId);
|
||||
let customerId = teamWithBilling?.platformBilling?.customerId;
|
||||
let customerId = teamWithBilling?.platformBilling?.customerId ?? "";
|
||||
|
||||
if (!teamWithBilling?.platformBilling) {
|
||||
customerId = await this.teamsRepository.createNewBillingRelation(teamId);
|
||||
@@ -73,10 +71,14 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
return customerId!;
|
||||
return customerId;
|
||||
}
|
||||
|
||||
async redirectToSubscribeCheckout(teamId: number, plan: PlatformPlan, customerId?: string) {
|
||||
async redirectToSubscribeCheckout(
|
||||
teamId: number,
|
||||
plan: PlatformPlan,
|
||||
customerId?: string
|
||||
): Promise<string> {
|
||||
const { url } = await this.stripeService.getStripe().checkout.sessions.create({
|
||||
customer: customerId,
|
||||
line_items: [
|
||||
@@ -109,7 +111,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
return url;
|
||||
}
|
||||
|
||||
async updateSubscriptionForTeam(teamId: number, plan: PlatformPlan) {
|
||||
async updateSubscriptionForTeam(teamId: number, plan: PlatformPlan): Promise<string> {
|
||||
const teamWithBilling = await this.teamsRepository.findByIdIncludeBilling(teamId);
|
||||
const customerId = teamWithBilling?.platformBilling?.customerId;
|
||||
|
||||
@@ -134,7 +136,11 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
return url;
|
||||
}
|
||||
|
||||
async setPerBookingSubscriptionForTeam(teamId: number, subscriptionId: string, plan: PlatformPlan) {
|
||||
async setPerBookingSubscriptionForTeam(
|
||||
teamId: number,
|
||||
subscriptionId: string,
|
||||
plan: PlatformPlan
|
||||
): Promise<ReturnType<typeof BillingRepository.prototype.updateTeamBilling>> {
|
||||
const billingCycleStart = DateTime.now().get("day");
|
||||
const billingCycleEnd = DateTime.now().plus({ month: 1 }).get("day");
|
||||
|
||||
@@ -152,7 +158,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
subscriptionId: string,
|
||||
plan: PlatformPlan,
|
||||
priceId: string
|
||||
) {
|
||||
): Promise<ReturnType<typeof BillingRepository.prototype.updateTeamBilling>> {
|
||||
const billingCycleStart = DateTime.now().get("day");
|
||||
const billingCycleEnd = DateTime.now().plus({ month: 1 }).get("day");
|
||||
|
||||
@@ -166,12 +172,12 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
async handleStripeSubscriptionDeleted(event: Stripe.Event) {
|
||||
async handleStripeSubscriptionDeleted(event: Stripe.Event): Promise<void> {
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
const teamId = subscription?.metadata?.teamId;
|
||||
const plan = PlatformPlan[subscription?.metadata?.plan?.toUpperCase() as keyof typeof PlatformPlan];
|
||||
if (teamId && plan) {
|
||||
const currentBilling = await this.billingRepository.getBillingForTeam(Number.parseInt(teamId));
|
||||
const currentBilling = await this.billingRepository.getBillingForTeam(Number.parseInt(teamId, 10));
|
||||
if (currentBilling?.subscriptionId === subscription.id) {
|
||||
await this.billingRepository.deleteBilling(currentBilling.id);
|
||||
this.logger.log(`Stripe Subscription deleted`, {
|
||||
@@ -207,7 +213,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async handleStripePaymentSuccess(event: Stripe.Event) {
|
||||
async handleStripePaymentSuccess(event: Stripe.Event): Promise<void> {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
const subscriptionId = this.getSubscriptionIdFromInvoice(invoice);
|
||||
const customerId = this.getCustomerIdFromInvoice(invoice);
|
||||
@@ -216,7 +222,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async handleStripePaymentFailed(event: Stripe.Event) {
|
||||
async handleStripePaymentFailed(event: Stripe.Event): Promise<void> {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
const subscriptionId = this.getSubscriptionIdFromInvoice(invoice);
|
||||
const customerId = this.getCustomerIdFromInvoice(invoice);
|
||||
@@ -225,7 +231,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async handleStripePaymentPastDue(event: Stripe.Event) {
|
||||
async handleStripePaymentPastDue(event: Stripe.Event): Promise<void> {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
const subscriptionId = this.getSubscriptionIdFromInvoice(invoice);
|
||||
const customerId = this.getCustomerIdFromInvoice(invoice);
|
||||
@@ -249,14 +255,14 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async handleStripeCheckoutEvents(event: Stripe.Event) {
|
||||
async handleStripeCheckoutEvents(event: Stripe.Event): Promise<void> {
|
||||
const checkoutSession = event.data.object as Stripe.Checkout.Session;
|
||||
|
||||
if (!checkoutSession.metadata?.teamId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const teamId = Number.parseInt(checkoutSession.metadata.teamId);
|
||||
const teamId = Number.parseInt(checkoutSession.metadata.teamId, 10);
|
||||
const plan = checkoutSession.metadata.plan;
|
||||
if (!plan || !teamId) {
|
||||
this.logger.log("Webhook received but not pertaining to Platform, discarding.");
|
||||
@@ -288,7 +294,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
async handleStripeSubscriptionForActiveManagedUsers(event: Stripe.Event) {
|
||||
async handleStripeSubscriptionForActiveManagedUsers(event: Stripe.Event): Promise<void> {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
const subscriptionId = this.getSubscriptionIdFromInvoice(invoice);
|
||||
|
||||
@@ -299,12 +305,16 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
const teamWithBilling = await this.billingRepository.getBillingForTeamBySubscriptionId(subscriptionId);
|
||||
|
||||
if (teamWithBilling?.plan === "PER_ACTIVE_USER") {
|
||||
const activeManagedUsersCount = await this.getActiveManagedUsersCount(
|
||||
let activeManagedUsersCount = await this.getActiveManagedUsersCount(
|
||||
subscriptionId,
|
||||
new Date(invoice.period_start * 1000),
|
||||
new Date(invoice.period_end * 1000)
|
||||
);
|
||||
|
||||
if (activeManagedUsersCount < 0) {
|
||||
activeManagedUsersCount = 1;
|
||||
}
|
||||
|
||||
const existingSubscription = await this.stripeService
|
||||
.getStripe()
|
||||
.subscriptions.retrieve(subscriptionId);
|
||||
@@ -321,17 +331,18 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
}
|
||||
|
||||
await this.stripeService.getStripe().subscriptions.update(subscriptionId, {
|
||||
items: [
|
||||
{ id: subscriptionItem.id, quantity: activeManagedUsersCount > 0 ? activeManagedUsersCount : 1 },
|
||||
],
|
||||
items: [{ id: subscriptionItem.id, quantity: activeManagedUsersCount }],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getActiveManagedUsersCount(subscriptionId: string, invoiceStart: Date, invoiceEnd: Date) {
|
||||
const managedUsersEmails = await this.usersRepository.getOrgsManagedUserEmailsBySubscriptionId(
|
||||
subscriptionId
|
||||
);
|
||||
async getActiveManagedUsersCount(
|
||||
subscriptionId: string,
|
||||
invoiceStart: Date,
|
||||
invoiceEnd: Date
|
||||
): Promise<number> {
|
||||
const managedUsersEmails =
|
||||
await this.usersRepository.getOrgsManagedUserEmailsBySubscriptionId(subscriptionId);
|
||||
|
||||
if (!managedUsersEmails) return 0;
|
||||
|
||||
@@ -362,7 +373,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
return activeManagedUserEmailsAsAttendee.length + activeManagedUserEmailsAsHost.length;
|
||||
}
|
||||
|
||||
async updateStripeSubscriptionForTeam(teamId: number, plan: PlatformPlan) {
|
||||
async updateStripeSubscriptionForTeam(teamId: number, plan: PlatformPlan): Promise<void> {
|
||||
const teamWithBilling = await this.teamsRepository.findByIdIncludeBilling(teamId);
|
||||
|
||||
if (!teamWithBilling?.platformBilling || !teamWithBilling?.platformBilling.subscriptionId) {
|
||||
@@ -424,13 +435,37 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
startTime: Date;
|
||||
fromReschedule?: string | null;
|
||||
}
|
||||
) {
|
||||
): Promise<boolean | undefined> {
|
||||
if (this.configService.get("e2e")) {
|
||||
return true;
|
||||
}
|
||||
const { uid, startTime, fromReschedule } = booking;
|
||||
|
||||
const delay = startTime.getTime() - Date.now();
|
||||
if (this.configService.get("enableAsyncTasker")) {
|
||||
if (fromReschedule) {
|
||||
this.platformBillingTasker.rescheduleUsageIncrement({
|
||||
payload: { bookingUid: uid },
|
||||
options: { delay: startTime },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
this.platformBillingTasker.incrementUsage({
|
||||
payload: { userId },
|
||||
options: {
|
||||
delay: startTime,
|
||||
tags: [`platform.billing.usage.${uid}`],
|
||||
idempotencyKey: `platform.billing.usage.${uid}-${userId}`,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
let delay = startTime.getTime() - Date.now();
|
||||
|
||||
if (delay < 0) {
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
if (fromReschedule) {
|
||||
// cancel the usage increment job for the booking that is being rescheduled
|
||||
await this.cancelUsageByBookingUid(fromReschedule);
|
||||
@@ -441,7 +476,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
{
|
||||
userId,
|
||||
} satisfies IncrementJobDataType,
|
||||
{ delay: delay > 0 ? delay : 0, jobId: `increment-${uid}`, removeOnComplete: true }
|
||||
{ delay: delay, jobId: `increment-${uid}`, removeOnComplete: true }
|
||||
);
|
||||
this.logger.log(`Added stripe usage increment job for booking ${uid} and user ${userId}`);
|
||||
}
|
||||
@@ -451,10 +486,16 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
* Cancels the usage increment job for a booking when it is cancelled.
|
||||
* Removing an attendee from a booking does not cancel the usage increment job.
|
||||
*/
|
||||
async cancelUsageByBookingUid(bookingUid: string) {
|
||||
async cancelUsageByBookingUid(bookingUid: string): Promise<boolean | undefined> {
|
||||
if (this.configService.get("e2e")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.configService.get("enableAsyncTasker")) {
|
||||
await this.platformBillingTasker.cancelUsageIncrement({ payload: { bookingUid } });
|
||||
return true;
|
||||
}
|
||||
|
||||
const job = await this.billingQueue.getJob(`increment-${bookingUid}`);
|
||||
if (job) {
|
||||
await job.remove();
|
||||
@@ -462,7 +503,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async cancelTeamSubscription(teamId: number) {
|
||||
async cancelTeamSubscription(teamId: number): Promise<void> {
|
||||
const teamWithBilling = await this.teamsRepository.findByIdIncludeBilling(teamId);
|
||||
const customerId = teamWithBilling?.platformBilling?.customerId;
|
||||
|
||||
@@ -484,7 +525,7 @@ export class BillingService implements IBillingService, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
try {
|
||||
await this.billingQueue.close();
|
||||
} catch (err) {
|
||||
|
||||
@@ -83,6 +83,13 @@ export interface IBillingProviderService {
|
||||
|
||||
finalizeInvoice(invoiceId: string): Promise<void>;
|
||||
|
||||
// Usage-based billing
|
||||
createSubscriptionUsageRecord(args: {
|
||||
subscriptionId: string;
|
||||
action: "increment" | "set";
|
||||
quantity: number;
|
||||
}): Promise<void>;
|
||||
|
||||
// Subscription queries
|
||||
getSubscription(subscriptionId: string): Promise<{
|
||||
items: Array<{
|
||||
|
||||
@@ -272,6 +272,40 @@ export class StripeBillingService implements IBillingProviderService {
|
||||
await this.stripe.invoices.finalizeInvoice(invoiceId);
|
||||
}
|
||||
|
||||
async createSubscriptionUsageRecord(
|
||||
args: Parameters<IBillingProviderService["createSubscriptionUsageRecord"]>[0]
|
||||
) {
|
||||
const { subscriptionId, action, quantity } = args;
|
||||
const log = logger.getSubLogger({ prefix: ["createSubscriptionUsageRecord"] });
|
||||
|
||||
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
|
||||
if (!subscription?.id) {
|
||||
log.error(`Failed to retrieve stripe subscription (${subscriptionId})`);
|
||||
throw new Error(`Failed to retrieve stripe subscription (${subscriptionId})`);
|
||||
}
|
||||
|
||||
const meteredItem = subscription.items.data.find(
|
||||
(item) => item.price?.recurring?.usage_type === "metered"
|
||||
);
|
||||
if (!meteredItem) {
|
||||
log.error(`Stripe subscription (${subscriptionId}) is not usage based`);
|
||||
throw new Error(`Stripe subscription (${subscriptionId}) is not usage based`);
|
||||
}
|
||||
|
||||
await this.stripe.subscriptionItems.createUsageRecord(meteredItem.id, {
|
||||
action,
|
||||
quantity,
|
||||
timestamp: "now",
|
||||
});
|
||||
|
||||
log.info(`Created usage record for subscription ${subscriptionId}`, {
|
||||
subscriptionId,
|
||||
itemId: meteredItem.id,
|
||||
action,
|
||||
quantity,
|
||||
});
|
||||
}
|
||||
|
||||
async getSubscription(subscriptionId: string) {
|
||||
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
|
||||
if (!subscription) return null;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
|
||||
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
|
||||
import { PlatformBillingRepository } from "@calcom/features/ee/organizations/repositories/PlatformBillingRepository";
|
||||
|
||||
import { PLATFORM_BILLING_TASKER_DI_TOKENS } from "./tokens";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_BILLING_REPOSITORY;
|
||||
const moduleToken = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_BILLING_REPOSITORY_MODULE;
|
||||
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: PlatformBillingRepository,
|
||||
dep: prismaModuleLoader,
|
||||
});
|
||||
|
||||
export const moduleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
} satisfies ModuleLoader;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
|
||||
import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service";
|
||||
import { PlatformOrganizationBillingSyncTasker } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingSyncTasker";
|
||||
|
||||
import { moduleLoader as taskServiceModuleLoader } from "./PlatformOrganizationBillingTaskService.module";
|
||||
import { PLATFORM_BILLING_TASKER_DI_TOKENS } from "./tokens";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_SYNC_TASKER;
|
||||
const moduleToken = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_SYNC_TASKER_MODULE;
|
||||
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: PlatformOrganizationBillingSyncTasker,
|
||||
depsMap: {
|
||||
logger: loggerServiceModule,
|
||||
billingTaskService: taskServiceModuleLoader,
|
||||
},
|
||||
});
|
||||
|
||||
export const moduleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
} satisfies ModuleLoader;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { createContainer } from "@calcom/features/di/di";
|
||||
import { PlatformOrganizationBillingTaskService } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTaskService";
|
||||
|
||||
import { moduleLoader as taskServiceModuleLoader } from "./PlatformOrganizationBillingTaskService.module";
|
||||
|
||||
const container = createContainer();
|
||||
|
||||
export function getPlatformOrganizationBillingTaskService(): PlatformOrganizationBillingTaskService {
|
||||
taskServiceModuleLoader.loadModule(container);
|
||||
return container.get<PlatformOrganizationBillingTaskService>(taskServiceModuleLoader.token);
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
|
||||
import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service";
|
||||
import { billingProviderServiceModuleLoader } from "@calcom/features/ee/billing/di/modules/BillingProviderService";
|
||||
import { PlatformOrganizationBillingTaskService } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTaskService";
|
||||
|
||||
import { moduleLoader as organizationRepositoryModuleLoader } from "../OrganizationRepository.module";
|
||||
import { moduleLoader as platformBillingRepositoryModuleLoader } from "./PlatformBillingRepository.module";
|
||||
import { PLATFORM_BILLING_TASKER_DI_TOKENS } from "./tokens";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_TASK_SERVICE;
|
||||
const moduleToken = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_TASK_SERVICE_MODULE;
|
||||
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: PlatformOrganizationBillingTaskService,
|
||||
depsMap: {
|
||||
logger: loggerServiceModule,
|
||||
organizationRepository: organizationRepositoryModuleLoader,
|
||||
platformBillingRepository: platformBillingRepositoryModuleLoader,
|
||||
billingProviderService: billingProviderServiceModuleLoader,
|
||||
},
|
||||
});
|
||||
|
||||
export const moduleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
} satisfies ModuleLoader;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { createContainer } from "@calcom/features/di/di";
|
||||
import { PlatformOrganizationBillingTasker } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTasker";
|
||||
|
||||
import { moduleLoader as taskerModuleLoader } from "./PlatformOrganizationBillingTasker.module";
|
||||
|
||||
const container = createContainer();
|
||||
|
||||
export function getPlatformOrganizationBillingTasker(): PlatformOrganizationBillingTasker {
|
||||
taskerModuleLoader.loadModule(container);
|
||||
return container.get<PlatformOrganizationBillingTasker>(taskerModuleLoader.token);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
|
||||
import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service";
|
||||
import { PlatformOrganizationBillingTasker } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTasker";
|
||||
|
||||
import { moduleLoader as syncTaskerModuleLoader } from "./PlatformOrganizationBillingSyncTasker.module";
|
||||
import { moduleLoader as triggerTaskerModuleLoader } from "./PlatformOrganizationBillingTriggerTasker.module";
|
||||
import { PLATFORM_BILLING_TASKER_DI_TOKENS } from "./tokens";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_TASKER;
|
||||
const moduleToken = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_TASKER_MODULE;
|
||||
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: PlatformOrganizationBillingTasker,
|
||||
depsMap: {
|
||||
logger: loggerServiceModule,
|
||||
asyncTasker: triggerTaskerModuleLoader,
|
||||
syncTasker: syncTaskerModuleLoader,
|
||||
},
|
||||
});
|
||||
|
||||
export const moduleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
} satisfies ModuleLoader;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
|
||||
import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service";
|
||||
import { PlatformOrganizationBillingTriggerTasker } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTriggerTasker";
|
||||
|
||||
import { PLATFORM_BILLING_TASKER_DI_TOKENS } from "./tokens";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_TRIGGER_TASKER;
|
||||
const moduleToken = PLATFORM_BILLING_TASKER_DI_TOKENS.PLATFORM_ORGANIZATION_BILLING_TRIGGER_TASKER_MODULE;
|
||||
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: PlatformOrganizationBillingTriggerTasker,
|
||||
depsMap: {
|
||||
logger: loggerServiceModule,
|
||||
},
|
||||
});
|
||||
|
||||
export const moduleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
} satisfies ModuleLoader;
|
||||
@@ -0,0 +1,12 @@
|
||||
export const PLATFORM_BILLING_TASKER_DI_TOKENS = {
|
||||
PLATFORM_BILLING_REPOSITORY: Symbol("PlatformBillingRepository"),
|
||||
PLATFORM_BILLING_REPOSITORY_MODULE: Symbol("PlatformBillingRepositoryModule"),
|
||||
PLATFORM_ORGANIZATION_BILLING_TASK_SERVICE: Symbol("PlatformOrganizationBillingTaskService"),
|
||||
PLATFORM_ORGANIZATION_BILLING_TASK_SERVICE_MODULE: Symbol("PlatformOrganizationBillingTaskServiceModule"),
|
||||
PLATFORM_ORGANIZATION_BILLING_SYNC_TASKER: Symbol("PlatformOrganizationBillingSyncTasker"),
|
||||
PLATFORM_ORGANIZATION_BILLING_SYNC_TASKER_MODULE: Symbol("PlatformOrganizationBillingSyncTaskerModule"),
|
||||
PLATFORM_ORGANIZATION_BILLING_TRIGGER_TASKER: Symbol("PlatformOrganizationBillingTriggerTasker"),
|
||||
PLATFORM_ORGANIZATION_BILLING_TRIGGER_TASKER_MODULE: Symbol("PlatformOrganizationBillingTriggerTaskerModule"),
|
||||
PLATFORM_ORGANIZATION_BILLING_TASKER: Symbol("PlatformOrganizationBillingTasker"),
|
||||
PLATFORM_ORGANIZATION_BILLING_TASKER_MODULE: Symbol("PlatformOrganizationBillingTaskerModule"),
|
||||
};
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import type { ITaskerDependencies } from "@calcom/lib/tasker/types";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import type { PlatformOrganizationBillingTaskService } from "./PlatformOrganizationBillingTaskService";
|
||||
import type { IPlatformOrganizationBillingTasker } from "./types";
|
||||
|
||||
export interface IPlatformOrganizationBillingSyncTaskerDependencies {
|
||||
billingTaskService: PlatformOrganizationBillingTaskService;
|
||||
}
|
||||
|
||||
export class PlatformOrganizationBillingSyncTasker implements IPlatformOrganizationBillingTasker {
|
||||
constructor(
|
||||
public readonly dependencies: ITaskerDependencies & IPlatformOrganizationBillingSyncTaskerDependencies
|
||||
) {}
|
||||
|
||||
async incrementUsage(
|
||||
payload: Parameters<IPlatformOrganizationBillingTasker["incrementUsage"]>[0]
|
||||
): Promise<{ runId: string }> {
|
||||
const runId = `sync_${nanoid(10)}`;
|
||||
this.dependencies.logger.info(
|
||||
"Delayed tasks are not supported in sync mode, did not increment usage",
|
||||
runId,
|
||||
payload
|
||||
);
|
||||
return { runId };
|
||||
}
|
||||
|
||||
async cancelUsageIncrement(
|
||||
payload: Parameters<IPlatformOrganizationBillingTasker["cancelUsageIncrement"]>[0]
|
||||
): Promise<{ runId: string }> {
|
||||
const runId = `sync_${nanoid(10)}`;
|
||||
this.dependencies.logger.info(
|
||||
"Delayed tasks are not supported in sync mode, did not cancel usage increment",
|
||||
runId,
|
||||
payload
|
||||
);
|
||||
return { runId };
|
||||
}
|
||||
|
||||
async rescheduleUsageIncrement(
|
||||
payload: Parameters<IPlatformOrganizationBillingTasker["rescheduleUsageIncrement"]>[0]
|
||||
): Promise<{ runId: string }> {
|
||||
const runId = `sync_${nanoid(10)}`;
|
||||
this.dependencies.logger.info(
|
||||
"Delayed tasks are not supported in sync mode, did not reschedule usage increment",
|
||||
runId,
|
||||
payload
|
||||
);
|
||||
return { runId };
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import type { IBillingProviderService } from "@calcom/features/ee/billing/service/billingProvider/IBillingProviderService";
|
||||
import type { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
||||
import type { PlatformBillingRepository } from "@calcom/features/ee/organizations/repositories/PlatformBillingRepository";
|
||||
import type { ITaskerDependencies } from "@calcom/lib/tasker/types";
|
||||
|
||||
import type { PlatformOrganizationBillingTasks } from "./types";
|
||||
|
||||
export interface IPlatformOrganizationBillingTaskServiceDependencies {
|
||||
organizationRepository: OrganizationRepository;
|
||||
platformBillingRepository: PlatformBillingRepository;
|
||||
billingProviderService: Pick<IBillingProviderService, "createSubscriptionUsageRecord">;
|
||||
}
|
||||
|
||||
export class PlatformOrganizationBillingTaskService implements PlatformOrganizationBillingTasks {
|
||||
constructor(
|
||||
public readonly dependencies: {
|
||||
logger: ITaskerDependencies["logger"];
|
||||
} & IPlatformOrganizationBillingTaskServiceDependencies
|
||||
) {}
|
||||
|
||||
async incrementUsage(
|
||||
payload: Parameters<PlatformOrganizationBillingTasks["incrementUsage"]>[0]
|
||||
): Promise<void> {
|
||||
const { userId } = payload;
|
||||
const { organizationRepository, platformBillingRepository, billingProviderService, logger } =
|
||||
this.dependencies;
|
||||
|
||||
const team = await organizationRepository.findPlatformOrgByUserId(userId);
|
||||
const teamId = team?.id;
|
||||
if (!teamId) {
|
||||
logger.error(`User (${userId}) is not part of the platform organization (${teamId})`, {
|
||||
teamId,
|
||||
userId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const billingSubscription = await platformBillingRepository.findByTeamId(teamId);
|
||||
if (!billingSubscription || !billingSubscription?.subscriptionId) {
|
||||
logger.error(`Team ${teamId} did not have stripe subscription associated to it`, {
|
||||
teamId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await billingProviderService.createSubscriptionUsageRecord({
|
||||
subscriptionId: billingSubscription.subscriptionId,
|
||||
action: "increment",
|
||||
quantity: 1,
|
||||
});
|
||||
|
||||
logger.info("Increased organization usage for subscription", {
|
||||
subscriptionId: billingSubscription.subscriptionId,
|
||||
teamId,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import { Tasker } from "@calcom/lib/tasker/Tasker";
|
||||
import type { ILogger } from "@calcom/lib/tasker/types";
|
||||
import type { TriggerOptions } from "@trigger.dev/sdk";
|
||||
import type { PlatformOrganizationBillingSyncTasker } from "./PlatformOrganizationBillingSyncTasker";
|
||||
import type { PlatformOrganizationBillingTriggerTasker } from "./PlatformOrganizationBillingTriggerTasker";
|
||||
import type { IPlatformOrganizationBillingTasker, PlatformOrganizationBillingTaskPayload } from "./types";
|
||||
|
||||
export interface IPlatformOrganizationBillingTaskerDependencies {
|
||||
asyncTasker: PlatformOrganizationBillingTriggerTasker;
|
||||
syncTasker: PlatformOrganizationBillingSyncTasker;
|
||||
logger: ILogger;
|
||||
}
|
||||
|
||||
export class PlatformOrganizationBillingTasker extends Tasker<IPlatformOrganizationBillingTasker> {
|
||||
constructor(public readonly dependencies: IPlatformOrganizationBillingTaskerDependencies) {
|
||||
super(dependencies);
|
||||
}
|
||||
|
||||
public async incrementUsage(data: {
|
||||
payload: PlatformOrganizationBillingTaskPayload;
|
||||
options?: TriggerOptions;
|
||||
}): Promise<{ runId: string }> {
|
||||
const { payload } = data;
|
||||
let taskResponse: {
|
||||
runId: string;
|
||||
} = { runId: "task-not-found" };
|
||||
|
||||
try {
|
||||
taskResponse = await this.dispatch("incrementUsage", payload, data.options);
|
||||
|
||||
this.logger.info(`PlatformOrganizationBillingTasker incrementUsage success:`, taskResponse, {
|
||||
userId: payload.userId,
|
||||
});
|
||||
} catch {
|
||||
taskResponse = { runId: "task-failed" };
|
||||
this.logger.error(`PlatformOrganizationBillingTasker incrementUsage failed`, taskResponse, {
|
||||
userId: payload.userId,
|
||||
});
|
||||
}
|
||||
|
||||
return taskResponse;
|
||||
}
|
||||
|
||||
public async cancelUsageIncrement(data: { payload: { bookingUid: string } }): Promise<{ runId: string }> {
|
||||
const { payload } = data;
|
||||
let taskResponse: {
|
||||
runId: string;
|
||||
} = { runId: "task-not-found" };
|
||||
|
||||
try {
|
||||
taskResponse = await this.dispatch("cancelUsageIncrement", payload);
|
||||
|
||||
this.logger.info(`PlatformOrganizationBillingTasker cancelUsageIncrement success:`, taskResponse, {
|
||||
bookingUid: payload.bookingUid,
|
||||
});
|
||||
} catch {
|
||||
taskResponse = { runId: "task-failed" };
|
||||
this.logger.error(`PlatformOrganizationBillingTasker cancelUsageIncrement failed`, taskResponse, {
|
||||
bookingUid: payload.bookingUid,
|
||||
});
|
||||
}
|
||||
|
||||
return taskResponse;
|
||||
}
|
||||
|
||||
public async rescheduleUsageIncrement(data: {
|
||||
payload: { bookingUid: string };
|
||||
options: { delay: NonNullable<TriggerOptions["delay"]> };
|
||||
}): Promise<{ runId: string }> {
|
||||
const { payload } = data;
|
||||
let taskResponse: {
|
||||
runId: string;
|
||||
} = { runId: "task-not-found" };
|
||||
|
||||
try {
|
||||
taskResponse = await this.dispatch("rescheduleUsageIncrement", payload, data.options);
|
||||
this.logger.info(`PlatformOrganizationBillingTasker rescheduleUsageIncrement success:`, taskResponse, {
|
||||
bookingUid: payload.bookingUid,
|
||||
});
|
||||
} catch {
|
||||
taskResponse = { runId: "task-failed" };
|
||||
this.logger.error(`PlatformOrganizationBillingTasker rescheduleUsageIncrement failed`, taskResponse, {
|
||||
bookingUid: payload.bookingUid,
|
||||
});
|
||||
}
|
||||
|
||||
return taskResponse;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import type { ITaskerDependencies } from "@calcom/lib/tasker/types";
|
||||
import type { TriggerOptions } from "@trigger.dev/sdk";
|
||||
import type { IPlatformOrganizationBillingTasker } from "./types";
|
||||
|
||||
export class PlatformOrganizationBillingTriggerTasker implements IPlatformOrganizationBillingTasker {
|
||||
constructor(public readonly dependencies: ITaskerDependencies) {}
|
||||
|
||||
async incrementUsage(
|
||||
payload: Parameters<IPlatformOrganizationBillingTasker["incrementUsage"]>[0],
|
||||
options?: TriggerOptions
|
||||
): Promise<{ runId: string }> {
|
||||
const { incrementUsage } = await import("./trigger/increment-usage");
|
||||
const handle = await incrementUsage.trigger(payload, options);
|
||||
return { runId: handle.id };
|
||||
}
|
||||
|
||||
async cancelUsageIncrement(
|
||||
payload: Parameters<IPlatformOrganizationBillingTasker["cancelUsageIncrement"]>[0],
|
||||
options?: TriggerOptions
|
||||
): Promise<{ runId: string }> {
|
||||
const { cancelUsageIncrement } = await import("./trigger/cancel-usage-increment");
|
||||
const handle = await cancelUsageIncrement.trigger(payload, options);
|
||||
return { runId: handle.id };
|
||||
}
|
||||
|
||||
async rescheduleUsageIncrement(
|
||||
payload: Parameters<IPlatformOrganizationBillingTasker["rescheduleUsageIncrement"]>[0],
|
||||
options?: TriggerOptions
|
||||
): Promise<{ runId: string }> {
|
||||
const { rescheduleUsageIncrement } = await import("./trigger/reschedule-usage-increment");
|
||||
const handle = await rescheduleUsageIncrement.trigger(payload, options);
|
||||
return { runId: handle.id };
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { logger, runs, schemaTask, type TaskWithSchema } from "@trigger.dev/sdk";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { platformBillingTaskConfig } from "./config";
|
||||
import { platformBillingCancelUsageIncrementTaskSchema } from "./schema";
|
||||
|
||||
export const CANCEL_USAGE_INCREMENT_JOB_ID = "platform.billing.cancel-usage-increment";
|
||||
|
||||
export const cancelUsageIncrement: TaskWithSchema<
|
||||
typeof CANCEL_USAGE_INCREMENT_JOB_ID,
|
||||
typeof platformBillingCancelUsageIncrementTaskSchema
|
||||
> = schemaTask({
|
||||
id: CANCEL_USAGE_INCREMENT_JOB_ID,
|
||||
...platformBillingTaskConfig,
|
||||
schema: platformBillingCancelUsageIncrementTaskSchema,
|
||||
run: async (payload: z.infer<typeof platformBillingCancelUsageIncrementTaskSchema>) => {
|
||||
const runId: string = (
|
||||
await runs.list({
|
||||
tag: `increment-${payload.bookingUid}`,
|
||||
limit: 1,
|
||||
})
|
||||
)?.data?.[0]?.id;
|
||||
|
||||
if (!runId) {
|
||||
logger.info("No run found for booking uid", { bookingUid: payload.bookingUid });
|
||||
return;
|
||||
}
|
||||
|
||||
await runs.cancel(runId);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Queue, schemaTask } from "@trigger.dev/sdk";
|
||||
import { queue } from "@trigger.dev/sdk";
|
||||
|
||||
type PlatformBillingTask = Pick<Parameters<typeof schemaTask>[0], "machine" | "retry" | "queue">;
|
||||
|
||||
export const platformBillingQueue: Queue = queue({
|
||||
name: "platform-billing",
|
||||
concurrencyLimit: 5,
|
||||
});
|
||||
|
||||
export const platformBillingTaskConfig: PlatformBillingTask = {
|
||||
queue: platformBillingQueue,
|
||||
machine: "small-2x",
|
||||
retry: {
|
||||
maxAttempts: 3,
|
||||
factor: 1.8,
|
||||
minTimeoutInMs: 500,
|
||||
maxTimeoutInMs: 30_000,
|
||||
randomize: true,
|
||||
outOfMemory: {
|
||||
machine: "medium-1x",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ErrorWithCode } from "@calcom/lib/errors";
|
||||
import { logger, schemaTask, type TaskWithSchema } from "@trigger.dev/sdk";
|
||||
import type { z } from "zod";
|
||||
import { platformBillingTaskConfig } from "./config";
|
||||
import { platformBillingTaskSchema } from "./schema";
|
||||
|
||||
export const INCREMENT_USAGE_JOB_ID = "platform.billing.increment-usage";
|
||||
|
||||
export const incrementUsage: TaskWithSchema<typeof INCREMENT_USAGE_JOB_ID, typeof platformBillingTaskSchema> =
|
||||
schemaTask({
|
||||
id: INCREMENT_USAGE_JOB_ID,
|
||||
...platformBillingTaskConfig,
|
||||
schema: platformBillingTaskSchema,
|
||||
run: async (payload: z.infer<typeof platformBillingTaskSchema>) => {
|
||||
const { getPlatformOrganizationBillingTaskService } = await import(
|
||||
"@calcom/features/ee/organizations/di/tasker/PlatformOrganizationBillingTaskService.container"
|
||||
);
|
||||
|
||||
const billingTaskService = getPlatformOrganizationBillingTaskService();
|
||||
try {
|
||||
await billingTaskService.incrementUsage(payload);
|
||||
} catch (error) {
|
||||
if (error instanceof Error || error instanceof ErrorWithCode) logger.error(error.message);
|
||||
else logger.error("Unknown error in incrementUsage", { error });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { logger, runs, schemaTask, type TaskWithSchema } from "@trigger.dev/sdk";
|
||||
import type z from "zod";
|
||||
import { platformBillingTaskConfig } from "./config";
|
||||
import { platformBillingRescheduleUsageIncrementTaskSchema } from "./schema";
|
||||
|
||||
export const RESCHEDULE_USAGE_INCREMENT_JOB_ID = "platform.billing.reschedule-usage-increment";
|
||||
|
||||
export const rescheduleUsageIncrement: TaskWithSchema<
|
||||
typeof RESCHEDULE_USAGE_INCREMENT_JOB_ID,
|
||||
typeof platformBillingRescheduleUsageIncrementTaskSchema
|
||||
> = schemaTask({
|
||||
id: RESCHEDULE_USAGE_INCREMENT_JOB_ID,
|
||||
...platformBillingTaskConfig,
|
||||
schema: platformBillingRescheduleUsageIncrementTaskSchema,
|
||||
run: async (payload: z.infer<typeof platformBillingRescheduleUsageIncrementTaskSchema>) => {
|
||||
const runId: string = (
|
||||
await runs.list({
|
||||
tag: `platform.billing.usage.${payload.bookingUid}`,
|
||||
limit: 1,
|
||||
})
|
||||
)?.data?.[0]?.id;
|
||||
|
||||
if (!runId) {
|
||||
logger.info("No run found for booking uid", { bookingUid: payload.bookingUid });
|
||||
return;
|
||||
}
|
||||
|
||||
await runs.cancel(runId);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const platformBillingTaskSchema: z.ZodObject<
|
||||
{
|
||||
userId: z.ZodNumber;
|
||||
},
|
||||
"strip",
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
userId: number;
|
||||
},
|
||||
{
|
||||
userId: number;
|
||||
}
|
||||
> = z.object({
|
||||
userId: z.number(),
|
||||
});
|
||||
|
||||
export const platformBillingCancelUsageIncrementTaskSchema: z.ZodObject<
|
||||
{
|
||||
bookingUid: z.ZodString;
|
||||
},
|
||||
"strip",
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
bookingUid: string;
|
||||
},
|
||||
{
|
||||
bookingUid: string;
|
||||
}
|
||||
> = z.object({
|
||||
bookingUid: z.string(),
|
||||
});
|
||||
|
||||
export const platformBillingRescheduleUsageIncrementTaskSchema: z.ZodObject<
|
||||
{
|
||||
bookingUid: z.ZodString;
|
||||
},
|
||||
"strip",
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
bookingUid: string;
|
||||
},
|
||||
{
|
||||
bookingUid: string;
|
||||
}
|
||||
> = z.object({
|
||||
bookingUid: z.string(),
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { TriggerOptions } from "@trigger.dev/sdk";
|
||||
import type { z } from "zod";
|
||||
import type { platformBillingTaskSchema } from "./trigger/schema";
|
||||
|
||||
type WithVoidReturns<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: infer P) => unknown ? (...args: P) => void : T[K];
|
||||
};
|
||||
|
||||
export type PlatformOrganizationBillingTaskPayload = z.infer<typeof platformBillingTaskSchema>;
|
||||
|
||||
export interface IPlatformOrganizationBillingTasker {
|
||||
incrementUsage(
|
||||
payload: PlatformOrganizationBillingTaskPayload,
|
||||
options?: TriggerOptions
|
||||
): Promise<{ runId: string }>;
|
||||
|
||||
cancelUsageIncrement(payload: { bookingUid: string }): Promise<{ runId: string }>;
|
||||
|
||||
rescheduleUsageIncrement(
|
||||
payload: { bookingUid: string },
|
||||
options: { delay: NonNullable<TriggerOptions["delay"]> }
|
||||
): Promise<{ runId: string }>;
|
||||
}
|
||||
|
||||
export type PlatformOrganizationBillingTasks = WithVoidReturns<
|
||||
Pick<IPlatformOrganizationBillingTasker, "incrementUsage">
|
||||
>;
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
|
||||
import { getParsedTeam } from "@calcom/features/ee/teams/lib/getParsedTeam";
|
||||
import { createAProfileForAnExistingUser } from "@calcom/features/profile/lib/createAProfileForAnExistingUser";
|
||||
@@ -7,10 +5,11 @@ import { UserRepository } from "@calcom/features/users/repositories/UserReposito
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { CreationSource } from "@calcom/prisma/enums";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { z } from "zod";
|
||||
|
||||
const orgSelect = {
|
||||
id: true,
|
||||
@@ -531,4 +530,23 @@ export class OrganizationRepository {
|
||||
|
||||
return org?.organizationSettings ?? null;
|
||||
}
|
||||
|
||||
async findPlatformOrgByUserId(userId: number) {
|
||||
return this.prismaClient.team.findFirst({
|
||||
where: {
|
||||
orgProfiles: {
|
||||
some: {
|
||||
userId: userId,
|
||||
},
|
||||
},
|
||||
isPlatform: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
isPlatform: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
|
||||
export class PlatformBillingRepository {
|
||||
constructor(private readonly prismaClient: PrismaClient) {}
|
||||
|
||||
async findByTeamId(teamId: number) {
|
||||
return this.prismaClient.platformBilling.findUnique({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
select: {
|
||||
subscriptionId: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export default defineConfig({
|
||||
"./bookings/lib/tasker/trigger/notifications",
|
||||
"./calendars/lib/tasker/trigger",
|
||||
"./ee/billing/service/proration/tasker/trigger",
|
||||
"./ee/organizations/lib/billing/tasker/trigger",
|
||||
], // Customize based on your project structure
|
||||
|
||||
// Retry configuration
|
||||
|
||||
@@ -6,13 +6,13 @@ import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooki
|
||||
import { getClientSecretFromPayment } from "@calcom/features/ee/payments/pages/getClientSecretFromPayment";
|
||||
import { getTeamMemberEmailForResponseOrContactUsingUrlQuery } from "@calcom/features/ee/teams/lib/getTeamMemberEmailFromCrm";
|
||||
import {
|
||||
verifyPhoneNumber,
|
||||
sendVerificationCode,
|
||||
verifyPhoneNumber,
|
||||
} from "@calcom/features/ee/workflows/lib/reminders/verifyPhoneNumber";
|
||||
import { handleCreatePhoneCall } from "@calcom/features/handleCreatePhoneCall";
|
||||
import handleMarkNoShow from "@calcom/features/handleMarkNoShow";
|
||||
import { getRoutedUrl } from "@calcom/features/routing-forms/lib/getRoutedUrl";
|
||||
import { symmetricEncrypt, symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "@calcom/lib/crypto";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
@@ -23,44 +23,39 @@ export { slugify } from "@calcom/lib/slugify";
|
||||
export { slugifyLenient } from "@calcom/lib/slugify-lenient";
|
||||
export { getBookingForReschedule };
|
||||
|
||||
export type { EventBusyDate } from "@calcom/types/Calendar";
|
||||
|
||||
export { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents";
|
||||
export {
|
||||
DEFAULT_WEBHOOK_VERSION,
|
||||
WebhookVersion,
|
||||
} from "@calcom/features/webhooks/lib/interface/IWebhookRepository";
|
||||
export {
|
||||
CreationSource,
|
||||
SchedulingType,
|
||||
PeriodType,
|
||||
AttributeType,
|
||||
CreationSource,
|
||||
MembershipRole,
|
||||
PeriodType,
|
||||
SchedulingType,
|
||||
TimeUnit,
|
||||
WebhookTriggerEvents,
|
||||
WorkflowTriggerEvents,
|
||||
WorkflowActions,
|
||||
WorkflowTemplates,
|
||||
WorkflowTriggerEvents,
|
||||
} from "@calcom/prisma/enums";
|
||||
|
||||
export {
|
||||
WebhookVersion,
|
||||
DEFAULT_WEBHOOK_VERSION,
|
||||
} from "@calcom/features/webhooks/lib/interface/IWebhookRepository";
|
||||
|
||||
export { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents";
|
||||
export type { EventBusyDate } from "@calcom/types/Calendar";
|
||||
|
||||
export { handleMarkNoShow };
|
||||
export { handleCreatePhoneCall };
|
||||
|
||||
export { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } from "@calcom/features/calendars/lib/getConnectedDestinationCalendars";
|
||||
|
||||
export { getBusyCalendarTimes } from "@calcom/features/calendars/lib/CalendarManager";
|
||||
|
||||
export type {
|
||||
BookingCreateBody,
|
||||
BookingResponse,
|
||||
} from "@calcom/features/bookings/types";
|
||||
|
||||
export { MINUTES_TO_BOOK, ENABLE_ASYNC_TASKER } from "@calcom/lib/constants";
|
||||
export { getBusyCalendarTimes } from "@calcom/features/calendars/lib/CalendarManager";
|
||||
export { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } from "@calcom/features/calendars/lib/getConnectedDestinationCalendars";
|
||||
export type { CityTimezones } from "@calcom/features/cityTimezones/cityTimezonesHandler";
|
||||
|
||||
export { cityTimezonesHandler } from "@calcom/features/cityTimezones/cityTimezonesHandler";
|
||||
export type { CityTimezones } from "@calcom/features/cityTimezones/cityTimezonesHandler";
|
||||
export { ENABLE_ASYNC_TASKER, MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
||||
|
||||
export { TRPCError } from "@trpc/server";
|
||||
export { createNewUsersConnectToOrgIfExists };
|
||||
@@ -69,23 +64,19 @@ export { getAllUserBookings };
|
||||
export { getBookingInfo };
|
||||
export { handleCancelBooking };
|
||||
|
||||
export {
|
||||
userMetadata,
|
||||
bookingMetadataSchema,
|
||||
teamMetadataSchema,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
export { dynamicEvent } from "@calcom/features/eventtypes/lib/defaultEvents";
|
||||
|
||||
export { parseBookingLimit } from "@calcom/lib/intervalLimits/isBookingLimits";
|
||||
|
||||
export { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
export { dynamicEvent } from "@calcom/features/eventtypes/lib/defaultEvents";
|
||||
export { bookingMetadataSchema, teamMetadataSchema, userMetadata } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export { symmetricEncrypt, symmetricDecrypt };
|
||||
|
||||
export { getTranslation };
|
||||
|
||||
export { roundRobinReassignment } from "@calcom/features/ee/round-robin/roundRobinReassignment";
|
||||
export { roundRobinManualReassignment } from "@calcom/features/ee/round-robin/roundRobinManualReassignment";
|
||||
export { roundRobinReassignment } from "@calcom/features/ee/round-robin/roundRobinReassignment";
|
||||
|
||||
export { validateCustomEventName } from "@calcom/features/eventtypes/lib/eventNaming";
|
||||
|
||||
@@ -109,45 +100,38 @@ export { credentialForCalendarServiceSelect };
|
||||
export { paymentDataSelect };
|
||||
export { getClientSecretFromPayment };
|
||||
|
||||
export { confirmHandler as confirmBookingHandler } from "@calcom/trpc/server/routers/viewer/bookings/confirm.handler";
|
||||
export { groupMembershipAttributes } from "@calcom/trpc/server/routers/viewer/attributes/getByUserId.handler";
|
||||
export type { GroupedAttribute } from "@calcom/trpc/server/routers/viewer/attributes/getByUserId.handler";
|
||||
export { groupMembershipAttributes } from "@calcom/trpc/server/routers/viewer/attributes/getByUserId.handler";
|
||||
export { confirmHandler as confirmBookingHandler } from "@calcom/trpc/server/routers/viewer/bookings/confirm.handler";
|
||||
export { getBookingFieldsWithSystemFields };
|
||||
|
||||
export { getRoutedUrl };
|
||||
|
||||
export { getTeamMemberEmailForResponseOrContactUsingUrlQuery };
|
||||
|
||||
export { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
export { getCalendarLinks } from "@calcom/features/bookings/lib/getCalendarLinks";
|
||||
export { findTeamMembersMatchingAttributeLogic } from "@calcom/features/routing-forms/lib/findTeamMembersMatchingAttributeLogic";
|
||||
export { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
export { encryptServiceAccountKey } from "@calcom/lib/server/serviceAccountKey";
|
||||
export { createHandler as createApiKeyHandler } from "@calcom/trpc/server/routers/viewer/apiKeys/create.handler";
|
||||
export { getCalendarLinks } from "@calcom/features/bookings/lib/getCalendarLinks";
|
||||
|
||||
export { findTeamMembersMatchingAttributeLogic } from "@calcom/features/routing-forms/lib/findTeamMembersMatchingAttributeLogic";
|
||||
export type { TFindTeamMembersMatchingAttributeLogicInputSchema } from "@calcom/trpc/server/routers/viewer/attributes/findTeamMembersMatchingAttributeLogic.schema";
|
||||
export { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
|
||||
export { verifyPhoneNumber, sendVerificationCode };
|
||||
|
||||
export { verifyCodeUnAuthenticated } from "@calcom/features/auth/lib/verifyCodeUnAuthenticated";
|
||||
|
||||
export { verifyCode as verifyCodeAuthenticated } from "@calcom/trpc/server/routers/viewer/organizations/verifyCode.handler";
|
||||
|
||||
export { sendEmailVerificationByCode } from "@calcom/features/auth/lib/verifyEmail";
|
||||
|
||||
export { checkEmailVerificationRequired } from "@calcom/trpc/server/routers/publicViewer/checkIfUserEmailVerificationRequired.handler";
|
||||
export { BookingAccessService } from "@calcom/features/bookings/services/BookingAccessService";
|
||||
export { StripeBillingService } from "@calcom/features/ee/billing/service/billingProvider/StripeBillingService";
|
||||
|
||||
export { TeamService } from "@calcom/features/ee/teams/services/teamService";
|
||||
|
||||
export { BookingAccessService } from "@calcom/features/bookings/services/BookingAccessService";
|
||||
export { getTasker } from "@calcom/features/tasker/tasker-factory";
|
||||
export type { Tasker } from "@calcom/features/tasker/tasker";
|
||||
|
||||
export { validateUrlForSSRFSync } from "@calcom/lib/ssrfProtection";
|
||||
|
||||
export { verifyCodeChallenge } from "@calcom/lib/pkce";
|
||||
export { generateSecret } from "@calcom/features/oauth/utils/generateSecret";
|
||||
export { OAuthService } from "@calcom/features/oauth/services/OAuthService";
|
||||
|
||||
export { generateSecret } from "@calcom/features/oauth/utils/generateSecret";
|
||||
export { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
|
||||
export type { Tasker } from "@calcom/features/tasker/tasker";
|
||||
export { getTasker } from "@calcom/features/tasker/tasker-factory";
|
||||
export { verifyCodeChallenge } from "@calcom/lib/pkce";
|
||||
export { validateUrlForSSRFSync } from "@calcom/lib/ssrfProtection";
|
||||
export { checkEmailVerificationRequired } from "@calcom/trpc/server/routers/publicViewer/checkIfUserEmailVerificationRequired.handler";
|
||||
export { verifyCode as verifyCodeAuthenticated } from "@calcom/trpc/server/routers/viewer/organizations/verifyCode.handler";
|
||||
export type { OrgMembershipLookup } from "@calcom/trpc/server/routers/viewer/slots/util";
|
||||
|
||||
@@ -5,3 +5,10 @@ export type { IOrganizationRepository } from "@calcom/features/ee/organizations/
|
||||
|
||||
export { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
export { getBookerBaseUrlSync } from "@calcom/features/ee/organizations/lib/getBookerBaseUrlSync";
|
||||
|
||||
export { PlatformBillingRepository } from "@calcom/features/ee/organizations/repositories/PlatformBillingRepository";
|
||||
export { PlatformOrganizationBillingTasker } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTasker";
|
||||
export { PlatformOrganizationBillingSyncTasker } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingSyncTasker";
|
||||
export { PlatformOrganizationBillingTriggerTasker } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTriggerTasker";
|
||||
export { PlatformOrganizationBillingTaskService } from "@calcom/features/ee/organizations/lib/billing/tasker/PlatformOrganizationBillingTaskService";
|
||||
export type { IBillingProviderService } from "@calcom/features/ee/billing/service/billingProvider/IBillingProviderService";
|
||||
|
||||
Reference in New Issue
Block a user