From 644ce369aab5e6a535430031a5d4efe618a1b93a Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Fri, 1 Nov 2024 10:24:56 +0100 Subject: [PATCH] fix: booking confirmation validation --- .../booking-confirmation/page.tsx | 27 ++- server/routers/booking/output.ts | 18 +- server/routers/booking/query.ts | 12 +- server/routers/hotels/input.ts | 2 + server/routers/hotels/query.ts | 204 +++++++++--------- 5 files changed, 145 insertions(+), 118 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx index ef7e818c4..c4a682da9 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx @@ -45,8 +45,8 @@ export default async function BookingConfirmationPage({ } ) - const fromDate = dt(booking.temp.fromDate).locale(params.lang) - const toDate = dt(booking.temp.toDate).locale(params.lang) + const fromDate = dt(booking.checkInDate).locale(params.lang) + const toDate = dt(booking.checkOutDate).locale(params.lang) const nights = intl.formatMessage( { id: "booking.nights" }, { @@ -77,7 +77,7 @@ export default async function BookingConfirmationPage({ textTransform="regular" type="h1" > - {booking.hotel.name} + {booking.hotel?.data.attributes.name} @@ -91,7 +91,7 @@ export default async function BookingConfirmationPage({ {intl.formatMessage( { id: "Reference #{bookingNr}" }, - { bookingNr: "A92320VV" } + { bookingNr: booking.confirmationNumber } )} @@ -183,11 +183,13 @@ export default async function BookingConfirmationPage({
- {booking.hotel.name} + {booking.hotel?.data.attributes.name} - {booking.hotel.email} - {booking.hotel.phoneNumber} + {booking.hotel?.data.attributes.contactInformation.email} + + + {booking.hotel?.data.attributes.contactInformation.phoneNumber}
@@ -219,7 +221,16 @@ export default async function BookingConfirmationPage({ {intl.formatMessage({ id: "Total cost" })} - {booking.temp.total} + + {" "} + {intl.formatMessage( + { id: "{amount} {currency}" }, + { + amount: intl.formatNumber(booking.totalPrice), + currency: booking.currencyCode, + } + )} + {`${intl.formatMessage({ id: "Approx." })} ${booking.temp.totalInEuro}`} diff --git a/server/routers/booking/output.ts b/server/routers/booking/output.ts index aacf1ca6b..cf4536a0d 100644 --- a/server/routers/booking/output.ts +++ b/server/routers/booking/output.ts @@ -44,14 +44,18 @@ const childrenAgesSchema = z.object({ const guestSchema = z.object({ firstName: z.string(), lastName: z.string(), + email: z.string().nullable(), + phoneNumber: z.string().nullable(), }) -const packagesSchema = z.object({ - accessibility: z.boolean(), - allergyFriendly: z.boolean(), - breakfast: z.boolean(), - petFriendly: z.boolean(), -}) +const packagesSchema = z.array( + z.object({ + accessibility: z.boolean().optional(), + allergyFriendly: z.boolean().optional(), + breakfast: z.boolean().optional(), + petFriendly: z.boolean().optional(), + }) +) export const bookingConfirmationSchema = z .object({ @@ -66,7 +70,7 @@ export const bookingConfirmationSchema = z confirmationNumber: z.string(), currencyCode: z.string(), guest: guestSchema, - hasPayRouting: z.boolean(), + hasPayRouting: z.boolean().optional(), hotelId: z.string(), packages: packagesSchema, rateCode: z.string(), diff --git a/server/routers/booking/query.ts b/server/routers/booking/query.ts index 5c72bb284..17555f8c4 100644 --- a/server/routers/booking/query.ts +++ b/server/routers/booking/query.ts @@ -4,6 +4,7 @@ import * as api from "@/lib/api" import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc" import { router, serviceProcedure } from "@/server/trpc" +import { getHotelData } from "../hotels/query" import { bookingConfirmationInput, getBookingStatusInput } from "./input" import { bookingConfirmationSchema, createBookingSchema } from "./output" @@ -81,6 +82,11 @@ export const bookingQueryRouter = router({ throw badRequestError() } + const hotelData = await getHotelData( + { hotelId: booking.data.hotelId, language: ctx.lang }, + ctx.serviceToken + ) + getBookingConfirmationSuccessCounter.add(1, { confirmationNumber }) console.info( "api.booking.confirmation success", @@ -91,6 +97,7 @@ export const bookingQueryRouter = router({ return { ...booking.data, + hotel: hotelData, temp: { breakfastFrom: "06:30", breakfastTo: "11:00", @@ -127,11 +134,6 @@ export const bookingQueryRouter = router({ memberbershipNumber: "19822", phoneNumber: "+46702446688", }, - hotel: { - email: "bookings@scandichotels.com", - name: "Downtown Camper by Scandic", - phoneNumber: "+4689001350", - }, } }), status: serviceProcedure.input(getBookingStatusInput).query(async function ({ diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index d4fa1a109..ecf4d5cad 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -68,6 +68,8 @@ export const getHotelDataInputSchema = z.object({ include: z.array(z.nativeEnum(HotelIncludeEnum)).optional(), }) +export type HotelDataInput = z.input + export const getBreakfastPackageInputSchema = z.object({ adults: z.number().min(1, { message: "at least one adult is required" }), fromDate: z diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index a8d21ca22..627ad1f8d 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -39,6 +39,7 @@ import { getRatesInputSchema, getRoomsAvailabilityInputSchema, getSelectedRoomAvailabilityInputSchema, + type HotelDataInput, } from "./input" import { breakfastPackagesSchema, @@ -162,6 +163,110 @@ async function getContentstackData(lang: Lang, uid?: string | null) { return hotelPageData.data.hotel_page } +export async function getHotelData( + input: HotelDataInput, + serviceToken: string +) { + const { hotelId, language, include, isCardOnlyPayment } = input + + const params: Record = { + hotelId, + language, + } + + if (include) { + params.include = include.join(",") + } + + getHotelCounter.add(1, { + hotelId, + language, + include, + }) + console.info( + "api.hotels.hotelData start", + JSON.stringify({ query: { hotelId, params } }) + ) + + const apiResponse = await api.get( + api.endpoints.v1.Hotel.Hotels.hotel(hotelId), + { + headers: { + Authorization: `Bearer ${serviceToken}`, + }, + }, + params + ) + + if (!apiResponse.ok) { + const text = await apiResponse.text() + getHotelFailCounter.add(1, { + hotelId, + language, + include, + error_type: "http_error", + error: JSON.stringify({ + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }), + }) + console.error( + "api.hotels.hotelData error", + JSON.stringify({ + query: { hotelId, params }, + error: { + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }, + }) + ) + return null + } + + const apiJson = await apiResponse.json() + const validateHotelData = getHotelDataSchema.safeParse(apiJson) + + if (!validateHotelData.success) { + getHotelFailCounter.add(1, { + hotelId, + language, + include, + error_type: "validation_error", + error: JSON.stringify(validateHotelData.error), + }) + + console.error( + "api.hotels.hotelData validation error", + JSON.stringify({ + query: { hotelId, params }, + error: validateHotelData.error, + }) + ) + throw badRequestError() + } + + getHotelSuccessCounter.add(1, { + hotelId, + language, + include, + }) + console.info( + "api.hotels.hotelData success", + JSON.stringify({ + query: { hotelId, params: params }, + }) + ) + + if (isCardOnlyPayment) { + validateHotelData.data.data.attributes.merchantInformationData.alternatePaymentOptions = + [] + } + + return validateHotelData.data +} + export const hotelQueryRouter = router({ get: contentStackUidWithServiceProcedure .input(getHotelInputSchema) @@ -753,104 +858,7 @@ export const hotelQueryRouter = router({ get: serviceProcedure .input(getHotelDataInputSchema) .query(async ({ ctx, input }) => { - const { hotelId, language, include, isCardOnlyPayment } = input - - const params: Record = { - hotelId, - language, - } - - if (include) { - params.include = include.join(",") - } - - getHotelCounter.add(1, { - hotelId, - language, - include, - }) - console.info( - "api.hotels.hotelData start", - JSON.stringify({ query: { hotelId, params } }) - ) - - const apiResponse = await api.get( - api.endpoints.v1.Hotel.Hotels.hotel(hotelId), - { - headers: { - Authorization: `Bearer ${ctx.serviceToken}`, - }, - }, - params - ) - - if (!apiResponse.ok) { - const text = await apiResponse.text() - getHotelFailCounter.add(1, { - hotelId, - language, - include, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.hotels.hotelData error", - JSON.stringify({ - query: { hotelId, params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - return null - } - - const apiJson = await apiResponse.json() - const validateHotelData = getHotelDataSchema.safeParse(apiJson) - - if (!validateHotelData.success) { - getHotelFailCounter.add(1, { - hotelId, - language, - include, - error_type: "validation_error", - error: JSON.stringify(validateHotelData.error), - }) - - console.error( - "api.hotels.hotelData validation error", - JSON.stringify({ - query: { hotelId, params }, - error: validateHotelData.error, - }) - ) - throw badRequestError() - } - - getHotelSuccessCounter.add(1, { - hotelId, - language, - include, - }) - console.info( - "api.hotels.hotelData success", - JSON.stringify({ - query: { hotelId, params: params }, - }) - ) - - if (isCardOnlyPayment) { - validateHotelData.data.data.attributes.merchantInformationData.alternatePaymentOptions = - [] - } - - return validateHotelData.data + return getHotelData(input, ctx.serviceToken) }), }), locations: router({