diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx
index e58b853aa5..4c0f2cfa45 100644
--- a/apps/web/pages/booking/[uid].tsx
+++ b/apps/web/pages/booking/[uid].tsx
@@ -208,7 +208,7 @@ export default function Success(props: SuccessProps) {
}
const status = props.bookingInfo?.status;
const reschedule = props.bookingInfo.status === BookingStatus.ACCEPTED;
- const cancellationReason = props.bookingInfo.cancellationReason;
+ const cancellationReason = props.bookingInfo.cancellationReason || props.bookingInfo.rejectionReason;
const attendeeName =
typeof props?.bookingInfo?.attendees?.[0]?.name === "string"
@@ -440,6 +440,17 @@ export default function Success(props: SuccessProps) {
+ {props.paymentStatus && (
+
+ {!props.paymentStatus.success &&
+ !props.paymentStatus.refunded &&
+ t("booking_with_payment_cancelled")}
+ {props.paymentStatus.success &&
+ !props.paymentStatus.refunded &&
+ t("booking_with_payment_cancelled_already_paid")}
+ {props.paymentStatus.refunded && t("booking_with_payment_cancelled_refunded")}
+
+ )}
{(isCancelled || reschedule) && cancellationReason && (
@@ -1013,6 +1024,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
status: true,
metadata: true,
cancellationReason: true,
+ rejectionReason: true,
user: {
select: {
id: true,
@@ -1115,6 +1127,20 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
});
}
+ const payment = await prisma.payment.findFirst({
+ where: {
+ bookingId: bookingInfo.id,
+ },
+ select: {
+ success: true,
+ refunded: true,
+ },
+ });
+ const paymentStatus = {
+ success: payment?.success,
+ refunded: payment?.refunded,
+ };
+
return {
props: {
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
@@ -1124,6 +1150,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
trpcState: ssr.dehydrate(),
dynamicEventName: bookingInfo?.eventType?.eventName || "",
bookingInfo,
+ paymentStatus: payment ? paymentStatus : null,
},
};
}
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 2cb00bb0aa..132e1040ba 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1591,5 +1591,9 @@
"book_your_team_members": "Book your team members together with collective events or cycle through to get the right person with round-robin.",
"under_maintenance": "Down for maintenance",
"under_maintenance_description": "The {{appName}} team are performing scheduled maintenance. If you have any questions, please contact support.",
- "event_type_seats": "{{numberOfSeats}} seats"
+ "event_type_seats": "{{numberOfSeats}} seats",
+ "booking_with_payment_cancelled": "Paying for this event is no longer possible",
+ "booking_with_payment_cancelled_already_paid": "A refund for this booking payment it's on the way.",
+ "booking_with_payment_cancelled_refunded": "This booking payment has been refunded.",
+ "booking_confirmation_failed": "Booking confirmation failed"
}
diff --git a/packages/features/ee/payments/pages/payment.tsx b/packages/features/ee/payments/pages/payment.tsx
index e4513f9041..fc2839700d 100644
--- a/packages/features/ee/payments/pages/payment.tsx
+++ b/packages/features/ee/payments/pages/payment.tsx
@@ -8,6 +8,8 @@ import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { ssrInit } from "@server/lib/ssr";
+import { BookingStatus } from ".prisma/client";
+
export type PaymentPageProps = inferSSRProps;
const querySchema = z.object({
@@ -44,6 +46,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
eventTypeId: true,
location: true,
+ status: true,
+ rejectionReason: true,
+ cancellationReason: true,
eventType: {
select: {
id: true,
@@ -104,6 +109,19 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
hideBranding: eventType.team?.hideBranding || user?.hideBranding || null,
};
+ if (
+ ([BookingStatus.CANCELLED, BookingStatus.REJECTED] as BookingStatus[]).includes(
+ booking.status as BookingStatus
+ )
+ ) {
+ return {
+ redirect: {
+ destination: `/booking/${booking.uid}`,
+ permanent: false,
+ },
+ };
+ }
+
return {
props: {
user,
diff --git a/packages/trpc/server/routers/viewer/bookings.tsx b/packages/trpc/server/routers/viewer/bookings.tsx
index a06178f083..c65fca6d8b 100644
--- a/packages/trpc/server/routers/viewer/bookings.tsx
+++ b/packages/trpc/server/routers/viewer/bookings.tsx
@@ -772,12 +772,8 @@ export const bookingsRouter = router({
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
if (isConfirmed) throw new TRPCError({ code: "BAD_REQUEST", message: "Booking already confirmed" });
- /** When a booking that requires payment its being confirmed but doesn't have any payment,
- * we shouldn’t save it on DestinationCalendars
- *
- * FIXME: This can cause unintended confirmations on rejection.
- */
- if (booking.payment.length > 0 && !booking.paid) {
+ // If booking requires payment and is not paid, we don't allow confirmation
+ if (confirmed && booking.payment.length > 0 && !booking.paid) {
await prisma.booking.update({
where: {
id: bookingId,
@@ -789,6 +785,7 @@ export const bookingsRouter = router({
return { message: "Booking confirmed", status: BookingStatus.ACCEPTED };
}
+
const attendeesListPromises = booking.attendees.map(async (attendee) => {
return {
name: attendee.name,
@@ -1083,66 +1080,66 @@ export const bookingsRouter = router({
if (!!booking.payment.length) {
const successPayment = booking.payment.find((payment) => payment.success);
if (!successPayment) {
- throw new Error("Cannot reject a booking without a successful payment");
- }
+ // Disable paymentLink for this booking
+ } else {
+ let eventTypeOwnerId;
+ if (booking.eventType?.owner) {
+ eventTypeOwnerId = booking.eventType.owner.id;
+ } else if (booking.eventType?.teamId) {
+ const teamOwner = await prisma.membership.findFirst({
+ where: {
+ teamId: booking.eventType.teamId,
+ role: MembershipRole.OWNER,
+ },
+ select: {
+ userId: true,
+ },
+ });
+ eventTypeOwnerId = teamOwner?.userId;
+ }
- let eventTypeOwnerId;
- if (booking.eventType?.owner) {
- eventTypeOwnerId = booking.eventType.owner.id;
- } else if (booking.eventType?.teamId) {
- const teamOwner = await prisma.membership.findFirst({
+ if (!eventTypeOwnerId) {
+ throw new Error("Event Type owner not found for obtaining payment app credentials");
+ }
+
+ const paymentAppCredentials = await prisma.credential.findMany({
where: {
- teamId: booking.eventType.teamId,
- role: MembershipRole.OWNER,
+ userId: eventTypeOwnerId,
+ appId: successPayment.appId,
},
select: {
- userId: true,
- },
- });
- eventTypeOwnerId = teamOwner?.userId;
- }
-
- if (!eventTypeOwnerId) {
- throw new Error("Event Type owner not found for obtaining payment app credentials");
- }
-
- const paymentAppCredentials = await prisma.credential.findMany({
- where: {
- userId: eventTypeOwnerId,
- appId: successPayment.appId,
- },
- select: {
- key: true,
- appId: true,
- app: {
- select: {
- categories: true,
- dirName: true,
+ key: true,
+ appId: true,
+ app: {
+ select: {
+ categories: true,
+ dirName: true,
+ },
},
},
- },
- });
+ });
- const paymentAppCredential = paymentAppCredentials.find((credential) => {
- return credential.appId === successPayment.appId;
- });
+ const paymentAppCredential = paymentAppCredentials.find((credential) => {
+ return credential.appId === successPayment.appId;
+ });
- if (!paymentAppCredential) {
- throw new Error("Payment app credentials not found");
- }
+ if (!paymentAppCredential) {
+ throw new Error("Payment app credentials not found");
+ }
- // Posible to refactor TODO:
- const paymentApp = appStore[paymentAppCredential?.app?.dirName as keyof typeof appStore];
- if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) {
- console.warn(`payment App service of type ${paymentApp} is not implemented`);
- return null;
- }
+ // Posible to refactor TODO:
+ const paymentApp = appStore[paymentAppCredential?.app?.dirName as keyof typeof appStore];
+ if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) {
+ console.warn(`payment App service of type ${paymentApp} is not implemented`);
+ return null;
+ }
- const PaymentService = paymentApp.lib.PaymentService;
- const paymentInstance = new PaymentService(paymentAppCredential);
- const paymentData = await paymentInstance.refund(successPayment.id);
- if (!paymentData.refunded) {
- throw new Error("Payment could not be refunded");
+ const PaymentService = paymentApp.lib.PaymentService;
+ const paymentInstance = new PaymentService(paymentAppCredential);
+ const paymentData = await paymentInstance.refund(successPayment.id);
+ if (!paymentData.refunded) {
+ throw new Error("Payment could not be refunded");
+ }
}
}
// end handle refunds.