feat: skip platform billing for non-platform-managed users in 2024-08-13 API (#27586)
* feat: skip platform billing for non-platform-managed users in 2024-08-13 API - Add isPlatformManaged check in billBooking() to skip billing for regular users - Add isPlatformManaged check in billRescheduledBooking() to skip billing for regular users - Add isPlatformManagedUserBooking check in cancelBooking() to skip billing cancellation for regular users - Add E2E tests to verify billing behavior for both regular and platform-managed users Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: reuse booking data for isPlatformManaged instead of fetching user - Capture isPlatformManagedUserBooking from raw booking data returned by regularBookingService.createBooking() and recurringBookingService.createBooking() - Pass the flag through the output booking objects - Update billBooking() and billRescheduledBooking() to use the flag instead of fetching user from database - Matches the pattern used in 2024-04-15 controller Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: skip platform billing in booking service for non platform * fix: use double negation for boolean type safety in isPlatformManagedUserBooking 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:
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
2c28c9c028
commit
2a30ba9d3c
@@ -0,0 +1,392 @@
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type { BookingOutput_2024_08_13, CreateBookingInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { BillingService } from "@/modules/billing/services/billing.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:4321";
|
||||
|
||||
describe("Bookings Billing E2E - 2024-08-13", () => {
|
||||
describe("Regular user (non-platform-managed)", () => {
|
||||
jest.setTimeout(30000);
|
||||
let app: INestApplication;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let bookingsRepositoryFixture: BookingsRepositoryFixture;
|
||||
let schedulesService: SchedulesService_2024_04_15;
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
let billingService: BillingService;
|
||||
let increaseUsageSpy: jest.SpyInstance;
|
||||
let cancelUsageSpy: jest.SpyInstance;
|
||||
|
||||
const userEmail = `billing-regular-user-2024-08-13-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let eventTypeId: number;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
})
|
||||
.compile();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
billingService = moduleRef.get<BillingService>(BillingService);
|
||||
|
||||
// Spy on the billing service methods
|
||||
increaseUsageSpy = jest.spyOn(billingService, "increaseUsageByUserId");
|
||||
cancelUsageSpy = jest.spyOn(billingService, "cancelUsageByBookingUid");
|
||||
|
||||
// Create a regular user (not platform-managed)
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
isPlatformManaged: false,
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: `billing-test-schedule-2024-08-13-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: `billing-test-event-type-2024-08-13-${randomString()}`,
|
||||
slug: `billing-test-event-type-2024-08-13-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("should NOT call billing service when creating a booking for a regular user", async () => {
|
||||
increaseUsageSpy.mockClear();
|
||||
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2040, 4, 21, 9, 30, 0)).toISOString(),
|
||||
eventTypeId: eventTypeId,
|
||||
attendee: {
|
||||
name: "Test Attendee",
|
||||
email: "attendee-billing-2024-08-13@example.com",
|
||||
timeZone: "Europe/London",
|
||||
language: "en",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201);
|
||||
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
|
||||
// Verify billing service was NOT called for regular user
|
||||
expect(increaseUsageSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should NOT call billing cancel service when cancelling a booking for a regular user", async () => {
|
||||
// First create a booking
|
||||
const createBody: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2040, 4, 22, 9, 30, 0)).toISOString(),
|
||||
eventTypeId: eventTypeId,
|
||||
attendee: {
|
||||
name: "Test Attendee Cancel",
|
||||
email: "attendee-cancel-billing-2024-08-13@example.com",
|
||||
timeZone: "Europe/London",
|
||||
language: "en",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(createBody)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201);
|
||||
|
||||
const createResponseBody: CreateBookingOutput_2024_08_13 = createResponse.body;
|
||||
const data = createResponseBody.data as BookingOutput_2024_08_13;
|
||||
const bookingUid = data.uid;
|
||||
|
||||
// Clear the spy before cancelling
|
||||
cancelUsageSpy.mockClear();
|
||||
|
||||
// Cancel the booking
|
||||
await request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${bookingUid}/cancel`)
|
||||
.send({})
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200);
|
||||
|
||||
// Verify billing cancel service was NOT called for regular user
|
||||
expect(cancelUsageSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Platform-managed user", () => {
|
||||
jest.setTimeout(30000);
|
||||
let app: INestApplication;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let bookingsRepositoryFixture: BookingsRepositoryFixture;
|
||||
let schedulesService: SchedulesService_2024_04_15;
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let profilesRepositoryFixture: ProfileRepositoryFixture;
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
let billingService: BillingService;
|
||||
let increaseUsageSpy: jest.SpyInstance;
|
||||
let cancelUsageSpy: jest.SpyInstance;
|
||||
|
||||
const platformAdminEmail = `billing-platform-admin-2024-08-13-${randomString()}@api.com`;
|
||||
const managedUserEmail = `billing-managed-user-2024-08-13-${randomString()}@api.com`;
|
||||
let platformAdmin: User;
|
||||
let managedUser: User;
|
||||
let organization: Team;
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let eventTypeId: number;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
managedUserEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
})
|
||||
.compile();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
billingService = moduleRef.get<BillingService>(BillingService);
|
||||
|
||||
// Spy on the billing service methods
|
||||
increaseUsageSpy = jest.spyOn(billingService, "increaseUsageByUserId");
|
||||
cancelUsageSpy = jest.spyOn(billingService, "cancelUsageByBookingUid");
|
||||
|
||||
// Create platform admin
|
||||
platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail });
|
||||
|
||||
// Create organization
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `billing-test-organization-2024-08-13-${randomString()}`,
|
||||
isPlatform: true,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
// Create OAuth client
|
||||
oAuthClient = await oauthClientRepositoryFixture.create(
|
||||
organization.id,
|
||||
{
|
||||
logo: "logo-url",
|
||||
name: "billing-test-oauth-client-2024-08-13",
|
||||
redirectUris: [CLIENT_REDIRECT_URI],
|
||||
permissions: 1023,
|
||||
},
|
||||
"secret"
|
||||
);
|
||||
|
||||
// Create profile for platform admin
|
||||
await profilesRepositoryFixture.create({
|
||||
uid: `billing-test-profile-2024-08-13-${randomString()}`,
|
||||
username: platformAdminEmail,
|
||||
organization: { connect: { id: organization.id } },
|
||||
user: { connect: { id: platformAdmin.id } },
|
||||
});
|
||||
|
||||
// Create membership for platform admin
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: platformAdmin.id } },
|
||||
team: { connect: { id: organization.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
// Create a platform-managed user
|
||||
managedUser = await userRepositoryFixture.create({
|
||||
email: managedUserEmail,
|
||||
username: managedUserEmail,
|
||||
isPlatformManaged: true,
|
||||
platformOAuthClients: {
|
||||
connect: { id: oAuthClient.id },
|
||||
},
|
||||
});
|
||||
|
||||
// Create profile for managed user
|
||||
await profilesRepositoryFixture.create({
|
||||
uid: `billing-managed-user-profile-2024-08-13-${randomString()}`,
|
||||
username: managedUserEmail,
|
||||
organization: { connect: { id: organization.id } },
|
||||
user: { connect: { id: managedUser.id } },
|
||||
});
|
||||
|
||||
// Create membership for managed user
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: managedUser.id } },
|
||||
team: { connect: { id: organization.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: `billing-managed-user-schedule-2024-08-13-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(managedUser.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: `billing-managed-user-event-type-2024-08-13-${randomString()}`,
|
||||
slug: `billing-managed-user-event-type-2024-08-13-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
managedUser.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await bookingsRepositoryFixture.deleteAllBookings(managedUser.id, managedUser.email);
|
||||
await userRepositoryFixture.deleteByEmail(managedUser.email);
|
||||
await userRepositoryFixture.deleteByEmail(platformAdmin.email);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("should call billing service when creating a booking for a platform-managed user", async () => {
|
||||
increaseUsageSpy.mockClear();
|
||||
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2040, 4, 21, 9, 30, 0)).toISOString(),
|
||||
eventTypeId: eventTypeId,
|
||||
attendee: {
|
||||
name: "Test Attendee",
|
||||
email: "attendee-billing-managed-2024-08-13@example.com",
|
||||
timeZone: "Europe/London",
|
||||
language: "en",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201);
|
||||
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
|
||||
const data = responseBody.data as BookingOutput_2024_08_13;
|
||||
|
||||
// Verify billing service WAS called for platform-managed user
|
||||
expect(increaseUsageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(increaseUsageSpy).toHaveBeenCalledWith(
|
||||
managedUser.id,
|
||||
expect.objectContaining({
|
||||
uid: data.uid,
|
||||
startTime: expect.any(Date),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should call billing cancel service when cancelling a booking for a platform-managed user", async () => {
|
||||
// First create a booking
|
||||
const createBody: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2040, 4, 22, 9, 30, 0)).toISOString(),
|
||||
eventTypeId: eventTypeId,
|
||||
attendee: {
|
||||
name: "Test Attendee Cancel",
|
||||
email: "attendee-cancel-managed-2024-08-13@example.com",
|
||||
timeZone: "Europe/London",
|
||||
language: "en",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(createBody)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201);
|
||||
|
||||
const createResponseBody: CreateBookingOutput_2024_08_13 = createResponse.body;
|
||||
const data = createResponseBody.data as BookingOutput_2024_08_13;
|
||||
const bookingUid = data.uid;
|
||||
|
||||
// Clear the spy before cancelling
|
||||
cancelUsageSpy.mockClear();
|
||||
|
||||
// Cancel the booking
|
||||
await request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${bookingUid}/cancel`)
|
||||
.send({})
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200);
|
||||
|
||||
// Verify billing cancel service WAS called for platform-managed user
|
||||
expect(cancelUsageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(cancelUsageSpy).toHaveBeenCalledWith(bookingUid);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -73,6 +73,7 @@ type CreatedBooking = {
|
||||
hosts: { id: number }[];
|
||||
uid: string;
|
||||
start: string;
|
||||
isPlatformManagedUserBooking?: boolean;
|
||||
};
|
||||
|
||||
const eventTypeBookingFieldSchema = z
|
||||
@@ -441,7 +442,12 @@ export class BookingsService_2024_08_13 {
|
||||
throw new Error(`Booking with id=${booking.bookingId} was not found in the database`);
|
||||
}
|
||||
|
||||
return this.outputService.getOutputBooking(databaseBooking);
|
||||
const outputBooking = await this.outputService.getOutputBooking(databaseBooking);
|
||||
// Instant bookings don't have a host assigned yet (AWAITING_HOST status),
|
||||
// so we can't determine isPlatformManaged. Default to false to skip billing.
|
||||
return Object.assign(outputBooking, {
|
||||
isPlatformManagedUserBooking: false,
|
||||
});
|
||||
}
|
||||
|
||||
async createRecurringBooking(
|
||||
@@ -466,7 +472,11 @@ export class BookingsService_2024_08_13 {
|
||||
creationSource: "API_V2",
|
||||
});
|
||||
const ids = bookings.map((booking) => booking.id || 0);
|
||||
return this.outputService.getOutputRecurringBookings(ids);
|
||||
const outputBookings = await this.outputService.getOutputRecurringBookings(ids);
|
||||
const isPlatformManagedUserBooking = !!(bookings[0]?.userId && bookings[0]?.user?.isPlatformManaged);
|
||||
return outputBookings.map((outputBooking) =>
|
||||
Object.assign(outputBooking, { isPlatformManagedUserBooking })
|
||||
);
|
||||
}
|
||||
|
||||
async createRecurringSeatedBooking(
|
||||
@@ -490,10 +500,14 @@ export class BookingsService_2024_08_13 {
|
||||
},
|
||||
creationSource: "API_V2",
|
||||
});
|
||||
return this.outputService.getOutputCreateRecurringSeatedBookings(
|
||||
const outputBookings = await this.outputService.getOutputCreateRecurringSeatedBookings(
|
||||
bookings.map((booking) => ({ uid: booking.uid || "", seatUid: booking.seatReferenceUid || "" })),
|
||||
userIsEventTypeAdminOrOwner
|
||||
);
|
||||
const isPlatformManagedUserBooking = !!(bookings[0]?.userId && bookings[0]?.user?.isPlatformManaged);
|
||||
return outputBookings.map((outputBooking) =>
|
||||
Object.assign(outputBooking, { isPlatformManagedUserBooking })
|
||||
);
|
||||
}
|
||||
|
||||
async createRegularBooking(
|
||||
@@ -525,7 +539,15 @@ export class BookingsService_2024_08_13 {
|
||||
throw new Error(`Booking with uid=${booking.uid} was not found in the database`);
|
||||
}
|
||||
|
||||
return this.outputService.getOutputBooking(databaseBooking);
|
||||
const outputBooking = await this.outputService.getOutputBooking(databaseBooking);
|
||||
return Object.assign(
|
||||
outputBooking,
|
||||
booking.userId
|
||||
? {
|
||||
isPlatformManagedUserBooking: booking.user?.isPlatformManaged ?? false,
|
||||
}
|
||||
: {}
|
||||
);
|
||||
}
|
||||
|
||||
async createSeatedBooking(
|
||||
@@ -560,11 +582,19 @@ export class BookingsService_2024_08_13 {
|
||||
throw new Error(`Booking with uid=${booking.uid} was not found in the database`);
|
||||
}
|
||||
|
||||
return this.outputService.getOutputCreateSeatedBooking(
|
||||
const outputBooking = await this.outputService.getOutputCreateSeatedBooking(
|
||||
databaseBooking,
|
||||
booking.seatReferenceUid || "",
|
||||
userIsEventTypeAdminOrOwner
|
||||
);
|
||||
return Object.assign(
|
||||
outputBooking,
|
||||
booking.userId
|
||||
? {
|
||||
isPlatformManagedUserBooking: booking.user?.isPlatformManaged ?? false,
|
||||
}
|
||||
: {}
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message === "booking_seats_full_error") {
|
||||
@@ -816,25 +846,30 @@ export class BookingsService_2024_08_13 {
|
||||
: false;
|
||||
const isRecurring = !!databaseBooking.recurringEventId;
|
||||
const isSeated = !!databaseBooking.eventType?.seatsPerTimeSlot;
|
||||
const isPlatformManagedUserBooking = !!(booking.userId && booking.user?.isPlatformManaged);
|
||||
|
||||
if (isRecurring && !isSeated) {
|
||||
return this.outputService.getOutputRecurringBooking(databaseBooking);
|
||||
const outputBooking = await this.outputService.getOutputRecurringBooking(databaseBooking);
|
||||
return Object.assign(outputBooking, { isPlatformManagedUserBooking });
|
||||
}
|
||||
if (isRecurring && isSeated) {
|
||||
return this.outputService.getOutputCreateRecurringSeatedBooking(
|
||||
const outputBooking = await this.outputService.getOutputCreateRecurringSeatedBooking(
|
||||
databaseBooking,
|
||||
booking?.seatReferenceUid || "",
|
||||
userIsEventTypeAdminOrOwner
|
||||
);
|
||||
return Object.assign(outputBooking, { isPlatformManagedUserBooking });
|
||||
}
|
||||
if (isSeated) {
|
||||
return this.outputService.getOutputCreateSeatedBooking(
|
||||
const outputBooking = await this.outputService.getOutputCreateSeatedBooking(
|
||||
databaseBooking,
|
||||
booking.seatReferenceUid || "",
|
||||
userIsEventTypeAdminOrOwner
|
||||
);
|
||||
return Object.assign(outputBooking, { isPlatformManagedUserBooking });
|
||||
}
|
||||
return this.outputService.getOutputBooking(databaseBooking);
|
||||
const outputBooking = await this.outputService.getOutputBooking(databaseBooking);
|
||||
return Object.assign(outputBooking, { isPlatformManagedUserBooking });
|
||||
} catch (error) {
|
||||
this.errorsBookingsService.handleBookingError(error, false);
|
||||
}
|
||||
@@ -943,7 +978,7 @@ export class BookingsService_2024_08_13 {
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
});
|
||||
|
||||
if (!res.onlyRemovedAttendee) {
|
||||
if (!res.onlyRemovedAttendee && res.isPlatformManagedUserBooking) {
|
||||
await this.billingService.cancelUsageByBookingUid(res.bookingUid);
|
||||
}
|
||||
|
||||
@@ -1021,6 +1056,10 @@ export class BookingsService_2024_08_13 {
|
||||
}
|
||||
|
||||
async billBooking(booking: CreatedBooking) {
|
||||
if (!booking.isPlatformManagedUserBooking) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hostId = booking.hosts?.[0]?.id;
|
||||
if (!hostId) {
|
||||
this.logger.error(`Booking with uid=${booking.uid} has no host`);
|
||||
@@ -1034,7 +1073,11 @@ export class BookingsService_2024_08_13 {
|
||||
}
|
||||
|
||||
async billRescheduledBooking(newBooking: CreatedBooking, oldBookingUid: string) {
|
||||
const hostId = newBooking.hosts[0].id;
|
||||
if (!newBooking.isPlatformManagedUserBooking) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hostId = newBooking.hosts[0]?.id;
|
||||
if (!hostId) {
|
||||
this.logger.error(`Booking with uid=${newBooking.uid} has no host`);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user