import {z} from 'zod' export type CardExpiry = {month: string, year: string}; export type Acquirer = {bin: string, merchantIdentifier: string, country: unknown}; export type Address = {line1: string, line2?: string | undefined, city: string, state?: unknown | undefined, postalCode: string, country: unknown}; export type ThreeDSChallenge = {preference: 'no-preference' | 'challenge-requested' | 'challenge-mandated' | 'no-challenge-requested', reason?: ('null' | 'data-sharing' | 'low-value' | 'low-risk' | 'secure-corporate-payment' | 'authentication-already-performed' | null) | undefined}; export type ThreeDSInitiator = {type?: 'customer' | undefined} | {type?: 'merchant' | undefined, reason?: ('recurring' | 'installment' | 'mail-order' | 'telephone-order' | 'add-card' | 'maintain-card' | 'other-payment' | 'account-verification' | 'split-shipment' | 'delayed-shipment') | undefined, initialSession?: string | undefined}; export type CreateApiPayments3dsSessionsArgs = {body: {merchant: {name: string, website: string, categoryCode: string, country: unknown} | string, card: {number: string, expiry: CardExpiry} | string, acquirer?: (Acquirer | string) | undefined, customer?: ({name?: string | undefined, phone?: (Array<{type: 'mobile' | 'home' | 'work', countryCode: string, number: string}>) | undefined, email?: string | undefined, shipping?: ({address?: Address | undefined}) | undefined, billing?: ({address?: Address | undefined, taxIdentifier?: string | undefined}) | undefined, deviceId?: string | undefined, clientIp?: string | undefined}) | undefined, payment?: ({type: 'one-off', amount: number, currency: unknown} | {type: 'installment', amount: number, currency: unknown, installments: number, frequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'annually', expiry?: number | undefined} | {type: 'recurring', amount: number, currency: unknown, frequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'annually', expiry: number}) | undefined, preferredVersions?: Array<'2.2.0'> | undefined, challenge?: ThreeDSChallenge | undefined, initiator?: ThreeDSInitiator | undefined, externalCustomerReference?: string | undefined}}; export type CreateApiPayments3dsSessionsOptions = {baseUrl?: string | undefined, ApiKey?: {username: string, password: string} | undefined, fetch?: typeof fetch | undefined}; export const cardExpiry = z.object({month: z.string(), year: z.string()}); export const acquirer = z.object({bin: z.string().min(6).max(11), merchantIdentifier: z.string(), country: z.string()}); export const threeDSChallenge = z.object({preference: z.enum(["no-preference", "challenge-requested", "challenge-mandated", "no-challenge-requested"]), reason: z.enum(["null", "data-sharing", "low-value", "low-risk", "secure-corporate-payment", "authentication-already-performed"]).nullable().optional()}); export const threeDSInitiator = z.union([z.object({type: z.literal("customer").optional()}), z.object({type: z.literal("merchant").optional(), reason: z.enum(["recurring", "installment", "mail-order", "telephone-order", "add-card", "maintain-card", "other-payment", "account-verification", "split-shipment", "delayed-shipment"]).optional(), initialSession: z.string().optional()})]); export const threeDSAccessControlServer = z.object({transactionIdentifier: z.string(), referenceNumber: z.string(), operatorIdentifier: z.string().nullable().optional()}); export const threeDSDirectoryServer = z.object({transactionIdentifier: z.string(), referenceNumber: z.string(), network: z.enum(["visa", "mastercard", "american-express", "discover", "jcb", "unionpay", "rupay"])}); export const threeDSDcap = z.object({qualified: z.boolean(), accepted: z.boolean().nullable().optional()}).nullable(); export const threeDSSession = z.object({id: z.string(), card: z.object({lastFour: z.string(), expiry: cardExpiry, brand: z.string(), funding: z.string(), segment: z.string(), issuer: z.string().optional(), country: z.string(), currency: z.string()}), acquirer: acquirer, merchant: z.object({name: z.string(), website: z.string(), categoryCode: z.string(), country: z.string()}), payment: z.union([z.object({type: z.literal("installment"), amount: z.number().int(), currency: z.string(), installments: z.number().int(), frequency: z.enum(["daily", "weekly", "monthly", "quarterly", "annually"]), expiry: z.number().int().optional()}), z.object({type: z.literal("one-off"), amount: z.number().int(), currency: z.string()}), z.object({type: z.literal("recurring"), amount: z.number().int(), currency: z.string(), frequency: z.enum(["daily", "weekly", "monthly", "quarterly", "annually"]), expiry: z.number().int()})]).optional(), challenge: threeDSChallenge.optional(), initiator: threeDSInitiator.optional(), accessControlServer: threeDSAccessControlServer.optional(), directoryServer: threeDSDirectoryServer.optional(), threeDSServer: z.object({transactionIdentifier: z.string().optional()}).optional(), version: z.literal("2.2.0"), status: z.enum(["action-required", "success", "failure"]), authentication: z.object({flow: z.enum(["frictionless", "challenge", "attempt"]), methods: z.array(z.enum(["static-passcode", "sms-otp", "emv-card-reader-otp", "app-otp", "other-otp", "knowledge-based", "push-confirmation", "oob-biometrics", "oob-login", "oob-other", "webauthn", "secure-payment-confirmation", "behavioral-biometrics", "electronic", "decoupled"])).optional()}).optional(), nextAction: z.object({type: z.literal("use-sdk")}).optional(), cryptogram: z.string().optional(), eci: z.object({value: z.enum(["00", "01", "02", "04", "05", "06", "07"]).optional(), descriptor: z.enum(["fully-authenticated", "attempted", "not-authenticated"]).optional(), liabilityShift: z.boolean().optional()}).optional(), failureReason: z.enum(["failed-authentication", "card-not-enrolled", "no-card-record", "suspected-fraud", "invalid-acquirer-details", "cancelled", "unsupported-version", "challenge-required", "authentication-timed-out", "acs-unavailable", "directory-server-unavailable", "transient-system-failure", "invalid-card-details", "transaction-not-permitted", "required-data-element-missing", "protocol-error"]).optional(), ares: z.object({transStatus: z.object({value: z.enum(["Y", "N", "U", "A", "C", "D", "R", "I"]).optional(), detail: z.string().optional()}).optional(), transStatusReason: z.object({value: z.string().optional(), detail: z.string().optional()}).nullable().optional(), cardholderInfo: z.string().nullable().optional()}).optional(), cres: z.object({transStatus: z.object({value: z.enum(["Y", "N"]).optional(), detail: z.string().optional()}).optional()}).nullable().optional(), rreq: z.object({transStatus: z.object({value: z.enum(["Y", "N", "U", "A", "R"]).optional(), detail: z.string().optional()}).optional()}).nullable().optional(), dcap: threeDSDcap.optional(), externalCustomerReference: z.string().optional(), createdAt: z.number().int(), updatedAt: z.number().int().optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Create a 3DS Session * * POST /payments/3ds-sessions */ export const createApiPayments3dsSessions = async (args: CreateApiPayments3dsSessionsArgs, options?: CreateApiPayments3dsSessionsOptions) => { const {baseUrl = "https://api.evervault.com", fetch: fetchFn = fetch, ApiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (ApiKey !== undefined) headers.set('authorization', `Basic ${btoa(`${ApiKey.username}:${ApiKey.password}`)}`) const url = `${baseUrl.replace(/\/+$/, '')}/payments/3ds-sessions${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(threeDSSession, await res.json()) };