diff --git a/components/HotelReservation/SelectRate/Payment/index.tsx b/components/HotelReservation/SelectRate/Payment/index.tsx
index a9915a310..ed8068076 100644
--- a/components/HotelReservation/SelectRate/Payment/index.tsx
+++ b/components/HotelReservation/SelectRate/Payment/index.tsx
@@ -1,6 +1,62 @@
"use client"
-import styles from "./payment.module.css"
+
+import { trpc } from "@/lib/trpc/client"
+
+import Button from "@/components/TempDesignSystem/Button"
export default function Payment() {
- return
Payment TBI
+ const initiateBooking = trpc.booking.booking.create.useMutation({
+ onSuccess: (result) => {
+ // TODO: Handle success, poll for payment link and redirect the user to the payment
+ console.log("Res", result)
+ },
+ onError: () => {
+ // TODO: Handle error
+ console.log("Error")
+ },
+ })
+
+ return (
+
+ )
}
diff --git a/components/HotelReservation/SelectRate/Payment/payment.module.css b/components/HotelReservation/SelectRate/Payment/payment.module.css
deleted file mode 100644
index ec81ef8e9..000000000
--- a/components/HotelReservation/SelectRate/Payment/payment.module.css
+++ /dev/null
@@ -1,2 +0,0 @@
-.wrapper {
-}
diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts
index 03af495fc..14460c1a9 100644
--- a/lib/api/endpoints.ts
+++ b/lib/api/endpoints.ts
@@ -16,6 +16,7 @@ export namespace endpoints {
hotels = "hotel/v1/Hotels",
intiateSaveCard = `${creditCards}/initiateSaveCard`,
deleteCreditCard = `${profile}/creditCards`,
+ booking = "booking/v1/Bookings",
}
}
diff --git a/server/index.ts b/server/index.ts
index 9b4f7ca5c..6f41f4391 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -1,10 +1,12 @@
/** Routers */
+import { bookingRouter } from "./routers/booking"
import { contentstackRouter } from "./routers/contentstack"
import { hotelsRouter } from "./routers/hotels"
import { userRouter } from "./routers/user"
import { router } from "./trpc"
export const appRouter = router({
+ booking: bookingRouter,
contentstack: contentstackRouter,
hotel: hotelsRouter,
user: userRouter,
diff --git a/server/routers/booking/index.ts b/server/routers/booking/index.ts
new file mode 100644
index 000000000..65b968733
--- /dev/null
+++ b/server/routers/booking/index.ts
@@ -0,0 +1,5 @@
+import { mergeRouters } from "@/server/trpc"
+
+import { bookingMutationRouter } from "./mutation"
+
+export const bookingRouter = mergeRouters(bookingMutationRouter)
diff --git a/server/routers/booking/input.ts b/server/routers/booking/input.ts
new file mode 100644
index 000000000..46a88110e
--- /dev/null
+++ b/server/routers/booking/input.ts
@@ -0,0 +1,38 @@
+import { z } from "zod"
+
+// Query
+// Mutation
+export const createBookingInput = z.object({
+ hotelId: z.string(),
+ checkInDate: z.string(),
+ checkOutDate: z.string(),
+ rooms: z.array(
+ z.object({
+ adults: z.number().int().nonnegative(),
+ children: z.number().int().nonnegative(),
+ rateCode: z.string(),
+ roomTypeCode: z.string(),
+ guest: z.object({
+ title: z.string(),
+ firstName: z.string(),
+ lastName: z.string(),
+ email: z.string().email(),
+ phoneCountryCodePrefix: z.string(),
+ phoneNumber: z.string(),
+ countryCode: z.string(),
+ }),
+ smsConfirmationRequested: z.boolean(),
+ })
+ ),
+ payment: z.object({
+ cardHolder: z.object({
+ Email: z.string().email(),
+ Name: z.string(),
+ PhoneCountryCode: z.string(),
+ PhoneSubscriber: z.string(),
+ }),
+ success: z.string(),
+ error: z.string(),
+ cancel: z.string(),
+ }),
+})
diff --git a/server/routers/booking/mutation.ts b/server/routers/booking/mutation.ts
new file mode 100644
index 000000000..2b35f56d4
--- /dev/null
+++ b/server/routers/booking/mutation.ts
@@ -0,0 +1,129 @@
+import { metrics } from "@opentelemetry/api"
+
+import * as api from "@/lib/api"
+import { getVerifiedUser } from "@/server/routers/user/query"
+import { router, safeProtectedProcedure } from "@/server/trpc"
+
+import { getMembership } from "@/utils/user"
+
+import { createBookingInput } from "./input"
+import { createBookingSchema } from "./output"
+
+import type { Session } from "next-auth"
+
+const meter = metrics.getMeter("trpc.bookings")
+const createBookingCounter = meter.createCounter("trpc.bookings.create")
+const createBookingSuccessCounter = meter.createCounter(
+ "trpc.bookings.create-success"
+)
+const createBookingFailCounter = meter.createCounter(
+ "trpc.bookings.create-fail"
+)
+
+async function getMembershipNumber(
+ session: Session | null
+): Promise {
+ if (!session) return undefined
+
+ const verifiedUser = await getVerifiedUser({ session })
+ if (!verifiedUser || "error" in verifiedUser) {
+ return undefined
+ }
+
+ const membership = getMembership(verifiedUser.data.memberships)
+ return membership?.membershipNumber
+}
+
+export const bookingMutationRouter = router({
+ booking: router({
+ create: safeProtectedProcedure
+ .input(createBookingInput)
+ .mutation(async function ({ ctx, input }) {
+ const { checkInDate, checkOutDate, hotelId } = input
+
+ const loggingAttributes = {
+ membershipNumber: await getMembershipNumber(ctx.session),
+ checkInDate,
+ checkOutDate,
+ hotelId,
+ }
+
+ createBookingCounter.add(1, { hotelId, checkInDate, checkOutDate })
+
+ console.info(
+ "api.booking.booking.create start",
+ JSON.stringify({
+ query: loggingAttributes,
+ })
+ )
+ const headers = ctx.session
+ ? {
+ Authorization: `Bearer ${ctx.session?.token.access_token}`,
+ }
+ : undefined
+ const apiResponse = await api.post(api.endpoints.v1.booking, {
+ headers,
+ body: input,
+ })
+
+ if (!apiResponse.ok) {
+ const text = await apiResponse.text()
+ createBookingFailCounter.add(1, {
+ hotelId,
+ checkInDate,
+ checkOutDate,
+ error_type: "http_error",
+ error: JSON.stringify({
+ status: apiResponse.status,
+ }),
+ })
+ console.error(
+ "api.booking.booking.create error",
+ JSON.stringify({
+ query: loggingAttributes,
+ error: {
+ status: apiResponse.status,
+ statusText: apiResponse.statusText,
+ error: text,
+ },
+ })
+ )
+ return null
+ }
+
+ const apiJson = await apiResponse.json()
+ const verifiedData = createBookingSchema.safeParse(apiJson)
+ if (!verifiedData.success) {
+ createBookingFailCounter.add(1, {
+ hotelId,
+ checkInDate,
+ checkOutDate,
+ error_type: "validation_error",
+ })
+
+ console.error(
+ "api.booking.booking.create validation error",
+ JSON.stringify({
+ query: loggingAttributes,
+ error: verifiedData.error,
+ })
+ )
+ return null
+ }
+
+ createBookingSuccessCounter.add(1, {
+ hotelId,
+ checkInDate,
+ checkOutDate,
+ })
+
+ console.info(
+ "api.booking.booking.create success",
+ JSON.stringify({
+ query: loggingAttributes,
+ })
+ )
+ return verifiedData.data
+ }),
+ }),
+})
diff --git a/server/routers/booking/output.ts b/server/routers/booking/output.ts
new file mode 100644
index 000000000..8fedd8716
--- /dev/null
+++ b/server/routers/booking/output.ts
@@ -0,0 +1,34 @@
+import { z } from "zod"
+
+export const createBookingSchema = z
+ .object({
+ data: z.object({
+ attributes: z.object({
+ confirmationNumber: z.string(),
+ cancellationNumber: z.string().nullable(),
+ reservationStatus: z.string(),
+ paymentUrl: z.string().nullable(),
+ }),
+ type: z.string(),
+ id: z.string(),
+ links: z.object({
+ self: z.object({
+ href: z.string().url(),
+ meta: z.object({
+ method: z.string(),
+ }),
+ }),
+ }),
+ }),
+ })
+ .transform((d) => ({
+ id: d.data.id,
+ links: d.data.links,
+ type: d.data.type,
+ confirmationNumber: d.data.attributes.confirmationNumber,
+ cancellationNumber: d.data.attributes.cancellationNumber,
+ reservationStatus: d.data.attributes.reservationStatus,
+ paymentUrl: d.data.attributes.paymentUrl,
+ }))
+
+type CreateBookingData = z.infer
diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts
index 94312edc4..a482f9787 100644
--- a/server/routers/user/query.ts
+++ b/server/routers/user/query.ts
@@ -84,7 +84,7 @@ const getCreditCardsFailCounter = meter.createCounter(
"trpc.user.creditCards-fail"
)
-async function getVerifiedUser({ session }: { session: Session }) {
+export async function getVerifiedUser({ session }: { session: Session }) {
const now = Date.now()
if (session.token.expires_at && session.token.expires_at < now) {