import * as Sentry from "@sentry/nextjs" import { TRPCError } from "@trpc/server" import { cookies } from "next/headers" import { v4 as uuidv4 } from "uuid" import { z } from "zod" import { env } from "@/env/server" import { protectedProcedure } from "@/server/trpc" import { getSasToken } from "../../getSasToken" import { SAS_REQUEST_OTP_STATE_STORAGE_COOKIE_NAME } from "../constants" import { parseSASRequestOtpError, type RequestOtpGeneralError, } from "./requestOtpError" import type { OtpState } from "../getOTPState" const successSchema = z.object({ status: z.literal("SENT"), referenceId: z.string().uuid(), databaseUUID: z.string().uuid(), otpExpiration: z.number(), otpReceiver: z.string(), }) const failureSchema = z.object({ status: z.enum([ "VERIFIED", "ABUSED", "EXPIRED", "PENDING", "RETRY", "NULL", "NOTSENT", ]), }) const outputSchema = z.union([successSchema, failureSchema]) export const requestOtp = protectedProcedure .output(outputSchema) .mutation(async function () { const sasAuthToken = await getSasToken() if (!sasAuthToken) { throw createError("AUTH_TOKEN_NOT_FOUND") } const tokenResponse = await fetchRequestOtp({ sasAuthToken }) console.log( "[SAS] requestOtp", tokenResponse.status, tokenResponse.statusText ) const body = await tokenResponse.json() const parseResult = outputSchema.safeParse(body) if (!parseResult.success) { console.error("[SAS] requestOtp error", body) if (!tokenResponse.ok) { throw createError(body) } throw createError(parseResult.error) } if (parseResult.data.status === "SENT") { setSASOtpCookie(parseResult.data) } else { const sasRequestOtpErrorMessage = `[SAS] requestOtp did not return SENT status with body: ${JSON.stringify(body)}` console.warn(sasRequestOtpErrorMessage) Sentry.captureMessage(sasRequestOtpErrorMessage) } return parseResult.data }) function createError( errorBody: | { status: string error: string errorCode: number databaseUUID: string } | Error | RequestOtpGeneralError ): TRPCError { const errorInfo = parseSASRequestOtpError(errorBody) console.error("[SAS] createError", errorInfo) return new TRPCError({ code: "BAD_REQUEST", cause: errorInfo, }) } async function fetchRequestOtp({ sasAuthToken }: { sasAuthToken: string }) { const endpoint = `${env.SAS_API_ENDPOINT}/api/scandic-partnership/customer/send-otp` console.log("[SAS]: Requesting OTP") return await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json", "Ocp-Apim-Subscription-Key": env.SAS_OCP_APIM, Authorization: `Bearer ${sasAuthToken}`, }, body: JSON.stringify({ referenceId: uuidv4(), }), }) } async function setSASOtpCookie({ referenceId, databaseUUID, }: { referenceId: string databaseUUID: string }) { const cookieStore = await cookies() cookieStore.set( SAS_REQUEST_OTP_STATE_STORAGE_COOKIE_NAME, JSON.stringify({ referenceId: referenceId, databaseUUID: databaseUUID, } satisfies OtpState), { httpOnly: true, maxAge: 3600, } ) }