Files
web/packages/trpc/lib/routers/partners/sas/getEuroBonusProfile.ts
Joakim Jäderberg 291310e841 Merged in feature/curity-social-login (pull request #2963)
feat(SW-3541): Do social login after login to SAS

* feat(auth): wip social login via curity

* Setup social login auth flow

* Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login

* Added support for getting scandic tokens and refresh them

* feat: Enhance social login and session management with auto-refresh and improved error handling

* Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login

* wrap layout in suspense

* revert app/layout.tsx

* fix import

* cleanup

* merge

* merge

* dont pass client_secret in the url to curity

* add state validation when doing social login through /authorize

* remove debug logging


Approved-by: Anton Gunnarsson
2025-10-16 12:47:12 +00:00

107 lines
3.1 KiB
TypeScript

import { z } from "zod"
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
import { env } from "../../../../env/server"
import { protectedProcedure } from "../../../procedures"
import type { LoginType } from "@scandic-hotels/common/constants/loginType"
const outputSchema = z.object({
eurobonusNumber: z.string(),
firstName: z.string().optional(),
lastName: z.string().optional(),
linkStatus: z.enum(["LINKED", "PENDING", "UNLINKED"]),
isBlocked: z.boolean(),
enrollmentDate: z.string(),
birthdate: z.string(),
mobileNumber: z.string(),
email: z.string().email(),
points: z.object({
balances: z.array(
z.object({
expirationDate: z.string(),
points: z.number(),
})
),
total: z.number(),
}),
tier: z.string(),
tierStartDate: z.string(),
/* `null` if customer is (i.) Lifetime Gold and tier is Gold or (ii.) tier is Basic. If customer is Lifetime Gold and tier is above Gold (e.g., Diamond), there will be a tier end date */
tierEndDate: z.string().nullable(),
lifeTimeGold: z.boolean(),
tierBoostRequestedByScandic: z.boolean(),
whoBoosted: z
.enum(["NO-BOOST", "SAS", "SCANDIC", "UNKNOWN"])
.default("UNKNOWN")
.catch((ctx) => {
sasLogger.warn(`Unknown whoBoosted value received: ${ctx.input}`)
return "UNKNOWN"
}),
tierBoostDescription: z.string().optional(),
tierMatchStatus: z
.enum(["MATCHED", "FAILED", "PENDING", "UNMATCHED"])
.optional(),
})
const sasLogger = createLogger("SAS")
const url = new URL("/api/scandic-partnership/v1/profile", env.SAS_API_ENDPOINT)
const requiredLoginType: LoginType[] = ["sas"]
export const getEuroBonusProfile = protectedProcedure
.output(outputSchema)
.query(async function ({ ctx }) {
return await getEuroBonusProfileData({
accessToken: ctx.session.token.access_token,
loginType: ctx.session.token.loginType,
})
})
export async function getEuroBonusProfileData({
accessToken,
loginType,
}: {
loginType: LoginType
accessToken: string
}) {
if (!accessToken) {
throw new Error("Access token is required to fetch EuroBonus profile")
}
if (!requiredLoginType.includes(loginType)) {
throw new Error(
`Failed to fetch EuroBonus profile, expected loginType to be "${requiredLoginType}" but was "${loginType}"`
)
}
const response = await fetch(url, {
headers: {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": env.SAS_OCP_APIM,
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
sasLogger.error(
`Failed to get EuroBonus profile, status: ${response.status}, statusText: ${response.statusText}`
)
throw new Error("Failed to fetch EuroBonus profile", {
cause: { status: response.status, statusText: response.statusText },
})
}
const responseJson = await response.json()
const data = outputSchema.safeParse(responseJson)
if (!data.success) {
sasLogger.error(
`Failed to parse EuroBonus profile, cause: ${data.error.cause}, message: ${data.error.message}`
)
throw new Error(`Failed to parse EuroBonus profile: ${data.error.message}`)
}
return data.data
}