Feat/SW-431 payment flow * feat(SW-431): Update mock hotel data * feat(SW-431): Added route handler and trpc routes * feat(SW-431): List payment methods and handle booking status and redirection * feat(SW-431): Updated booking page to poll for booking status * feat(SW-431): Updated create booking contract * feat(SW-431): small fix * fix(SW-431): Added intl string and sorted dictionaries * fix(SW-431): Changes from PR * fix(SW-431): fixes from PR * fix(SW-431): add todo comments * fix(SW-431): update schema prop Approved-by: Simon.Emanuelsson
162 lines
4.7 KiB
TypeScript
162 lines
4.7 KiB
TypeScript
"use client"
|
|
|
|
import { useRouter } from "next/navigation"
|
|
import { useEffect, useState } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import {
|
|
BOOKING_CONFIRMATION_NUMBER,
|
|
BookingStatusEnum,
|
|
} from "@/constants/booking"
|
|
import { trpc } from "@/lib/trpc/client"
|
|
|
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
|
import Button from "@/components/TempDesignSystem/Button"
|
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
|
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
|
|
import useLang from "@/hooks/useLang"
|
|
|
|
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 [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>("")
|
|
|
|
const initiateBooking = trpc.booking.booking.create.useMutation({
|
|
onSuccess: (result) => {
|
|
if (result?.confirmationNumber) {
|
|
// Planet doesn't support query params so we have to store values in session storage
|
|
sessionStorage.setItem(
|
|
BOOKING_CONFIRMATION_NUMBER,
|
|
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 (bookingStatus?.data?.paymentUrl) {
|
|
router.push(bookingStatus.data.paymentUrl)
|
|
}
|
|
}, [bookingStatus, router])
|
|
|
|
function handleSubmit() {
|
|
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: true,
|
|
},
|
|
],
|
|
payment: {
|
|
paymentMethod: selectedPaymentMethod,
|
|
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 (
|
|
<div>
|
|
<div>
|
|
<div className={styles.paymentItemContainer}>
|
|
<button
|
|
className={styles.paymentItem}
|
|
onClick={() => setSelectedPaymentMethod("card")}
|
|
>
|
|
<input
|
|
type="radio"
|
|
name="payment-method"
|
|
id="card"
|
|
value="card"
|
|
checked={selectedPaymentMethod === "card"}
|
|
/>
|
|
<label htmlFor="card">card</label>
|
|
</button>
|
|
{hotel.merchantInformationData.alternatePaymentOptions.map(
|
|
(paymentOption) => (
|
|
<button
|
|
key={paymentOption}
|
|
className={styles.paymentItem}
|
|
onClick={() => setSelectedPaymentMethod(paymentOption)}
|
|
>
|
|
<input
|
|
type="radio"
|
|
name="payment-method"
|
|
id={paymentOption}
|
|
value={paymentOption}
|
|
checked={selectedPaymentMethod === paymentOption}
|
|
/>
|
|
<label htmlFor={paymentOption}>{paymentOption}</label>
|
|
</button>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
<Button disabled={!selectedPaymentMethod} onClick={handleSubmit}>
|
|
{intl.formatMessage({ id: "Complete booking & go to payment" })}
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|