fix: booking confirmation validation
This commit is contained in:
@@ -45,8 +45,8 @@ export default async function BookingConfirmationPage({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const fromDate = dt(booking.temp.fromDate).locale(params.lang)
|
const fromDate = dt(booking.checkInDate).locale(params.lang)
|
||||||
const toDate = dt(booking.temp.toDate).locale(params.lang)
|
const toDate = dt(booking.checkOutDate).locale(params.lang)
|
||||||
const nights = intl.formatMessage(
|
const nights = intl.formatMessage(
|
||||||
{ id: "booking.nights" },
|
{ id: "booking.nights" },
|
||||||
{
|
{
|
||||||
@@ -77,7 +77,7 @@ export default async function BookingConfirmationPage({
|
|||||||
textTransform="regular"
|
textTransform="regular"
|
||||||
type="h1"
|
type="h1"
|
||||||
>
|
>
|
||||||
{booking.hotel.name}
|
{booking.hotel?.data.attributes.name}
|
||||||
</Title>
|
</Title>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
<Body className={styles.body} textAlign="center">
|
<Body className={styles.body} textAlign="center">
|
||||||
@@ -91,7 +91,7 @@ export default async function BookingConfirmationPage({
|
|||||||
<Subtitle color="burgundy" type="two">
|
<Subtitle color="burgundy" type="two">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Reference #{bookingNr}" },
|
{ id: "Reference #{bookingNr}" },
|
||||||
{ bookingNr: "A92320VV" }
|
{ bookingNr: booking.confirmationNumber }
|
||||||
)}
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
</header>
|
</header>
|
||||||
@@ -183,11 +183,13 @@ export default async function BookingConfirmationPage({
|
|||||||
</Caption>
|
</Caption>
|
||||||
<div>
|
<div>
|
||||||
<Body color="burgundy" textTransform="bold">
|
<Body color="burgundy" textTransform="bold">
|
||||||
{booking.hotel.name}
|
{booking.hotel?.data.attributes.name}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">{booking.hotel.email}</Body>
|
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{booking.hotel.phoneNumber}
|
{booking.hotel?.data.attributes.contactInformation.email}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{booking.hotel?.data.attributes.contactInformation.phoneNumber}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,7 +221,16 @@ export default async function BookingConfirmationPage({
|
|||||||
<Body color="burgundy" textTransform="bold">
|
<Body color="burgundy" textTransform="bold">
|
||||||
{intl.formatMessage({ id: "Total cost" })}
|
{intl.formatMessage({ id: "Total cost" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">{booking.temp.total}</Body>
|
<Body color="uiTextHighContrast">
|
||||||
|
{" "}
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{amount} {currency}" },
|
||||||
|
{
|
||||||
|
amount: intl.formatNumber(booking.totalPrice),
|
||||||
|
currency: booking.currencyCode,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
<Caption color="uiTextPlaceholder">
|
<Caption color="uiTextPlaceholder">
|
||||||
{`${intl.formatMessage({ id: "Approx." })} ${booking.temp.totalInEuro}`}
|
{`${intl.formatMessage({ id: "Approx." })} ${booking.temp.totalInEuro}`}
|
||||||
</Caption>
|
</Caption>
|
||||||
|
|||||||
@@ -44,14 +44,18 @@ const childrenAgesSchema = z.object({
|
|||||||
const guestSchema = z.object({
|
const guestSchema = z.object({
|
||||||
firstName: z.string(),
|
firstName: z.string(),
|
||||||
lastName: z.string(),
|
lastName: z.string(),
|
||||||
|
email: z.string().nullable(),
|
||||||
|
phoneNumber: z.string().nullable(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const packagesSchema = z.object({
|
const packagesSchema = z.array(
|
||||||
accessibility: z.boolean(),
|
z.object({
|
||||||
allergyFriendly: z.boolean(),
|
accessibility: z.boolean().optional(),
|
||||||
breakfast: z.boolean(),
|
allergyFriendly: z.boolean().optional(),
|
||||||
petFriendly: z.boolean(),
|
breakfast: z.boolean().optional(),
|
||||||
})
|
petFriendly: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
export const bookingConfirmationSchema = z
|
export const bookingConfirmationSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -66,7 +70,7 @@ export const bookingConfirmationSchema = z
|
|||||||
confirmationNumber: z.string(),
|
confirmationNumber: z.string(),
|
||||||
currencyCode: z.string(),
|
currencyCode: z.string(),
|
||||||
guest: guestSchema,
|
guest: guestSchema,
|
||||||
hasPayRouting: z.boolean(),
|
hasPayRouting: z.boolean().optional(),
|
||||||
hotelId: z.string(),
|
hotelId: z.string(),
|
||||||
packages: packagesSchema,
|
packages: packagesSchema,
|
||||||
rateCode: z.string(),
|
rateCode: z.string(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as api from "@/lib/api"
|
|||||||
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
|
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
|
||||||
import { router, serviceProcedure } from "@/server/trpc"
|
import { router, serviceProcedure } from "@/server/trpc"
|
||||||
|
|
||||||
|
import { getHotelData } from "../hotels/query"
|
||||||
import { bookingConfirmationInput, getBookingStatusInput } from "./input"
|
import { bookingConfirmationInput, getBookingStatusInput } from "./input"
|
||||||
import { bookingConfirmationSchema, createBookingSchema } from "./output"
|
import { bookingConfirmationSchema, createBookingSchema } from "./output"
|
||||||
|
|
||||||
@@ -81,6 +82,11 @@ export const bookingQueryRouter = router({
|
|||||||
throw badRequestError()
|
throw badRequestError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hotelData = await getHotelData(
|
||||||
|
{ hotelId: booking.data.hotelId, language: ctx.lang },
|
||||||
|
ctx.serviceToken
|
||||||
|
)
|
||||||
|
|
||||||
getBookingConfirmationSuccessCounter.add(1, { confirmationNumber })
|
getBookingConfirmationSuccessCounter.add(1, { confirmationNumber })
|
||||||
console.info(
|
console.info(
|
||||||
"api.booking.confirmation success",
|
"api.booking.confirmation success",
|
||||||
@@ -91,6 +97,7 @@ export const bookingQueryRouter = router({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...booking.data,
|
...booking.data,
|
||||||
|
hotel: hotelData,
|
||||||
temp: {
|
temp: {
|
||||||
breakfastFrom: "06:30",
|
breakfastFrom: "06:30",
|
||||||
breakfastTo: "11:00",
|
breakfastTo: "11:00",
|
||||||
@@ -127,11 +134,6 @@ export const bookingQueryRouter = router({
|
|||||||
memberbershipNumber: "19822",
|
memberbershipNumber: "19822",
|
||||||
phoneNumber: "+46702446688",
|
phoneNumber: "+46702446688",
|
||||||
},
|
},
|
||||||
hotel: {
|
|
||||||
email: "bookings@scandichotels.com",
|
|
||||||
name: "Downtown Camper by Scandic",
|
|
||||||
phoneNumber: "+4689001350",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
status: serviceProcedure.input(getBookingStatusInput).query(async function ({
|
status: serviceProcedure.input(getBookingStatusInput).query(async function ({
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ export const getHotelDataInputSchema = z.object({
|
|||||||
include: z.array(z.nativeEnum(HotelIncludeEnum)).optional(),
|
include: z.array(z.nativeEnum(HotelIncludeEnum)).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export type HotelDataInput = z.input<typeof getHotelDataInputSchema>
|
||||||
|
|
||||||
export const getBreakfastPackageInputSchema = z.object({
|
export const getBreakfastPackageInputSchema = z.object({
|
||||||
adults: z.number().min(1, { message: "at least one adult is required" }),
|
adults: z.number().min(1, { message: "at least one adult is required" }),
|
||||||
fromDate: z
|
fromDate: z
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
getRatesInputSchema,
|
getRatesInputSchema,
|
||||||
getRoomsAvailabilityInputSchema,
|
getRoomsAvailabilityInputSchema,
|
||||||
getSelectedRoomAvailabilityInputSchema,
|
getSelectedRoomAvailabilityInputSchema,
|
||||||
|
type HotelDataInput,
|
||||||
} from "./input"
|
} from "./input"
|
||||||
import {
|
import {
|
||||||
breakfastPackagesSchema,
|
breakfastPackagesSchema,
|
||||||
@@ -162,6 +163,110 @@ async function getContentstackData(lang: Lang, uid?: string | null) {
|
|||||||
return hotelPageData.data.hotel_page
|
return hotelPageData.data.hotel_page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getHotelData(
|
||||||
|
input: HotelDataInput,
|
||||||
|
serviceToken: string
|
||||||
|
) {
|
||||||
|
const { hotelId, language, include, isCardOnlyPayment } = input
|
||||||
|
|
||||||
|
const params: Record<string, string> = {
|
||||||
|
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({
|
export const hotelQueryRouter = router({
|
||||||
get: contentStackUidWithServiceProcedure
|
get: contentStackUidWithServiceProcedure
|
||||||
.input(getHotelInputSchema)
|
.input(getHotelInputSchema)
|
||||||
@@ -753,104 +858,7 @@ export const hotelQueryRouter = router({
|
|||||||
get: serviceProcedure
|
get: serviceProcedure
|
||||||
.input(getHotelDataInputSchema)
|
.input(getHotelDataInputSchema)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { hotelId, language, include, isCardOnlyPayment } = input
|
return getHotelData(input, ctx.serviceToken)
|
||||||
|
|
||||||
const params: Record<string, string> = {
|
|
||||||
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
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
locations: router({
|
locations: router({
|
||||||
|
|||||||
Reference in New Issue
Block a user