From ac493fe32535a7563ff9ccfc2c89f9dbf1c552e4 Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Thu, 20 Mar 2025 07:38:29 +0000 Subject: [PATCH] Merged in feat/SW-1149-handle-status-polling (pull request #1562) Feat/SW-1149 handle status polling * feat(SW-1149): move terms and conditions sections to separate component and added copy * feat(SW-1149): Added client component to handle success callback for payment flow * fix: check for bookingCompleted status as well * feat(SW-1587): use alert instead of toast for showing payment errors * fix: added enum for payment callback status * fix: proper way of checking for multiple statuses * fix: update schema type * fix: use localised link to customer service * fix: update to use enum for status strings Approved-by: Arvid Norlin --- .../gla-payment-callback/page.tsx | 17 ++-- .../payment-callback/page.tsx | 38 ++++----- .../EnterDetails/Confirm/confirm.module.css | 1 - .../EnterDetails/Confirm/index.tsx | 53 +----------- .../Payment/PaymentAlert/index.tsx | 84 +++++++++++++++++++ .../PaymentAlert/paymentAlert.module.css | 4 + .../{index.tsx => HandleErrorCallback.tsx} | 9 +- .../PaymentCallback/HandleSuccessCallback.tsx | 68 +++++++++++++++ .../PaymentCallback/TimeoutSpinner/index.tsx | 45 ++++++++++ .../TimeoutSpinner/timeoutSpinner.module.css | 18 ++++ .../EnterDetails/Payment/PaymentClient.tsx | 71 +++------------- .../Payment/TermsAndConditions/index.tsx | 70 ++++++++++++++++ .../MyStay/GuaranteeLateArrival/index.tsx | 4 +- .../TempDesignSystem/Alert/alert.module.css | 6 ++ .../TempDesignSystem/Alert/alert.ts | 1 + .../TempDesignSystem/Alert/index.tsx | 10 +++ apps/scandic-web/constants/booking.ts | 6 ++ .../hooks/booking/useHandleBookingStatus.ts | 10 ++- apps/scandic-web/i18n/dictionaries/da.json | 2 + apps/scandic-web/i18n/dictionaries/de.json | 2 + apps/scandic-web/i18n/dictionaries/en.json | 2 + apps/scandic-web/i18n/dictionaries/fi.json | 2 + apps/scandic-web/i18n/dictionaries/no.json | 2 + apps/scandic-web/i18n/dictionaries/sv.json | 2 + .../server/routers/booking/output.ts | 12 +-- 25 files changed, 384 insertions(+), 155 deletions(-) create mode 100644 apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/paymentAlert.module.css rename apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/{index.tsx => HandleErrorCallback.tsx} (84%) create mode 100644 apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback.tsx create mode 100644 apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/timeoutSpinner.module.css create mode 100644 apps/scandic-web/components/HotelReservation/EnterDetails/Payment/TermsAndConditions/index.tsx diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx index fa59294f6..b029551ce 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx @@ -1,6 +1,9 @@ import { redirect } from "next/navigation" -import { BookingErrorCodeEnum } from "@/constants/booking" +import { + BookingErrorCodeEnum, + PaymentCallbackStatusEnum, +} from "@/constants/booking" import { hotelreservation } from "@/constants/routes/hotelReservation" import { serverClient } from "@/lib/trpc/server" @@ -14,7 +17,7 @@ export default async function GuaranteePaymentCallbackPage({ }: PageArgs< LangParams, { - status: "error" | "success" | "cancel" + status: PaymentCallbackStatusEnum refId: string confirmationNumber?: string } @@ -26,7 +29,11 @@ export default async function GuaranteePaymentCallbackPage({ const refId = searchParams.refId const myStayUrl = `${hotelreservation(lang)}/my-stay?RefId=${refId}` - if (status === "success" && confirmationNumber && refId) { + if ( + status === PaymentCallbackStatusEnum.Success && + confirmationNumber && + refId + ) { console.log(`[gla-payment-callback] redirecting to: ${myStayUrl}`) redirect(myStayUrl) } @@ -58,10 +65,10 @@ export default async function GuaranteePaymentCallbackPage({ console.error( `[gla-payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}` ) - if (status === "cancel") { + if (status === PaymentCallbackStatusEnum.Cancel) { searchObject.set("errorCode", BookingErrorCodeEnum.TransactionCancelled) } - if (status === "error") { + if (status === PaymentCallbackStatusEnum.Error) { searchObject.set( "errorCode", BookingErrorCodeEnum.TransactionFailed.toString() diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx index db5229d5e..74984d9e0 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx @@ -1,9 +1,7 @@ -import { redirect } from "next/navigation" - import { BOOKING_CONFIRMATION_NUMBER, BookingErrorCodeEnum, - MEMBERSHIP_FAILED_ERROR, + PaymentCallbackStatusEnum, } from "@/constants/booking" import { bookingConfirmation, @@ -11,7 +9,8 @@ import { } from "@/constants/routes/hotelReservation" import { serverClient } from "@/lib/trpc/server" -import PaymentCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback" +import HandleErrorCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleErrorCallback" +import HandleSuccessCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback" import type { LangParams, PageArgs } from "@/types/params" @@ -21,7 +20,7 @@ export default async function PaymentCallbackPage({ }: PageArgs< LangParams, { - status: "error" | "success" | "cancel" + status: PaymentCallbackStatusEnum confirmationNumber?: string hotel?: string } @@ -31,21 +30,18 @@ export default async function PaymentCallbackPage({ const status = searchParams.status const confirmationNumber = searchParams.confirmationNumber - if (status === "success" && confirmationNumber) { - const bookingStatus = await serverClient().booking.status({ - confirmationNumber, - }) - const membershipFailedError = bookingStatus.errors.find( - (e) => e.errorCode === MEMBERSHIP_FAILED_ERROR + if (status === PaymentCallbackStatusEnum.Success && confirmationNumber) { + const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${confirmationNumber}` + console.log( + `[payment-callback] rendering success callback with confirmation number: ${confirmationNumber}` ) - const errorParam = membershipFailedError - ? `&errorCode=${membershipFailedError.errorCode}` - : "" - const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${confirmationNumber}${errorParam}` - - console.log(`[payment-callback] redirecting to: ${confirmationUrl}`) - redirect(confirmationUrl) + return ( + + ) } const returnUrl = details(lang) @@ -76,10 +72,10 @@ export default async function PaymentCallbackPage({ console.error( `[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}` ) - if (status === "cancel") { + if (status === PaymentCallbackStatusEnum.Cancel) { searchObject.set("errorCode", BookingErrorCodeEnum.TransactionCancelled) } - if (status === "error") { + if (status === PaymentCallbackStatusEnum.Error) { searchObject.set("errorCode", BookingErrorCodeEnum.TransactionFailed) errorMessage = `Failed to get booking status for ${confirmationNumber}, status: ${status}` } @@ -87,7 +83,7 @@ export default async function PaymentCallbackPage({ } return ( -
- - -

- {intl.formatMessage({ - id: "I would like to get my booking confirmation via sms", - })} -

-
-
-
- - -

- {intl.formatMessage( - { - id: "By paying with any of the payment methods available, I accept the terms for this booking and the general Terms & Conditions, and understand that Scandic will process my personal data for this booking in accordance with Scandic's Privacy policy. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.", - }, - { - termsAndConditionsLink: (str) => ( - - {str} - - ), - privacyPolicyLink: (str) => ( - - {str} - - ), - } - )} -

-
-
-
+
) diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/index.tsx new file mode 100644 index 000000000..9edd83637 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/index.tsx @@ -0,0 +1,84 @@ +"use client" + +import { usePathname, useSearchParams } from "next/navigation" +import { useEffect, useState } from "react" +import { useIntl } from "react-intl" + +import { BookingErrorCodeEnum } from "@/constants/booking" +import { useEnterDetailsStore } from "@/stores/enter-details" + +import Alert from "@/components/TempDesignSystem/Alert" + +import styles from "./paymentAlert.module.css" + +import { AlertTypeEnum } from "@/types/enums/alert" + +function useBookingErrorAlert() { + const updateSearchParams = useEnterDetailsStore( + (state) => state.actions.updateSeachParamString + ) + const intl = useIntl() + const searchParams = useSearchParams() + const pathname = usePathname() + + const errorCode = searchParams.get("errorCode") + const errorMessage = getErrorMessage(errorCode) + const severityLevel = + errorCode === BookingErrorCodeEnum.TransactionCancelled + ? AlertTypeEnum.Warning + : AlertTypeEnum.Alarm + + const [showAlert, setShowAlert] = useState(!!errorCode) + + function getErrorMessage(errorCode: string | null) { + switch (errorCode) { + case BookingErrorCodeEnum.TransactionCancelled: + return intl.formatMessage({ + id: "You have now cancelled your payment.", + }) + default: + return intl.formatMessage({ + id: "We had an issue processing your booking. Please try again. No charges have been made.", + }) + } + } + + function discardAlert() { + setShowAlert(false) + const queryParams = new URLSearchParams(searchParams.toString()) + queryParams.delete("errorCode") + updateSearchParams(queryParams.toString()) + + window.history.replaceState({}, "", `${pathname}?${queryParams.toString()}`) + } + + return { showAlert, errorMessage, severityLevel, discardAlert, setShowAlert } +} + +interface PaymentAlertProps { + isVisible?: boolean +} + +export default function PaymentAlert({ isVisible = false }: PaymentAlertProps) { + const { showAlert, errorMessage, severityLevel, discardAlert, setShowAlert } = + useBookingErrorAlert() + + useEffect(() => { + if (isVisible) { + setShowAlert(true) + } + }, [isVisible, setShowAlert]) + + if (!showAlert) return null + + return ( +
+ +
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/paymentAlert.module.css b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/paymentAlert.module.css new file mode 100644 index 000000000..df4d7a180 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/paymentAlert.module.css @@ -0,0 +1,4 @@ +.wrapper { + margin-top: var(--Spacing-x3); + max-width: min(100%, 620px); +} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleErrorCallback.tsx similarity index 84% rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/index.tsx rename to apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleErrorCallback.tsx index e0a148dff..f9be4f179 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleErrorCallback.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/navigation" import { useEffect } from "react" +import { PaymentCallbackStatusEnum } from "@/constants/booking" import { detailsStorageName } from "@/stores/enter-details" import LoadingSpinner from "@/components/LoadingSpinner" @@ -11,7 +12,7 @@ import { convertObjToSearchParams } from "@/utils/url" import type { PersistedState } from "@/types/stores/enter-details" -export default function PaymentCallback({ +export default function HandleErrorCallback({ returnUrl, searchObject, status, @@ -19,7 +20,7 @@ export default function PaymentCallback({ }: { returnUrl: string searchObject: URLSearchParams - status: "error" | "success" | "cancel" + status: PaymentCallbackStatusEnum errorMessage?: string }) { const router = useRouter() @@ -34,14 +35,14 @@ export default function PaymentCallback({ searchObject ) - if (status === "cancel") { + if (status === PaymentCallbackStatusEnum.Cancel) { trackPaymentEvent({ event: "paymentCancel", hotelId: detailsStorage.booking.hotelId, status: "cancelled", }) } - if (status === "error") { + if (status === PaymentCallbackStatusEnum.Error) { trackPaymentEvent({ event: "paymentFail", hotelId: detailsStorage.booking.hotelId, diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback.tsx new file mode 100644 index 000000000..1d4f03aea --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback.tsx @@ -0,0 +1,68 @@ +"use client" + +import { useRouter } from "next/navigation" +import { useEffect } from "react" + +import { BookingStatusEnum, MEMBERSHIP_FAILED_ERROR } from "@/constants/booking" + +import LoadingSpinner from "@/components/LoadingSpinner" +import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus" + +import TimeoutSpinner from "./TimeoutSpinner" + +const validBookingStatuses = [ + BookingStatusEnum.PaymentSucceeded, + BookingStatusEnum.BookingCompleted, +] + +interface HandleStatusPollingProps { + confirmationNumber: string + successRedirectUrl: string +} + +export default function HandleSuccessCallback({ + confirmationNumber, + successRedirectUrl, +}: HandleStatusPollingProps) { + const router = useRouter() + + const { + data: bookingStatus, + error, + isTimeout, + } = useHandleBookingStatus({ + confirmationNumber, + expectedStatuses: validBookingStatuses, + maxRetries: 10, + retryInterval: 2000, + enabled: true, + }) + + useEffect(() => { + if (!bookingStatus?.reservationStatus) { + return + } + + if ( + validBookingStatuses.includes( + bookingStatus.reservationStatus as BookingStatusEnum + ) + ) { + // a successful booking can still have membership errors + const membershipFailedError = bookingStatus.errors.find( + (e) => e.errorCode === MEMBERSHIP_FAILED_ERROR + ) + const errorParam = membershipFailedError + ? `&errorCode=${membershipFailedError.errorCode}` + : "" + + router.replace(`${successRedirectUrl}${errorParam}`) + } + }, [bookingStatus, successRedirectUrl, router]) + + if (isTimeout || error) { + return + } + + return +} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/index.tsx new file mode 100644 index 000000000..879f91e33 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/index.tsx @@ -0,0 +1,45 @@ +"use client" + +import { useIntl } from "react-intl" + +import { customerService } from "@/constants/currentWebHrefs" + +import LoadingSpinner from "@/components/LoadingSpinner" +import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import useLang from "@/hooks/useLang" + +import styles from "./timeoutSpinner.module.css" + +export default function TimeoutSpinner() { + const intl = useIntl() + const lang = useLang() + + return ( +
+ + + {intl.formatMessage({ id: "Taking longer than usual" })} + + + {intl.formatMessage( + { + id: "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.", + }, + { + link: (text) => ( + + {text} + + ), + } + )} + +
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/timeoutSpinner.module.css b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/timeoutSpinner.module.css new file mode 100644 index 000000000..acce4b75b --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentCallback/TimeoutSpinner/timeoutSpinner.module.css @@ -0,0 +1,18 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: var(--Spacing-x2); + text-align: center; +} + +.container .heading { + margin-bottom: var(--Spacing-x1); +} + +.messageContainer { + max-width: 435px; + text-align: center; +} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx index 23e2300f0..2639d84dd 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx @@ -13,10 +13,6 @@ import { PAYMENT_METHOD_TITLES, PaymentMethodEnum, } from "@/constants/booking" -import { - bookingTermsAndConditions, - privacyPolicy, -} from "@/constants/currentWebHrefs" import { bookingConfirmation, selectRate, @@ -27,15 +23,10 @@ import { useEnterDetailsStore } from "@/stores/enter-details" import LoadingSpinner from "@/components/LoadingSpinner" import Button from "@/components/TempDesignSystem/Button" -import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" -import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" -import { toast } from "@/components/TempDesignSystem/Toasts" import { useAvailablePaymentOptions } from "@/hooks/booking/useAvailablePaymentOptions" import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus" -import { usePaymentFailedToast } from "@/hooks/booking/usePaymentFailedToast" import useLang from "@/hooks/useLang" import { trackPaymentEvent } from "@/utils/tracking" @@ -46,8 +37,10 @@ import GuaranteeDetails from "./GuaranteeDetails" import { hasFlexibleRate, hasPrepaidRate, isPaymentMethodEnum } from "./helpers" import MixedRatePaymentBreakdown from "./MixedRatePaymentBreakdown" import MySavedCards from "./MySavedCards" +import PaymentAlert from "./PaymentAlert" import PaymentOption from "./PaymentOption" import { type PaymentFormData, paymentSchema } from "./schema" +import TermsAndConditions from "./TermsAndConditions" import styles from "./payment.module.css" @@ -74,6 +67,8 @@ export default function PaymentClient({ const intl = useIntl() const searchParams = useSearchParams() + const [showPaymentAlert, setShowPaymentAlert] = useState(false) + const { booking, canProceedToPayment, rooms, totalPrice } = useEnterDetailsStore((state) => ({ booking: state.booking, @@ -109,8 +104,6 @@ export default function PaymentClient({ const hasFlexRates = rooms.some(hasFlexibleRate) const hasMixedRates = hasPrepaidRates && hasFlexRates - usePaymentFailedToast() - const methods = useForm({ defaultValues: { paymentMethod: savedCreditCards?.length @@ -181,7 +174,7 @@ export default function PaymentClient({ const bookingStatus = useHandleBookingStatus({ confirmationNumber: bookingNumber, - expectedStatus: BookingStatusEnum.BookingCompleted, + expectedStatuses: [BookingStatusEnum.BookingCompleted], maxRetries, retryInterval, enabled: isPollingForBookingStatus, @@ -189,11 +182,8 @@ export default function PaymentClient({ const handlePaymentError = useCallback( (errorMessage: string) => { - toast.error( - intl.formatMessage({ - id: "We had an issue processing your booking. Please try again. No charges have been made.", - }) - ) + setShowPaymentAlert(true) + const currentPaymentMethod = methods.getValues("paymentMethod") const smsEnable = methods.getValues("smsConfirmation") const isSavedCreditCard = savedCreditCards?.some( @@ -210,7 +200,7 @@ export default function PaymentClient({ status: "failed", }) }, - [intl, methods, savedCreditCards, hotelId] + [methods, savedCreditCards, hotelId] ) useEffect(() => { @@ -396,6 +386,7 @@ export default function PaymentClient({ ? confirm : payment} +
- - {intl.formatMessage( - { - id: "By paying with any of the payment methods available, I accept the terms for this booking and the general Terms & Conditions, and understand that Scandic will process my personal data for this booking in accordance with Scandic's Privacy policy. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.", - }, - { - termsAndConditionsLink: (str) => ( - - {str} - - ), - privacyPolicyLink: (str) => ( - - {str} - - ), - } - )} - - - - {intl.formatMessage({ - id: "I accept the terms and conditions", - })} - - - - - {intl.formatMessage({ - id: "I would like to get my booking confirmation via sms", - })} - - +
)} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/TermsAndConditions/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/TermsAndConditions/index.tsx new file mode 100644 index 000000000..58a8aa783 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/TermsAndConditions/index.tsx @@ -0,0 +1,70 @@ +import { useIntl } from "react-intl" + +import { + bookingTermsAndConditions, + privacyPolicy, +} from "@/constants/currentWebHrefs" + +import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" +import Link from "@/components/TempDesignSystem/Link" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import useLang from "@/hooks/useLang" + +import styles from "../payment.module.css" + +export default function TermsAndConditions() { + const intl = useIntl() + const lang = useLang() + + return ( + <> + + {intl.formatMessage( + { + id: "By paying with any of the payment methods available, I accept the terms for this booking and the general Terms & Conditions, and understand that Scandic will process my personal data for this booking in accordance with Scandic's Privacy policy. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.", + }, + { + termsAndConditionsLink: (str) => ( + + {str} + + ), + privacyPolicyLink: (str) => ( + + {str} + + ), + } + )} + + + + {intl.formatMessage({ + id: "I accept the terms and conditions", + })} + + + + + {intl.formatMessage({ + id: "I would like to get my booking confirmation via sms", + })} + + + + ) +} diff --git a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx index 90b897922..1c8f0102f 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx @@ -98,7 +98,7 @@ export default function GuaranteeLateArrival({ const bookingStatus = useHandleBookingStatus({ confirmationNumber: booking.confirmationNumber, - expectedStatus: BookingStatusEnum.BookingCompleted, + expectedStatuses: [BookingStatusEnum.BookingCompleted], maxRetries, retryInterval, enabled: isPollingForBookingStatus, @@ -195,7 +195,6 @@ export default function GuaranteeLateArrival({ { termsAndConditionsLink: (str) => ( ( { title: string keepSearchParams?: boolean } | null + close?: () => void ariaRole?: AriaRole ariaLive?: "off" | "assertive" | "polite" } diff --git a/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx b/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx index 48bc91975..30f3f426f 100644 --- a/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Alert/index.tsx @@ -1,5 +1,8 @@ "use client" +import { Button } from "@scandic-hotels/design-system/Button" + +import { CloseLargeIcon } from "@/components/Icons" import Body from "@/components/TempDesignSystem/Text/Body" import Link from "../Link" @@ -18,6 +21,7 @@ export default function Alert({ heading, text, link, + close, phoneContact, sidepeekCtaText, sidepeekContent, @@ -67,6 +71,7 @@ export default function Alert({ ) : null} + {link ? ( ) : null} + {close ? ( + + ) : null} ) diff --git a/apps/scandic-web/constants/booking.ts b/apps/scandic-web/constants/booking.ts index 49592e722..0c74ba5e4 100644 --- a/apps/scandic-web/constants/booking.ts +++ b/apps/scandic-web/constants/booking.ts @@ -163,3 +163,9 @@ export enum CancellationRuleEnum { NonCancellable = "NonCancellable", Changeable = "Changeable", } + +export enum PaymentCallbackStatusEnum { + Success = "success", + Error = "error", + Cancel = "cancel", +} diff --git a/apps/scandic-web/hooks/booking/useHandleBookingStatus.ts b/apps/scandic-web/hooks/booking/useHandleBookingStatus.ts index 4f12176d6..c47d01148 100644 --- a/apps/scandic-web/hooks/booking/useHandleBookingStatus.ts +++ b/apps/scandic-web/hooks/booking/useHandleBookingStatus.ts @@ -8,13 +8,13 @@ import type { BookingStatusEnum } from "@/constants/booking" export function useHandleBookingStatus({ confirmationNumber, - expectedStatus, + expectedStatuses, maxRetries, retryInterval, enabled, }: { confirmationNumber: string | null - expectedStatus: BookingStatusEnum + expectedStatuses: BookingStatusEnum[] maxRetries: number retryInterval: number enabled: boolean @@ -32,7 +32,11 @@ export function useHandleBookingStatus({ return false } - if (query.state.data?.reservationStatus === expectedStatus) { + if ( + expectedStatuses.includes( + query.state.data?.reservationStatus as BookingStatusEnum + ) + ) { return false } diff --git a/apps/scandic-web/i18n/dictionaries/da.json b/apps/scandic-web/i18n/dictionaries/da.json index a12813123..69d255ac1 100644 --- a/apps/scandic-web/i18n/dictionaries/da.json +++ b/apps/scandic-web/i18n/dictionaries/da.json @@ -712,6 +712,7 @@ "Surprise!": "Overraskelse!", "Surprises": "Surprises", "TUI Points": "TUI Points", + "Taking longer than usual": "Tager længere end normalt", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.", "Terms and conditions": "Vilkår og betingelser", "Thank you": "Tak", @@ -786,6 +787,7 @@ "View your booking": "Se din booking", "Visiting address": "Besøgsadresse", "Voucher": "Voucher", + "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.": "Vi er stadig bekræftende din booking. Dette er normalt en sak, der tager få minutter, og vi beklager ventetiden. Bedes du tjekke din indbakke for en booking bekræftelsesmail og hvis du stadig ikke har modtaget den indtil kl. 17.00, bedes du kontakte vores kundesupport.", "We could not add a card right now, please try again later.": "Vi kunne ikke tilføje et kort lige nu. Prøv venligst igen senere.", "We could not connect your accounts": "We could not connect your accounts", "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.": "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.", diff --git a/apps/scandic-web/i18n/dictionaries/de.json b/apps/scandic-web/i18n/dictionaries/de.json index e7731c594..428727fd3 100644 --- a/apps/scandic-web/i18n/dictionaries/de.json +++ b/apps/scandic-web/i18n/dictionaries/de.json @@ -711,6 +711,7 @@ "Surprise!": "Überraschung!", "Surprises": "Surprises", "TUI Points": "TUI Points", + "Taking longer than usual": "Länger als normal", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.", "Terms and conditions": "Geschäftsbedingungen", "Thank you": "Danke", @@ -784,6 +785,7 @@ "View your booking": "Ihre Buchung ansehen", "Visiting address": "Besuchsadresse", "Voucher": "Gutschein", + "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.": "Wir bestätigen Ihre Buchung noch. Dies dauert normalerweise nur wenige Minuten und wir bitten um Ihr Verständnis. Bitte überprüfen Sie Ihre E-Mail-Postfach für eine Buchungsbestätigung und wenn Sie die E-Mail noch nicht erhalten haben, kontaktieren Sie uns bitte bis zum Ende des Tages unter Kundensupport.", "We could not add a card right now, please try again later.": "Wir konnten momentan keine Karte hinzufügen. Bitte versuchen Sie es später noch einmal.", "We could not connect your accounts": "We could not connect your accounts", "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.": "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.", diff --git a/apps/scandic-web/i18n/dictionaries/en.json b/apps/scandic-web/i18n/dictionaries/en.json index 5fc74b4a7..3c88da9e3 100644 --- a/apps/scandic-web/i18n/dictionaries/en.json +++ b/apps/scandic-web/i18n/dictionaries/en.json @@ -710,6 +710,7 @@ "Surprise!": "Surprise!", "Surprises": "Surprises", "TUI Points": "TUI Points", + "Taking longer than usual": "Taking longer than usual", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.", "Terms and conditions": "Terms and conditions", "Thank you": "Thank you", @@ -782,6 +783,7 @@ "View your booking": "View your booking", "Visiting address": "Visiting address", "Voucher": "Voucher", + "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.": "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.", "We could not add a card right now, please try again later.": "We could not add a card right now, please try again later.", "We could not connect your accounts": "We could not connect your accounts", "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.": "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.", diff --git a/apps/scandic-web/i18n/dictionaries/fi.json b/apps/scandic-web/i18n/dictionaries/fi.json index 53ac3fdaf..886ad6284 100644 --- a/apps/scandic-web/i18n/dictionaries/fi.json +++ b/apps/scandic-web/i18n/dictionaries/fi.json @@ -711,6 +711,7 @@ "Surprise!": "Yllätys!", "Surprises": "Surprises", "TUI Points": "TUI Points", + "Taking longer than usual": "Tällainen kestää pidemmän aikaa kuin normaalisti", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.", "Terms and conditions": "Säännöt ja ehdot", "Thank you": "Kiitos", @@ -784,6 +785,7 @@ "View your booking": "Näytä varauksesi", "Visiting address": "Käyntiosoite", "Voucher": "Ravintolakuponki", + "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.": "Olemme vielä vahvistamassa varauksessasi. Tämä tavallisesti kestää muutaman minuutin, ja me olemme pahoillamme siitä odotusaikaa. Tarkista postilaatikkoosi varauksen vahvistamiseksi ja jos et ole vielä saanut sitä päivän loppuun mennessä, ota yhteyttä asiakaspalveluumme.", "We could not add a card right now, please try again later.": "Emme voineet lisätä korttia juuri nyt. Yritä myöhemmin uudelleen.", "We could not connect your accounts": "We could not connect your accounts", "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.": "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.", diff --git a/apps/scandic-web/i18n/dictionaries/no.json b/apps/scandic-web/i18n/dictionaries/no.json index 5c87f4e06..9df7bc3bb 100644 --- a/apps/scandic-web/i18n/dictionaries/no.json +++ b/apps/scandic-web/i18n/dictionaries/no.json @@ -708,6 +708,7 @@ "Surprise!": "Overraskelse!", "Surprises": "Surprises", "TUI Points": "TUI Points", + "Taking longer than usual": "Tar lenger tid enn normalt", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.", "Terms and conditions": "Vilkår og betingelser", "Thank you": "Takk", @@ -780,6 +781,7 @@ "View your booking": "Se din bestilling", "Visiting address": "Besøksadresse", "Voucher": "Voucher", + "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.": "Vi bekrefter din bestilling. Dette gjør vi normalt i løpet av noen minutter, og vi beklager ventetiden. Vennligst sjekk e-posten din for en bestillingsbekreftelsesmail og hvis du ikke har mottatt den før kl. 17.00, vennligst kontakt vår kundesupport.", "We could not add a card right now, please try again later.": "Vi kunne ikke legge til et kort akkurat nå. Prøv igjen senere.", "We could not connect your accounts": "We could not connect your accounts", "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.": "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.", diff --git a/apps/scandic-web/i18n/dictionaries/sv.json b/apps/scandic-web/i18n/dictionaries/sv.json index aa89aeaf8..30d527258 100644 --- a/apps/scandic-web/i18n/dictionaries/sv.json +++ b/apps/scandic-web/i18n/dictionaries/sv.json @@ -709,6 +709,7 @@ "Surprise!": "Överraskning!", "Surprises": "Surprises", "TUI Points": "TUI Points", + "Taking longer than usual": "Tar längre tid än vanligt", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.", "Terms and conditions": "Allmänna villkor", "Thank you": "Tack", @@ -782,6 +783,7 @@ "View your booking": "Visa din bokning", "Visiting address": "Besöksadress", "Voucher": "Kupong", + "We are still confirming your booking. This is usually a matter of minutes and we do apologise for the wait. Please check your inbox for a booking confirmation email and if you still haven't received it by end of day, please contact our customer support.": "Vi bekräftar din bokning. Detta gör vi normalt inom några minuter, och vi beklagar denna väntan. Vänligen kontrollera din inkorg för en bokningsbekräftelse och om du inte har fått detta senare på dagen, vänligen kontakta vår kundsupport.", "We could not add a card right now, please try again later.": "Vi kunde inte lägga till ett kort just nu, vänligen försök igen senare.", "We could not connect your accounts": "We could not connect your accounts", "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.": "We could not connect your accounts to give you access. Please contact us and we’ll help you resolve this issue.", diff --git a/apps/scandic-web/server/routers/booking/output.ts b/apps/scandic-web/server/routers/booking/output.ts index 479b9ae37..0768cfc50 100644 --- a/apps/scandic-web/server/routers/booking/output.ts +++ b/apps/scandic-web/server/routers/booking/output.ts @@ -88,17 +88,17 @@ export type Guest = z.output export const packageSchema = z .object({ - type: z.string().nullable(), + type: z.string().nullish(), description: nullableStringValidator, - code: z.string().nullable().default(""), + code: nullableStringValidator, price: z.object({ - unit: z.number().int().nullable(), + unit: z.number().int().nullish(), unitPrice: z.number(), - totalPrice: z.number().nullable(), - totalUnit: z.number().int().nullable(), + totalPrice: z.number().nullish(), + totalUnit: z.number().int().nullish(), currency: z.string().default(""), }), - comment: z.string().nullable().optional(), + comment: z.string().nullish(), }) .transform((packageData) => ({ type: packageData.type,