Remove the changes, just keep the tests
parent
21bb589dc9
commit
1f968cddba
|
@ -1,290 +0,0 @@
|
||||||
import type { Prisma, PrismaClient, Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client";
|
|
||||||
|
|
||||||
import { scheduleTrigger } from "@calcom/app-store/zapier/lib/nodeScheduler";
|
|
||||||
import type { EventManagerUser } from "@calcom/core/EventManager";
|
|
||||||
import EventManager from "@calcom/core/EventManager";
|
|
||||||
import { sendScheduledEmails } from "@calcom/emails";
|
|
||||||
import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
|
||||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
|
||||||
import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
|
|
||||||
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
|
|
||||||
import logger from "@calcom/lib/logger";
|
|
||||||
import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums";
|
|
||||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
||||||
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
|
|
||||||
|
|
||||||
const log = logger.getChildLogger({ prefix: ["[handleConfirmation] book:user"] });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supposed to do whatever is needed when a booking is requested.
|
|
||||||
*/
|
|
||||||
export async function handleBookingRequested(args: {
|
|
||||||
user: EventManagerUser & { username: string | null };
|
|
||||||
evt: CalendarEvent;
|
|
||||||
recurringEventId?: string;
|
|
||||||
prisma: PrismaClient;
|
|
||||||
bookingId: number;
|
|
||||||
booking: {
|
|
||||||
eventType: {
|
|
||||||
currency: string;
|
|
||||||
description: string | null;
|
|
||||||
id: number;
|
|
||||||
length: number;
|
|
||||||
price: number;
|
|
||||||
requiresConfirmation: boolean;
|
|
||||||
title: string;
|
|
||||||
teamId?: number | null;
|
|
||||||
} | null;
|
|
||||||
eventTypeId: number | null;
|
|
||||||
smsReminderNumber: string | null;
|
|
||||||
userId: number | null;
|
|
||||||
};
|
|
||||||
paid?: boolean;
|
|
||||||
}) {
|
|
||||||
const { user, evt, recurringEventId, prisma, bookingId, booking, paid } = args;
|
|
||||||
const eventManager = new EventManager(user);
|
|
||||||
const scheduleResult = await eventManager.create(evt);
|
|
||||||
const results = scheduleResult.results;
|
|
||||||
|
|
||||||
if (results.length > 0 && results.every((res) => !res.success)) {
|
|
||||||
const error = {
|
|
||||||
errorCode: "BookingCreatingMeetingFailed",
|
|
||||||
message: "Booking failed",
|
|
||||||
};
|
|
||||||
|
|
||||||
log.error(`Booking ${user.username} failed`, error, results);
|
|
||||||
} else {
|
|
||||||
const metadata: AdditionalInformation = {};
|
|
||||||
|
|
||||||
if (results.length) {
|
|
||||||
// TODO: Handle created event metadata more elegantly
|
|
||||||
metadata.hangoutLink = results[0].createdEvent?.hangoutLink;
|
|
||||||
metadata.conferenceData = results[0].createdEvent?.conferenceData;
|
|
||||||
metadata.entryPoints = results[0].createdEvent?.entryPoints;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await sendScheduledEmails({ ...evt, additionalInformation: metadata });
|
|
||||||
} catch (error) {
|
|
||||||
log.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let updatedBookings: {
|
|
||||||
scheduledJobs: string[];
|
|
||||||
id: number;
|
|
||||||
description: string | null;
|
|
||||||
location: string | null;
|
|
||||||
attendees: {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
}[];
|
|
||||||
startTime: Date;
|
|
||||||
endTime: Date;
|
|
||||||
uid: string;
|
|
||||||
smsReminderNumber: string | null;
|
|
||||||
metadata: Prisma.JsonValue | null;
|
|
||||||
customInputs: Prisma.JsonValue;
|
|
||||||
eventType: {
|
|
||||||
bookingFields: Prisma.JsonValue | null;
|
|
||||||
slug: string;
|
|
||||||
owner: {
|
|
||||||
hideBranding?: boolean | null;
|
|
||||||
} | null;
|
|
||||||
workflows: (WorkflowsOnEventTypes & {
|
|
||||||
workflow: Workflow & {
|
|
||||||
steps: WorkflowStep[];
|
|
||||||
};
|
|
||||||
})[];
|
|
||||||
} | null;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
if (recurringEventId) {
|
|
||||||
// The booking to confirm is a recurring event and comes from /booking/recurring, proceeding to mark all related
|
|
||||||
// bookings as confirmed. Prisma updateMany does not support relations, so doing this in two steps for now.
|
|
||||||
const unconfirmedRecurringBookings = await prisma.booking.findMany({
|
|
||||||
where: {
|
|
||||||
recurringEventId,
|
|
||||||
status: BookingStatus.PENDING,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateBookingsPromise = unconfirmedRecurringBookings.map((recurringBooking) =>
|
|
||||||
prisma.booking.update({
|
|
||||||
where: {
|
|
||||||
id: recurringBooking.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: BookingStatus.ACCEPTED,
|
|
||||||
references: {
|
|
||||||
create: scheduleResult.referencesToCreate,
|
|
||||||
},
|
|
||||||
paid,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
eventType: {
|
|
||||||
select: {
|
|
||||||
slug: true,
|
|
||||||
bookingFields: true,
|
|
||||||
owner: {
|
|
||||||
select: {
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
workflows: {
|
|
||||||
include: {
|
|
||||||
workflow: {
|
|
||||||
include: {
|
|
||||||
steps: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: true,
|
|
||||||
attendees: true,
|
|
||||||
location: true,
|
|
||||||
uid: true,
|
|
||||||
startTime: true,
|
|
||||||
metadata: true,
|
|
||||||
endTime: true,
|
|
||||||
smsReminderNumber: true,
|
|
||||||
customInputs: true,
|
|
||||||
id: true,
|
|
||||||
scheduledJobs: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedBookingsResult = await Promise.all(updateBookingsPromise);
|
|
||||||
updatedBookings = updatedBookings.concat(updatedBookingsResult);
|
|
||||||
} else {
|
|
||||||
// @NOTE: be careful with this as if any error occurs before this booking doesn't get confirmed
|
|
||||||
// Should perform update on booking (confirm) -> then trigger the rest handlers
|
|
||||||
const updatedBooking = await prisma.booking.update({
|
|
||||||
where: {
|
|
||||||
id: bookingId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: BookingStatus.ACCEPTED,
|
|
||||||
references: {
|
|
||||||
create: scheduleResult.referencesToCreate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
eventType: {
|
|
||||||
select: {
|
|
||||||
slug: true,
|
|
||||||
bookingFields: true,
|
|
||||||
owner: {
|
|
||||||
select: {
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
workflows: {
|
|
||||||
include: {
|
|
||||||
workflow: {
|
|
||||||
include: {
|
|
||||||
steps: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
uid: true,
|
|
||||||
startTime: true,
|
|
||||||
metadata: true,
|
|
||||||
endTime: true,
|
|
||||||
smsReminderNumber: true,
|
|
||||||
description: true,
|
|
||||||
attendees: true,
|
|
||||||
location: true,
|
|
||||||
customInputs: true,
|
|
||||||
id: true,
|
|
||||||
scheduledJobs: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
updatedBookings.push(updatedBooking);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Workflows - set reminders for confirmed events
|
|
||||||
try {
|
|
||||||
for (let index = 0; index < updatedBookings.length; index++) {
|
|
||||||
if (updatedBookings[index].eventType?.workflows) {
|
|
||||||
const evtOfBooking = evt;
|
|
||||||
evtOfBooking.startTime = updatedBookings[index].startTime.toISOString();
|
|
||||||
evtOfBooking.endTime = updatedBookings[index].endTime.toISOString();
|
|
||||||
evtOfBooking.uid = updatedBookings[index].uid;
|
|
||||||
const eventTypeSlug = updatedBookings[index].eventType?.slug || "";
|
|
||||||
|
|
||||||
const isFirstBooking = index === 0;
|
|
||||||
const videoCallUrl =
|
|
||||||
bookingMetadataSchema.parse(updatedBookings[index].metadata || {})?.videoCallUrl || "";
|
|
||||||
|
|
||||||
await scheduleWorkflowReminders({
|
|
||||||
workflows: updatedBookings[index]?.eventType?.workflows || [],
|
|
||||||
smsReminderNumber: updatedBookings[index].smsReminderNumber,
|
|
||||||
calendarEvent: {
|
|
||||||
...evtOfBooking,
|
|
||||||
...{ metadata: { videoCallUrl }, eventType: { slug: eventTypeSlug } },
|
|
||||||
},
|
|
||||||
isFirstRecurringEvent: isFirstBooking,
|
|
||||||
hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Silently fail
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const subscribersBookingCreated = await getWebhooks({
|
|
||||||
userId: booking.userId,
|
|
||||||
eventTypeId: booking.eventTypeId,
|
|
||||||
triggerEvent: WebhookTriggerEvents.BOOKING_CREATED,
|
|
||||||
teamId: booking.eventType?.teamId,
|
|
||||||
});
|
|
||||||
const subscribersMeetingEnded = await getWebhooks({
|
|
||||||
userId: booking.userId,
|
|
||||||
eventTypeId: booking.eventTypeId,
|
|
||||||
triggerEvent: WebhookTriggerEvents.MEETING_ENDED,
|
|
||||||
teamId: booking.eventType?.teamId,
|
|
||||||
});
|
|
||||||
|
|
||||||
subscribersMeetingEnded.forEach((subscriber) => {
|
|
||||||
updatedBookings.forEach((booking) => {
|
|
||||||
scheduleTrigger(booking, subscriber.subscriberUrl, subscriber);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const eventTypeInfo: EventTypeInfo = {
|
|
||||||
eventTitle: booking.eventType?.title,
|
|
||||||
eventDescription: booking.eventType?.description,
|
|
||||||
requiresConfirmation: booking.eventType?.requiresConfirmation || null,
|
|
||||||
price: booking.eventType?.price,
|
|
||||||
currency: booking.eventType?.currency,
|
|
||||||
length: booking.eventType?.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
const promises = subscribersBookingCreated.map((sub) =>
|
|
||||||
sendPayload(sub.secret, WebhookTriggerEvents.BOOKING_CREATED, new Date().toISOString(), sub, {
|
|
||||||
...evt,
|
|
||||||
...eventTypeInfo,
|
|
||||||
bookingId,
|
|
||||||
eventTypeId: booking.eventType?.id,
|
|
||||||
status: "ACCEPTED",
|
|
||||||
smsReminderNumber: booking.smsReminderNumber || undefined,
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error(
|
|
||||||
`Error executing webhook for event: ${WebhookTriggerEvents.BOOKING_CREATED}, URL: ${sub.subscriberUrl}`,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
await Promise.all(promises);
|
|
||||||
} catch (error) {
|
|
||||||
// Silently fail
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,15 +13,13 @@ import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||||
import logger from "@calcom/lib/logger";
|
|
||||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
|
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import { prisma, bookingMinimalSelect } from "@calcom/prisma";
|
import { prisma, bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { BookingStatus } from "@calcom/prisma/enums";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
const log = logger.getChildLogger({ prefix: ["[stripeWebhook]"] });
|
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: false,
|
bodyParser: false,
|
||||||
|
@ -131,7 +129,6 @@ async function getBooking(bookingId: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handlePaymentSuccess(event: Stripe.Event) {
|
async function handlePaymentSuccess(event: Stripe.Event) {
|
||||||
log.debug("Payment successful:", JSON.stringify(event));
|
|
||||||
const paymentIntent = event.data.object as Stripe.PaymentIntent;
|
const paymentIntent = event.data.object as Stripe.PaymentIntent;
|
||||||
const payment = await prisma.payment.findFirst({
|
const payment = await prisma.payment.findFirst({
|
||||||
where: {
|
where: {
|
||||||
|
@ -269,8 +266,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||||
|
|
||||||
await prisma.$transaction([paymentUpdate, bookingUpdate]);
|
await prisma.$transaction([paymentUpdate, bookingUpdate]);
|
||||||
|
|
||||||
if (!isConfirmed) {
|
if (!isConfirmed && !eventTypeRaw?.requiresConfirmation) {
|
||||||
if (!eventTypeRaw?.requiresConfirmation) {
|
|
||||||
await handleConfirmation({
|
await handleConfirmation({
|
||||||
user: userWithCredentials,
|
user: userWithCredentials,
|
||||||
evt,
|
evt,
|
||||||
|
@ -280,18 +276,6 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||||
paid: true,
|
paid: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await handleBookingRequested({
|
|
||||||
user: userWithCredentials,
|
|
||||||
evt,
|
|
||||||
prisma,
|
|
||||||
bookingId: booking.id,
|
|
||||||
booking,
|
|
||||||
paid: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
log.debug("handling confirmation:", JSON.stringify(eventTypeRaw));
|
|
||||||
} else {
|
|
||||||
log.debug("Sending Emails only:", JSON.stringify(eventTypeRaw));
|
|
||||||
await sendScheduledEmails({ ...evt });
|
await sendScheduledEmails({ ...evt });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +398,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
|
|
||||||
const event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET);
|
const event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET);
|
||||||
|
|
||||||
if (!event.account) {
|
// bypassing this validation for e2e tests
|
||||||
|
// in order to successfully confirm the payment
|
||||||
|
if (!event.account && !process.env.NEXT_PUBLIC_IS_E2E) {
|
||||||
throw new HttpCode({ statusCode: 202, message: "Incoming connected account" });
|
throw new HttpCode({ statusCode: 202, message: "Incoming connected account" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue