Merged in feat/sw-3642-inject-sas-eb-payment (pull request #3243)

feat(SW-3642): Enable SAS EB payments

* Wip add SAS eb payment

* Add validate payment call

* Check booking status payment method to determine validation

* Clean up getPaymentData

* Fix PartnerPoints casing

* Add comment for validatePartnerPayment error handling

* Remove comment


Approved-by: Joakim Jäderberg
This commit is contained in:
Anton Gunnarsson
2025-12-11 13:23:12 +00:00
parent eb90c382b8
commit 7faa9933a2
19 changed files with 489 additions and 110 deletions

View File

@@ -82,6 +82,9 @@ export namespace endpoints {
export function confirmNotification(confirmationNumber: string) {
return `${bookings}/${confirmationNumber}/confirmNotification`
}
export function validatePartnerPayment(confirmationNumber: string) {
return `${bookings}/${confirmationNumber}/validate`
}
export const enum Stays {
future = `${base.path.booking}/${version}/${base.enitity.Stays}/future`,

View File

@@ -1,5 +1,6 @@
import "server-only"
import { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod"
import { createCounter } from "@scandic-hotels/common/telemetry"
import * as api from "../../../../api"
@@ -38,6 +39,23 @@ export const create = safeProtectedServiceProcedure
Authorization: `Bearer ${ctx.token ?? ctx.serviceToken}`,
}
const includePartnerSpecific =
inputWithoutLang.payment?.paymentMethod ===
PaymentMethodEnum.PartnerPoints
if (includePartnerSpecific) {
const session = await ctx.auth()
const token = session?.token.access_token
if (!token) {
throw new Error(
"Cannot create booking with partner points without partner token"
)
}
inputWithoutLang.partnerSpecific = {
eurobonusAccessToken: session?.token.access_token,
}
}
const apiResponse = await api.post(
api.endpoints.v1.Booking.bookings,
{
@@ -60,7 +78,6 @@ export const create = safeProtectedServiceProcedure
}
const apiJson = await apiResponse.json()
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
metricsCreateBooking.validationError(verifiedData.error)

View File

@@ -105,6 +105,11 @@ export const createBookingInput = z.object({
rooms: roomsSchema,
payment: paymentSchema.optional(),
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
partnerSpecific: z
.object({
eurobonusAccessToken: z.string(),
})
.optional(),
})
export const createBookingSchema = z
@@ -114,6 +119,7 @@ export const createBookingSchema = z
reservationStatus: z.string(),
guest: guestSchema.optional(),
paymentUrl: z.string().nullable().optional(),
paymentMethod: z.string().nullable().optional(),
rooms: z
.array(
z.object({
@@ -161,6 +167,7 @@ export const createBookingSchema = z
type: d.data.type,
reservationStatus: d.data.attributes.reservationStatus,
paymentUrl: d.data.attributes.paymentUrl,
paymentMethod: d.data.attributes.paymentMethod,
rooms: d.data.attributes.rooms.map((room) => {
const lastName = d.data.attributes.guest?.lastName ?? ""
return {
@@ -171,3 +178,4 @@ export const createBookingSchema = z
errors: d.data.attributes.errors,
guest: d.data.attributes.guest,
}))
export type CreateBookingSchema = z.infer<typeof createBookingSchema>

View File

@@ -17,12 +17,14 @@ import { bookingConfirmationSchema } from "../output"
import { cancelBooking } from "../utils"
import { createBookingSchema } from "./create/schema"
import { create } from "./create"
import { validatePartnerPayment } from "./validatePartnerPayment"
const refIdPlugin = createRefIdPlugin()
const bookingLogger = createLogger("trpc.booking")
export const bookingMutationRouter = router({
create,
validatePartnerPayment,
priceChange: safeProtectedServiceProcedure
.concat(refIdPlugin.toConfirmationNumber)
.use(async ({ ctx, next }) => {

View File

@@ -0,0 +1,60 @@
import "server-only"
import z from "zod"
import { createCounter } from "@scandic-hotels/common/telemetry"
import * as api from "../../../api"
import { serverErrorByStatus } from "../../../errors"
import { safeProtectedServiceProcedure } from "../../../procedures"
import { toApiLang } from "../../../utils"
const validatePartnerPaymentInput = z.object({
confirmationNumber: z.string(),
})
export const validatePartnerPayment = safeProtectedServiceProcedure
.input(validatePartnerPaymentInput)
.use(async ({ ctx, next }) => {
const token = await ctx.getScandicUserToken()
return next({
ctx: {
token,
},
})
})
.mutation(async function ({ ctx, input }) {
const { confirmationNumber } = input
const getValidateBooking = createCounter("booking.validate")
const metricsValidateBooking = getValidateBooking.init({
confirmationNumber,
})
metricsValidateBooking.start()
const apiResponse = await api.put(
api.endpoints.v1.Booking.validatePartnerPayment(confirmationNumber),
{
headers: {
Authorization: `Bearer ${ctx.token ?? ctx.serviceToken}`,
},
},
{ language: toApiLang(ctx.lang) }
)
if (!apiResponse.ok) {
await metricsValidateBooking.httpError(apiResponse)
// If the booking is not found, return null.
if (apiResponse.status === 404) {
return null
}
throw serverErrorByStatus(apiResponse.status, apiResponse)
}
metricsValidateBooking.success()
return null
})