feat(SW-2116): Use refId instead of confirmationNumber
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import BookingConfirmation from "@/components/HotelReservation/BookingConfirmation"
|
import BookingConfirmation from "@/components/HotelReservation/BookingConfirmation"
|
||||||
@@ -6,9 +8,14 @@ import type { LangParams, PageArgs } from "@/types/params"
|
|||||||
|
|
||||||
export default async function BookingConfirmationPage({
|
export default async function BookingConfirmationPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: PageArgs<LangParams, { confirmationNumber: string }>) {
|
}: PageArgs<LangParams, { RefId?: string }>) {
|
||||||
void getBookingConfirmation(searchParams.confirmationNumber)
|
const refId = searchParams.RefId
|
||||||
return (
|
|
||||||
<BookingConfirmation confirmationNumber={searchParams.confirmationNumber} />
|
if (!refId) {
|
||||||
)
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
void getBookingConfirmation(refId)
|
||||||
|
|
||||||
|
return <BookingConfirmation refId={refId} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export default async function GuaranteePaymentCallbackPage({
|
|||||||
}: PageArgs<
|
}: PageArgs<
|
||||||
LangParams,
|
LangParams,
|
||||||
{
|
{
|
||||||
status: PaymentCallbackStatusEnum
|
status?: PaymentCallbackStatusEnum
|
||||||
RefId: string
|
RefId?: string
|
||||||
confirmationNumber?: string
|
confirmationNumber?: string
|
||||||
ancillary?: string
|
ancillary?: string
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ export default async function GuaranteePaymentCallbackPage({
|
|||||||
const status = searchParams.status
|
const status = searchParams.status
|
||||||
const confirmationNumber = searchParams.confirmationNumber
|
const confirmationNumber = searchParams.confirmationNumber
|
||||||
const refId = searchParams.RefId
|
const refId = searchParams.RefId
|
||||||
if (!refId) {
|
if (!status || !confirmationNumber || !refId) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
const isAncillaryFlow = searchParams.ancillary
|
const isAncillaryFlow = searchParams.ancillary
|
||||||
@@ -43,6 +43,7 @@ export default async function GuaranteePaymentCallbackPage({
|
|||||||
return (
|
return (
|
||||||
<GuaranteeCallback
|
<GuaranteeCallback
|
||||||
returnUrl={myStayUrl}
|
returnUrl={myStayUrl}
|
||||||
|
refId={refId}
|
||||||
confirmationNumber={confirmationNumber}
|
confirmationNumber={confirmationNumber}
|
||||||
lang={lang}
|
lang={lang}
|
||||||
/>
|
/>
|
||||||
@@ -54,10 +55,10 @@ export default async function GuaranteePaymentCallbackPage({
|
|||||||
|
|
||||||
let errorMessage = undefined
|
let errorMessage = undefined
|
||||||
|
|
||||||
if (confirmationNumber) {
|
if (refId) {
|
||||||
try {
|
try {
|
||||||
const bookingStatus = await serverClient().booking.status({
|
const bookingStatus = await serverClient().booking.status({
|
||||||
confirmationNumber,
|
refId,
|
||||||
})
|
})
|
||||||
|
|
||||||
const error = bookingStatus.errors.find((e) => e.errorCode)
|
const error = bookingStatus.errors.find((e) => e.errorCode)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BOOKING_CONFIRMATION_NUMBER,
|
|
||||||
BookingErrorCodeEnum,
|
BookingErrorCodeEnum,
|
||||||
PaymentCallbackStatusEnum,
|
PaymentCallbackStatusEnum,
|
||||||
} from "@/constants/booking"
|
} from "@/constants/booking"
|
||||||
@@ -8,7 +9,10 @@ import {
|
|||||||
details,
|
details,
|
||||||
} from "@/constants/routes/hotelReservation"
|
} from "@/constants/routes/hotelReservation"
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
import { getBooking } from "@/server/routers/booking/utils"
|
||||||
|
import { getServiceToken } from "@/server/tokenManager"
|
||||||
|
|
||||||
|
import { auth } from "@/auth"
|
||||||
import HandleErrorCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleErrorCallback"
|
import HandleErrorCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleErrorCallback"
|
||||||
import HandleSuccessCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback"
|
import HandleSuccessCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback"
|
||||||
|
|
||||||
@@ -20,7 +24,7 @@ export default async function PaymentCallbackPage({
|
|||||||
}: PageArgs<
|
}: PageArgs<
|
||||||
LangParams,
|
LangParams,
|
||||||
{
|
{
|
||||||
status: PaymentCallbackStatusEnum
|
status?: PaymentCallbackStatusEnum
|
||||||
confirmationNumber?: string
|
confirmationNumber?: string
|
||||||
hotel?: string
|
hotel?: string
|
||||||
}
|
}
|
||||||
@@ -30,15 +34,42 @@ export default async function PaymentCallbackPage({
|
|||||||
const status = searchParams.status
|
const status = searchParams.status
|
||||||
const confirmationNumber = searchParams.confirmationNumber
|
const confirmationNumber = searchParams.confirmationNumber
|
||||||
|
|
||||||
|
if (!status || !confirmationNumber) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = ""
|
||||||
|
const session = await auth()
|
||||||
|
if (session) {
|
||||||
|
token = session.token.access_token
|
||||||
|
} else {
|
||||||
|
const serviceToken = await getServiceToken()
|
||||||
|
if (serviceToken) {
|
||||||
|
token = serviceToken.access_token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
const booking = await getBooking(confirmationNumber, params.lang, token)
|
||||||
|
|
||||||
|
if (!booking) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { refId } = booking
|
||||||
|
|
||||||
if (status === PaymentCallbackStatusEnum.Success && confirmationNumber) {
|
if (status === PaymentCallbackStatusEnum.Success && confirmationNumber) {
|
||||||
const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${confirmationNumber}`
|
const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(refId)}`
|
||||||
console.log(
|
console.log(
|
||||||
`[payment-callback] rendering success callback with confirmation number: ${confirmationNumber}`
|
`[payment-callback] rendering success callback with confirmation number: ${confirmationNumber}`
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HandleSuccessCallback
|
<HandleSuccessCallback
|
||||||
confirmationNumber={confirmationNumber}
|
refId={refId}
|
||||||
successRedirectUrl={confirmationUrl}
|
successRedirectUrl={confirmationUrl}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -49,10 +80,10 @@ export default async function PaymentCallbackPage({
|
|||||||
|
|
||||||
let errorMessage = undefined
|
let errorMessage = undefined
|
||||||
|
|
||||||
if (confirmationNumber) {
|
if (refId) {
|
||||||
try {
|
try {
|
||||||
const bookingStatus = await serverClient().booking.status({
|
const bookingStatus = await serverClient().booking.status({
|
||||||
confirmationNumber,
|
refId,
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: how to handle errors for multiple rooms?
|
// TODO: how to handle errors for multiple rooms?
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
getProfileSafely,
|
getProfileSafely,
|
||||||
getSavedPaymentCardsSafely,
|
getSavedPaymentCardsSafely,
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
} from "@/lib/trpc/memoizedRequests"
|
||||||
import { decrypt } from "@/server/routers/utils/encryption"
|
|
||||||
|
|
||||||
import { auth } from "@/auth"
|
import { auth } from "@/auth"
|
||||||
import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
|
import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
|
||||||
@@ -35,6 +34,7 @@ import Image from "@/components/Image"
|
|||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
import MyStayProvider from "@/providers/MyStay"
|
import MyStayProvider from "@/providers/MyStay"
|
||||||
|
import { parseRefId } from "@/utils/refId"
|
||||||
import { isValidSession } from "@/utils/session"
|
import { isValidSession } from "@/utils/session"
|
||||||
import { getCurrentWebUrl } from "@/utils/url"
|
import { getCurrentWebUrl } from "@/utils/url"
|
||||||
|
|
||||||
@@ -54,19 +54,18 @@ export default async function MyStay({
|
|||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = decrypt(refId)
|
const { confirmationNumber, lastName } = parseRefId(refId)
|
||||||
if (!value) {
|
if (!confirmationNumber) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await auth()
|
const session = await auth()
|
||||||
const isLoggedIn = isValidSession(session)
|
const isLoggedIn = isValidSession(session)
|
||||||
|
|
||||||
const [confirmationNumber, lastName] = value.split(",")
|
|
||||||
const bv = cookies().get("bv")?.value
|
const bv = cookies().get("bv")?.value
|
||||||
let bookingConfirmation
|
let bookingConfirmation
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
bookingConfirmation = await getBookingConfirmation(confirmationNumber)
|
bookingConfirmation = await getBookingConfirmation(refId)
|
||||||
} else if (bv) {
|
} else if (bv) {
|
||||||
const params = new URLSearchParams(bv)
|
const params = new URLSearchParams(bv)
|
||||||
const firstName = params.get("firstName")
|
const firstName = params.get("firstName")
|
||||||
@@ -113,9 +112,7 @@ export default async function MyStay({
|
|||||||
const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
|
const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
|
||||||
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
|
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
|
||||||
|
|
||||||
const linkedReservationsPromise = getLinkedReservations({
|
const linkedReservationsPromise = getLinkedReservations(booking.refId)
|
||||||
rooms: booking.linkedReservations,
|
|
||||||
})
|
|
||||||
|
|
||||||
const ancillariesInput = {
|
const ancillariesInput = {
|
||||||
fromDate,
|
fromDate,
|
||||||
@@ -187,7 +184,7 @@ export default async function MyStay({
|
|||||||
breakfastPackages={breakfastPackages}
|
breakfastPackages={breakfastPackages}
|
||||||
lang={params.lang}
|
lang={params.lang}
|
||||||
linkedReservationsPromise={linkedReservationsPromise}
|
linkedReservationsPromise={linkedReservationsPromise}
|
||||||
refId={refId}
|
refId={booking.refId}
|
||||||
roomCategories={roomCategories}
|
roomCategories={roomCategories}
|
||||||
savedCreditCards={savedCreditCards}
|
savedCreditCards={savedCreditCards}
|
||||||
>
|
>
|
||||||
@@ -215,7 +212,6 @@ export default async function MyStay({
|
|||||||
packages={breakfastPackages}
|
packages={breakfastPackages}
|
||||||
user={user}
|
user={user}
|
||||||
savedCreditCards={savedCreditCards}
|
savedCreditCards={savedCreditCards}
|
||||||
refId={refId}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
getProfileSafely,
|
getProfileSafely,
|
||||||
getSavedPaymentCardsSafely,
|
getSavedPaymentCardsSafely,
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
} from "@/lib/trpc/memoizedRequests"
|
||||||
import { decrypt } from "@/server/routers/utils/encryption"
|
|
||||||
|
|
||||||
import { auth } from "@/auth"
|
import { auth } from "@/auth"
|
||||||
import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
|
import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
|
||||||
@@ -35,6 +34,7 @@ import Image from "@/components/Image"
|
|||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
import MyStayProvider from "@/providers/MyStay"
|
import MyStayProvider from "@/providers/MyStay"
|
||||||
|
import { parseRefId } from "@/utils/refId"
|
||||||
import { isValidSession } from "@/utils/session"
|
import { isValidSession } from "@/utils/session"
|
||||||
import { getCurrentWebUrl } from "@/utils/url"
|
import { getCurrentWebUrl } from "@/utils/url"
|
||||||
|
|
||||||
@@ -54,18 +54,19 @@ export default async function MyStay({
|
|||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = decrypt(refId)
|
const { confirmationNumber, lastName } = parseRefId(refId)
|
||||||
if (!value) {
|
|
||||||
|
if (!confirmationNumber) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await auth()
|
const session = await auth()
|
||||||
const isLoggedIn = isValidSession(session)
|
const isLoggedIn = isValidSession(session)
|
||||||
|
|
||||||
const [confirmationNumber, lastName] = value.split(",")
|
|
||||||
const bv = cookies().get("bv")?.value
|
const bv = cookies().get("bv")?.value
|
||||||
let bookingConfirmation
|
let bookingConfirmation
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
bookingConfirmation = await getBookingConfirmation(confirmationNumber)
|
bookingConfirmation = await getBookingConfirmation(refId)
|
||||||
} else if (bv) {
|
} else if (bv) {
|
||||||
const params = new URLSearchParams(bv)
|
const params = new URLSearchParams(bv)
|
||||||
const firstName = params.get("firstName")
|
const firstName = params.get("firstName")
|
||||||
@@ -110,9 +111,7 @@ export default async function MyStay({
|
|||||||
const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
|
const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
|
||||||
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
|
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
|
||||||
|
|
||||||
const linkedReservationsPromise = getLinkedReservations({
|
const linkedReservationsPromise = getLinkedReservations(booking.refId)
|
||||||
rooms: booking.linkedReservations,
|
|
||||||
})
|
|
||||||
|
|
||||||
const ancillariesInput = {
|
const ancillariesInput = {
|
||||||
fromDate,
|
fromDate,
|
||||||
@@ -184,7 +183,7 @@ export default async function MyStay({
|
|||||||
breakfastPackages={breakfastPackages}
|
breakfastPackages={breakfastPackages}
|
||||||
lang={params.lang}
|
lang={params.lang}
|
||||||
linkedReservationsPromise={linkedReservationsPromise}
|
linkedReservationsPromise={linkedReservationsPromise}
|
||||||
refId={refId}
|
refId={booking.refId}
|
||||||
roomCategories={roomCategories}
|
roomCategories={roomCategories}
|
||||||
savedCreditCards={savedCreditCards}
|
savedCreditCards={savedCreditCards}
|
||||||
>
|
>
|
||||||
@@ -212,7 +211,6 @@ export default async function MyStay({
|
|||||||
packages={breakfastPackages}
|
packages={breakfastPackages}
|
||||||
user={user}
|
user={user}
|
||||||
savedCreditCards={savedCreditCards}
|
savedCreditCards={savedCreditCards}
|
||||||
refId={refId}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function Header({
|
|||||||
url: hotel.contactInformation.websiteUrl,
|
url: hotel.contactInformation.websiteUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookingUrlPath = `${myStay[lang]}?RefId=${refId}`
|
const bookingUrlPath = `${myStay[lang]}?RefId=${encodeURIComponent(refId)}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ import { CurrencyEnum } from "@/types/enums/currency"
|
|||||||
export function LinkedReservation({
|
export function LinkedReservation({
|
||||||
checkInTime,
|
checkInTime,
|
||||||
checkOutTime,
|
checkOutTime,
|
||||||
confirmationNumber,
|
refId,
|
||||||
roomIndex,
|
roomIndex,
|
||||||
roomNumber,
|
roomNumber,
|
||||||
}: LinkedReservationProps) {
|
}: LinkedReservationProps) {
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
|
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
|
||||||
confirmationNumber,
|
refId,
|
||||||
lang,
|
lang,
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
|
|
||||||
import { BookingStatusEnum } from "@/constants/booking"
|
import { BookingStatusEnum } from "@/constants/booking"
|
||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
import { getBookedHotelRoom } from "@/server/routers/booking/utils"
|
|
||||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||||
|
|
||||||
import { convertToChildType } from "@/components/HotelReservation/utils/convertToChildType"
|
import { convertToChildType } from "@/components/HotelReservation/utils/convertToChildType"
|
||||||
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
|
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
|
||||||
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
|
||||||
|
import { getBookedHotelRoom } from "@/utils/booking"
|
||||||
|
|
||||||
import styles from "./sidePeek.module.css"
|
import styles from "./sidePeek.module.css"
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ export default async function Rooms({
|
|||||||
checkInTime,
|
checkInTime,
|
||||||
checkOutTime,
|
checkOutTime,
|
||||||
mainRoom,
|
mainRoom,
|
||||||
linkedReservations,
|
|
||||||
}: BookingConfirmationRoomsProps) {
|
}: BookingConfirmationRoomsProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.rooms}>
|
<section className={styles.rooms}>
|
||||||
<div className={styles.room}>
|
<div className={styles.room}>
|
||||||
{linkedReservations.length ? (
|
{booking.linkedReservations.length ? (
|
||||||
<Typography variant="Title/Subtitle/md">
|
<Typography variant="Title/Subtitle/md">
|
||||||
<h2 className={styles.roomTitle}>
|
<h2 className={styles.roomTitle}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
@@ -42,7 +41,7 @@ export default async function Rooms({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{linkedReservations.map((reservation, idx) => (
|
{booking.linkedReservations.map((reservation, idx) => (
|
||||||
<div className={styles.room} key={reservation.confirmationNumber}>
|
<div className={styles.room} key={reservation.confirmationNumber}>
|
||||||
<Typography variant="Title/Subtitle/md">
|
<Typography variant="Title/Subtitle/md">
|
||||||
<h2 className={styles.roomTitle}>
|
<h2 className={styles.roomTitle}>
|
||||||
@@ -57,7 +56,7 @@ export default async function Rooms({
|
|||||||
<LinkedReservation
|
<LinkedReservation
|
||||||
checkInTime={checkInTime}
|
checkInTime={checkInTime}
|
||||||
checkOutTime={checkOutTime}
|
checkOutTime={checkOutTime}
|
||||||
confirmationNumber={reservation.confirmationNumber}
|
refId={reservation.refId}
|
||||||
roomIndex={idx + 1}
|
roomIndex={idx + 1}
|
||||||
roomNumber={idx + 2}
|
roomNumber={idx + 2}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||||
import { encrypt } from "@/server/routers/utils/encryption"
|
|
||||||
|
|
||||||
import HotelDetails from "@/components/HotelReservation/BookingConfirmation/HotelDetails"
|
import HotelDetails from "@/components/HotelReservation/BookingConfirmation/HotelDetails"
|
||||||
import PaymentDetails from "@/components/HotelReservation/BookingConfirmation/PaymentDetails"
|
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"
|
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||||
|
|
||||||
export default async function BookingConfirmation({
|
export default async function BookingConfirmation({
|
||||||
confirmationNumber,
|
refId,
|
||||||
}: BookingConfirmationProps) {
|
}: BookingConfirmationProps) {
|
||||||
const bookingConfirmation = await getBookingConfirmation(confirmationNumber)
|
const bookingConfirmation = await getBookingConfirmation(refId)
|
||||||
|
|
||||||
if (!bookingConfirmation) {
|
if (!bookingConfirmation) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { booking, hotel, room, roomCategories } = bookingConfirmation
|
const { booking, hotel, room, roomCategories } = bookingConfirmation
|
||||||
|
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const refId = encrypt(
|
|
||||||
`${booking.confirmationNumber},${booking.guest.lastName}`
|
|
||||||
)
|
|
||||||
|
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
return (
|
return (
|
||||||
<BookingConfirmationProvider
|
<BookingConfirmationProvider
|
||||||
@@ -62,7 +59,6 @@ export default async function BookingConfirmation({
|
|||||||
checkInTime={hotel.hotelFacts.checkin.checkInTime}
|
checkInTime={hotel.hotelFacts.checkin.checkInTime}
|
||||||
checkOutTime={hotel.hotelFacts.checkin.checkOutTime}
|
checkOutTime={hotel.hotelFacts.checkin.checkOutTime}
|
||||||
mainRoom={room}
|
mainRoom={room}
|
||||||
linkedReservations={booking.linkedReservations}
|
|
||||||
/>
|
/>
|
||||||
<PaymentDetails />
|
<PaymentDetails />
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export function mapRoomState(
|
|||||||
name: room.name,
|
name: room.name,
|
||||||
packages: booking.packages,
|
packages: booking.packages,
|
||||||
rateDefinition: booking.rateDefinition,
|
rateDefinition: booking.rateDefinition,
|
||||||
|
refId: booking.refId,
|
||||||
roomFeatures: booking.packages.filter((p) => p.type === "RoomFeature"),
|
roomFeatures: booking.packages.filter((p) => p.type === "RoomFeature"),
|
||||||
roomPoints: booking.roomPoints,
|
roomPoints: booking.roomPoints,
|
||||||
roomPrice: booking.roomPrice,
|
roomPrice: booking.roomPrice,
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ const validBookingStatuses = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
interface HandleStatusPollingProps {
|
interface HandleStatusPollingProps {
|
||||||
confirmationNumber: string
|
refId: string
|
||||||
successRedirectUrl: string
|
successRedirectUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HandleSuccessCallback({
|
export default function HandleSuccessCallback({
|
||||||
confirmationNumber,
|
refId,
|
||||||
successRedirectUrl,
|
successRedirectUrl,
|
||||||
}: HandleStatusPollingProps) {
|
}: HandleStatusPollingProps) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -33,7 +33,7 @@ export default function HandleSuccessCallback({
|
|||||||
error,
|
error,
|
||||||
isTimeout,
|
isTimeout,
|
||||||
} = useHandleBookingStatus({
|
} = useHandleBookingStatus({
|
||||||
confirmationNumber,
|
refId,
|
||||||
expectedStatuses: validBookingStatuses,
|
expectedStatuses: validBookingStatuses,
|
||||||
maxRetries: 10,
|
maxRetries: 10,
|
||||||
retryInterval: 2000,
|
retryInterval: 2000,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Button } from "@scandic-hotels/design-system/Button"
|
|||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BOOKING_CONFIRMATION_NUMBER,
|
|
||||||
BookingStatusEnum,
|
BookingStatusEnum,
|
||||||
PAYMENT_METHOD_TITLES,
|
PAYMENT_METHOD_TITLES,
|
||||||
PaymentMethodEnum,
|
PaymentMethodEnum,
|
||||||
@@ -111,7 +110,7 @@ export default function PaymentClient({
|
|||||||
(state) => state.actions.setIsSubmittingDisabled
|
(state) => state.actions.setIsSubmittingDisabled
|
||||||
)
|
)
|
||||||
|
|
||||||
const [bookingNumber, setBookingNumber] = useState<string>("")
|
const [refId, setRefId] = useState("")
|
||||||
const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
|
const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
|
||||||
useState(false)
|
useState(false)
|
||||||
|
|
||||||
@@ -156,13 +155,15 @@ export default function PaymentClient({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mainRoom = result.rooms[0]
|
||||||
|
|
||||||
if (result.reservationStatus == BookingStatusEnum.BookingCompleted) {
|
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)
|
router.push(confirmationUrl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setBookingNumber(result.id)
|
setRefId(mainRoom.refId)
|
||||||
|
|
||||||
const hasPriceChange = result.rooms.some((r) => r.priceChangedMetadata)
|
const hasPriceChange = result.rooms.some((r) => r.priceChangedMetadata)
|
||||||
if (hasPriceChange) {
|
if (hasPriceChange) {
|
||||||
@@ -200,7 +201,7 @@ export default function PaymentClient({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const bookingStatus = useHandleBookingStatus({
|
const bookingStatus = useHandleBookingStatus({
|
||||||
confirmationNumber: bookingNumber,
|
refId,
|
||||||
expectedStatuses: [BookingStatusEnum.BookingCompleted],
|
expectedStatuses: [BookingStatusEnum.BookingCompleted],
|
||||||
maxRetries,
|
maxRetries,
|
||||||
retryInterval,
|
retryInterval,
|
||||||
@@ -263,7 +264,8 @@ export default function PaymentClient({
|
|||||||
bookingStatus?.data?.reservationStatus ===
|
bookingStatus?.data?.reservationStatus ===
|
||||||
BookingStatusEnum.BookingCompleted
|
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)
|
router.push(confirmationUrl)
|
||||||
} else if (bookingStatus.isTimeout) {
|
} else if (bookingStatus.isTimeout) {
|
||||||
handlePaymentError("Timeout")
|
handlePaymentError("Timeout")
|
||||||
@@ -633,9 +635,7 @@ export default function PaymentClient({
|
|||||||
: ""
|
: ""
|
||||||
router.push(`${selectRate(lang)}${allSearchParams}`)
|
router.push(`${selectRate(lang)}${allSearchParams}`)
|
||||||
}}
|
}}
|
||||||
onAccept={() =>
|
onAccept={() => priceChange.mutate({ refId })}
|
||||||
priceChange.mutate({ confirmationNumber: bookingNumber })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function FindMyBooking() {
|
|||||||
const values = form.getValues()
|
const values = form.getValues()
|
||||||
const value = new URLSearchParams(values).toString()
|
const value = new URLSearchParams(values).toString()
|
||||||
document.cookie = `bv=${encodeURIComponent(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict`
|
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) => {
|
onError: (error) => {
|
||||||
console.error("Failed to create ref id", error)
|
console.error("Failed to create ref id", error)
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export default function AddAncillaryFlowModal({
|
|||||||
packages,
|
packages,
|
||||||
user,
|
user,
|
||||||
savedCreditCards,
|
savedCreditCards,
|
||||||
refId,
|
|
||||||
}: AddAncillaryFlowModalProps) {
|
}: AddAncillaryFlowModalProps) {
|
||||||
const {
|
const {
|
||||||
currentStep,
|
currentStep,
|
||||||
@@ -123,7 +122,7 @@ export default function AddAncillaryFlowModal({
|
|||||||
const addAncillary = trpc.booking.packages.useMutation()
|
const addAncillary = trpc.booking.packages.useMutation()
|
||||||
|
|
||||||
const { guaranteeBooking, isLoading, handleGuaranteeError } =
|
const { guaranteeBooking, isLoading, handleGuaranteeError } =
|
||||||
useGuaranteeBooking(booking.confirmationNumber, true, booking.hotelId)
|
useGuaranteeBooking(booking.refId, true, booking.hotelId)
|
||||||
|
|
||||||
function validateTermsAndConditions(data: AncillaryFormData): boolean {
|
function validateTermsAndConditions(data: AncillaryFormData): boolean {
|
||||||
if (!data.termsAndConditions) {
|
if (!data.termsAndConditions) {
|
||||||
@@ -145,7 +144,7 @@ export default function AddAncillaryFlowModal({
|
|||||||
) {
|
) {
|
||||||
addAncillary.mutate(
|
addAncillary.mutate(
|
||||||
{
|
{
|
||||||
confirmationNumber: booking.confirmationNumber,
|
refId: booking.refId,
|
||||||
ancillaryComment: data.optionalText,
|
ancillaryComment: data.optionalText,
|
||||||
ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
|
ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
|
||||||
? data.deliveryTime
|
? data.deliveryTime
|
||||||
@@ -176,7 +175,7 @@ export default function AddAncillaryFlowModal({
|
|||||||
clearAncillarySessionData()
|
clearAncillarySessionData()
|
||||||
closeModal()
|
closeModal()
|
||||||
utils.booking.get.invalidate({
|
utils.booking.get.invalidate({
|
||||||
confirmationNumber: booking.confirmationNumber,
|
refId: booking.refId,
|
||||||
})
|
})
|
||||||
router.refresh()
|
router.refresh()
|
||||||
} else {
|
} else {
|
||||||
@@ -202,7 +201,7 @@ export default function AddAncillaryFlowModal({
|
|||||||
selectedAncillary,
|
selectedAncillary,
|
||||||
data.deliveryTime
|
data.deliveryTime
|
||||||
)
|
)
|
||||||
if (booking.confirmationNumber) {
|
if (booking.refId) {
|
||||||
const card = savedCreditCard
|
const card = savedCreditCard
|
||||||
? {
|
? {
|
||||||
alias: savedCreditCard.alias,
|
alias: savedCreditCard.alias,
|
||||||
@@ -211,12 +210,12 @@ export default function AddAncillaryFlowModal({
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
guaranteeBooking.mutate({
|
guaranteeBooking.mutate({
|
||||||
confirmationNumber: booking.confirmationNumber,
|
refId: booking.refId,
|
||||||
language: lang,
|
language: lang,
|
||||||
...(card && { card }),
|
...(card && { card }),
|
||||||
success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
success: `${guaranteeRedirectUrl}?status=success&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
|
||||||
error: `${guaranteeRedirectUrl}?status=error&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
error: `${guaranteeRedirectUrl}?status=error&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
|
||||||
cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
cancel: `${guaranteeRedirectUrl}?status=cancel&ancillary=1&RefId=${encodeURIComponent(booking.refId)}`,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
handleGuaranteeError("No confirmation number")
|
handleGuaranteeError("No confirmation number")
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import { toast } from "@/components/TempDesignSystem/Toasts"
|
|||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
export default function RemoveButton({
|
export default function RemoveButton({
|
||||||
confirmationNumber,
|
refId,
|
||||||
codes,
|
codes,
|
||||||
title,
|
title,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}: {
|
}: {
|
||||||
confirmationNumber: string
|
refId: string
|
||||||
codes: string[]
|
codes: string[]
|
||||||
title?: string
|
title?: string
|
||||||
onSuccess: () => void
|
onSuccess: () => void
|
||||||
@@ -51,7 +51,7 @@ export default function RemoveButton({
|
|||||||
removePackage.mutate(
|
removePackage.mutate(
|
||||||
{
|
{
|
||||||
language: lang,
|
language: lang,
|
||||||
confirmationNumber,
|
refId,
|
||||||
codes,
|
codes,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export function AddedAncillaries({
|
|||||||
{booking.confirmationNumber && ancillary.code ? (
|
{booking.confirmationNumber && ancillary.code ? (
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<RemoveButton
|
<RemoveButton
|
||||||
confirmationNumber={booking.confirmationNumber}
|
refId={booking.refId}
|
||||||
codes={
|
codes={
|
||||||
ancillary.code ===
|
ancillary.code ===
|
||||||
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
|
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
|
||||||
@@ -192,7 +192,7 @@ export function AddedAncillaries({
|
|||||||
booking.canModifyAncillaries ? (
|
booking.canModifyAncillaries ? (
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<RemoveButton
|
<RemoveButton
|
||||||
confirmationNumber={booking.confirmationNumber}
|
refId={booking.refId}
|
||||||
codes={
|
codes={
|
||||||
ancillary.code ===
|
ancillary.code ===
|
||||||
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
|
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ import type { Lang } from "@/constants/languages"
|
|||||||
export default function GuaranteeAncillaryHandler({
|
export default function GuaranteeAncillaryHandler({
|
||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
returnUrl,
|
returnUrl,
|
||||||
|
refId,
|
||||||
lang,
|
lang,
|
||||||
}: {
|
}: {
|
||||||
confirmationNumber: string
|
confirmationNumber: string
|
||||||
|
refId: string
|
||||||
returnUrl: string
|
returnUrl: string
|
||||||
lang: Lang
|
lang: Lang
|
||||||
}) {
|
}) {
|
||||||
@@ -47,7 +49,7 @@ export default function GuaranteeAncillaryHandler({
|
|||||||
|
|
||||||
addAncillary.mutate(
|
addAncillary.mutate(
|
||||||
{
|
{
|
||||||
confirmationNumber,
|
refId,
|
||||||
ancillaryComment: formData.optionalText,
|
ancillaryComment: formData.optionalText,
|
||||||
ancillaryDeliveryTime: selectedAncillary.requiresDeliveryTime
|
ancillaryDeliveryTime: selectedAncillary.requiresDeliveryTime
|
||||||
? formData.deliveryTime
|
? formData.deliveryTime
|
||||||
@@ -86,7 +88,7 @@ export default function GuaranteeAncillaryHandler({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}, [confirmationNumber, returnUrl, addAncillary, lang, router])
|
}, [confirmationNumber, refId, returnUrl, addAncillary, lang, router])
|
||||||
|
|
||||||
return <LoadingSpinner />
|
return <LoadingSpinner />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export function Ancillaries({
|
|||||||
packages,
|
packages,
|
||||||
user,
|
user,
|
||||||
savedCreditCards,
|
savedCreditCards,
|
||||||
refId,
|
|
||||||
}: AncillariesProps) {
|
}: AncillariesProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const ancillaries = use(ancillariesPromise)
|
const ancillaries = use(ancillariesPromise)
|
||||||
@@ -221,7 +220,6 @@ export function Ancillaries({
|
|||||||
user={user}
|
user={user}
|
||||||
booking={booking}
|
booking={booking}
|
||||||
packages={packages}
|
packages={packages}
|
||||||
refId={refId}
|
|
||||||
savedCreditCards={savedCreditCards}
|
savedCreditCards={savedCreditCards}
|
||||||
/>
|
/>
|
||||||
</AncillaryFlowModalWrapper>
|
</AncillaryFlowModalWrapper>
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ import type { SafeUser } from "@/types/user"
|
|||||||
import type { Guest } from "@/server/routers/booking/output"
|
import type { Guest } from "@/server/routers/booking/output"
|
||||||
|
|
||||||
interface GuestDetailsProps {
|
interface GuestDetailsProps {
|
||||||
confirmationNumber: string
|
refId: string
|
||||||
guest: Guest
|
guest: Guest
|
||||||
isCancelled: boolean
|
isCancelled: boolean
|
||||||
user: SafeUser
|
user: SafeUser
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GuestDetails({
|
export default function GuestDetails({
|
||||||
confirmationNumber,
|
refId,
|
||||||
guest,
|
guest,
|
||||||
isCancelled,
|
isCancelled,
|
||||||
user,
|
user,
|
||||||
@@ -74,7 +74,7 @@ export default function GuestDetails({
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
utils.booking.get.invalidate({
|
utils.booking.get.invalidate({
|
||||||
confirmationNumber: data.confirmationNumber,
|
refId: data.refId,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(
|
toast.success(
|
||||||
@@ -106,7 +106,7 @@ export default function GuestDetails({
|
|||||||
|
|
||||||
async function onSubmit(data: ModifyContactSchema) {
|
async function onSubmit(data: ModifyContactSchema) {
|
||||||
updateGuest.mutate({
|
updateGuest.mutate({
|
||||||
confirmationNumber,
|
refId,
|
||||||
guest: {
|
guest: {
|
||||||
email: data.email,
|
email: data.email,
|
||||||
phoneNumber: data.phoneNumber,
|
phoneNumber: data.phoneNumber,
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
|
||||||
|
|
||||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
@@ -14,13 +12,14 @@ export default function Points({
|
|||||||
isCancelled,
|
isCancelled,
|
||||||
points,
|
points,
|
||||||
price,
|
price,
|
||||||
|
currencyCode,
|
||||||
}: {
|
}: {
|
||||||
isCancelled: boolean
|
isCancelled: boolean
|
||||||
points: number
|
points: number
|
||||||
price: number
|
price: number
|
||||||
|
currencyCode: CurrencyEnum
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const currency = useMyStayStore((state) => state.bookedRoom.currencyCode)
|
|
||||||
|
|
||||||
if (!points) {
|
if (!points) {
|
||||||
return <SkeletonShimmer width="100px" />
|
return <SkeletonShimmer width="100px" />
|
||||||
@@ -31,7 +30,7 @@ export default function Points({
|
|||||||
points,
|
points,
|
||||||
CurrencyEnum.POINTS,
|
CurrencyEnum.POINTS,
|
||||||
price,
|
price,
|
||||||
currency
|
currencyCode
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmat
|
|||||||
interface PriceTypeProps
|
interface PriceTypeProps
|
||||||
extends Pick<
|
extends Pick<
|
||||||
BookingConfirmation["booking"],
|
BookingConfirmation["booking"],
|
||||||
"cheques" | "rateDefinition" | "roomPoints" | "totalPrice" | "vouchers"
|
| "cheques"
|
||||||
|
| "currencyCode"
|
||||||
|
| "rateDefinition"
|
||||||
|
| "roomPoints"
|
||||||
|
| "totalPrice"
|
||||||
|
| "vouchers"
|
||||||
> {
|
> {
|
||||||
formattedTotalPrice: string
|
formattedTotalPrice: string
|
||||||
isCancelled: boolean
|
isCancelled: boolean
|
||||||
@@ -22,6 +27,7 @@ export default function PriceType({
|
|||||||
cheques,
|
cheques,
|
||||||
formattedTotalPrice,
|
formattedTotalPrice,
|
||||||
isCancelled,
|
isCancelled,
|
||||||
|
currencyCode,
|
||||||
priceType,
|
priceType,
|
||||||
rateDefinition,
|
rateDefinition,
|
||||||
roomPoints,
|
roomPoints,
|
||||||
@@ -51,6 +57,7 @@ export default function PriceType({
|
|||||||
isCancelled={isCancelled}
|
isCancelled={isCancelled}
|
||||||
points={roomPoints}
|
points={roomPoints}
|
||||||
price={totalPrice}
|
price={totalPrice}
|
||||||
|
currencyCode={currencyCode}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case PriceTypeEnum.voucher:
|
case PriceTypeEnum.voucher:
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
getBookingConfirmation,
|
getBookingConfirmation,
|
||||||
getProfileSafely,
|
getProfileSafely,
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
} from "@/lib/trpc/memoizedRequests"
|
||||||
import { decrypt } from "@/server/routers/utils/encryption"
|
|
||||||
|
|
||||||
import { auth } from "@/auth"
|
import { auth } from "@/auth"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
import { parseRefId } from "@/utils/refId"
|
||||||
import { isValidSession } from "@/utils/session"
|
import { isValidSession } from "@/utils/session"
|
||||||
|
|
||||||
import AdditionalInfoForm from "../../FindMyBooking/AdditionalInfoForm"
|
import AdditionalInfoForm from "../../FindMyBooking/AdditionalInfoForm"
|
||||||
@@ -32,18 +32,19 @@ import styles from "./receipt.module.css"
|
|||||||
import { CurrencyEnum } from "@/types/enums/currency"
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
|
||||||
export async function Receipt({ refId }: { refId: string }) {
|
export async function Receipt({ refId }: { refId: string }) {
|
||||||
const value = decrypt(refId)
|
const { confirmationNumber, lastName } = parseRefId(refId)
|
||||||
if (!value) {
|
|
||||||
|
if (!confirmationNumber) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await auth()
|
const session = await auth()
|
||||||
const isLoggedIn = isValidSession(session)
|
const isLoggedIn = isValidSession(session)
|
||||||
|
|
||||||
const [confirmationNumber, lastName] = value.split(",")
|
|
||||||
const bv = cookies().get("bv")?.value
|
const bv = cookies().get("bv")?.value
|
||||||
let bookingConfirmation
|
let bookingConfirmation
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
bookingConfirmation = await getBookingConfirmation(confirmationNumber)
|
bookingConfirmation = await getBookingConfirmation(refId)
|
||||||
} else if (bv) {
|
} else if (bv) {
|
||||||
const params = new URLSearchParams(bv)
|
const params = new URLSearchParams(bv)
|
||||||
const firstName = params.get("firstName")
|
const firstName = params.get("firstName")
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ export default function CancelStayPriceContainer() {
|
|||||||
const { totalAdults, totalChildren } = formRooms.reduce(
|
const { totalAdults, totalChildren } = formRooms.reduce(
|
||||||
(total, formRoom) => {
|
(total, formRoom) => {
|
||||||
if (formRoom.checked) {
|
if (formRoom.checked) {
|
||||||
const room = rooms.find(
|
const room = rooms.find((r) => r.refId === formRoom.refId)
|
||||||
(r) => r.confirmationNumber === formRoom.confirmationNumber
|
|
||||||
)
|
|
||||||
if (room) {
|
if (room) {
|
||||||
total.totalAdults = total.totalAdults + room.adults
|
total.totalAdults = total.totalAdults + room.adults
|
||||||
if (room.childrenInRoom.length) {
|
if (room.childrenInRoom.length) {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function FinalConfirmation({
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const cancelledRooms = rooms.filter((r) =>
|
const cancelledRooms = rooms.filter((r) =>
|
||||||
variables.confirmationNumbers.includes(r.confirmationNumber)
|
variables.refIds.includes(r.refId)
|
||||||
)
|
)
|
||||||
for (const cancelledRoom of cancelledRooms) {
|
for (const cancelledRoom of cancelledRooms) {
|
||||||
toast.success(
|
toast.success(
|
||||||
@@ -94,11 +94,11 @@ export default function FinalConfirmation({
|
|||||||
}
|
}
|
||||||
|
|
||||||
utils.booking.get.invalidate({
|
utils.booking.get.invalidate({
|
||||||
confirmationNumber: bookedRoom.confirmationNumber,
|
refId: bookedRoom.refId,
|
||||||
})
|
})
|
||||||
utils.booking.linkedReservations.invalidate({
|
utils.booking.linkedReservations.invalidate({
|
||||||
lang,
|
lang,
|
||||||
rooms: bookedRoom.linkedReservations,
|
refId: bookedRoom.refId,
|
||||||
})
|
})
|
||||||
closeModal()
|
closeModal()
|
||||||
},
|
},
|
||||||
@@ -113,12 +113,12 @@ export default function FinalConfirmation({
|
|||||||
|
|
||||||
function cancelBooking() {
|
function cancelBooking() {
|
||||||
if (Array.isArray(formRooms)) {
|
if (Array.isArray(formRooms)) {
|
||||||
const confirmationNumbersToCancel = formRooms
|
const refIdsToCancel = formRooms
|
||||||
.filter((r) => r.checked)
|
.filter((r) => r.checked)
|
||||||
.map((r) => r.confirmationNumber)
|
.map((r) => r.refId)
|
||||||
if (confirmationNumbersToCancel.length) {
|
if (refIdsToCancel.length) {
|
||||||
cancelBookingsMutation.mutate({
|
cancelBookingsMutation.mutate({
|
||||||
confirmationNumbers: confirmationNumbersToCancel,
|
refIds: refIdsToCancel,
|
||||||
language: lang,
|
language: lang,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function Steps({ closeModal }: StepsProps) {
|
|||||||
rooms: rooms.map((room, idx) => ({
|
rooms: rooms.map((room, idx) => ({
|
||||||
// Single room booking
|
// Single room booking
|
||||||
checked: rooms.length === 1,
|
checked: rooms.length === 1,
|
||||||
confirmationNumber: room.confirmationNumber,
|
refId: room.refId,
|
||||||
id: idx + 1,
|
id: idx + 1,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function Confirmation({
|
|||||||
onSuccess: (updatedBooking) => {
|
onSuccess: (updatedBooking) => {
|
||||||
if (updatedBooking) {
|
if (updatedBooking) {
|
||||||
utils.booking.get.invalidate({
|
utils.booking.get.invalidate({
|
||||||
confirmationNumber: updatedBooking.confirmationNumber,
|
refId: updatedBooking.refId,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(
|
toast.success(
|
||||||
@@ -86,7 +86,7 @@ export default function Confirmation({
|
|||||||
|
|
||||||
function handleModifyStay() {
|
function handleModifyStay() {
|
||||||
updateBooking.mutate({
|
updateBooking.mutate({
|
||||||
confirmationNumber: bookedRoom.confirmationNumber,
|
refId: bookedRoom.refId,
|
||||||
checkInDate,
|
checkInDate,
|
||||||
checkOutDate,
|
checkOutDate,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function Form() {
|
|||||||
confirmationNumber: state.bookedRoom.confirmationNumber,
|
confirmationNumber: state.bookedRoom.confirmationNumber,
|
||||||
currencyCode: state.bookedRoom.currencyCode,
|
currencyCode: state.bookedRoom.currencyCode,
|
||||||
hotelId: state.bookedRoom.hotelId,
|
hotelId: state.bookedRoom.hotelId,
|
||||||
refId: state.refId,
|
refId: state.bookedRoom.refId,
|
||||||
savedCreditCards: state.savedCreditCards,
|
savedCreditCards: state.savedCreditCards,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ export default function Form() {
|
|||||||
: undefined
|
: undefined
|
||||||
writeGlaToSessionStorage("yes", hotelId)
|
writeGlaToSessionStorage("yes", hotelId)
|
||||||
guaranteeBooking.mutate({
|
guaranteeBooking.mutate({
|
||||||
confirmationNumber,
|
refId,
|
||||||
language: lang,
|
language: lang,
|
||||||
...(card && { card }),
|
...(card && { card }),
|
||||||
success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}`,
|
success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}`,
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ export default function Room({ booking, roomNr, user }: RoomProps) {
|
|||||||
cheques={cheques}
|
cheques={cheques}
|
||||||
formattedTotalPrice={formattedTotalPrice}
|
formattedTotalPrice={formattedTotalPrice}
|
||||||
isCancelled={isCancelled}
|
isCancelled={isCancelled}
|
||||||
|
currencyCode={currencyCode}
|
||||||
priceType={priceType}
|
priceType={priceType}
|
||||||
rateDefinition={rateDefinition}
|
rateDefinition={rateDefinition}
|
||||||
roomPoints={roomPoints}
|
roomPoints={roomPoints}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default function PriceDetails() {
|
|||||||
cheques: state.bookedRoom.cheques,
|
cheques: state.bookedRoom.cheques,
|
||||||
formattedTotalPrice: state.totalPrice,
|
formattedTotalPrice: state.totalPrice,
|
||||||
isCancelled: state.bookedRoom.isCancelled,
|
isCancelled: state.bookedRoom.isCancelled,
|
||||||
|
currencyCode: state.bookedRoom.currencyCode,
|
||||||
priceType: state.bookedRoom.priceType,
|
priceType: state.bookedRoom.priceType,
|
||||||
rateDefinition: state.bookedRoom.rateDefinition,
|
rateDefinition: state.bookedRoom.rateDefinition,
|
||||||
roomPoints: state.bookedRoom.roomPoints,
|
roomPoints: state.bookedRoom.roomPoints,
|
||||||
|
|||||||
@@ -23,21 +23,15 @@ interface RoomProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SingleRoom({ user }: RoomProps) {
|
export default function SingleRoom({ user }: RoomProps) {
|
||||||
const {
|
const { refId, guest, isCancelled, isMultiRoom, roomName, roomNumber } =
|
||||||
confirmationNumber,
|
useMyStayStore((state) => ({
|
||||||
guest,
|
refId: state.bookedRoom.refId,
|
||||||
isCancelled,
|
guest: state.bookedRoom.guest,
|
||||||
isMultiRoom,
|
isCancelled: state.bookedRoom.isCancelled,
|
||||||
roomName,
|
isMultiRoom: state.rooms.length > 1,
|
||||||
roomNumber,
|
roomName: state.bookedRoom.roomName,
|
||||||
} = useMyStayStore((state) => ({
|
roomNumber: state.bookedRoom.roomNumber,
|
||||||
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,
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (isMultiRoom) {
|
if (isMultiRoom) {
|
||||||
return null
|
return null
|
||||||
@@ -70,7 +64,7 @@ export default function SingleRoom({ user }: RoomProps) {
|
|||||||
<Details />
|
<Details />
|
||||||
<div className={styles.guestDetailsDesktopWrapper}>
|
<div className={styles.guestDetailsDesktopWrapper}>
|
||||||
<GuestDetails
|
<GuestDetails
|
||||||
confirmationNumber={confirmationNumber}
|
refId={refId}
|
||||||
guest={guest}
|
guest={guest}
|
||||||
isCancelled={isCancelled}
|
isCancelled={isCancelled}
|
||||||
user={user}
|
user={user}
|
||||||
@@ -83,7 +77,7 @@ export default function SingleRoom({ user }: RoomProps) {
|
|||||||
<PriceDetails />
|
<PriceDetails />
|
||||||
<div className={styles.guestDetailsMobileWrapper}>
|
<div className={styles.guestDetailsMobileWrapper}>
|
||||||
<GuestDetails
|
<GuestDetails
|
||||||
confirmationNumber={confirmationNumber}
|
refId={refId}
|
||||||
guest={guest}
|
guest={guest}
|
||||||
isCancelled={isCancelled}
|
isCancelled={isCancelled}
|
||||||
user={user}
|
user={user}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default function TotalPrice() {
|
|||||||
cheques={bookedRoom.cheques}
|
cheques={bookedRoom.cheques}
|
||||||
formattedTotalPrice={formattedTotalPrice}
|
formattedTotalPrice={formattedTotalPrice}
|
||||||
isCancelled={bookedRoom.isCancelled}
|
isCancelled={bookedRoom.isCancelled}
|
||||||
|
currencyCode={bookedRoom.currencyCode}
|
||||||
priceType={bookedRoom.priceType}
|
priceType={bookedRoom.priceType}
|
||||||
rateDefinition={bookedRoom.rateDefinition}
|
rateDefinition={bookedRoom.rateDefinition}
|
||||||
roomPoints={bookedRoom.roomPoints}
|
roomPoints={bookedRoom.roomPoints}
|
||||||
|
|||||||
@@ -14,18 +14,32 @@ import type { Guest } from "@/server/routers/booking/output"
|
|||||||
describe("Access booking", () => {
|
describe("Access booking", () => {
|
||||||
describe("for logged in booking", () => {
|
describe("for logged in booking", () => {
|
||||||
it("should enable access if all is provided", () => {
|
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", () => {
|
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", () => {
|
it("should prompt to login without user", () => {
|
||||||
expect(accessBooking(loggedIn, "Booking", null)).toBe(ERROR_UNAUTHORIZED)
|
expect(accessBooking(loggedInGuest, "Booking", null)).toBe(
|
||||||
|
ERROR_UNAUTHORIZED
|
||||||
|
)
|
||||||
})
|
})
|
||||||
it("should deny access", () => {
|
it("should prompt to login if user mismatch", () => {
|
||||||
expect(accessBooking(loggedIn, "NotBooking", user)).toBe(ERROR_NOT_FOUND)
|
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", () => {
|
describe("for anonymous booking", () => {
|
||||||
it("should enable access if all is provided", () => {
|
it("should enable access if all is provided", () => {
|
||||||
const cookieString = new URLSearchParams({
|
const cookieString = new URLSearchParams({
|
||||||
@@ -34,7 +48,7 @@ describe("Access booking", () => {
|
|||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
email: "logged+out@scandichotels.com",
|
email: "logged+out@scandichotels.com",
|
||||||
}).toString()
|
}).toString()
|
||||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||||
ACCESS_GRANTED
|
ACCESS_GRANTED
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -45,7 +59,7 @@ describe("Access booking", () => {
|
|||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
email: "logged+out@scandichotels.com",
|
email: "logged+out@scandichotels.com",
|
||||||
}).toString()
|
}).toString()
|
||||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||||
ACCESS_GRANTED
|
ACCESS_GRANTED
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -56,7 +70,7 @@ describe("Access booking", () => {
|
|||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
email: "logged+out@scandichotels.com",
|
email: "logged+out@scandichotels.com",
|
||||||
}).toString()
|
}).toString()
|
||||||
expect(accessBooking(loggedOut, "BoOkInG", null, cookieString)).toBe(
|
expect(accessBooking(loggedOutGuest, "BoOkInG", null, cookieString)).toBe(
|
||||||
ACCESS_GRANTED
|
ACCESS_GRANTED
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -67,7 +81,7 @@ describe("Access booking", () => {
|
|||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
email: "LOGGED+out@scandichotels.com",
|
email: "LOGGED+out@scandichotels.com",
|
||||||
}).toString()
|
}).toString()
|
||||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||||
ACCESS_GRANTED
|
ACCESS_GRANTED
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -78,9 +92,14 @@ describe("Access booking", () => {
|
|||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
email: "logged+out@scandichotels.com",
|
email: "logged+out@scandichotels.com",
|
||||||
}).toString()
|
}).toString()
|
||||||
expect(accessBooking(loggedOut, "Booking", user, cookieString)).toBe(
|
expect(
|
||||||
ERROR_FORBIDDEN
|
accessBooking(
|
||||||
)
|
loggedOutGuest,
|
||||||
|
"Booking",
|
||||||
|
authenticatedUser,
|
||||||
|
cookieString
|
||||||
|
)
|
||||||
|
).toBe(ERROR_FORBIDDEN)
|
||||||
})
|
})
|
||||||
it("should prompt for more if first name is missing", () => {
|
it("should prompt for more if first name is missing", () => {
|
||||||
const cookieString = new URLSearchParams({
|
const cookieString = new URLSearchParams({
|
||||||
@@ -88,7 +107,7 @@ describe("Access booking", () => {
|
|||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
email: "logged+out@scandichotels.com",
|
email: "logged+out@scandichotels.com",
|
||||||
}).toString()
|
}).toString()
|
||||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||||
ERROR_BAD_REQUEST
|
ERROR_BAD_REQUEST
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -98,23 +117,25 @@ describe("Access booking", () => {
|
|||||||
firstName: "Anonymous",
|
firstName: "Anonymous",
|
||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
}).toString()
|
}).toString()
|
||||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||||
ERROR_BAD_REQUEST
|
ERROR_BAD_REQUEST
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
it("should prompt for more if cookie is invalid", () => {
|
it("should prompt for more if cookie is invalid", () => {
|
||||||
const cookieString = new URLSearchParams({}).toString()
|
const cookieString = new URLSearchParams({}).toString()
|
||||||
expect(accessBooking(loggedOut, "Booking", null, cookieString)).toBe(
|
expect(accessBooking(loggedOutGuest, "Booking", null, cookieString)).toBe(
|
||||||
ERROR_BAD_REQUEST
|
ERROR_BAD_REQUEST
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
it("should deny access", () => {
|
it("should deny access if refId mismatch", () => {
|
||||||
expect(accessBooking(loggedOut, "NotBooking", null)).toBe(ERROR_NOT_FOUND)
|
expect(accessBooking(loggedOutGuest, "NotBooking", null)).toBe(
|
||||||
|
ERROR_NOT_FOUND
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const user: SafeUser = {
|
const authenticatedUser: SafeUser = {
|
||||||
address: {
|
address: {
|
||||||
city: undefined,
|
city: undefined,
|
||||||
country: "Sweden",
|
country: "Sweden",
|
||||||
@@ -124,10 +145,10 @@ const user: SafeUser = {
|
|||||||
},
|
},
|
||||||
dateOfBirth: "",
|
dateOfBirth: "",
|
||||||
email: "",
|
email: "",
|
||||||
firstName: "",
|
firstName: "Authenticated",
|
||||||
language: undefined,
|
language: undefined,
|
||||||
lastName: "",
|
lastName: "Booking",
|
||||||
membershipNumber: "",
|
membershipNumber: "01234567890123",
|
||||||
membership: undefined,
|
membership: undefined,
|
||||||
loyalty: {
|
loyalty: {
|
||||||
memberships: [],
|
memberships: [],
|
||||||
@@ -145,7 +166,38 @@ const user: SafeUser = {
|
|||||||
profileId: "",
|
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",
|
email: "logged+out@scandichotels.com",
|
||||||
firstName: "Anonymous",
|
firstName: "Anonymous",
|
||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
@@ -154,7 +206,7 @@ const loggedOut: Guest = {
|
|||||||
countryCode: "SE",
|
countryCode: "SE",
|
||||||
}
|
}
|
||||||
|
|
||||||
const loggedIn: Guest = {
|
const loggedInGuest: Guest = {
|
||||||
email: "logged+in@scandichotels.com",
|
email: "logged+in@scandichotels.com",
|
||||||
firstName: "Authenticated",
|
firstName: "Authenticated",
|
||||||
lastName: "Booking",
|
lastName: "Booking",
|
||||||
|
|||||||
@@ -21,22 +21,20 @@ function accessBooking(
|
|||||||
) {
|
) {
|
||||||
if (guest.membershipNumber) {
|
if (guest.membershipNumber) {
|
||||||
if (user) {
|
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
|
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 (guest.lastName?.toLowerCase() === lastName.toLowerCase()) {
|
||||||
if (user) {
|
if (user) {
|
||||||
console.warn(
|
|
||||||
"Access to booking not granted due to logged in user attempting access to anonymous booking"
|
|
||||||
)
|
|
||||||
return ERROR_FORBIDDEN
|
return ERROR_FORBIDDEN
|
||||||
} else {
|
} else {
|
||||||
const params = new URLSearchParams(cookie)
|
const params = new URLSearchParams(cookie)
|
||||||
@@ -47,17 +45,11 @@ function accessBooking(
|
|||||||
) {
|
) {
|
||||||
return ACCESS_GRANTED
|
return ACCESS_GRANTED
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
|
||||||
"Access to booking not granted due to incorrect cookie values"
|
|
||||||
)
|
|
||||||
return ERROR_BAD_REQUEST
|
return ERROR_BAD_REQUEST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(
|
|
||||||
"Access to booking not granted due to anonymous user attempting access with incorrect lastname"
|
|
||||||
)
|
|
||||||
return ERROR_NOT_FOUND
|
return ERROR_NOT_FOUND
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ export function mapRoomDetails({
|
|||||||
priceType,
|
priceType,
|
||||||
rate,
|
rate,
|
||||||
rateDefinition: booking.rateDefinition,
|
rateDefinition: booking.rateDefinition,
|
||||||
|
refId: booking.refId,
|
||||||
reservationStatus: booking.reservationStatus,
|
reservationStatus: booking.reservationStatus,
|
||||||
room,
|
room,
|
||||||
roomName: room?.name ?? "",
|
roomName: room?.name ?? "",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export type Room = Pick<
|
|||||||
| "checkInDate"
|
| "checkInDate"
|
||||||
| "cheques"
|
| "cheques"
|
||||||
| "confirmationNumber"
|
| "confirmationNumber"
|
||||||
|
| "refId"
|
||||||
| "currencyCode"
|
| "currencyCode"
|
||||||
| "guest"
|
| "guest"
|
||||||
| "rateDefinition"
|
| "rateDefinition"
|
||||||
@@ -87,6 +88,7 @@ export default function BookedRoomSidePeek({
|
|||||||
cheques,
|
cheques,
|
||||||
childrenInRoom,
|
childrenInRoom,
|
||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
|
refId,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
guest,
|
guest,
|
||||||
isCancelled,
|
isCancelled,
|
||||||
@@ -377,6 +379,7 @@ export default function BookedRoomSidePeek({
|
|||||||
formattedTotalPrice={formattedTotalPrice}
|
formattedTotalPrice={formattedTotalPrice}
|
||||||
isCancelled={isCancelled}
|
isCancelled={isCancelled}
|
||||||
priceType={priceType}
|
priceType={priceType}
|
||||||
|
currencyCode={currencyCode}
|
||||||
rateDefinition={rateDefinition}
|
rateDefinition={rateDefinition}
|
||||||
roomPoints={roomPoints}
|
roomPoints={roomPoints}
|
||||||
totalPrice={totalPrice}
|
totalPrice={totalPrice}
|
||||||
@@ -409,7 +412,7 @@ export default function BookedRoomSidePeek({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<GuestDetails
|
<GuestDetails
|
||||||
confirmationNumber={confirmationNumber}
|
refId={refId}
|
||||||
guest={guest}
|
guest={guest}
|
||||||
isCancelled={isCancelled}
|
isCancelled={isCancelled}
|
||||||
user={user}
|
user={user}
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ export const FamilyAndFriendsCodes = ["D000029555", "D000029271", "D000029195"]
|
|||||||
export const REDEMPTION = "redemption"
|
export const REDEMPTION = "redemption"
|
||||||
export const SEARCHTYPE = "searchtype"
|
export const SEARCHTYPE = "searchtype"
|
||||||
|
|
||||||
export const BOOKING_CONFIRMATION_NUMBER = "confirmationNumber"
|
|
||||||
|
|
||||||
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"
|
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"
|
||||||
|
|
||||||
export enum PaymentMethodEnum {
|
export enum PaymentMethodEnum {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const maxRetries = 15
|
|||||||
const retryInterval = 2000
|
const retryInterval = 2000
|
||||||
|
|
||||||
export function useGuaranteeBooking(
|
export function useGuaranteeBooking(
|
||||||
confirmationNumber: string,
|
refId: string,
|
||||||
isAncillaryFlow = false,
|
isAncillaryFlow = false,
|
||||||
hotelId: string
|
hotelId: string
|
||||||
) {
|
) {
|
||||||
@@ -50,11 +50,12 @@ export function useGuaranteeBooking(
|
|||||||
const guaranteeBooking = trpc.booking.guarantee.useMutation({
|
const guaranteeBooking = trpc.booking.guarantee.useMutation({
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
|
const mainRoom = result.rooms[0]
|
||||||
if (result.reservationStatus == BookingStatusEnum.BookingCompleted) {
|
if (result.reservationStatus == BookingStatusEnum.BookingCompleted) {
|
||||||
utils.booking.get.invalidate({ confirmationNumber })
|
utils.booking.get.invalidate({ refId: mainRoom.refId })
|
||||||
} else {
|
} else {
|
||||||
setIsPollingForBookingStatus(true)
|
setIsPollingForBookingStatus(true)
|
||||||
utils.booking.status.invalidate({ confirmationNumber })
|
utils.booking.status.invalidate({ refId: mainRoom.refId })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleGuaranteeError()
|
handleGuaranteeError()
|
||||||
@@ -66,7 +67,7 @@ export function useGuaranteeBooking(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const bookingStatus = useHandleBookingStatus({
|
const bookingStatus = useHandleBookingStatus({
|
||||||
confirmationNumber,
|
refId,
|
||||||
expectedStatuses: [BookingStatusEnum.BookingCompleted],
|
expectedStatuses: [BookingStatusEnum.BookingCompleted],
|
||||||
maxRetries,
|
maxRetries,
|
||||||
retryInterval,
|
retryInterval,
|
||||||
@@ -76,7 +77,7 @@ export function useGuaranteeBooking(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bookingStatus?.data?.paymentUrl && isPollingForBookingStatus) {
|
if (bookingStatus?.data?.paymentUrl && isPollingForBookingStatus) {
|
||||||
router.push(bookingStatus.data.paymentUrl)
|
router.push(bookingStatus.data.paymentUrl)
|
||||||
utils.booking.get.invalidate({ confirmationNumber })
|
utils.booking.get.invalidate({ refId })
|
||||||
setIsPollingForBookingStatus(false)
|
setIsPollingForBookingStatus(false)
|
||||||
} else if (bookingStatus.isTimeout) {
|
} else if (bookingStatus.isTimeout) {
|
||||||
handleGuaranteeError("Timeout")
|
handleGuaranteeError("Timeout")
|
||||||
@@ -87,7 +88,7 @@ export function useGuaranteeBooking(
|
|||||||
handleGuaranteeError,
|
handleGuaranteeError,
|
||||||
setIsPollingForBookingStatus,
|
setIsPollingForBookingStatus,
|
||||||
isPollingForBookingStatus,
|
isPollingForBookingStatus,
|
||||||
confirmationNumber,
|
refId,
|
||||||
utils.booking.get,
|
utils.booking.get,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import { trpc } from "@/lib/trpc/client"
|
|||||||
import type { BookingStatusEnum } from "@/constants/booking"
|
import type { BookingStatusEnum } from "@/constants/booking"
|
||||||
|
|
||||||
export function useHandleBookingStatus({
|
export function useHandleBookingStatus({
|
||||||
confirmationNumber,
|
refId,
|
||||||
expectedStatuses,
|
expectedStatuses,
|
||||||
maxRetries,
|
maxRetries,
|
||||||
retryInterval,
|
retryInterval,
|
||||||
enabled,
|
enabled,
|
||||||
}: {
|
}: {
|
||||||
confirmationNumber: string | null
|
refId: string | null
|
||||||
expectedStatuses: BookingStatusEnum[]
|
expectedStatuses: BookingStatusEnum[]
|
||||||
maxRetries: number
|
maxRetries: number
|
||||||
retryInterval: number
|
retryInterval: number
|
||||||
@@ -22,7 +22,7 @@ export function useHandleBookingStatus({
|
|||||||
const retries = useRef(0)
|
const retries = useRef(0)
|
||||||
|
|
||||||
const query = trpc.booking.status.useQuery(
|
const query = trpc.booking.status.useQuery(
|
||||||
{ confirmationNumber: confirmationNumber ?? "" },
|
{ refId: refId ?? "" },
|
||||||
{
|
{
|
||||||
enabled,
|
enabled,
|
||||||
refetchInterval: (query) => {
|
refetchInterval: (query) => {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import type {
|
|||||||
HotelInput,
|
HotelInput,
|
||||||
} from "@/types/trpc/routers/hotel/hotel"
|
} from "@/types/trpc/routers/hotel/hotel"
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
import type { LinkedReservationsInput } from "@/server/routers/booking/input"
|
|
||||||
import type { GetHotelsByCSFilterInput } from "@/server/routers/hotels/input"
|
import type { GetHotelsByCSFilterInput } from "@/server/routers/hotels/input"
|
||||||
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
|
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
|
||||||
|
|
||||||
@@ -136,8 +135,8 @@ export const getPackages = cache(async function getMemoizedPackages(
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getBookingConfirmation = cache(
|
export const getBookingConfirmation = cache(
|
||||||
async function getMemoizedBookingConfirmation(confirmationNumber: string) {
|
async function getMemoizedBookingConfirmation(refId: string) {
|
||||||
return serverClient().booking.get({ confirmationNumber })
|
return serverClient().booking.get({ refId })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -156,8 +155,10 @@ export const findBooking = cache(async function getMemoizedFindBooking(
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getLinkedReservations = cache(
|
export const getLinkedReservations = cache(
|
||||||
async function getMemoizedLinkedReservations(input: LinkedReservationsInput) {
|
async function getMemoizedLinkedReservations(refId: string) {
|
||||||
return serverClient().booking.linkedReservations(input)
|
return serverClient().booking.linkedReservations({
|
||||||
|
refId,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function MyStayProvider({
|
|||||||
const { data, error, isFetching, isFetchedAfterMount } =
|
const { data, error, isFetching, isFetchedAfterMount } =
|
||||||
trpc.booking.get.useQuery(
|
trpc.booking.get.useQuery(
|
||||||
{
|
{
|
||||||
confirmationNumber: bookingConfirmation.booking.confirmationNumber,
|
refId: bookingConfirmation.booking.refId,
|
||||||
lang,
|
lang,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,7 +68,7 @@ export default function MyStayProvider({
|
|||||||
} = trpc.booking.linkedReservations.useQuery(
|
} = trpc.booking.linkedReservations.useQuery(
|
||||||
{
|
{
|
||||||
lang,
|
lang,
|
||||||
rooms: bookingConfirmation.booking.linkedReservations,
|
refId: bookingConfirmation.booking.refId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
initialData: linkedReservationsResponses,
|
initialData: linkedReservationsResponses,
|
||||||
|
|||||||
46
apps/scandic-web/server/plugins/refIdToConfirmationNumber.ts
Normal file
46
apps/scandic-web/server/plugins/refIdToConfirmationNumber.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { initTRPC } from "@trpc/server"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { parseRefId } from "@/utils/refId"
|
||||||
|
|
||||||
|
import type { Meta } from "@/types/trpc/meta"
|
||||||
|
import type { Context } from "../context"
|
||||||
|
|
||||||
|
export function createRefIdPlugin() {
|
||||||
|
const t = initTRPC.context<Context>().meta<Meta>().create()
|
||||||
|
|
||||||
|
return {
|
||||||
|
toConfirmationNumber: t.procedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
refId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.use(({ input, next }) => {
|
||||||
|
const { confirmationNumber } = parseRefId(input.refId)
|
||||||
|
return next({
|
||||||
|
ctx: {
|
||||||
|
confirmationNumber,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
toConfirmationNumbers: t.procedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
refIds: z.array(z.string()),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.use(({ input, next }) => {
|
||||||
|
const confirmationNumbers = input.refIds.map((refId) => {
|
||||||
|
const { confirmationNumber } = parseRefId(refId)
|
||||||
|
return confirmationNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
return next({
|
||||||
|
ctx: {
|
||||||
|
confirmationNumbers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,7 +103,6 @@ export const createBookingInput = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const addPackageInput = z.object({
|
export const addPackageInput = z.object({
|
||||||
confirmationNumber: z.string(),
|
|
||||||
ancillaryComment: z.string(),
|
ancillaryComment: z.string(),
|
||||||
ancillaryDeliveryTime: z.string().nullish(),
|
ancillaryDeliveryTime: z.string().nullish(),
|
||||||
packages: z.array(
|
packages: z.array(
|
||||||
@@ -117,22 +116,15 @@ export const addPackageInput = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const removePackageInput = z.object({
|
export const removePackageInput = z.object({
|
||||||
confirmationNumber: z.string(),
|
|
||||||
codes: z.array(z.string()),
|
codes: z.array(z.string()),
|
||||||
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
|
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const priceChangeInput = z.object({
|
|
||||||
confirmationNumber: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const cancelBookingsInput = z.object({
|
export const cancelBookingsInput = z.object({
|
||||||
confirmationNumbers: z.array(z.string()),
|
|
||||||
language: z.nativeEnum(Lang),
|
language: z.nativeEnum(Lang),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const guaranteeBookingInput = z.object({
|
export const guaranteeBookingInput = z.object({
|
||||||
confirmationNumber: z.string(),
|
|
||||||
card: z
|
card: z
|
||||||
.object({
|
.object({
|
||||||
alias: z.string(),
|
alias: z.string(),
|
||||||
@@ -156,7 +148,6 @@ export const createRefIdInput = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const updateBookingInput = z.object({
|
export const updateBookingInput = z.object({
|
||||||
confirmationNumber: z.string(),
|
|
||||||
checkInDate: z.string().optional(),
|
checkInDate: z.string().optional(),
|
||||||
checkOutDate: z.string().optional(),
|
checkOutDate: z.string().optional(),
|
||||||
guest: z
|
guest: z
|
||||||
@@ -169,19 +160,13 @@ export const updateBookingInput = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Query
|
// Query
|
||||||
const confirmationNumberInput = z.object({
|
|
||||||
confirmationNumber: z.string(),
|
export const getBookingInput = z.object({
|
||||||
lang: z.nativeEnum(Lang).optional(),
|
lang: z.nativeEnum(Lang).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getBookingInput = confirmationNumberInput
|
|
||||||
export const getLinkedReservationsInput = z.object({
|
export const getLinkedReservationsInput = z.object({
|
||||||
lang: z.nativeEnum(Lang).optional(),
|
lang: z.nativeEnum(Lang).optional(),
|
||||||
rooms: z.array(
|
|
||||||
z.object({
|
|
||||||
confirmationNumber: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const findBookingInput = z.object({
|
export const findBookingInput = z.object({
|
||||||
@@ -194,4 +179,6 @@ export const findBookingInput = z.object({
|
|||||||
|
|
||||||
export type LinkedReservationsInput = z.input<typeof getLinkedReservationsInput>
|
export type LinkedReservationsInput = z.input<typeof getLinkedReservationsInput>
|
||||||
|
|
||||||
export const getBookingStatusInput = confirmationNumberInput
|
export const getBookingStatusInput = z.object({
|
||||||
|
lang: z.nativeEnum(Lang).optional(),
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as api from "@/lib/api"
|
import * as api from "@/lib/api"
|
||||||
|
import { createRefIdPlugin } from "@/server/plugins/refIdToConfirmationNumber"
|
||||||
import { getMembershipNumber } from "@/server/routers/user/utils"
|
import { getMembershipNumber } from "@/server/routers/user/utils"
|
||||||
import { createCounter } from "@/server/telemetry"
|
import { createCounter } from "@/server/telemetry"
|
||||||
import { router, safeProtectedServiceProcedure } from "@/server/trpc"
|
import { router, safeProtectedServiceProcedure } from "@/server/trpc"
|
||||||
@@ -8,13 +9,14 @@ import {
|
|||||||
cancelBookingsInput,
|
cancelBookingsInput,
|
||||||
createBookingInput,
|
createBookingInput,
|
||||||
guaranteeBookingInput,
|
guaranteeBookingInput,
|
||||||
priceChangeInput,
|
|
||||||
removePackageInput,
|
removePackageInput,
|
||||||
updateBookingInput,
|
updateBookingInput,
|
||||||
} from "./input"
|
} from "./input"
|
||||||
import { bookingConfirmationSchema, createBookingSchema } from "./output"
|
import { bookingConfirmationSchema, createBookingSchema } from "./output"
|
||||||
import { cancelBooking } from "./utils"
|
import { cancelBooking } from "./utils"
|
||||||
|
|
||||||
|
const refIdPlugin = createRefIdPlugin()
|
||||||
|
|
||||||
export const bookingMutationRouter = router({
|
export const bookingMutationRouter = router({
|
||||||
create: safeProtectedServiceProcedure
|
create: safeProtectedServiceProcedure
|
||||||
.input(createBookingInput)
|
.input(createBookingInput)
|
||||||
@@ -72,9 +74,9 @@ export const bookingMutationRouter = router({
|
|||||||
return verifiedData.data
|
return verifiedData.data
|
||||||
}),
|
}),
|
||||||
priceChange: safeProtectedServiceProcedure
|
priceChange: safeProtectedServiceProcedure
|
||||||
.input(priceChangeInput)
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
.mutation(async function ({ ctx, input }) {
|
.mutation(async function ({ ctx }) {
|
||||||
const { confirmationNumber } = input
|
const { confirmationNumber } = ctx
|
||||||
|
|
||||||
const priceChangeCounter = createCounter("trpc.booking", "price-change")
|
const priceChangeCounter = createCounter("trpc.booking", "price-change")
|
||||||
const metricsPriceChange = priceChangeCounter.init({ confirmationNumber })
|
const metricsPriceChange = priceChangeCounter.init({ confirmationNumber })
|
||||||
@@ -91,7 +93,6 @@ export const bookingMutationRouter = router({
|
|||||||
api.endpoints.v1.Booking.priceChange(confirmationNumber),
|
api.endpoints.v1.Booking.priceChange(confirmationNumber),
|
||||||
{
|
{
|
||||||
headers,
|
headers,
|
||||||
body: input,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,9 +114,11 @@ export const bookingMutationRouter = router({
|
|||||||
}),
|
}),
|
||||||
cancel: safeProtectedServiceProcedure
|
cancel: safeProtectedServiceProcedure
|
||||||
.input(cancelBookingsInput)
|
.input(cancelBookingsInput)
|
||||||
|
.concat(refIdPlugin.toConfirmationNumbers)
|
||||||
.mutation(async function ({ ctx, input }) {
|
.mutation(async function ({ ctx, input }) {
|
||||||
const token = ctx.session?.token.access_token ?? ctx.serviceToken
|
const token = ctx.session?.token.access_token ?? ctx.serviceToken
|
||||||
const { confirmationNumbers, language } = input
|
const { confirmationNumbers } = ctx
|
||||||
|
const { language } = input
|
||||||
|
|
||||||
const responses = await Promise.allSettled(
|
const responses = await Promise.allSettled(
|
||||||
confirmationNumbers.map((confirmationNumber) =>
|
confirmationNumbers.map((confirmationNumber) =>
|
||||||
@@ -144,9 +147,11 @@ export const bookingMutationRouter = router({
|
|||||||
}),
|
}),
|
||||||
packages: safeProtectedServiceProcedure
|
packages: safeProtectedServiceProcedure
|
||||||
.input(addPackageInput)
|
.input(addPackageInput)
|
||||||
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
.mutation(async function ({ ctx, input }) {
|
.mutation(async function ({ ctx, input }) {
|
||||||
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
||||||
const { confirmationNumber, ...body } = input
|
const { confirmationNumber } = ctx
|
||||||
|
const { refId, ...body } = input
|
||||||
|
|
||||||
const addPackageCounter = createCounter("trpc.booking", "package.add")
|
const addPackageCounter = createCounter("trpc.booking", "package.add")
|
||||||
const metricsAddPackage = addPackageCounter.init({ confirmationNumber })
|
const metricsAddPackage = addPackageCounter.init({ confirmationNumber })
|
||||||
@@ -183,9 +188,11 @@ export const bookingMutationRouter = router({
|
|||||||
}),
|
}),
|
||||||
guarantee: safeProtectedServiceProcedure
|
guarantee: safeProtectedServiceProcedure
|
||||||
.input(guaranteeBookingInput)
|
.input(guaranteeBookingInput)
|
||||||
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
.mutation(async function ({ ctx, input }) {
|
.mutation(async function ({ ctx, input }) {
|
||||||
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
||||||
const { confirmationNumber, language, ...body } = input
|
const { confirmationNumber } = ctx
|
||||||
|
const { refId, language, ...body } = input
|
||||||
|
|
||||||
const guaranteeBookingCounter = createCounter("trpc.booking", "guarantee")
|
const guaranteeBookingCounter = createCounter("trpc.booking", "guarantee")
|
||||||
const metricsGuaranteeBooking = guaranteeBookingCounter.init({
|
const metricsGuaranteeBooking = guaranteeBookingCounter.init({
|
||||||
@@ -225,9 +232,11 @@ export const bookingMutationRouter = router({
|
|||||||
}),
|
}),
|
||||||
update: safeProtectedServiceProcedure
|
update: safeProtectedServiceProcedure
|
||||||
.input(updateBookingInput)
|
.input(updateBookingInput)
|
||||||
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
.mutation(async function ({ ctx, input }) {
|
.mutation(async function ({ ctx, input }) {
|
||||||
const accessToken = ctx.session?.token.access_token || ctx.serviceToken
|
const accessToken = ctx.session?.token.access_token || ctx.serviceToken
|
||||||
const { confirmationNumber, ...body } = input
|
const { confirmationNumber } = ctx
|
||||||
|
const { refId, ...body } = input
|
||||||
|
|
||||||
const updateBookingCounter = createCounter("trpc.booking", "update")
|
const updateBookingCounter = createCounter("trpc.booking", "update")
|
||||||
const metricsUpdateBooking = updateBookingCounter.init({
|
const metricsUpdateBooking = updateBookingCounter.init({
|
||||||
@@ -265,9 +274,11 @@ export const bookingMutationRouter = router({
|
|||||||
}),
|
}),
|
||||||
removePackage: safeProtectedServiceProcedure
|
removePackage: safeProtectedServiceProcedure
|
||||||
.input(removePackageInput)
|
.input(removePackageInput)
|
||||||
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
.mutation(async function ({ ctx, input }) {
|
.mutation(async function ({ ctx, input }) {
|
||||||
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
||||||
const { confirmationNumber, codes, language } = input
|
const { confirmationNumber } = ctx
|
||||||
|
const { codes, language } = input
|
||||||
|
|
||||||
const removePackageCounter = createCounter(
|
const removePackageCounter = createCounter(
|
||||||
"trpc.booking",
|
"trpc.booking",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { BookingStatusEnum, ChildBedTypeEnum } from "@/constants/booking"
|
import { BookingStatusEnum, ChildBedTypeEnum } from "@/constants/booking"
|
||||||
|
|
||||||
|
import { calculateRefId } from "@/utils/refId"
|
||||||
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
import { nullableIntValidator } from "@/utils/zod/numberValidator"
|
import { nullableIntValidator } from "@/utils/zod/numberValidator"
|
||||||
import {
|
import {
|
||||||
@@ -78,7 +79,13 @@ export const createBookingSchema = z
|
|||||||
type: d.data.type,
|
type: d.data.type,
|
||||||
reservationStatus: d.data.attributes.reservationStatus,
|
reservationStatus: d.data.attributes.reservationStatus,
|
||||||
paymentUrl: d.data.attributes.paymentUrl,
|
paymentUrl: d.data.attributes.paymentUrl,
|
||||||
rooms: d.data.attributes.rooms,
|
rooms: d.data.attributes.rooms.map((room) => {
|
||||||
|
const lastName = d.data.attributes.guest?.lastName ?? ""
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
refId: calculateRefId(room.confirmationNumber, lastName),
|
||||||
|
}
|
||||||
|
}),
|
||||||
errors: d.data.attributes.errors,
|
errors: d.data.attributes.errors,
|
||||||
guest: d.data.attributes.guest,
|
guest: d.data.attributes.guest,
|
||||||
}))
|
}))
|
||||||
@@ -248,6 +255,31 @@ export const bookingConfirmationSchema = z
|
|||||||
})
|
})
|
||||||
.transform(({ data }) => ({
|
.transform(({ data }) => ({
|
||||||
...data.attributes,
|
...data.attributes,
|
||||||
|
refId: calculateRefId(
|
||||||
|
data.attributes.confirmationNumber,
|
||||||
|
data.attributes.guest.lastName
|
||||||
|
),
|
||||||
|
linkedReservations: data.attributes.linkedReservations.map(
|
||||||
|
(linkedReservation) => {
|
||||||
|
/**
|
||||||
|
* We lazy load linked reservations in the client.
|
||||||
|
* The problem is that we need to load the reservation in order to
|
||||||
|
* calculate the refId for the reservation as the refId uses the guest's
|
||||||
|
* lastname in it. Ideally we should pass a promise to the React
|
||||||
|
* component that uses `use()` to resolve it. But right now we use tRPC
|
||||||
|
* in the client. That tRPC endpoint only uses the confirmationNumber
|
||||||
|
* from the refId. So that means we can pass whatever as the lastname
|
||||||
|
* here, because it is actually never read. We should change this ASAP.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
...linkedReservation,
|
||||||
|
refId: calculateRefId(
|
||||||
|
linkedReservation.confirmationNumber,
|
||||||
|
"" // TODO: Empty lastname here, see comment above
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
packages: data.attributes.packages.filter((p) => p.type !== "Ancillary"),
|
packages: data.attributes.packages.filter((p) => p.type !== "Ancillary"),
|
||||||
ancillaries: data.attributes.packages.filter((p) => p.type === "Ancillary"),
|
ancillaries: data.attributes.packages.filter((p) => p.type === "Ancillary"),
|
||||||
extraBedTypes: data.attributes.childBedPreferences,
|
extraBedTypes: data.attributes.childBedPreferences,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as api from "@/lib/api"
|
import * as api from "@/lib/api"
|
||||||
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
|
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
|
||||||
|
import { createRefIdPlugin } from "@/server/plugins/refIdToConfirmationNumber"
|
||||||
import { createCounter } from "@/server/telemetry"
|
import { createCounter } from "@/server/telemetry"
|
||||||
import {
|
import {
|
||||||
router,
|
router,
|
||||||
@@ -7,8 +8,10 @@ import {
|
|||||||
serviceProcedure,
|
serviceProcedure,
|
||||||
} from "@/server/trpc"
|
} from "@/server/trpc"
|
||||||
|
|
||||||
|
import { getBookedHotelRoom } from "@/utils/booking"
|
||||||
|
|
||||||
|
import { encrypt } from "../../../utils/encryption"
|
||||||
import { getHotel } from "../hotels/utils"
|
import { getHotel } from "../hotels/utils"
|
||||||
import { encrypt } from "../utils/encryption"
|
|
||||||
import {
|
import {
|
||||||
createRefIdInput,
|
createRefIdInput,
|
||||||
findBookingInput,
|
findBookingInput,
|
||||||
@@ -17,28 +20,31 @@ import {
|
|||||||
getLinkedReservationsInput,
|
getLinkedReservationsInput,
|
||||||
} from "./input"
|
} from "./input"
|
||||||
import { createBookingSchema } from "./output"
|
import { createBookingSchema } from "./output"
|
||||||
import { findBooking, getBookedHotelRoom, getBooking } from "./utils"
|
import { findBooking, getBooking } from "./utils"
|
||||||
|
|
||||||
|
const refIdPlugin = createRefIdPlugin()
|
||||||
|
|
||||||
export const bookingQueryRouter = router({
|
export const bookingQueryRouter = router({
|
||||||
get: safeProtectedServiceProcedure
|
get: safeProtectedServiceProcedure
|
||||||
.input(getBookingInput)
|
.input(getBookingInput)
|
||||||
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
const lang = input.lang ?? ctx.lang
|
const lang = input.lang ?? ctx.lang
|
||||||
const token = ctx.session?.token.access_token ?? ctx.serviceToken
|
|
||||||
return next({
|
return next({
|
||||||
ctx: {
|
ctx: {
|
||||||
lang,
|
lang,
|
||||||
token,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.query(async function ({ ctx, input: { confirmationNumber } }) {
|
.query(async function ({ ctx }) {
|
||||||
|
const { confirmationNumber, lang, serviceToken } = ctx
|
||||||
|
|
||||||
const getBookingCounter = createCounter("trpc.booking", "get")
|
const getBookingCounter = createCounter("trpc.booking", "get")
|
||||||
const metricsGetBooking = getBookingCounter.init({ confirmationNumber })
|
const metricsGetBooking = getBookingCounter.init({ confirmationNumber })
|
||||||
|
|
||||||
metricsGetBooking.start()
|
metricsGetBooking.start()
|
||||||
|
|
||||||
const booking = await getBooking(confirmationNumber, ctx.lang, ctx.token)
|
const booking = await getBooking(confirmationNumber, lang, serviceToken)
|
||||||
|
|
||||||
if (!booking) {
|
if (!booking) {
|
||||||
metricsGetBooking.dataError(
|
metricsGetBooking.dataError(
|
||||||
@@ -52,9 +58,9 @@ export const bookingQueryRouter = router({
|
|||||||
{
|
{
|
||||||
hotelId: booking.hotelId,
|
hotelId: booking.hotelId,
|
||||||
isCardOnlyPayment: false,
|
isCardOnlyPayment: false,
|
||||||
language: ctx.lang,
|
language: lang,
|
||||||
},
|
},
|
||||||
ctx.serviceToken
|
serviceToken
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!hotelData) {
|
if (!hotelData) {
|
||||||
@@ -141,6 +147,7 @@ export const bookingQueryRouter = router({
|
|||||||
}),
|
}),
|
||||||
linkedReservations: safeProtectedServiceProcedure
|
linkedReservations: safeProtectedServiceProcedure
|
||||||
.input(getLinkedReservationsInput)
|
.input(getLinkedReservationsInput)
|
||||||
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
const lang = input.lang ?? ctx.lang
|
const lang = input.lang ?? ctx.lang
|
||||||
const token = ctx.session?.token.access_token ?? ctx.serviceToken
|
const token = ctx.session?.token.access_token ?? ctx.serviceToken
|
||||||
@@ -151,27 +158,36 @@ export const bookingQueryRouter = router({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.query(async function ({ ctx, input: { rooms } }) {
|
.query(async function ({ ctx }) {
|
||||||
|
const { confirmationNumber, lang, token } = ctx
|
||||||
|
|
||||||
const getLinkedReservationsCounter = createCounter(
|
const getLinkedReservationsCounter = createCounter(
|
||||||
"trpc.booking",
|
"trpc.booking",
|
||||||
"linkedReservations"
|
"linkedReservations"
|
||||||
)
|
)
|
||||||
const metricsGetLinkedReservations = getLinkedReservationsCounter.init({
|
const metricsGetLinkedReservations = getLinkedReservationsCounter.init({
|
||||||
confirmationNumbers: rooms,
|
confirmationNumber,
|
||||||
})
|
})
|
||||||
|
|
||||||
metricsGetLinkedReservations.start()
|
metricsGetLinkedReservations.start()
|
||||||
|
|
||||||
const linkedReservationsResult = await Promise.allSettled(
|
const booking = await getBooking(confirmationNumber, lang, token)
|
||||||
rooms.map((room) =>
|
|
||||||
getBooking(room.confirmationNumber, ctx.lang, ctx.token)
|
if (!booking) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkedReservationsResults = await Promise.allSettled(
|
||||||
|
booking.linkedReservations.map((linkedReservation) =>
|
||||||
|
getBooking(linkedReservation.confirmationNumber, lang, token)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const linkedReservations = []
|
const linkedReservations = []
|
||||||
for (const booking of linkedReservationsResult) {
|
for (const linkedReservationsResult of linkedReservationsResults) {
|
||||||
if (booking.status === "fulfilled") {
|
if (linkedReservationsResult.status === "fulfilled") {
|
||||||
if (booking.value) {
|
if (linkedReservationsResult.value) {
|
||||||
linkedReservations.push(booking.value)
|
linkedReservations.push(linkedReservationsResult.value)
|
||||||
} else {
|
} else {
|
||||||
metricsGetLinkedReservations.dataError(
|
metricsGetLinkedReservations.dataError(
|
||||||
`Unexpected value for linked reservation`
|
`Unexpected value for linked reservation`
|
||||||
@@ -188,44 +204,44 @@ export const bookingQueryRouter = router({
|
|||||||
|
|
||||||
return linkedReservations
|
return linkedReservations
|
||||||
}),
|
}),
|
||||||
status: serviceProcedure.input(getBookingStatusInput).query(async function ({
|
status: serviceProcedure
|
||||||
ctx,
|
.input(getBookingStatusInput)
|
||||||
input,
|
.concat(refIdPlugin.toConfirmationNumber)
|
||||||
}) {
|
.query(async function ({ ctx }) {
|
||||||
const { confirmationNumber } = input
|
const { confirmationNumber } = ctx
|
||||||
|
|
||||||
const getBookingStatusCounter = createCounter("trpc.booking", "status")
|
const getBookingStatusCounter = createCounter("trpc.booking", "status")
|
||||||
const metricsGetBookingStatus = getBookingStatusCounter.init({
|
const metricsGetBookingStatus = getBookingStatusCounter.init({
|
||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
})
|
})
|
||||||
|
|
||||||
metricsGetBookingStatus.start()
|
metricsGetBookingStatus.start()
|
||||||
|
|
||||||
const apiResponse = await api.get(
|
const apiResponse = await api.get(
|
||||||
api.endpoints.v1.Booking.status(confirmationNumber),
|
api.endpoints.v1.Booking.status(confirmationNumber),
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!apiResponse.ok) {
|
||||||
|
await metricsGetBookingStatus.httpError(apiResponse)
|
||||||
|
throw serverErrorByStatus(apiResponse.status, apiResponse)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
if (!apiResponse.ok) {
|
const apiJson = await apiResponse.json()
|
||||||
await metricsGetBookingStatus.httpError(apiResponse)
|
const verifiedData = createBookingSchema.safeParse(apiJson)
|
||||||
throw serverErrorByStatus(apiResponse.status, apiResponse)
|
if (!verifiedData.success) {
|
||||||
}
|
metricsGetBookingStatus.validationError(verifiedData.error)
|
||||||
|
throw badRequestError()
|
||||||
|
}
|
||||||
|
|
||||||
const apiJson = await apiResponse.json()
|
metricsGetBookingStatus.success()
|
||||||
const verifiedData = createBookingSchema.safeParse(apiJson)
|
|
||||||
if (!verifiedData.success) {
|
|
||||||
metricsGetBookingStatus.validationError(verifiedData.error)
|
|
||||||
throw badRequestError()
|
|
||||||
}
|
|
||||||
|
|
||||||
metricsGetBookingStatus.success()
|
return verifiedData.data
|
||||||
|
}),
|
||||||
return verifiedData.data
|
|
||||||
}),
|
|
||||||
createRefId: serviceProcedure
|
createRefId: serviceProcedure
|
||||||
.input(createRefIdInput)
|
.input(createRefIdInput)
|
||||||
.mutation(async function ({ input }) {
|
.mutation(async function ({ input }) {
|
||||||
|
|||||||
@@ -5,35 +5,8 @@ import { toApiLang } from "@/server/utils"
|
|||||||
|
|
||||||
import { bookingConfirmationSchema, createBookingSchema } from "./output"
|
import { bookingConfirmationSchema, createBookingSchema } from "./output"
|
||||||
|
|
||||||
import type { Room } from "@/types/hotel"
|
|
||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
export function getBookedHotelRoom(
|
|
||||||
rooms: Room[],
|
|
||||||
roomTypeCode: BookingConfirmation["booking"]["roomTypeCode"]
|
|
||||||
) {
|
|
||||||
if (!rooms.length || !roomTypeCode) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const room = rooms.find((r) => {
|
|
||||||
return r.roomTypes.find((roomType) => roomType.code === roomTypeCode)
|
|
||||||
})
|
|
||||||
if (!room) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const bedType = room.roomTypes.find(
|
|
||||||
(roomType) => roomType.code === roomTypeCode
|
|
||||||
)
|
|
||||||
if (!bedType) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...room,
|
|
||||||
bedType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getBooking(
|
export async function getBooking(
|
||||||
confirmationNumber: string,
|
confirmationNumber: string,
|
||||||
lang: Lang,
|
lang: Lang,
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { myStay } from "@/constants/routes/myStay"
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
import * as api from "@/lib/api"
|
import * as api from "@/lib/api"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { encrypt } from "@/server/routers/utils/encryption"
|
|
||||||
import { createCounter } from "@/server/telemetry"
|
import { createCounter } from "@/server/telemetry"
|
||||||
|
|
||||||
import { cache } from "@/utils/cache"
|
import { cache } from "@/utils/cache"
|
||||||
|
import { encrypt } from "@/utils/encryption"
|
||||||
import * as maskValue from "@/utils/maskValue"
|
import * as maskValue from "@/utils/maskValue"
|
||||||
import { isValidSession } from "@/utils/session"
|
import { isValidSession } from "@/utils/session"
|
||||||
import { getCurrentWebUrl } from "@/utils/url"
|
import { getCurrentWebUrl } from "@/utils/url"
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import { produce } from "immer"
|
|||||||
import { useContext } from "react"
|
import { useContext } from "react"
|
||||||
import { create, useStore } from "zustand"
|
import { create, useStore } from "zustand"
|
||||||
|
|
||||||
import { getBookedHotelRoom } from "@/server/routers/booking/utils"
|
|
||||||
|
|
||||||
import { mapRoomDetails } from "@/components/HotelReservation/MyStay/utils/mapRoomDetails"
|
import { mapRoomDetails } from "@/components/HotelReservation/MyStay/utils/mapRoomDetails"
|
||||||
import { MyStayContext } from "@/contexts/MyStay"
|
import { MyStayContext } from "@/contexts/MyStay"
|
||||||
|
import { getBookedHotelRoom } from "@/utils/booking"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
calculateTotalPoints,
|
calculateTotalPoints,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
} from "@/types/trpc/routers/booking/confirmation"
|
} from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
export interface BookingConfirmationProps {
|
export interface BookingConfirmationProps {
|
||||||
confirmationNumber: string
|
refId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BookingConfirmationRoom extends Room {
|
export interface BookingConfirmationRoom extends Room {
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import type { z } from "zod"
|
|
||||||
|
|
||||||
import type { Room } from "@/types/hotel"
|
import type { Room } from "@/types/hotel"
|
||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
import type { linkedReservationSchema } from "@/server/routers/booking/output"
|
|
||||||
|
|
||||||
export interface LinkedReservationSchema
|
|
||||||
extends z.output<typeof linkedReservationSchema> {}
|
|
||||||
|
|
||||||
export interface BookingConfirmationRoomsProps
|
export interface BookingConfirmationRoomsProps
|
||||||
extends Pick<BookingConfirmation, "booking"> {
|
extends Pick<BookingConfirmation, "booking"> {
|
||||||
@@ -14,5 +8,4 @@ export interface BookingConfirmationRoomsProps
|
|||||||
}
|
}
|
||||||
checkInTime: string
|
checkInTime: string
|
||||||
checkOutTime: string
|
checkOutTime: string
|
||||||
linkedReservations: LinkedReservationSchema[]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export interface LinkedReservationProps {
|
export interface LinkedReservationProps {
|
||||||
checkInTime: string
|
checkInTime: string
|
||||||
checkOutTime: string
|
checkOutTime: string
|
||||||
confirmationNumber: string
|
refId: string
|
||||||
roomIndex: number
|
roomIndex: number
|
||||||
roomNumber: number
|
roomNumber: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const cancelStaySchema = z.object({
|
|||||||
rooms: z.array(
|
rooms: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
checked: z.boolean(),
|
checked: z.boolean(),
|
||||||
confirmationNumber: z.string(),
|
refId: z.string(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export interface AncillariesProps extends Pick<BookingConfirmation, "booking"> {
|
|||||||
packages: Packages | null
|
packages: Packages | null
|
||||||
user: User | null
|
user: User | null
|
||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
refId: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddedAncillariesProps {
|
export interface AddedAncillariesProps {
|
||||||
@@ -41,7 +40,6 @@ export interface AncillaryGridModalProps {
|
|||||||
export interface AddAncillaryFlowModalProps
|
export interface AddAncillaryFlowModalProps
|
||||||
extends Pick<BookingConfirmation, "booking"> {
|
extends Pick<BookingConfirmation, "booking"> {
|
||||||
packages: Packages | null
|
packages: Packages | null
|
||||||
refId: string
|
|
||||||
user: User | null
|
user: User | null
|
||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface Room {
|
|||||||
packages: BookingConfirmation["booking"]["packages"]
|
packages: BookingConfirmation["booking"]["packages"]
|
||||||
formattedRoomCost: string
|
formattedRoomCost: string
|
||||||
rateDefinition: BookingConfirmation["booking"]["rateDefinition"]
|
rateDefinition: BookingConfirmation["booking"]["rateDefinition"]
|
||||||
|
refId: string
|
||||||
roomFeatures?: PackageSchema[] | null
|
roomFeatures?: PackageSchema[] | null
|
||||||
roomPoints: number
|
roomPoints: number
|
||||||
roomPrice: number
|
roomPrice: number
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type Room = Pick<
|
|||||||
| "linkedReservations"
|
| "linkedReservations"
|
||||||
| "multiRoom"
|
| "multiRoom"
|
||||||
| "rateDefinition"
|
| "rateDefinition"
|
||||||
|
| "refId"
|
||||||
| "reservationStatus"
|
| "reservationStatus"
|
||||||
| "roomPoints"
|
| "roomPoints"
|
||||||
| "roomTypeCode"
|
| "roomTypeCode"
|
||||||
|
|||||||
27
apps/scandic-web/utils/booking.ts
Normal file
27
apps/scandic-web/utils/booking.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { Room } from "@/types/hotel"
|
||||||
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
|
export function getBookedHotelRoom(
|
||||||
|
rooms: Room[],
|
||||||
|
roomTypeCode: BookingConfirmation["booking"]["roomTypeCode"]
|
||||||
|
) {
|
||||||
|
if (!rooms.length || !roomTypeCode) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const room = rooms.find((r) => {
|
||||||
|
return r.roomTypes.find((roomType) => roomType.code === roomTypeCode)
|
||||||
|
})
|
||||||
|
if (!room) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const bedType = room.roomTypes.find(
|
||||||
|
(roomType) => roomType.code === roomTypeCode
|
||||||
|
)
|
||||||
|
if (!bedType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
bedType,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,11 @@ import crypto from "crypto"
|
|||||||
|
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
export { decrypt, encrypt }
|
|
||||||
|
|
||||||
const algorithm = "DES-ECB"
|
const algorithm = "DES-ECB"
|
||||||
const encryptionKey = env.BOOKING_ENCRYPTION_KEY
|
const encryptionKey = env.BOOKING_ENCRYPTION_KEY
|
||||||
const bufferKey = Buffer.from(encryptionKey, "utf8")
|
const bufferKey = Buffer.from(encryptionKey, "utf8")
|
||||||
|
|
||||||
function encrypt(originalString: string) {
|
export function encrypt(originalString: string) {
|
||||||
try {
|
try {
|
||||||
const cipher = crypto.createCipheriv(algorithm, bufferKey, null)
|
const cipher = crypto.createCipheriv(algorithm, bufferKey, null)
|
||||||
cipher.setAutoPadding(false)
|
cipher.setAutoPadding(false)
|
||||||
@@ -32,7 +30,7 @@ function encrypt(originalString: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrypt(encryptedString: string) {
|
export function decrypt(encryptedString: string) {
|
||||||
try {
|
try {
|
||||||
const decipher = crypto.createDecipheriv(algorithm, bufferKey, null)
|
const decipher = crypto.createDecipheriv(algorithm, bufferKey, null)
|
||||||
decipher.setAutoPadding(false)
|
decipher.setAutoPadding(false)
|
||||||
21
apps/scandic-web/utils/refId.ts
Normal file
21
apps/scandic-web/utils/refId.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import "server-only"
|
||||||
|
|
||||||
|
import { decrypt, encrypt } from "./encryption"
|
||||||
|
|
||||||
|
export function calculateRefId(confirmationNumber: string, lastName: string) {
|
||||||
|
const encryptedRefId = encrypt(`${confirmationNumber},${lastName}`)
|
||||||
|
|
||||||
|
return encryptedRefId
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRefId(refId: string) {
|
||||||
|
const data = decrypt(refId)
|
||||||
|
const parts = data.split(",")
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
throw new Error("Invalid refId format")
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
confirmationNumber: parts[0],
|
||||||
|
lastName: parts[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user