From 4585d7402d70a371f6d417fe22cbd4a7291bc7bb Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:30:54 +0530 Subject: [PATCH] fix: BOOKING_PAID webhook & workflow isn't triggering after payment successful (#26044) * fix booking paid webhook * Update handlePaymentSuccess to include updatedBooking status * Fix videoCallUrl undefined error in BOOKING_PAID workflow trigger - Import getVideoCallUrlFromCalEvent to properly compute meeting URL - Use conditional metadata to avoid passing undefined videoCallUrl - Matches original handleConfirmation.ts behavior Co-Authored-By: anik@cal.com * Add missing schedulingType to calendarEventForWorkflow - Added schedulingType field to match original handleConfirmation.ts behavior - This field was present in the original implementation but missing in the refactor Co-Authored-By: anik@cal.com * Fix paymentMetadata nullability to match original behavior - Changed from ?? null to optional chaining (undefined) to match original handleConfirmation.ts - This ensures webhook consumers using z.string().optional() schemas continue to work - undefined values are omitted from JSON, while null values would be explicitly included Co-Authored-By: anik@cal.com * Fix type errors: add schedulingType to getBooking and use null for metadata - Added schedulingType to getBooking.ts eventType select to make it available - Changed paymentMetadata to use ?? null instead of undefined for type compatibility - The metadata type expects { [key: string]: string | number | boolean | null } Co-Authored-By: anik@cal.com * await * add tests --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../payments/handlePaymentSuccess.test.ts | 362 ++++++++++++++++++ .../_utils/payments/handlePaymentSuccess.ts | 139 ++++++- .../bookings/lib/handleConfirmation.ts | 97 ----- .../bookings/lib/payment/getBooking.ts | 1 + 4 files changed, 499 insertions(+), 100 deletions(-) create mode 100644 packages/app-store/_utils/payments/handlePaymentSuccess.test.ts diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.test.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.test.ts new file mode 100644 index 0000000000..6ca01b54f7 --- /dev/null +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.test.ts @@ -0,0 +1,362 @@ +/** + * @vitest-environment node + */ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { + BookingStatus, + WebhookTriggerEvents, + WorkflowTriggerEvents, +} from "@calcom/prisma/enums"; +import { handlePaymentSuccess } from "./handlePaymentSuccess"; + +// Mock dependencies +vi.mock("@calcom/features/bookings/lib/payment/getBooking"); +vi.mock("@calcom/features/webhooks/lib/getWebhooks"); +vi.mock("@calcom/features/webhooks/lib/sendOrSchedulePayload"); +vi.mock("@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType"); +vi.mock("@calcom/features/ee/workflows/lib/service/WorkflowService"); +vi.mock("@calcom/features/tasker"); +vi.mock( + "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials", + () => ({ + getAllCredentialsIncludeServiceAccountKey: vi.fn().mockResolvedValue([]), + }) +); +vi.mock("@calcom/features/users/repositories/UserRepository", () => ({ + UserRepository: class { + constructor() {} + enrichUserWithItsProfile = vi.fn().mockResolvedValue({ profile: null }); + }, +})); +vi.mock("@calcom/prisma", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + default: { + ...actual.default, + payment: { update: vi.fn() }, + booking: { update: vi.fn() }, + $transaction: vi.fn(), + profile: { findMany: vi.fn().mockResolvedValue([]) }, + credential: { findMany: vi.fn().mockResolvedValue([]) }, + team: { + findFirst: vi.fn().mockResolvedValue(null), + findUnique: vi.fn().mockResolvedValue(null), + }, + }, + }; +}); +vi.mock("@calcom/lib/getOrgIdFromMemberOrTeamId"); +vi.mock("@calcom/features/ee/organizations/lib/getBookerUrlServer"); +vi.mock("@calcom/lib/getTeamIdFromEventType"); +vi.mock("@calcom/lib/CalEventParser"); +vi.mock("@calcom/features/ee/billing/credit-service"); +vi.mock( + "@calcom/features/platform-oauth-client/platform-oauth-client.repository", + () => ({ + PlatformOAuthClientRepository: class { + constructor() {} + getByUserId = vi.fn().mockResolvedValue(null); + }, + }) +); +vi.mock("@calcom/features/platform-oauth-client/get-platform-params"); +vi.mock("@calcom/features/bookings/lib/handleConfirmation"); +vi.mock("@calcom/features/bookings/lib/handleBookingRequested"); +vi.mock("@calcom/emails/email-manager"); +vi.mock("@calcom/lib/tracing/factory"); +vi.mock("@calcom/lib/logger", () => ({ + default: { + getSubLogger: vi.fn(() => ({ + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + })), + }, +})); + +import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; +import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; +import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; +import { getAllWorkflowsFromEventType } from "@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType"; +import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; +import prisma from "@calcom/prisma"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; +import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; +import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; +import { CreditService } from "@calcom/features/ee/billing/credit-service"; +import type { TraceContext } from "@calcom/lib/tracing"; +import { WebhookVersion } from "@calcom/features/webhooks/lib/interface/IWebhookRepository"; + +describe("handlePaymentSuccess", () => { + const mockBookingId = 1; + const mockPaymentId = 100; + const mockTraceContext: TraceContext = { + traceId: "test-trace-id", + spanId: "test-span-id", + operation: "test-operation", + }; + + const mockBooking = { + id: mockBookingId, + userId: 1, + eventTypeId: 10, + status: BookingStatus.PENDING, + uid: "test-booking-uid", + location: "Test Location", + smsReminderNumber: null, + userPrimaryEmail: "user@example.com", + title: "Test Event", + description: "Test Description", + customInputs: {}, + startTime: new Date("2025-01-01T10:00:00Z"), + endTime: new Date("2025-01-01T10:30:00Z"), + attendees: [], + responses: {}, + metadata: null, + destinationCalendar: null, + eventType: { + id: 10, + title: "Test Event", + description: "Test Description", + slug: "test-event", + price: 1000, + currency: "USD", + length: 30, + requiresConfirmation: false, + teamId: null, + parentId: null, + schedulingType: null, + hosts: [], + owner: { hideBranding: false }, + metadata: {}, + }, + } as any; + + const mockUser = { + id: 1, + email: "user@example.com", + name: "Test User", + username: "testuser", + timeZone: "UTC", + isPlatformManaged: false, + credentials: [], + locale: "en", + timeFormat: 12, + destinationCalendar: null, + } as any; + + const mockEvt = { + type: "test-event", + title: "Test Event", + startTime: "2025-01-01T10:00:00Z", + endTime: "2025-01-01T10:30:00Z", + organizer: { + email: "user@example.com", + name: "Test User", + timeZone: "UTC", + language: { locale: "en", translate: vi.fn() }, + id: 1, + }, + attendees: [ + { + email: "attendee@example.com", + name: "Attendee", + timeZone: "UTC", + language: { locale: "en", translate: vi.fn() }, + }, + ], + uid: "test-booking-uid", + bookingId: mockBookingId, + } as any; + + const mockEventType = { + id: 10, + metadata: {}, + }; + + const mockWebhookSubscriber = { + id: "webhook-1", + subscriberUrl: "https://example.com/webhook", + secret: "webhook-secret", + appId: "app-id", + payloadTemplate: null, + eventTriggers: [WebhookTriggerEvents.BOOKING_PAID], + timeUnit: null, + time: null, + version: WebhookVersion.V_2021_10_20, + }; + + const mockWorkflow = { + id: 1, + name: "Test Workflow", + trigger: WorkflowTriggerEvents.BOOKING_PAID, + time: 24, + timeUnit: "HOUR" as const, + userId: 1, + teamId: null, + steps: [], + } as any; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock getBooking + vi.mocked(getBooking).mockResolvedValue({ + booking: mockBooking, + user: mockUser, + evt: mockEvt, + eventType: mockEventType, + }); + + // Mock prisma transaction + vi.mocked(prisma.$transaction).mockResolvedValue([ + { id: mockPaymentId, externalId: "ext-123" }, + { status: BookingStatus.ACCEPTED }, + ]); + + // Mock webhook functions + vi.mocked(getWebhooks).mockResolvedValue([mockWebhookSubscriber]); + vi.mocked(sendPayload).mockResolvedValue({ ok: true, status: 200 }); + + // Mock workflow functions + vi.mocked(getAllWorkflowsFromEventType).mockResolvedValue([mockWorkflow]); + vi.mocked( + WorkflowService.scheduleWorkflowsFilteredByTriggerEvent + ).mockResolvedValue(undefined); + + // Mock utility functions + vi.mocked(getOrgIdFromMemberOrTeamId).mockResolvedValue(undefined); + vi.mocked(getBookerBaseUrl).mockResolvedValue("https://cal.com"); + vi.mocked(getTeamIdFromEventType).mockResolvedValue(null); + vi.mocked(getVideoCallUrlFromCalEvent).mockReturnValue(""); + vi.mocked(CreditService.prototype.hasAvailableCredits).mockResolvedValue( + true + ); + }); + + it("should trigger BOOKING_PAID webhooks with correct payload", async () => { + await expect( + handlePaymentSuccess(mockPaymentId, mockBookingId, mockTraceContext) + ).rejects.toThrow(); // Function throws HttpCode 200 at the end + + // Verify webhooks were fetched + expect(getWebhooks).toHaveBeenCalledWith({ + userId: mockBooking.userId, + eventTypeId: mockBooking.eventTypeId, + triggerEvent: WebhookTriggerEvents.BOOKING_PAID, + teamId: null, + orgId: undefined, + oAuthClientId: undefined, + }); + + // Verify webhook payload was sent + expect(sendPayload).toHaveBeenCalledWith( + mockWebhookSubscriber.secret, + WebhookTriggerEvents.BOOKING_PAID, + expect.any(String), + mockWebhookSubscriber, + expect.objectContaining({ + bookingId: mockBookingId, + eventTypeId: mockBooking.eventType.id, + status: BookingStatus.ACCEPTED, + paymentId: mockPaymentId, + metadata: expect.objectContaining({ + identifier: "cal.com", + bookingId: mockBookingId, + eventTypeId: mockBooking.eventType.id, + bookerEmail: mockEvt.attendees[0].email, + eventTitle: mockBooking.eventType.title, + externalId: "ext-123", + }), + }) + ); + }); + + it("should trigger BOOKING_PAID workflows with correct calendar event", async () => { + await expect( + handlePaymentSuccess(mockPaymentId, mockBookingId, mockTraceContext) + ).rejects.toThrow(); // Function throws HttpCode 200 at the end + + // Verify workflows were fetched + expect(getAllWorkflowsFromEventType).toHaveBeenCalledWith( + mockBooking.eventType, + mockBooking.userId + ); + + // Verify workflows were scheduled + expect( + WorkflowService.scheduleWorkflowsFilteredByTriggerEvent + ).toHaveBeenCalledWith({ + workflows: [mockWorkflow], + smsReminderNumber: null, + calendarEvent: expect.objectContaining({ + type: mockEvt.type, + title: mockEvt.title, + startTime: mockEvt.startTime, + endTime: mockEvt.endTime, + eventType: expect.objectContaining({ + slug: mockBooking.eventType.slug, + }), + bookerUrl: "https://cal.com", + }), + hideBranding: false, + triggers: [WorkflowTriggerEvents.BOOKING_PAID], + creditCheckFn: expect.any(Function), + }); + }); + + it("should handle webhook errors gracefully without blocking", async () => { + const webhookError = new Error("Webhook failed"); + vi.mocked(sendPayload).mockRejectedValueOnce(webhookError); + + // Should not throw (webhook errors are caught) + await expect( + handlePaymentSuccess(mockPaymentId, mockBookingId, mockTraceContext) + ).rejects.toThrow(); // Throws HttpCode 200 at the end + + // Verify webhook was still attempted + expect(sendPayload).toHaveBeenCalled(); + }); + + it("should handle workflow errors gracefully without blocking", async () => { + const workflowError = new Error("Workflow failed"); + vi.mocked( + WorkflowService.scheduleWorkflowsFilteredByTriggerEvent + ).mockRejectedValueOnce(workflowError); + + // Should not throw (workflow errors are caught) + await expect( + handlePaymentSuccess(mockPaymentId, mockBookingId, mockTraceContext) + ).rejects.toThrow(); // Throws HttpCode 200 at the end + + // Verify workflow was still attempted + expect( + WorkflowService.scheduleWorkflowsFilteredByTriggerEvent + ).toHaveBeenCalled(); + }); + + it("should include payment metadata in webhook payload", async () => { + await expect( + handlePaymentSuccess(mockPaymentId, mockBookingId, mockTraceContext) + ).rejects.toThrow(); // Function throws HttpCode 200 at the end + + expect(sendPayload).toHaveBeenCalledWith( + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(Object), + expect.objectContaining({ + metadata: expect.objectContaining({ + identifier: "cal.com", + bookingId: mockBookingId, + eventTypeId: mockBooking.eventType.id, + bookerEmail: mockEvt.attendees[0].email, + eventTitle: mockBooking.eventType.title, + externalId: "ext-123", + }), + }) + ); + }); +}); diff --git a/packages/app-store/_utils/payments/handlePaymentSuccess.ts b/packages/app-store/_utils/payments/handlePaymentSuccess.ts index 024056bb54..cc8f4d7eed 100644 --- a/packages/app-store/_utils/payments/handlePaymentSuccess.ts +++ b/packages/app-store/_utils/payments/handlePaymentSuccess.ts @@ -6,16 +6,27 @@ import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/book import { handleBookingRequested } from "@calcom/features/bookings/lib/handleBookingRequested"; import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation"; import { getBooking } from "@calcom/features/bookings/lib/payment/getBooking"; +import { CreditService } from "@calcom/features/ee/billing/credit-service"; +import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer"; +import { getAllWorkflowsFromEventType } from "@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType"; +import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; +import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; +import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; +import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; +import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; +import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import tasker from "@calcom/features/tasker"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; import type { TraceContext } from "@calcom/lib/tracing"; import { distributedTracing } from "@calcom/lib/tracing/factory"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; -import { BookingStatus } from "@calcom/prisma/enums"; +import { BookingStatus, WebhookTriggerEvents, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; const log = logger.getSubLogger({ prefix: ["[handlePaymentSuccess]"] }); @@ -84,6 +95,10 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number, data: { success: true, }, + select: { + id: true, + externalId: true, + }, }); const bookingUpdate = prisma.booking.update({ @@ -91,9 +106,127 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number, id: booking.id, }, data: bookingData, + select: { + status: true, + }, }); - await prisma.$transaction([paymentUpdate, bookingUpdate]); + const [payment, updatedBooking] = await prisma.$transaction([paymentUpdate, bookingUpdate]); + + const platformClientParams = platformOAuthClient ? getPlatformParams(platformOAuthClient) : undefined; + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: booking.eventType?.teamId ?? null }, + parentId: booking.eventType?.parentId ?? null, + }, + }); + const triggerForUser = !teamId || (teamId && booking.eventType?.parentId); + const userId = triggerForUser ? booking.userId : null; + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + const bookerUrl = await getBookerBaseUrl(orgId ?? null); + + try { + // Get workflows for BOOKING_PAID trigger + const workflows = await getAllWorkflowsFromEventType(booking.eventType, booking.userId); + + const paymentExternalId = payment.externalId; + + const paymentMetadata = { + identifier: "cal.com", + bookingId, + eventTypeId: booking.eventType?.id ?? null, + bookerEmail: evt.attendees[0].email, + eventTitle: booking.eventType?.title ?? null, + externalId: paymentExternalId ?? null, + }; + + 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 payload: EventPayloadType = { + ...evt, + ...eventTypeInfo, + bookingId, + eventTypeId: booking.eventType?.id, + status: updatedBooking.status, + smsReminderNumber: booking.smsReminderNumber || undefined, + paymentId: paymentId, + metadata: paymentMetadata, + ...(platformClientParams ? platformClientParams : {}), + }; + + // Trigger BOOKING_PAID webhooks + const subscriberMeetingPaid = await getWebhooks({ + userId, + eventTypeId: booking.eventTypeId, + triggerEvent: WebhookTriggerEvents.BOOKING_PAID, + teamId: booking.eventType?.teamId, + orgId, + oAuthClientId: platformClientParams?.platformClientId, + }); + + const tracingLogger = distributedTracing.getTracingLogger(updatedTraceContext); + const bookingPaidSubscribers = subscriberMeetingPaid.map((sub) => + sendPayload( + sub.secret, + WebhookTriggerEvents.BOOKING_PAID, + new Date().toISOString(), + sub, + payload + ).catch((e) => { + tracingLogger.error( + `Error executing webhook for event: ${WebhookTriggerEvents.BOOKING_PAID}, URL: ${sub.subscriberUrl}, bookingId: ${evt.bookingId}, bookingUid: ${evt.uid}`, + safeStringify(e) + ); + }) + ); + + // Wait for webhook invocations to finish before returning + await Promise.all(bookingPaidSubscribers); + + // Trigger BOOKING_PAID workflows + try { + const meetingUrl = getVideoCallUrlFromCalEvent(evt); + const calendarEventForWorkflow = { + ...evt, + eventType: { + slug: booking.eventType?.slug || "", + schedulingType: booking.eventType?.schedulingType, + hosts: + booking.eventType?.hosts?.map((host) => ({ + user: { + email: host.user.email, + destinationCalendar: host.user.destinationCalendar, + }, + })) || [], + }, + bookerUrl: bookerUrl, + metadata: meetingUrl ? { videoCallUrl: meetingUrl } : undefined, + }; + + const creditService = new CreditService(); + + await WorkflowService.scheduleWorkflowsFilteredByTriggerEvent({ + workflows, + smsReminderNumber: booking.smsReminderNumber, + calendarEvent: calendarEventForWorkflow, + hideBranding: !!booking.eventType?.owner?.hideBranding, + triggers: [WorkflowTriggerEvents.BOOKING_PAID], + creditCheckFn: creditService.hasAvailableCredits.bind(creditService), + }); + } catch (error) { + log.error("Error while scheduling workflow reminders for booking paid", safeStringify(error)); + } + } catch (error) { + log.error("Error while triggering BOOKING_PAID webhook", safeStringify(error)); + } + if (!isConfirmed) { if (!requiresConfirmation) { await handleConfirmation({ @@ -103,7 +236,7 @@ export async function handlePaymentSuccess(paymentId: number, bookingId: number, bookingId: booking.id, booking, paid: true, - platformClientParams: platformOAuthClient ? getPlatformParams(platformOAuthClient) : undefined, + platformClientParams, traceContext: updatedTraceContext, }); } else { diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index e58bf66961..548b6f28f0 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -504,103 +504,6 @@ export async function handleConfirmation(args: { ); await Promise.all(promises); - - if (paid) { - let paymentExternalId: string | undefined; - const subscriberMeetingPaid = await getWebhooks({ - userId, - eventTypeId: booking.eventTypeId, - triggerEvent: WebhookTriggerEvents.BOOKING_PAID, - teamId: eventType?.teamId, - orgId, - oAuthClientId: platformClientParams?.platformClientId, - }); - const bookingWithPayment = await prisma.booking.findUnique({ - where: { - id: bookingId, - }, - select: { - payment: { - select: { - id: true, - success: true, - externalId: true, - }, - }, - }, - }); - const successPayment = bookingWithPayment?.payment?.find((item) => item.success); - if (successPayment) { - paymentExternalId = successPayment.externalId; - } - - const paymentMetadata = { - identifier: "cal.com", - bookingId, - eventTypeId: eventType?.id, - bookerEmail: evt.attendees[0].email, - eventTitle: eventType?.title, - externalId: paymentExternalId, - }; - - payload.paymentId = bookingWithPayment?.payment?.[0].id; - payload.metadata = { - ...(paid ? paymentMetadata : {}), - }; - - const bookingPaidSubscribers = subscriberMeetingPaid.map((sub) => - sendPayload( - sub.secret, - WebhookTriggerEvents.BOOKING_PAID, - new Date().toISOString(), - sub, - payload - ).catch((e) => { - tracingLogger.error( - `Error executing webhook for event: ${WebhookTriggerEvents.BOOKING_PAID}, URL: ${sub.subscriberUrl}, bookingId: ${evt.bookingId}, bookingUid: ${evt.uid}`, - safeStringify(e) - ); - }) - ); - - // I don't need to await for this - Promise.all(bookingPaidSubscribers); - - try { - const calendarEventForWorkflow = { - ...evt, - eventType: { - slug: updatedBookings[0].eventType?.slug || "", - schedulingType: updatedBookings[0].eventType?.schedulingType, - hosts: - updatedBookings[0].eventType?.hosts?.map((host) => ({ - user: { - email: host.user.email, - destinationCalendar: host.user.destinationCalendar, - }, - })) || [], - }, - bookerUrl: bookerUrl, - metadata: { videoCallUrl: meetingUrl }, - }; - - const creditService = new CreditService(); - - await WorkflowService.scheduleWorkflowsFilteredByTriggerEvent({ - workflows, - smsReminderNumber: booking.smsReminderNumber, - calendarEvent: calendarEventForWorkflow, - hideBranding: !!updatedBookings[0].eventType?.owner?.hideBranding, - triggers: [WorkflowTriggerEvents.BOOKING_PAID], - creditCheckFn: creditService.hasAvailableCredits.bind(creditService), - }); - } catch (error) { - tracingLogger.error( - "Error while scheduling workflow reminders for booking paid", - safeStringify(error) - ); - } - } } catch (error) { // Silently fail console.error(error); diff --git a/packages/features/bookings/lib/payment/getBooking.ts b/packages/features/bookings/lib/payment/getBooking.ts index b1917a3469..25d5a035b6 100644 --- a/packages/features/bookings/lib/payment/getBooking.ts +++ b/packages/features/bookings/lib/payment/getBooking.ts @@ -72,6 +72,7 @@ export async function getBooking(bookingId: number) { }, }, slug: true, + schedulingType: true, workflows: { select: { workflow: {