-
Notifications
You must be signed in to change notification settings - Fork 0
fix: Make BOOKING_CANCELLED` webhook payload consistent for regular cancellation and request-reschedule flow #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: qodo_action_req_1_base_fix_make_booking_cancelled_webhook_payload_consistent_for_regular_cancellation_and_request-reschedule_flow_pr11
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,7 +54,12 @@ import { getBookingToDelete } from "./getBookingToDelete"; | |
| import { handleInternalNote } from "./handleInternalNote"; | ||
| import cancelAttendeeSeat from "./handleSeats/cancel/cancelAttendeeSeat"; | ||
| import type { IBookingCancelService } from "./interfaces/IBookingCancelService"; | ||
| import { buildActorEmail, getUniqueIdentifier, makeGuestActor, makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; | ||
| import { | ||
| buildActorEmail, | ||
| getUniqueIdentifier, | ||
| makeGuestActor, | ||
| makeUserActor, | ||
| } from "@calcom/features/booking-audit/lib/makeActor"; | ||
| import type { Actor } from "@calcom/features/booking-audit/lib/dto/types"; | ||
|
|
||
| const log = logger.getSubLogger({ prefix: ["handleCancelBooking"] }); | ||
|
|
@@ -102,12 +107,17 @@ function getAuditActor({ | |
| }) | ||
| ); | ||
| // Having fallback prefix makes it clear that we created guest actor from fallback logic | ||
| actorEmail = buildActorEmail({ identifier: getUniqueIdentifier({ prefix: "fallback" }), actorType: "guest" }); | ||
| } | ||
| else { | ||
| actorEmail = buildActorEmail({ | ||
| identifier: getUniqueIdentifier({ prefix: "fallback" }), | ||
| actorType: "guest", | ||
| }); | ||
| } else { | ||
| // We can't trust cancelledByEmail and thus can't reuse it as is because it can be set anything by anyone. If we use that as guest actor, we could accidentally attribute the action to the wrong guest actor. | ||
| // Having param prefix makes it clear that we created guest actor from query param and we still don't use the email as is. | ||
| actorEmail = buildActorEmail({ identifier: getUniqueIdentifier({ prefix: "param" }), actorType: "guest" }); | ||
| actorEmail = buildActorEmail({ | ||
| identifier: getUniqueIdentifier({ prefix: "param" }), | ||
| actorType: "guest", | ||
| }); | ||
| } | ||
|
|
||
| return makeGuestActor({ email: actorEmail, name: null }); | ||
|
|
@@ -141,10 +151,13 @@ async function handler(input: CancelBookingInput) { | |
| // Extract action source once for reuse | ||
| const actionSource = input.actionSource ?? "UNKNOWN"; | ||
| if (actionSource === "UNKNOWN") { | ||
| log.warn("Booking cancellation with unknown actionSource", safeStringify({ | ||
| bookingUid: bookingToDelete.uid, | ||
| userUuid, | ||
| })); | ||
| log.warn( | ||
| "Booking cancellation with unknown actionSource", | ||
| safeStringify({ | ||
| bookingUid: bookingToDelete.uid, | ||
| userUuid, | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| const actorToUse = getAuditActor({ | ||
|
|
@@ -358,12 +371,12 @@ async function handler(input: CancelBookingInput) { | |
| cancellationReason: cancellationReason, | ||
| ...(teamMembers && | ||
| teamId && { | ||
| team: { | ||
| name: bookingToDelete?.eventType?.team?.name || "Nameless", | ||
| members: teamMembers, | ||
| id: teamId, | ||
| }, | ||
| }), | ||
| team: { | ||
| name: bookingToDelete?.eventType?.team?.name || "Nameless", | ||
| members: teamMembers, | ||
| id: teamId, | ||
| }, | ||
| }), | ||
| seatsPerTimeSlot: bookingToDelete.eventType?.seatsPerTimeSlot, | ||
| seatsShowAttendees: bookingToDelete.eventType?.seatsShowAttendees, | ||
| iCalUID: bookingToDelete.iCalUID, | ||
|
|
@@ -397,20 +410,24 @@ async function handler(input: CancelBookingInput) { | |
| message: "Attendee successfully removed.", | ||
| } satisfies HandleCancelBookingResponse; | ||
|
|
||
| const promises = webhooks.map((webhook) => | ||
| // Only send webhooks if enabled in environment | ||
| const webhooksEnabled = process.env.ENABLE_WEBHOOKS !== "false"; | ||
|
|
||
| const promises = webhooksEnabled ? webhooks.map((webhook) => | ||
| sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, { | ||
| ...evt, | ||
| ...eventTypeInfo, | ||
| status: "CANCELLED", | ||
| smsReminderNumber: bookingToDelete.smsReminderNumber || undefined, | ||
| cancelledBy: cancelledBy, | ||
| requestReschedule: false, | ||
| }).catch((e) => { | ||
| logger.error( | ||
| `Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}, bookingId: ${evt.bookingId}, bookingUid: ${evt.uid}`, | ||
| safeStringify(e) | ||
| ); | ||
| }) | ||
| ); | ||
| ) : []; | ||
|
Comment on lines
+413
to
+430
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Webhook gate incomplete • ENABLE_WEBHOOKS gating was added only to the main cancellation handler; other cancellation paths still emit webhooks unconditionally. • This creates unreliable operational behavior: setting ENABLE_WEBHOOKS=false will not actually disable all cancellation-related webhooks, leading to partial/accidental emissions. Agent prompt
|
||
| await Promise.all(promises); | ||
|
|
||
| const workflows = await getAllWorkflowsFromEventType(bookingToDelete.eventType, bookingToDelete.userId); | ||
|
|
@@ -714,7 +731,7 @@ type BookingCancelServiceDependencies = { | |
| * Handles both individual booking cancellations and bulk cancellations for recurring events. | ||
| */ | ||
| export class BookingCancelService implements IBookingCancelService { | ||
| constructor(private readonly deps: BookingCancelServiceDependencies) { } | ||
| constructor(private readonly deps: BookingCancelServiceDependencies) {} | ||
|
|
||
| async cancelBooking(input: { bookingData: CancelRegularBookingData; bookingMeta?: CancelBookingMeta }) { | ||
| const cancelBookingInput: CancelBookingInput = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ export class BookingPayloadBuilder extends BaseBookingPayloadBuilder { | |
| extra: { | ||
| cancelledBy: dto.cancelledBy, | ||
| cancellationReason: dto.cancellationReason, | ||
| requestReschedule: dto.requestReschedule ?? true, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Wrong reschedule default • Webhook v2021-10-20 builder defaults requestReschedule to true when omitted, which will mark *all* BOOKING_CANCELLED webhooks as reschedule-related for subscribers on that version. • This contradicts the docs and other cancellation senders in this PR that use/assume false, and can break downstream automations that rely on this flag. Agent prompt
|
||
| }, | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,10 +103,7 @@ export const requestRescheduleHandler = async ({ ctx, input, source }: RequestRe | |
| throw new TRPCError({ code: "FORBIDDEN", message: "User isn't owner of the current booking" }); | ||
| } | ||
|
|
||
| let event: Partial<EventType> = {}; | ||
| if (bookingToReschedule.eventType) { | ||
| event = bookingToReschedule.eventType; | ||
| } | ||
| const event: Partial<EventType> = bookingToReschedule.eventType ?? {}; | ||
| await bookingRepository.updateBookingStatus({ | ||
| bookingId: bookingToReschedule.id, | ||
| status: BookingStatus.CANCELLED, | ||
|
|
@@ -274,6 +271,11 @@ export const requestRescheduleHandler = async ({ ctx, input, source }: RequestRe | |
| smsReminderNumber: bookingToReschedule.smsReminderNumber, | ||
| }), | ||
| cancelledBy: user.email, | ||
| eventTypeId: bookingToReschedule.eventTypeId, | ||
| length: bookingToReschedule.eventType?.length ?? null, | ||
| iCalSequence: (bookingToReschedule.iCalSequence ?? 0) + 1, | ||
| eventTitle: bookingToReschedule.eventType?.title ?? null, | ||
| requestReschedule: true, | ||
|
Comment on lines
+274
to
+278
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4. Icalsequence not persisted • The request-reschedule flow includes an incremented iCalSequence in the webhook payload, but does not persist the increment to the booking record. • This can cause inconsistencies between webhook data and the booking source-of-truth (booking.iCalSequence), and diverges from the standard cancellation flow which persists iCalSequence when cancelling. Agent prompt
|
||
| }); | ||
|
|
||
| // Send webhook | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. process.env in handlecancelbooking
📘 Rule violation⛯ ReliabilityAgent prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools