218 lines
6.4 KiB
TypeScript
218 lines
6.4 KiB
TypeScript
"use client"
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { useRouter } from "next/navigation"
|
|
import { useEffect, useState } from "react"
|
|
import { Label as AriaLabel } from "react-aria-components"
|
|
import { FormProvider, useForm } from "react-hook-form"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import {
|
|
BOOKING_CONFIRMATION_NUMBER,
|
|
BookingStatusEnum,
|
|
PAYMENT_METHOD_TITLES,
|
|
PaymentMethodEnum,
|
|
} from "@/constants/booking"
|
|
import {
|
|
bookingTermsAndConditions,
|
|
privacyPolicy,
|
|
} from "@/constants/currentWebHrefs"
|
|
import { trpc } from "@/lib/trpc/client"
|
|
|
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
|
import Button from "@/components/TempDesignSystem/Button"
|
|
import Checkbox from "@/components/TempDesignSystem/Checkbox"
|
|
import Link from "@/components/TempDesignSystem/Link"
|
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
|
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
|
|
import useLang from "@/hooks/useLang"
|
|
|
|
import PaymentOption from "./PaymentOption"
|
|
import { PaymentFormData, paymentSchema } from "./schema"
|
|
|
|
import styles from "./payment.module.css"
|
|
|
|
import { PaymentProps } from "@/types/components/hotelReservation/selectRate/section"
|
|
|
|
const maxRetries = 40
|
|
const retryInterval = 2000
|
|
|
|
export default function Payment({ hotel }: PaymentProps) {
|
|
const router = useRouter()
|
|
const lang = useLang()
|
|
const intl = useIntl()
|
|
const [confirmationNumber, setConfirmationNumber] = useState<string>("")
|
|
|
|
const methods = useForm<PaymentFormData>({
|
|
defaultValues: {
|
|
paymentMethod: PaymentMethodEnum.card,
|
|
smsConfirmation: false,
|
|
termsAndConditions: false,
|
|
},
|
|
mode: "all",
|
|
reValidateMode: "onChange",
|
|
resolver: zodResolver(paymentSchema),
|
|
})
|
|
|
|
const initiateBooking = trpc.booking.booking.create.useMutation({
|
|
onSuccess: (result) => {
|
|
if (result?.confirmationNumber) {
|
|
setConfirmationNumber(result.confirmationNumber)
|
|
} else {
|
|
// TODO: add proper error message
|
|
toast.error("Failed to create booking")
|
|
}
|
|
},
|
|
onError: (error) => {
|
|
console.error("Error", error)
|
|
// TODO: add proper error message
|
|
toast.error("Failed to create booking")
|
|
},
|
|
})
|
|
|
|
const bookingStatus = useHandleBookingStatus(
|
|
confirmationNumber,
|
|
BookingStatusEnum.PaymentRegistered,
|
|
maxRetries,
|
|
retryInterval
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (confirmationNumber && bookingStatus?.data?.paymentUrl) {
|
|
// Planet doesn't support query params so we have to store values in session storage
|
|
sessionStorage.setItem(BOOKING_CONFIRMATION_NUMBER, confirmationNumber)
|
|
router.push(bookingStatus.data.paymentUrl)
|
|
}
|
|
}, [confirmationNumber, bookingStatus, router])
|
|
|
|
function handleSubmit(data: PaymentFormData) {
|
|
initiateBooking.mutate({
|
|
hotelId: hotel.operaId,
|
|
checkInDate: "2024-12-10",
|
|
checkOutDate: "2024-12-11",
|
|
rooms: [
|
|
{
|
|
adults: 1,
|
|
childrenAges: [],
|
|
rateCode: "SAVEEU",
|
|
roomTypeCode: "QC",
|
|
guest: {
|
|
title: "Mr",
|
|
firstName: "Test",
|
|
lastName: "User",
|
|
email: "test.user@scandichotels.com",
|
|
phoneCountryCodePrefix: "string",
|
|
phoneNumber: "string",
|
|
countryCode: "string",
|
|
},
|
|
packages: {
|
|
breakfast: true,
|
|
allergyFriendly: true,
|
|
petFriendly: true,
|
|
accessibility: true,
|
|
},
|
|
smsConfirmationRequested: data.smsConfirmation,
|
|
},
|
|
],
|
|
payment: {
|
|
paymentMethod: data.paymentMethod,
|
|
cardHolder: {
|
|
email: "test.user@scandichotels.com",
|
|
name: "Test User",
|
|
phoneCountryCode: "",
|
|
phoneSubscriber: "",
|
|
},
|
|
success: `api/web/payment-callback/${lang}/success`,
|
|
error: `api/web/payment-callback/${lang}/error`,
|
|
cancel: `api/web/payment-callback/${lang}/cancel`,
|
|
},
|
|
})
|
|
}
|
|
|
|
if (
|
|
initiateBooking.isPending ||
|
|
(confirmationNumber && !bookingStatus.data?.paymentUrl)
|
|
) {
|
|
return <LoadingSpinner />
|
|
}
|
|
|
|
return (
|
|
<FormProvider {...methods}>
|
|
<form
|
|
className={styles.paymentContainer}
|
|
onSubmit={methods.handleSubmit(handleSubmit)}
|
|
>
|
|
<div className={styles.paymentOptionContainer}>
|
|
<PaymentOption
|
|
name="paymentMethod"
|
|
value={PaymentMethodEnum.card}
|
|
label={intl.formatMessage({ id: "Credit card" })}
|
|
/>
|
|
{hotel.merchantInformationData.alternatePaymentOptions.map(
|
|
(paymentMethod) => (
|
|
<PaymentOption
|
|
key={paymentMethod}
|
|
name="paymentMethod"
|
|
value={paymentMethod as PaymentMethodEnum}
|
|
label={
|
|
PAYMENT_METHOD_TITLES[paymentMethod as PaymentMethodEnum]
|
|
}
|
|
/>
|
|
)
|
|
)}
|
|
</div>
|
|
<Checkbox name="smsConfirmation">
|
|
<Caption>
|
|
{intl.formatMessage({
|
|
id: "I would like to get my booking confirmation via sms",
|
|
})}
|
|
</Caption>
|
|
</Checkbox>
|
|
|
|
<AriaLabel className={styles.terms}>
|
|
<Checkbox name="termsAndConditions" />
|
|
<Caption>
|
|
{intl.formatMessage<React.ReactNode>(
|
|
{
|
|
id: "booking.terms",
|
|
},
|
|
{
|
|
termsLink: (str) => (
|
|
<Link
|
|
className={styles.link}
|
|
variant="underscored"
|
|
href={bookingTermsAndConditions[lang]}
|
|
target="_blank"
|
|
>
|
|
{str}
|
|
</Link>
|
|
),
|
|
privacyLink: (str) => (
|
|
<Link
|
|
className={styles.link}
|
|
variant="underscored"
|
|
href={privacyPolicy[lang]}
|
|
target="_blank"
|
|
>
|
|
{str}
|
|
</Link>
|
|
),
|
|
}
|
|
)}
|
|
</Caption>
|
|
</AriaLabel>
|
|
<Button
|
|
type="submit"
|
|
className={styles.submitButton}
|
|
disabled={
|
|
!methods.formState.isValid || methods.formState.isSubmitting
|
|
}
|
|
>
|
|
{intl.formatMessage({ id: "Complete booking & go to payment" })}
|
|
</Button>
|
|
</form>
|
|
</FormProvider>
|
|
)
|
|
}
|