Merged in feat/SW-431-payment-flow (pull request #635)

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
This commit is contained in:
Tobias Johansson
2024-10-04 09:37:09 +00:00
parent 105f721dc9
commit 4103e3fb37
26 changed files with 711 additions and 287 deletions

View File

@@ -1,6 +1,6 @@
import { notFound } from "next/navigation"
import { serverClient } from "@/lib/trpc/server"
import { getHotelDataSchema } from "@/server/routers/hotels/output"
import tempHotelData from "@/server/routers/hotels/tempHotelData.json"
import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader"
import BedSelection from "@/components/HotelReservation/SelectRate/BedSelection"
@@ -80,12 +80,19 @@ export default async function SectionsPage({
}: PageArgs<LangParams & { section: string }, SectionPageProps>) {
setLang(params.lang)
// TODO: Use real endpoint.
const hotel = getHotelDataSchema.parse(tempHotelData)
const hotel = await serverClient().hotel.hotelData.get({
hotelId: "811",
language: params.lang,
})
if (!hotel) {
// TODO: handle case with hotel missing
return notFound()
}
const rooms = await serverClient().hotel.rates.get({
// TODO: pass the correct hotel ID and all other parameters that should be included in the search
hotelId: "1",
hotelId: hotel.data.id,
})
const intl = await getIntl()
@@ -170,7 +177,9 @@ export default async function SectionsPage({
header={intl.formatMessage({ id: "Payment info" })}
path={`payment?${currentSearchParams}`}
>
{params.section === "payment" && <Payment />}
{params.section === "payment" && (
<Payment hotel={hotel.data.attributes} />
)}
</SectionAccordion>
</div>
<div className={styles.summary}>

View File

@@ -0,0 +1,5 @@
import LoadingSpinner from "@/components/LoadingSpinner"
export default function Loading() {
return <LoadingSpinner />
}

View File

@@ -1,20 +1,67 @@
"use client"
import { useMemo } from "react"
import {
BOOKING_CONFIRMATION_NUMBER,
BookingStatusEnum,
} from "@/constants/booking"
import IntroSection from "@/components/HotelReservation/BookingConfirmation/IntroSection"
import StaySection from "@/components/HotelReservation/BookingConfirmation/StaySection"
import SummarySection from "@/components/HotelReservation/BookingConfirmation/SummarySection"
import { tempConfirmationData } from "@/components/HotelReservation/BookingConfirmation/tempConfirmationData"
import LoadingSpinner from "@/components/LoadingSpinner"
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
import styles from "./page.module.css"
const maxRetries = 10
const retryInterval = 2000
export default function BookingConfirmationPage() {
const { email, hotel, stay, summary } = tempConfirmationData
return (
<main className={styles.main}>
<section className={styles.section}>
<IntroSection email={email} />
<StaySection hotel={hotel} stay={stay} />
<SummarySection summary={summary} />
</section>
</main>
const confirmationNumber = useMemo(() => {
if (typeof window === "undefined") return ""
const storedConfirmationNumber = sessionStorage.getItem(
BOOKING_CONFIRMATION_NUMBER
)
// TODO: cleanup stored values
// sessionStorage.removeItem(BOOKING_CONFIRMATION_NUMBER)
return storedConfirmationNumber
}, [])
const bookingStatus = useHandleBookingStatus(
confirmationNumber,
BookingStatusEnum.BookingCompleted,
maxRetries,
retryInterval
)
if (
confirmationNumber === null ||
bookingStatus.isError ||
(bookingStatus.isFetched && !bookingStatus.data)
) {
// TODO: handle error
throw new Error("Error fetching booking status")
}
if (
bookingStatus.data?.reservationStatus === BookingStatusEnum.BookingCompleted
) {
return (
<main className={styles.main}>
<section className={styles.section}>
<IntroSection email={email} />
<StaySection hotel={hotel} stay={stay} />
<SummarySection summary={summary} />
</section>
</main>
)
}
return <LoadingSpinner />
}

View File

@@ -0,0 +1,37 @@
import { NextRequest, NextResponse } from "next/server"
import { env } from "process"
import { Lang } from "@/constants/languages"
import {
bookingConfirmation,
payment,
} from "@/constants/routes/hotelReservation"
export async function GET(
request: NextRequest,
{ params }: { params: { lang: string; status: string } }
): Promise<NextResponse> {
console.log(`[payment-callback] callback started`)
const lang = params.lang as Lang
const status = params.status
const returnUrl = new URL(`${env.PUBLIC_URL}/${payment[lang]}`)
if (status === "success") {
const confirmationUrl = new URL(
`${env.PUBLIC_URL}/${bookingConfirmation[lang]}`
)
console.log(`[payment-callback] redirecting to: ${confirmationUrl}`)
return NextResponse.redirect(confirmationUrl)
}
if (status === "cancel") {
returnUrl.searchParams.set("cancel", "true")
}
if (status === "error") {
returnUrl.searchParams.set("error", "true")
}
console.log(`[payment-callback] redirecting to: ${returnUrl}`)
return NextResponse.redirect(returnUrl)
}