fix: webhooks for events with seats (#10143)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
pull/10151/head
Carina Wollendorfer 2023-07-13 16:40:16 -04:00 committed by GitHub
parent 4de1de3418
commit 1419c534c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 59 deletions

View File

@ -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 };
} }

View File

@ -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);