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:
alannnc
2023-02-13 12:26:28 +00:00
committed by GitHub
parent d6594dab0f
commit c3e7981917
4 changed files with 104 additions and 58 deletions
+28 -1
View File
@@ -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.