feat(SW-2116): Use refId instead of confirmationNumber
This commit is contained in:
@@ -52,7 +52,7 @@ export default function Header({
|
||||
url: hotel.contactInformation.websiteUrl,
|
||||
}
|
||||
|
||||
const bookingUrlPath = `${myStay[lang]}?RefId=${refId}`
|
||||
const bookingUrlPath = `${myStay[lang]}?RefId=${encodeURIComponent(refId)}`
|
||||
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
|
||||
@@ -20,13 +20,13 @@ import { CurrencyEnum } from "@/types/enums/currency"
|
||||
export function LinkedReservation({
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
confirmationNumber,
|
||||
refId,
|
||||
roomIndex,
|
||||
roomNumber,
|
||||
}: LinkedReservationProps) {
|
||||
const lang = useLang()
|
||||
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
lang,
|
||||
})
|
||||
const {
|
||||
|
||||
@@ -8,12 +8,12 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { BookingStatusEnum } from "@/constants/booking"
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
import { getBookedHotelRoom } from "@/server/routers/booking/utils"
|
||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||
|
||||
import { convertToChildType } from "@/components/HotelReservation/utils/convertToChildType"
|
||||
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
|
||||
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
||||
import { getBookedHotelRoom } from "@/utils/booking"
|
||||
|
||||
import styles from "./sidePeek.module.css"
|
||||
|
||||
|
||||
@@ -14,14 +14,13 @@ export default async function Rooms({
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
mainRoom,
|
||||
linkedReservations,
|
||||
}: BookingConfirmationRoomsProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<section className={styles.rooms}>
|
||||
<div className={styles.room}>
|
||||
{linkedReservations.length ? (
|
||||
{booking.linkedReservations.length ? (
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h2 className={styles.roomTitle}>
|
||||
{intl.formatMessage(
|
||||
@@ -42,7 +41,7 @@ export default async function Rooms({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{linkedReservations.map((reservation, idx) => (
|
||||
{booking.linkedReservations.map((reservation, idx) => (
|
||||
<div className={styles.room} key={reservation.confirmationNumber}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h2 className={styles.roomTitle}>
|
||||
@@ -57,7 +56,7 @@ export default async function Rooms({
|
||||
<LinkedReservation
|
||||
checkInTime={checkInTime}
|
||||
checkOutTime={checkOutTime}
|
||||
confirmationNumber={reservation.confirmationNumber}
|
||||
refId={reservation.refId}
|
||||
roomIndex={idx + 1}
|
||||
roomNumber={idx + 2}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||
import { encrypt } from "@/server/routers/utils/encryption"
|
||||
|
||||
import HotelDetails from "@/components/HotelReservation/BookingConfirmation/HotelDetails"
|
||||
import PaymentDetails from "@/components/HotelReservation/BookingConfirmation/PaymentDetails"
|
||||
@@ -23,22 +22,20 @@ import styles from "./bookingConfirmation.module.css"
|
||||
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||
|
||||
export default async function BookingConfirmation({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
}: BookingConfirmationProps) {
|
||||
const bookingConfirmation = await getBookingConfirmation(confirmationNumber)
|
||||
const bookingConfirmation = await getBookingConfirmation(refId)
|
||||
|
||||
if (!bookingConfirmation) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const { booking, hotel, room, roomCategories } = bookingConfirmation
|
||||
|
||||
if (!room) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const refId = encrypt(
|
||||
`${booking.confirmationNumber},${booking.guest.lastName}`
|
||||
)
|
||||
|
||||
const intl = await getIntl()
|
||||
return (
|
||||
<BookingConfirmationProvider
|
||||
@@ -62,7 +59,6 @@ export default async function BookingConfirmation({
|
||||
checkInTime={hotel.hotelFacts.checkin.checkInTime}
|
||||
checkOutTime={hotel.hotelFacts.checkin.checkOutTime}
|
||||
mainRoom={room}
|
||||
linkedReservations={booking.linkedReservations}
|
||||
/>
|
||||
<PaymentDetails />
|
||||
<Divider color="primaryLightSubtle" />
|
||||
|
||||
@@ -64,6 +64,7 @@ export function mapRoomState(
|
||||
name: room.name,
|
||||
packages: booking.packages,
|
||||
rateDefinition: booking.rateDefinition,
|
||||
refId: booking.refId,
|
||||
roomFeatures: booking.packages.filter((p) => p.type === "RoomFeature"),
|
||||
roomPoints: booking.roomPoints,
|
||||
roomPrice: booking.roomPrice,
|
||||
|
||||
@@ -18,12 +18,12 @@ const validBookingStatuses = [
|
||||
]
|
||||
|
||||
interface HandleStatusPollingProps {
|
||||
confirmationNumber: string
|
||||
refId: string
|
||||
successRedirectUrl: string
|
||||
}
|
||||
|
||||
export default function HandleSuccessCallback({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
successRedirectUrl,
|
||||
}: HandleStatusPollingProps) {
|
||||
const router = useRouter()
|
||||
@@ -33,7 +33,7 @@ export default function HandleSuccessCallback({
|
||||
error,
|
||||
isTimeout,
|
||||
} = useHandleBookingStatus({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
expectedStatuses: validBookingStatuses,
|
||||
maxRetries: 10,
|
||||
retryInterval: 2000,
|
||||
|
||||
@@ -12,7 +12,6 @@ import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import {
|
||||
BOOKING_CONFIRMATION_NUMBER,
|
||||
BookingStatusEnum,
|
||||
PAYMENT_METHOD_TITLES,
|
||||
PaymentMethodEnum,
|
||||
@@ -111,7 +110,7 @@ export default function PaymentClient({
|
||||
(state) => state.actions.setIsSubmittingDisabled
|
||||
)
|
||||
|
||||
const [bookingNumber, setBookingNumber] = useState<string>("")
|
||||
const [refId, setRefId] = useState("")
|
||||
const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
|
||||
useState(false)
|
||||
|
||||
@@ -156,13 +155,15 @@ export default function PaymentClient({
|
||||
return
|
||||
}
|
||||
|
||||
const mainRoom = result.rooms[0]
|
||||
|
||||
if (result.reservationStatus == BookingStatusEnum.BookingCompleted) {
|
||||
const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${result.id}`
|
||||
const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(mainRoom.refId)}`
|
||||
router.push(confirmationUrl)
|
||||
return
|
||||
}
|
||||
|
||||
setBookingNumber(result.id)
|
||||
setRefId(mainRoom.refId)
|
||||
|
||||
const hasPriceChange = result.rooms.some((r) => r.priceChangedMetadata)
|
||||
if (hasPriceChange) {
|
||||
@@ -200,7 +201,7 @@ export default function PaymentClient({
|
||||
})
|
||||
|
||||
const bookingStatus = useHandleBookingStatus({
|
||||
confirmationNumber: bookingNumber,
|
||||
refId,
|
||||
expectedStatuses: [BookingStatusEnum.BookingCompleted],
|
||||
maxRetries,
|
||||
retryInterval,
|
||||
@@ -263,7 +264,8 @@ export default function PaymentClient({
|
||||
bookingStatus?.data?.reservationStatus ===
|
||||
BookingStatusEnum.BookingCompleted
|
||||
) {
|
||||
const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${bookingStatus?.data?.id}`
|
||||
const mainRoom = bookingStatus.data.rooms[0]
|
||||
const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(mainRoom.refId)}`
|
||||
router.push(confirmationUrl)
|
||||
} else if (bookingStatus.isTimeout) {
|
||||
handlePaymentError("Timeout")
|
||||
@@ -633,9 +635,7 @@ export default function PaymentClient({
|
||||
: ""
|
||||
router.push(`${selectRate(lang)}${allSearchParams}`)
|
||||
}}
|
||||
onAccept={() =>
|
||||
priceChange.mutate({ confirmationNumber: bookingNumber })
|
||||
}
|
||||
onAccept={() => priceChange.mutate({ refId })}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function FindMyBooking() {
|
||||
const values = form.getValues()
|
||||
const value = new URLSearchParams(values).toString()
|
||||
document.cookie = `bv=${encodeURIComponent(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict`
|
||||
router.push(`${myStay[lang]}?RefId=${result.refId}`)
|
||||
router.push(`${myStay[lang]}?RefId=${encodeURIComponent(result.refId)}`)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to create ref id", error)
|
||||
|
||||
@@ -57,7 +57,6 @@ export default function AddAncillaryFlowModal({
|
||||
packages,
|
||||
user,
|
||||
savedCreditCards,
|
||||
refId,
|
||||
}: AddAncillaryFlowModalProps) {
|
||||
const {
|
||||
currentStep,
|
||||
@@ -123,7 +122,7 @@ export default function AddAncillaryFlowModal({
|
||||
const addAncillary = trpc.booking.packages.useMutation()
|
||||
|
||||
const { guaranteeBooking, isLoading, handleGuaranteeError } =
|
||||
useGuaranteeBooking(booking.confirmationNumber, true, booking.hotelId)
|
||||
useGuaranteeBooking(booking.refId, true, booking.hotelId)
|
||||
|
||||
function validateTermsAndConditions(data: AncillaryFormData): boolean {
|
||||
if (!data.termsAndConditions) {
|
||||
@@ -145,7 +144,7 @@ export default function AddAncillaryFlowModal({
|
||||
) {
|
||||
addAncillary.mutate(
|
||||
{
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
refId: booking.refId,
|
||||
ancillaryComment: data.optionalText,
|
||||
ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
|
||||
? data.deliveryTime
|
||||
@@ -176,7 +175,7 @@ export default function AddAncillaryFlowModal({
|
||||
clearAncillarySessionData()
|
||||
closeModal()
|
||||
utils.booking.get.invalidate({
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
refId: booking.refId,
|
||||
})
|
||||
router.refresh()
|
||||
} else {
|
||||
@@ -202,7 +201,7 @@ export default function AddAncillaryFlowModal({
|
||||
selectedAncillary,
|
||||
data.deliveryTime
|
||||
)
|
||||
if (booking.confirmationNumber) {
|
||||
if (booking.refId) {
|
||||
const card = savedCreditCard
|
||||
? {
|
||||
alias: savedCreditCard.alias,
|
||||
@@ -211,12 +210,12 @@ export default function AddAncillaryFlowModal({
|
||||
}
|
||||
: undefined
|
||||
guaranteeBooking.mutate({
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
refId: booking.refId,
|
||||
language: lang,
|
||||
...(card && { card }),
|
||||
success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
||||
error: `${guaranteeRedirectUrl}?status=error&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
||||
cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
||||
success: `${guaranteeRedirectUrl}?status=success&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
|
||||
error: `${guaranteeRedirectUrl}?status=error&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
|
||||
cancel: `${guaranteeRedirectUrl}?status=cancel&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
|
||||
})
|
||||
} else {
|
||||
handleGuaranteeError("No confirmation number")
|
||||
|
||||
@@ -10,12 +10,12 @@ import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
export default function RemoveButton({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
codes,
|
||||
title,
|
||||
onSuccess,
|
||||
}: {
|
||||
confirmationNumber: string
|
||||
refId: string
|
||||
codes: string[]
|
||||
title?: string
|
||||
onSuccess: () => void
|
||||
@@ -51,7 +51,7 @@ export default function RemoveButton({
|
||||
removePackage.mutate(
|
||||
{
|
||||
language: lang,
|
||||
confirmationNumber,
|
||||
refId,
|
||||
codes,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -126,7 +126,7 @@ export function AddedAncillaries({
|
||||
{booking.confirmationNumber && ancillary.code ? (
|
||||
<div className={styles.actions}>
|
||||
<RemoveButton
|
||||
confirmationNumber={booking.confirmationNumber}
|
||||
refId={booking.refId}
|
||||
codes={
|
||||
ancillary.code ===
|
||||
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
|
||||
@@ -192,7 +192,7 @@ export function AddedAncillaries({
|
||||
booking.canModifyAncillaries ? (
|
||||
<div className={styles.actions}>
|
||||
<RemoveButton
|
||||
confirmationNumber={booking.confirmationNumber}
|
||||
refId={booking.refId}
|
||||
codes={
|
||||
ancillary.code ===
|
||||
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
|
||||
|
||||
@@ -21,9 +21,11 @@ import type { Lang } from "@/constants/languages"
|
||||
export default function GuaranteeAncillaryHandler({
|
||||
confirmationNumber,
|
||||
returnUrl,
|
||||
refId,
|
||||
lang,
|
||||
}: {
|
||||
confirmationNumber: string
|
||||
refId: string
|
||||
returnUrl: string
|
||||
lang: Lang
|
||||
}) {
|
||||
@@ -47,7 +49,7 @@ export default function GuaranteeAncillaryHandler({
|
||||
|
||||
addAncillary.mutate(
|
||||
{
|
||||
confirmationNumber,
|
||||
refId,
|
||||
ancillaryComment: formData.optionalText,
|
||||
ancillaryDeliveryTime: selectedAncillary.requiresDeliveryTime
|
||||
? formData.deliveryTime
|
||||
@@ -86,7 +88,7 @@ export default function GuaranteeAncillaryHandler({
|
||||
},
|
||||
}
|
||||
)
|
||||
}, [confirmationNumber, returnUrl, addAncillary, lang, router])
|
||||
}, [confirmationNumber, refId, returnUrl, addAncillary, lang, router])
|
||||
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ export function Ancillaries({
|
||||
packages,
|
||||
user,
|
||||
savedCreditCards,
|
||||
refId,
|
||||
}: AncillariesProps) {
|
||||
const intl = useIntl()
|
||||
const ancillaries = use(ancillariesPromise)
|
||||
@@ -221,7 +220,6 @@ export function Ancillaries({
|
||||
user={user}
|
||||
booking={booking}
|
||||
packages={packages}
|
||||
refId={refId}
|
||||
savedCreditCards={savedCreditCards}
|
||||
/>
|
||||
</AncillaryFlowModalWrapper>
|
||||
|
||||
@@ -31,14 +31,14 @@ import type { SafeUser } from "@/types/user"
|
||||
import type { Guest } from "@/server/routers/booking/output"
|
||||
|
||||
interface GuestDetailsProps {
|
||||
confirmationNumber: string
|
||||
refId: string
|
||||
guest: Guest
|
||||
isCancelled: boolean
|
||||
user: SafeUser
|
||||
}
|
||||
|
||||
export default function GuestDetails({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
guest,
|
||||
isCancelled,
|
||||
user,
|
||||
@@ -74,7 +74,7 @@ export default function GuestDetails({
|
||||
onSuccess: (data) => {
|
||||
if (data) {
|
||||
utils.booking.get.invalidate({
|
||||
confirmationNumber: data.confirmationNumber,
|
||||
refId: data.refId,
|
||||
})
|
||||
|
||||
toast.success(
|
||||
@@ -106,7 +106,7 @@ export default function GuestDetails({
|
||||
|
||||
async function onSubmit(data: ModifyContactSchema) {
|
||||
updateGuest.mutate({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
guest: {
|
||||
email: data.email,
|
||||
phoneNumber: data.phoneNumber,
|
||||
|
||||
@@ -3,8 +3,6 @@ import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
@@ -14,13 +12,14 @@ export default function Points({
|
||||
isCancelled,
|
||||
points,
|
||||
price,
|
||||
currencyCode,
|
||||
}: {
|
||||
isCancelled: boolean
|
||||
points: number
|
||||
price: number
|
||||
currencyCode: CurrencyEnum
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
const currency = useMyStayStore((state) => state.bookedRoom.currencyCode)
|
||||
|
||||
if (!points) {
|
||||
return <SkeletonShimmer width="100px" />
|
||||
@@ -31,7 +30,7 @@ export default function Points({
|
||||
points,
|
||||
CurrencyEnum.POINTS,
|
||||
price,
|
||||
currency
|
||||
currencyCode
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,7 +11,12 @@ import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmat
|
||||
interface PriceTypeProps
|
||||
extends Pick<
|
||||
BookingConfirmation["booking"],
|
||||
"cheques" | "rateDefinition" | "roomPoints" | "totalPrice" | "vouchers"
|
||||
| "cheques"
|
||||
| "currencyCode"
|
||||
| "rateDefinition"
|
||||
| "roomPoints"
|
||||
| "totalPrice"
|
||||
| "vouchers"
|
||||
> {
|
||||
formattedTotalPrice: string
|
||||
isCancelled: boolean
|
||||
@@ -22,6 +27,7 @@ export default function PriceType({
|
||||
cheques,
|
||||
formattedTotalPrice,
|
||||
isCancelled,
|
||||
currencyCode,
|
||||
priceType,
|
||||
rateDefinition,
|
||||
roomPoints,
|
||||
@@ -51,6 +57,7 @@ export default function PriceType({
|
||||
isCancelled={isCancelled}
|
||||
points={roomPoints}
|
||||
price={totalPrice}
|
||||
currencyCode={currencyCode}
|
||||
/>
|
||||
)
|
||||
case PriceTypeEnum.voucher:
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
getBookingConfirmation,
|
||||
getProfileSafely,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
import { decrypt } from "@/server/routers/utils/encryption"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { parseRefId } from "@/utils/refId"
|
||||
import { isValidSession } from "@/utils/session"
|
||||
|
||||
import AdditionalInfoForm from "../../FindMyBooking/AdditionalInfoForm"
|
||||
@@ -32,18 +32,19 @@ import styles from "./receipt.module.css"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
|
||||
export async function Receipt({ refId }: { refId: string }) {
|
||||
const value = decrypt(refId)
|
||||
if (!value) {
|
||||
const { confirmationNumber, lastName } = parseRefId(refId)
|
||||
|
||||
if (!confirmationNumber) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const session = await auth()
|
||||
const isLoggedIn = isValidSession(session)
|
||||
|
||||
const [confirmationNumber, lastName] = value.split(",")
|
||||
const bv = cookies().get("bv")?.value
|
||||
let bookingConfirmation
|
||||
if (isLoggedIn) {
|
||||
bookingConfirmation = await getBookingConfirmation(confirmationNumber)
|
||||
bookingConfirmation = await getBookingConfirmation(refId)
|
||||
} else if (bv) {
|
||||
const params = new URLSearchParams(bv)
|
||||
const firstName = params.get("firstName")
|
||||
|
||||
@@ -29,9 +29,7 @@ export default function CancelStayPriceContainer() {
|
||||
const { totalAdults, totalChildren } = formRooms.reduce(
|
||||
(total, formRoom) => {
|
||||
if (formRoom.checked) {
|
||||
const room = rooms.find(
|
||||
(r) => r.confirmationNumber === formRoom.confirmationNumber
|
||||
)
|
||||
const room = rooms.find((r) => r.refId === formRoom.refId)
|
||||
if (room) {
|
||||
total.totalAdults = total.totalAdults + room.adults
|
||||
if (room.childrenInRoom.length) {
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function FinalConfirmation({
|
||||
)
|
||||
} else {
|
||||
const cancelledRooms = rooms.filter((r) =>
|
||||
variables.confirmationNumbers.includes(r.confirmationNumber)
|
||||
variables.refIds.includes(r.refId)
|
||||
)
|
||||
for (const cancelledRoom of cancelledRooms) {
|
||||
toast.success(
|
||||
@@ -94,11 +94,11 @@ export default function FinalConfirmation({
|
||||
}
|
||||
|
||||
utils.booking.get.invalidate({
|
||||
confirmationNumber: bookedRoom.confirmationNumber,
|
||||
refId: bookedRoom.refId,
|
||||
})
|
||||
utils.booking.linkedReservations.invalidate({
|
||||
lang,
|
||||
rooms: bookedRoom.linkedReservations,
|
||||
refId: bookedRoom.refId,
|
||||
})
|
||||
closeModal()
|
||||
},
|
||||
@@ -113,12 +113,12 @@ export default function FinalConfirmation({
|
||||
|
||||
function cancelBooking() {
|
||||
if (Array.isArray(formRooms)) {
|
||||
const confirmationNumbersToCancel = formRooms
|
||||
const refIdsToCancel = formRooms
|
||||
.filter((r) => r.checked)
|
||||
.map((r) => r.confirmationNumber)
|
||||
if (confirmationNumbersToCancel.length) {
|
||||
.map((r) => r.refId)
|
||||
if (refIdsToCancel.length) {
|
||||
cancelBookingsMutation.mutate({
|
||||
confirmationNumbers: confirmationNumbersToCancel,
|
||||
refIds: refIdsToCancel,
|
||||
language: lang,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function Steps({ closeModal }: StepsProps) {
|
||||
rooms: rooms.map((room, idx) => ({
|
||||
// Single room booking
|
||||
checked: rooms.length === 1,
|
||||
confirmationNumber: room.confirmationNumber,
|
||||
refId: room.refId,
|
||||
id: idx + 1,
|
||||
})),
|
||||
},
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function Confirmation({
|
||||
onSuccess: (updatedBooking) => {
|
||||
if (updatedBooking) {
|
||||
utils.booking.get.invalidate({
|
||||
confirmationNumber: updatedBooking.confirmationNumber,
|
||||
refId: updatedBooking.refId,
|
||||
})
|
||||
|
||||
toast.success(
|
||||
@@ -86,7 +86,7 @@ export default function Confirmation({
|
||||
|
||||
function handleModifyStay() {
|
||||
updateBooking.mutate({
|
||||
confirmationNumber: bookedRoom.confirmationNumber,
|
||||
refId: bookedRoom.refId,
|
||||
checkInDate,
|
||||
checkOutDate,
|
||||
})
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function Form() {
|
||||
confirmationNumber: state.bookedRoom.confirmationNumber,
|
||||
currencyCode: state.bookedRoom.currencyCode,
|
||||
hotelId: state.bookedRoom.hotelId,
|
||||
refId: state.refId,
|
||||
refId: state.bookedRoom.refId,
|
||||
savedCreditCards: state.savedCreditCards,
|
||||
}))
|
||||
|
||||
@@ -85,7 +85,7 @@ export default function Form() {
|
||||
: undefined
|
||||
writeGlaToSessionStorage("yes", hotelId)
|
||||
guaranteeBooking.mutate({
|
||||
confirmationNumber,
|
||||
refId,
|
||||
language: lang,
|
||||
...(card && { card }),
|
||||
success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}`,
|
||||
|
||||
@@ -276,6 +276,7 @@ export default function Room({ booking, roomNr, user }: RoomProps) {
|
||||
cheques={cheques}
|
||||
formattedTotalPrice={formattedTotalPrice}
|
||||
isCancelled={isCancelled}
|
||||
currencyCode={currencyCode}
|
||||
priceType={priceType}
|
||||
rateDefinition={rateDefinition}
|
||||
roomPoints={roomPoints}
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function PriceDetails() {
|
||||
cheques: state.bookedRoom.cheques,
|
||||
formattedTotalPrice: state.totalPrice,
|
||||
isCancelled: state.bookedRoom.isCancelled,
|
||||
currencyCode: state.bookedRoom.currencyCode,
|
||||
priceType: state.bookedRoom.priceType,
|
||||
rateDefinition: state.bookedRoom.rateDefinition,
|
||||
roomPoints: state.bookedRoom.roomPoints,
|
||||
|
||||
@@ -23,21 +23,15 @@ interface RoomProps {
|
||||
}
|
||||
|
||||
export default function SingleRoom({ user }: RoomProps) {
|
||||
const {
|
||||
confirmationNumber,
|
||||
guest,
|
||||
isCancelled,
|
||||
isMultiRoom,
|
||||
roomName,
|
||||
roomNumber,
|
||||
} = useMyStayStore((state) => ({
|
||||
confirmationNumber: state.bookedRoom.confirmationNumber,
|
||||
guest: state.bookedRoom.guest,
|
||||
isCancelled: state.bookedRoom.isCancelled,
|
||||
isMultiRoom: state.rooms.length > 1,
|
||||
roomName: state.bookedRoom.roomName,
|
||||
roomNumber: state.bookedRoom.roomNumber,
|
||||
}))
|
||||
const { refId, guest, isCancelled, isMultiRoom, roomName, roomNumber } =
|
||||
useMyStayStore((state) => ({
|
||||
refId: state.bookedRoom.refId,
|
||||
guest: state.bookedRoom.guest,
|
||||
isCancelled: state.bookedRoom.isCancelled,
|
||||
isMultiRoom: state.rooms.length > 1,
|
||||
roomName: state.bookedRoom.roomName,
|
||||
roomNumber: state.bookedRoom.roomNumber,
|
||||
}))
|
||||
|
||||
if (isMultiRoom) {
|
||||
return null
|
||||
@@ -70,7 +64,7 @@ export default function SingleRoom({ user }: RoomProps) {
|
||||
<Details />
|
||||
<div className={styles.guestDetailsDesktopWrapper}>
|
||||
<GuestDetails
|
||||
confirmationNumber={confirmationNumber}
|
||||
refId={refId}
|
||||
guest={guest}
|
||||
isCancelled={isCancelled}
|
||||
user={user}
|
||||
@@ -83,7 +77,7 @@ export default function SingleRoom({ user }: RoomProps) {
|
||||
<PriceDetails />
|
||||
<div className={styles.guestDetailsMobileWrapper}>
|
||||
<GuestDetails
|
||||
confirmationNumber={confirmationNumber}
|
||||
refId={refId}
|
||||
guest={guest}
|
||||
isCancelled={isCancelled}
|
||||
user={user}
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function TotalPrice() {
|
||||
cheques={bookedRoom.cheques}
|
||||
formattedTotalPrice={formattedTotalPrice}
|
||||
isCancelled={bookedRoom.isCancelled}
|
||||
currencyCode={bookedRoom.currencyCode}
|
||||
priceType={bookedRoom.priceType}
|
||||
rateDefinition={bookedRoom.rateDefinition}
|
||||
roomPoints={bookedRoom.roomPoints}
|
||||
|
||||
@@ -14,18 +14,32 @@ import type { Guest } from "@/server/routers/booking/output"
|
||||
describe("Access booking", () => {
|
||||
describe("for logged in booking", () => {
|
||||
it("should enable access if all is provided", () => {
|
||||
expect(accessBooking(loggedIn, "Booking", user)).toBe(ACCESS_GRANTED)
|
||||
expect(accessBooking(loggedInGuest, "Booking", authenticatedUser)).toBe(
|
||||
ACCESS_GRANTED
|
||||
)
|
||||
})
|
||||
it("should enable access if all is provided and be case-insensitive", () => {
|
||||
expect(accessBooking(loggedIn, "BoOkInG", user)).toBe(ACCESS_GRANTED)
|
||||
expect(accessBooking(loggedInGuest, "BoOkInG", authenticatedUser)).toBe(
|
||||
ACCESS_GRANTED
|
||||
)
|
||||
})
|
||||
it("should prompt to login", () => {
|
||||
expect(accessBooking(loggedIn, "Booking", null)).toBe(ERROR_UNAUTHORIZED)
|
||||
it("should prompt to login without user", () => {
|
||||
expect(accessBooking(loggedInGuest, "Booking", null)).toBe(
|
||||
ERROR_UNAUTHORIZED
|
||||
)
|
||||
})
|
||||
it("should deny access", () => {
|
||||
expect(accessBooking(loggedIn, "NotBooking", user)).toBe(ERROR_NOT_FOUND)
|
||||
it("should prompt to login if user mismatch", () => {
|
||||
expect(
|
||||
accessBooking(loggedInGuest, "Booking", badAuthenticatedUser)
|
||||
).toBe(ERROR_UNAUTHORIZED)
|
||||
})
|
||||
it("should deny access if refId mismatch", () => {
|
||||
expect(
|
||||
accessBooking(loggedInGuest, "NotBooking", authenticatedUser)
|
||||
).toBe(ERROR_UNAUTHORIZED)
|
||||
})
|
||||
})
|
||||
|
||||
describe("for anonymous booking", () => {
|
||||
it("should enable access if all is provided", () => {
|
||||
const cookieString = new URLSearchParams({
|
||||
@@ -34,7 +48,7 @@ describe("Access booking", () => {
|
||||
lastName: "Booking",
|
||||
email: "logged+out@scandichotels.com",
|
||||
}).toString()
|
||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
||||
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||
ACCESS_GRANTED
|
||||
)
|
||||
})
|
||||
@@ -45,7 +59,7 @@ describe("Access booking", () => {
|
||||
lastName: "Booking",
|
||||
email: "logged+out@scandichotels.com",
|
||||
}).toString()
|
||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
||||
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||
ACCESS_GRANTED
|
||||
)
|
||||
})
|
||||
@@ -56,7 +70,7 @@ describe("Access booking", () => {
|
||||
lastName: "Booking",
|
||||
email: "logged+out@scandichotels.com",
|
||||
}).toString()
|
||||
expect(accessBooking(loggedOut, "BoOkInG", null, cookieString)).toBe(
|
||||
expect(accessBooking(loggedOutGuest, "BoOkInG", null, cookieString)).toBe(
|
||||
ACCESS_GRANTED
|
||||
)
|
||||
})
|
||||
@@ -67,7 +81,7 @@ describe("Access booking", () => {
|
||||
lastName: "Booking",
|
||||
email: "LOGGED+out@scandichotels.com",
|
||||
}).toString()
|
||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
||||
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||
ACCESS_GRANTED
|
||||
)
|
||||
})
|
||||
@@ -78,9 +92,14 @@ describe("Access booking", () => {
|
||||
lastName: "Booking",
|
||||
email: "logged+out@scandichotels.com",
|
||||
}).toString()
|
||||
expect(accessBooking(loggedOut, "Booking", user, cookieString)).toBe(
|
||||
ERROR_FORBIDDEN
|
||||
)
|
||||
expect(
|
||||
accessBooking(
|
||||
loggedOutGuest,
|
||||
"Booking",
|
||||
authenticatedUser,
|
||||
cookieString
|
||||
)
|
||||
).toBe(ERROR_FORBIDDEN)
|
||||
})
|
||||
it("should prompt for more if first name is missing", () => {
|
||||
const cookieString = new URLSearchParams({
|
||||
@@ -88,7 +107,7 @@ describe("Access booking", () => {
|
||||
lastName: "Booking",
|
||||
email: "logged+out@scandichotels.com",
|
||||
}).toString()
|
||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
||||
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||
ERROR_BAD_REQUEST
|
||||
)
|
||||
})
|
||||
@@ -98,23 +117,25 @@ describe("Access booking", () => {
|
||||
firstName: "Anonymous",
|
||||
lastName: "Booking",
|
||||
}).toString()
|
||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
||||
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||
ERROR_BAD_REQUEST
|
||||
)
|
||||
})
|
||||
it("should prompt for more if cookie is invalid", () => {
|
||||
const cookieString = new URLSearchParams({}).toString()
|
||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
||||
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||
ERROR_BAD_REQUEST
|
||||
)
|
||||
})
|
||||
it("should deny access", () => {
|
||||
expect(accessBooking(loggedOut, "NotBooking", null)).toBe(ERROR_NOT_FOUND)
|
||||
it("should deny access if refId mismatch", () => {
|
||||
expect(accessBooking(loggedOutGuest, "NotBooking", null)).toBe(
|
||||
ERROR_NOT_FOUND
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const user: SafeUser = {
|
||||
const authenticatedUser: SafeUser = {
|
||||
address: {
|
||||
city: undefined,
|
||||
country: "Sweden",
|
||||
@@ -124,10 +145,10 @@ const user: SafeUser = {
|
||||
},
|
||||
dateOfBirth: "",
|
||||
email: "",
|
||||
firstName: "",
|
||||
firstName: "Authenticated",
|
||||
language: undefined,
|
||||
lastName: "",
|
||||
membershipNumber: "",
|
||||
lastName: "Booking",
|
||||
membershipNumber: "01234567890123",
|
||||
membership: undefined,
|
||||
loyalty: {
|
||||
memberships: [],
|
||||
@@ -145,7 +166,38 @@ const user: SafeUser = {
|
||||
profileId: "",
|
||||
}
|
||||
|
||||
const loggedOut: Guest = {
|
||||
const badAuthenticatedUser: SafeUser = {
|
||||
address: {
|
||||
city: undefined,
|
||||
country: "Sweden",
|
||||
countryCode: "SE",
|
||||
streetAddress: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
dateOfBirth: "",
|
||||
email: "",
|
||||
firstName: "Authenticated",
|
||||
language: undefined,
|
||||
lastName: `Bad name ${Math.random()}`,
|
||||
membershipNumber: "0987654321",
|
||||
membership: undefined,
|
||||
loyalty: {
|
||||
memberships: [],
|
||||
pointExpirations: [],
|
||||
points: {
|
||||
earned: 0,
|
||||
spent: 0,
|
||||
spendable: 0,
|
||||
},
|
||||
tier: "L1",
|
||||
tierExpires: "",
|
||||
},
|
||||
name: "",
|
||||
phoneNumber: undefined,
|
||||
profileId: "",
|
||||
}
|
||||
|
||||
const loggedOutGuest: Guest = {
|
||||
email: "logged+out@scandichotels.com",
|
||||
firstName: "Anonymous",
|
||||
lastName: "Booking",
|
||||
@@ -154,7 +206,7 @@ const loggedOut: Guest = {
|
||||
countryCode: "SE",
|
||||
}
|
||||
|
||||
const loggedIn: Guest = {
|
||||
const loggedInGuest: Guest = {
|
||||
email: "logged+in@scandichotels.com",
|
||||
firstName: "Authenticated",
|
||||
lastName: "Booking",
|
||||
|
||||
@@ -21,22 +21,20 @@ function accessBooking(
|
||||
) {
|
||||
if (guest.membershipNumber) {
|
||||
if (user) {
|
||||
if (lastName.toLowerCase() === guest.lastName?.toLowerCase()) {
|
||||
if (
|
||||
user.membershipNumber === guest.membershipNumber &&
|
||||
user.lastName.toLowerCase() === lastName.toLowerCase() &&
|
||||
lastName.toLowerCase() === guest.lastName?.toLowerCase()
|
||||
) {
|
||||
return ACCESS_GRANTED
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
"Access to booking not granted due to anonymous user attempting accessing to logged in booking"
|
||||
)
|
||||
return ERROR_UNAUTHORIZED
|
||||
}
|
||||
|
||||
return ERROR_UNAUTHORIZED
|
||||
}
|
||||
|
||||
if (guest.lastName?.toLowerCase() === lastName.toLowerCase()) {
|
||||
if (user) {
|
||||
console.warn(
|
||||
"Access to booking not granted due to logged in user attempting access to anonymous booking"
|
||||
)
|
||||
return ERROR_FORBIDDEN
|
||||
} else {
|
||||
const params = new URLSearchParams(cookie)
|
||||
@@ -47,17 +45,11 @@ function accessBooking(
|
||||
) {
|
||||
return ACCESS_GRANTED
|
||||
} else {
|
||||
console.warn(
|
||||
"Access to booking not granted due to incorrect cookie values"
|
||||
)
|
||||
return ERROR_BAD_REQUEST
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Access to booking not granted due to anonymous user attempting access with incorrect lastname"
|
||||
)
|
||||
return ERROR_NOT_FOUND
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ export function mapRoomDetails({
|
||||
priceType,
|
||||
rate,
|
||||
rateDefinition: booking.rateDefinition,
|
||||
refId: booking.refId,
|
||||
reservationStatus: booking.reservationStatus,
|
||||
room,
|
||||
roomName: room?.name ?? "",
|
||||
|
||||
Reference in New Issue
Block a user