Fix/bookings unintended confirmations on rejection 6975 (#7047)
* Fixed translation message and added paymentStatus to booking page * Fix booking confirmation with payment * Remove previous code unnecesary
This commit is contained in:
@@ -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) {
|
||||
<div className="mt-3">
|
||||
<p className="text-gray-600 dark:text-gray-300">{getTitle()}</p>
|
||||
</div>
|
||||
{props.paymentStatus && (
|
||||
<h4>
|
||||
{!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")}
|
||||
</h4>
|
||||
)}
|
||||
|
||||
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-200 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
|
||||
{(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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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<typeof getServerSideProps>;
|
||||
|
||||
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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user