fix: webhooks for events with seats (#10143)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>pull/10151/head
parent
4de1de3418
commit
1419c534c8
|
@ -1,4 +1,4 @@
|
||||||
import type { Prisma, WebhookTriggerEvents, WorkflowReminder } from "@prisma/client";
|
import type { Prisma, WorkflowReminder } from "@prisma/client";
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import appStore from "@calcom/app-store";
|
import appStore from "@calcom/app-store";
|
||||||
|
@ -23,7 +23,7 @@ import logger from "@calcom/lib/logger";
|
||||||
import { handleRefundError } from "@calcom/lib/payment/handleRefundError";
|
import { handleRefundError } from "@calcom/lib/payment/handleRefundError";
|
||||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { BookingStatus, MembershipRole, WorkflowMethods } from "@calcom/prisma/enums";
|
import { BookingStatus, MembershipRole, WorkflowMethods, WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||||
import { schemaBookingCancelParams } from "@calcom/prisma/zod-utils";
|
import { schemaBookingCancelParams } from "@calcom/prisma/zod-utils";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
|
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
|
||||||
|
@ -126,9 +126,25 @@ async function handler(req: CustomRequest) {
|
||||||
throw new HttpError({ statusCode: 400, message: "User not found" });
|
throw new HttpError({ statusCode: 400, message: "User not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's just an attendee of a booking then just remove them from that booking
|
// get webhooks
|
||||||
const result = await handleSeatedEventCancellation(req);
|
const eventTrigger: WebhookTriggerEvents = "BOOKING_CANCELLED";
|
||||||
if (result) return { success: true };
|
|
||||||
|
const subscriberOptions = {
|
||||||
|
userId: bookingToDelete.userId,
|
||||||
|
eventTypeId: bookingToDelete.eventTypeId as number,
|
||||||
|
triggerEvent: eventTrigger,
|
||||||
|
teamId: bookingToDelete.eventType?.teamId,
|
||||||
|
};
|
||||||
|
const eventTypeInfo: EventTypeInfo = {
|
||||||
|
eventTitle: bookingToDelete?.eventType?.title || null,
|
||||||
|
eventDescription: bookingToDelete?.eventType?.description || null,
|
||||||
|
requiresConfirmation: bookingToDelete?.eventType?.requiresConfirmation || null,
|
||||||
|
price: bookingToDelete?.eventType?.price || null,
|
||||||
|
currency: bookingToDelete?.eventType?.currency || null,
|
||||||
|
length: bookingToDelete?.eventType?.length || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const webhooks = await getWebhooks(subscriberOptions);
|
||||||
|
|
||||||
const organizer = await prisma.user.findFirstOrThrow({
|
const organizer = await prisma.user.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
|
@ -207,6 +223,12 @@ async function handler(req: CustomRequest) {
|
||||||
seatsShowAttendees: bookingToDelete.eventType?.seatsShowAttendees,
|
seatsShowAttendees: bookingToDelete.eventType?.seatsShowAttendees,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dataForWebhooks = { evt, webhooks, eventTypeInfo };
|
||||||
|
|
||||||
|
// If it's just an attendee of a booking then just remove them from that booking
|
||||||
|
const result = await handleSeatedEventCancellation(req, dataForWebhooks);
|
||||||
|
if (result) return { success: true };
|
||||||
|
|
||||||
// If it's just an attendee of a booking then just remove them from that booking
|
// If it's just an attendee of a booking then just remove them from that booking
|
||||||
if (seatReferenceUid && bookingToDelete.attendees.length > 1) {
|
if (seatReferenceUid && bookingToDelete.attendees.length > 1) {
|
||||||
const seatReference = bookingToDelete.seatsReferences.find(
|
const seatReference = bookingToDelete.seatsReferences.find(
|
||||||
|
@ -308,26 +330,6 @@ async function handler(req: CustomRequest) {
|
||||||
return { message: "No longer attending event" };
|
return { message: "No longer attending event" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook up the webhook logic here
|
|
||||||
const eventTrigger: WebhookTriggerEvents = "BOOKING_CANCELLED";
|
|
||||||
// Send Webhook call if hooked to BOOKING.CANCELLED
|
|
||||||
const subscriberOptions = {
|
|
||||||
userId: bookingToDelete.userId,
|
|
||||||
eventTypeId: bookingToDelete.eventTypeId as number,
|
|
||||||
triggerEvent: eventTrigger,
|
|
||||||
teamId: bookingToDelete.eventType?.teamId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventTypeInfo: EventTypeInfo = {
|
|
||||||
eventTitle: bookingToDelete?.eventType?.title || null,
|
|
||||||
eventDescription: bookingToDelete?.eventType?.description || null,
|
|
||||||
requiresConfirmation: bookingToDelete?.eventType?.requiresConfirmation || null,
|
|
||||||
price: bookingToDelete?.eventType?.price || null,
|
|
||||||
currency: bookingToDelete?.eventType?.currency || null,
|
|
||||||
length: bookingToDelete?.eventType?.length || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const webhooks = await getWebhooks(subscriberOptions);
|
|
||||||
const promises = webhooks.map((webhook) =>
|
const promises = webhooks.map((webhook) =>
|
||||||
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
||||||
...evt,
|
...evt,
|
||||||
|
@ -677,12 +679,31 @@ async function handler(req: CustomRequest) {
|
||||||
return { message: "Booking successfully cancelled." };
|
return { message: "Booking successfully cancelled." };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSeatedEventCancellation(req: CustomRequest) {
|
async function handleSeatedEventCancellation(
|
||||||
|
req: CustomRequest,
|
||||||
|
dataForWebhooks: {
|
||||||
|
webhooks: {
|
||||||
|
id: string;
|
||||||
|
subscriberUrl: string;
|
||||||
|
payloadTemplate: string | null;
|
||||||
|
appId: string | null;
|
||||||
|
secret: string | null;
|
||||||
|
}[];
|
||||||
|
evt: CalendarEvent;
|
||||||
|
eventTypeInfo: EventTypeInfo;
|
||||||
|
}
|
||||||
|
) {
|
||||||
const { seatReferenceUid } = schemaBookingCancelParams.parse(req.body);
|
const { seatReferenceUid } = schemaBookingCancelParams.parse(req.body);
|
||||||
|
const { webhooks, evt, eventTypeInfo } = dataForWebhooks;
|
||||||
if (!seatReferenceUid) return;
|
if (!seatReferenceUid) return;
|
||||||
if (!req.bookingToDelete?.attendees.length || req.bookingToDelete.attendees.length < 2) return;
|
const bookingToDelete = req.bookingToDelete;
|
||||||
|
if (!bookingToDelete?.attendees.length || bookingToDelete.attendees.length < 2) return;
|
||||||
|
|
||||||
const seatReference = req.bookingToDelete.seatsReferences.find(
|
if (!bookingToDelete.userId) {
|
||||||
|
throw new HttpError({ statusCode: 400, message: "User not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const seatReference = bookingToDelete.seatsReferences.find(
|
||||||
(reference) => reference.referenceUid === seatReferenceUid
|
(reference) => reference.referenceUid === seatReferenceUid
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -701,6 +722,36 @@ async function handleSeatedEventCancellation(req: CustomRequest) {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
req.statusCode = 200;
|
req.statusCode = 200;
|
||||||
|
|
||||||
|
const attendee = bookingToDelete?.attendees.find((attendee) => attendee.id === seatReference.attendeeId);
|
||||||
|
|
||||||
|
evt.attendees = attendee
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
...attendee,
|
||||||
|
language: {
|
||||||
|
translate: await getTranslation(attendee.locale ?? "en", "common"),
|
||||||
|
locale: attendee.locale ?? "en",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const promises = webhooks.map((webhook) =>
|
||||||
|
sendPayload(webhook.secret, WebhookTriggerEvents.BOOKING_CANCELLED, new Date().toISOString(), webhook, {
|
||||||
|
...evt,
|
||||||
|
...eventTypeInfo,
|
||||||
|
status: "CANCELLED",
|
||||||
|
smsReminderNumber: bookingToDelete.smsReminderNumber || undefined,
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(
|
||||||
|
`Error executing webhook for event: ${WebhookTriggerEvents.BOOKING_CANCELLED}, URL: ${webhook.subscriberUrl}`,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1135,6 +1135,40 @@ async function handler(
|
||||||
return deletedReferences;
|
return deletedReferences;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// data needed for triggering webhooks
|
||||||
|
const eventTypeInfo: EventTypeInfo = {
|
||||||
|
eventTitle: eventType.title,
|
||||||
|
eventDescription: eventType.description,
|
||||||
|
requiresConfirmation: requiresConfirmation || null,
|
||||||
|
price: paymentAppData.price,
|
||||||
|
currency: eventType.currency,
|
||||||
|
length: eventType.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
const teamId = await getTeamId({ eventType });
|
||||||
|
|
||||||
|
const subscriberOptions: GetSubscriberOptions = {
|
||||||
|
userId: organizerUser.id,
|
||||||
|
eventTypeId,
|
||||||
|
triggerEvent: WebhookTriggerEvents.BOOKING_CREATED,
|
||||||
|
teamId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventTrigger: WebhookTriggerEvents = rescheduleUid
|
||||||
|
? WebhookTriggerEvents.BOOKING_RESCHEDULED
|
||||||
|
: WebhookTriggerEvents.BOOKING_CREATED;
|
||||||
|
|
||||||
|
subscriberOptions.triggerEvent = eventTrigger;
|
||||||
|
|
||||||
|
const subscriberOptionsMeetingEnded = {
|
||||||
|
userId: organizerUser.id,
|
||||||
|
eventTypeId,
|
||||||
|
triggerEvent: WebhookTriggerEvents.MEETING_ENDED,
|
||||||
|
teamId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
|
||||||
|
|
||||||
const handleSeats = async () => {
|
const handleSeats = async () => {
|
||||||
let resultBooking:
|
let resultBooking:
|
||||||
| (Partial<Booking> & {
|
| (Partial<Booking> & {
|
||||||
|
@ -1167,6 +1201,9 @@ async function handler(
|
||||||
startTime: true,
|
startTime: true,
|
||||||
user: true,
|
user: true,
|
||||||
status: true,
|
status: true,
|
||||||
|
smsReminderNumber: true,
|
||||||
|
endTime: true,
|
||||||
|
scheduledJobs: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1701,6 +1738,25 @@ async function handler(
|
||||||
log.error("Error while scheduling workflow reminders", error);
|
log.error("Error while scheduling workflow reminders", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const webhookData = {
|
||||||
|
...evt,
|
||||||
|
...eventTypeInfo,
|
||||||
|
bookingId: booking?.id,
|
||||||
|
rescheduleUid,
|
||||||
|
rescheduleStartTime: originalRescheduledBooking?.startTime
|
||||||
|
? dayjs(originalRescheduledBooking?.startTime).utc().format()
|
||||||
|
: undefined,
|
||||||
|
rescheduleEndTime: originalRescheduledBooking?.endTime
|
||||||
|
? dayjs(originalRescheduledBooking?.endTime).utc().format()
|
||||||
|
: undefined,
|
||||||
|
metadata: { ...metadata, ...reqBody.metadata },
|
||||||
|
eventTypeId,
|
||||||
|
status: "ACCEPTED",
|
||||||
|
smsReminderNumber: booking?.smsReminderNumber || undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
await handleWebhookTrigger({ subscriberOptions, eventTrigger, webhookData });
|
||||||
|
|
||||||
return resultBooking;
|
return resultBooking;
|
||||||
};
|
};
|
||||||
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
|
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
|
||||||
|
@ -2231,14 +2287,6 @@ async function handler(
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const eventTypeInfo: EventTypeInfo = {
|
|
||||||
eventTitle: eventType.title,
|
|
||||||
eventDescription: eventType.description,
|
|
||||||
requiresConfirmation: requiresConfirmation || null,
|
|
||||||
price: paymentAppData.price,
|
|
||||||
currency: eventType.currency,
|
|
||||||
length: eventType.length,
|
|
||||||
};
|
|
||||||
const webhookData = {
|
const webhookData = {
|
||||||
...evt,
|
...evt,
|
||||||
...eventTypeInfo,
|
...eventTypeInfo,
|
||||||
|
@ -2255,30 +2303,7 @@ async function handler(
|
||||||
status: "ACCEPTED",
|
status: "ACCEPTED",
|
||||||
smsReminderNumber: booking?.smsReminderNumber || undefined,
|
smsReminderNumber: booking?.smsReminderNumber || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const teamId = await getTeamId({ eventType });
|
|
||||||
|
|
||||||
const subscriberOptions: GetSubscriberOptions = {
|
|
||||||
userId: organizerUser.id,
|
|
||||||
eventTypeId,
|
|
||||||
triggerEvent: WebhookTriggerEvents.BOOKING_CREATED,
|
|
||||||
teamId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isConfirmedByDefault) {
|
if (isConfirmedByDefault) {
|
||||||
const eventTrigger: WebhookTriggerEvents = rescheduleUid
|
|
||||||
? WebhookTriggerEvents.BOOKING_RESCHEDULED
|
|
||||||
: WebhookTriggerEvents.BOOKING_CREATED;
|
|
||||||
|
|
||||||
subscriberOptions.triggerEvent = eventTrigger;
|
|
||||||
|
|
||||||
const subscriberOptionsMeetingEnded = {
|
|
||||||
userId: organizerUser.id,
|
|
||||||
eventTypeId,
|
|
||||||
triggerEvent: WebhookTriggerEvents.MEETING_ENDED,
|
|
||||||
teamId,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
|
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue