Merged in feat/sw-2877-move-payment-callback (pull request #2755)

feat(SW-2877): Move payment callback page

* WIP move

* Add payment callback page to partner-sas


Approved-by: Joakim Jäderberg
This commit is contained in:
Anton Gunnarsson
2025-09-11 09:06:11 +00:00
parent c3e78fcadd
commit 3e5263bf72
5 changed files with 186 additions and 129 deletions

View File

@@ -1,4 +1,26 @@
export default async function PaymentCallbackPage() {
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
return <div>payment-callback</div>
import { PaymentCallbackPage as PaymentCallbackPagePrimitive } from "@scandic-hotels/booking-flow/pages/PaymentCallbackPage"
import { logger } from "@scandic-hotels/common/logger"
import type { LangParams, PageArgs } from "@/types/params"
export default async function PaymentCallbackPage(props: PageArgs<LangParams>) {
const searchParams = await props.searchParams
const params = await props.params
logger.debug(`[payment-callback] callback started`)
const lang = params.lang
let userAccessToken = null
// TODO fix when auth is implemented
// const session = await auth()
// if (isValidSession(session)) {
// userAccessToken = session.token.access_token
// }
return (
<PaymentCallbackPagePrimitive
lang={lang}
userAccessToken={userAccessToken}
searchParams={searchParams}
/>
)
}

View File

@@ -1,23 +1,11 @@
import { notFound } from "next/navigation"
import { HandleErrorCallback } from "@scandic-hotels/booking-flow/components/EnterDetails/Payment/PaymentCallback/HandleErrorCallback"
import { HandleSuccessCallback } from "@scandic-hotels/booking-flow/components/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback"
import { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/paymentCallbackStatusEnum"
import {
bookingConfirmation,
details,
} from "@scandic-hotels/common/constants/routes/hotelReservation"
import { PaymentCallbackPage as PaymentCallbackPagePrimitive } from "@scandic-hotels/booking-flow/pages/PaymentCallbackPage"
import { logger } from "@scandic-hotels/common/logger"
import { getServiceToken } from "@scandic-hotels/common/tokenManager"
import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode"
import { getBooking } from "@scandic-hotels/trpc/routers/booking/utils"
import { encrypt } from "@scandic-hotels/trpc/utils/encryption"
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
import { serverClient } from "@/lib/trpc/server"
import { auth } from "@/auth"
import type { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/paymentCallbackStatusEnum"
import type { LangParams, PageArgs } from "@/types/params"
export default async function PaymentCallbackPage(
@@ -26,7 +14,6 @@ export default async function PaymentCallbackPage(
{
status?: PaymentCallbackStatusEnum
confirmationNumber?: string
hotel?: string
}
>
) {
@@ -34,121 +21,18 @@ export default async function PaymentCallbackPage(
const params = await props.params
logger.debug(`[payment-callback] callback started`)
const lang = params.lang
const status = searchParams.status
const confirmationNumber = searchParams.confirmationNumber
if (!status || !confirmationNumber) {
logger.error(
`[payment-callback] missing status or confirmationNumber in search params`
)
notFound()
}
const returnUrl = details(lang)
const searchObject = new URLSearchParams()
let errorMessage = undefined
if (status === PaymentCallbackStatusEnum.Cancel) {
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionCancelled)
return (
<HandleErrorCallback
returnUrl={returnUrl.toString()}
searchObject={searchObject}
status={status}
errorMessage={errorMessage}
/>
)
}
let token = ""
let userAccessToken = null
const session = await auth()
if (isValidSession(session)) {
token = session.token.access_token
} else {
const serviceToken = await getServiceToken()
if (serviceToken) {
token = serviceToken.access_token
}
}
if (!token) {
logger.error(
`[payment-callback] no token found for user, cannot fetch booking`
)
notFound()
}
const booking = await getBooking(confirmationNumber, params.lang, token)
const refId = booking?.refId
if (
status === PaymentCallbackStatusEnum.Success &&
confirmationNumber &&
refId
) {
const expire = Math.floor(Date.now() / 1000) + 60
const sig = encrypt(expire.toString())
const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(refId)}`
logger.debug(
`[payment-callback] rendering success callback with confirmation number: ${confirmationNumber}`
)
return (
<HandleSuccessCallback
refId={refId}
sig={sig}
successRedirectUrl={confirmationUrl}
cardType={booking.guaranteeInfo?.cardType}
/>
)
}
if (refId) {
try {
const caller = await serverClient()
const bookingStatus = await caller.booking.status({
refId,
})
const { booking } = bookingStatus
// TODO: how to handle errors for multiple rooms?
const error = booking.errors.find((e) => e.errorCode)
errorMessage =
error?.description ??
`No error message found for booking ${confirmationNumber}, status: ${status}`
searchObject.set(
"errorCode",
error
? error.errorCode.toString()
: BookingErrorCodeEnum.TransactionFailed
)
} catch {
logger.error(
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
)
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionFailed)
errorMessage = `Failed to get booking status for ${confirmationNumber}, status: ${status}`
}
}
if (status === PaymentCallbackStatusEnum.Error) {
logger.error(
`[payment-callback] error status received for ${confirmationNumber}, status: ${status}`
)
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionFailed)
errorMessage = `Failed to get booking status for ${confirmationNumber}, status: ${status}`
userAccessToken = session.token.access_token
}
return (
<HandleErrorCallback
returnUrl={returnUrl.toString()}
searchObject={searchObject}
status={status}
errorMessage={errorMessage}
<PaymentCallbackPagePrimitive
lang={lang}
userAccessToken={userAccessToken}
searchParams={searchParams}
/>
)
}

View File

@@ -0,0 +1,151 @@
import { notFound } from "next/navigation"
import { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/paymentCallbackStatusEnum"
import {
bookingConfirmation,
details,
} from "@scandic-hotels/common/constants/routes/hotelReservation"
import { logger } from "@scandic-hotels/common/logger"
import { getServiceToken } from "@scandic-hotels/common/tokenManager"
import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode"
import { getBooking } from "@scandic-hotels/trpc/routers/booking/utils"
import { encrypt } from "@scandic-hotels/trpc/utils/encryption"
import { HandleErrorCallback } from "../components/EnterDetails/Payment/PaymentCallback/HandleErrorCallback"
import { HandleSuccessCallback } from "../components/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback"
import { serverClient } from "../trpc"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { NextSearchParams } from "../types"
type PaymentCallbackPageProps = {
lang: Lang
searchParams: NextSearchParams
userAccessToken: string | null
}
export async function PaymentCallbackPage({
lang,
userAccessToken,
searchParams,
}: PaymentCallbackPageProps) {
const { status, confirmationNumber } = searchParams
if (
!status ||
!confirmationNumber ||
typeof confirmationNumber !== "string" ||
typeof status !== "string"
) {
logger.error(
`[payment-callback] missing status or confirmationNumber in search params`
)
notFound()
}
const returnUrl = details(lang)
const searchObject = new URLSearchParams()
let errorMessage = undefined
if (status === PaymentCallbackStatusEnum.Cancel) {
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionCancelled)
return (
<HandleErrorCallback
returnUrl={returnUrl.toString()}
searchObject={searchObject}
status={status}
errorMessage={errorMessage}
/>
)
}
let token = userAccessToken
if (!token) {
const serviceToken = await getServiceToken()
if (serviceToken) {
token = serviceToken.access_token
}
}
if (!token) {
logger.error(
`[payment-callback] no token found for user, cannot fetch booking`
)
notFound()
}
const booking = await getBooking(confirmationNumber, lang, token)
const refId = booking?.refId
if (
status === PaymentCallbackStatusEnum.Success &&
confirmationNumber &&
refId
) {
const expire = Math.floor(Date.now() / 1000) + 60
const sig = encrypt(expire.toString())
const confirmationUrl = `${bookingConfirmation(lang)}?RefId=${encodeURIComponent(refId)}`
logger.debug(
`[payment-callback] rendering success callback with confirmation number: ${confirmationNumber}`
)
return (
<HandleSuccessCallback
refId={refId}
sig={sig}
successRedirectUrl={confirmationUrl}
cardType={booking.guaranteeInfo?.cardType}
/>
)
}
if (refId) {
try {
const caller = await serverClient()
const bookingStatus = await caller.booking.status({
refId,
})
const { booking } = bookingStatus
// TODO: how to handle errors for multiple rooms?
const error = booking.errors.find((e) => e.errorCode)
errorMessage =
error?.description ??
`No error message found for booking ${confirmationNumber}, status: ${status}`
searchObject.set(
"errorCode",
error
? error.errorCode.toString()
: BookingErrorCodeEnum.TransactionFailed
)
} catch {
logger.error(
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
)
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionFailed)
errorMessage = `Failed to get booking status for ${confirmationNumber}, status: ${status}`
}
}
if (status === PaymentCallbackStatusEnum.Error) {
logger.error(
`[payment-callback] error status received for ${confirmationNumber}, status: ${status}`
)
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionFailed)
errorMessage = `Failed to get booking status for ${confirmationNumber}, status: ${status}`
}
return (
<HandleErrorCallback
returnUrl={returnUrl.toString()}
searchObject={searchObject}
// TODO we should parse instead of cast
status={status as PaymentCallbackStatusEnum}
errorMessage={errorMessage}
/>
)
}

View File

@@ -61,7 +61,6 @@
"./pages/*": "./lib/pages/*.tsx",
"./providers/BookingConfirmationProvider": "./lib/providers/BookingConfirmationProvider.tsx",
"./searchType": "./lib/misc/searchType.ts",
"./stores/booking-confirmation": "./lib/stores/booking-confirmation/index.ts",
"./stores/bookingCode-filter": "./lib/stores/bookingCode-filter.ts",
"./stores/enter-details": "./lib/stores/enter-details/index.ts",
"./stores/enter-details/types": "./lib/stores/enter-details/types.ts",

View File

@@ -27,6 +27,7 @@
"./constants/rateType": "./constants/rateType.ts",
"./constants/routes/*": "./constants/routes/*.ts",
"./constants/signatureHotels": "./constants/signatureHotels.ts",
"./constants/paymentCallbackStatus": "./constants/paymentCallbackStatus.ts",
"./dataCache": "./dataCache/index.ts",
"./dt": "./dt/dt.ts",
"./dt/utils/hasOverlappingDates": "./dt/utils/hasOverlappingDates.ts",