import {z} from 'zod' export type Reference = string; export type AmountMinor = number; export type Currency = unknown; export type CardDynamicDescriptor = string; export type CardPaymentMethodRequest = {type: string, dynamic_descriptor: CardDynamicDescriptor}; export type SddRemittanceInfo = string; export type SddPaymentMethodRequest = {type: string, remittance_info: SddRemittanceInfo}; export type PaymentMethodRequest = CardPaymentMethodRequest | SddPaymentMethodRequest; export type SplitsType = 'per_transaction'; export type SplitsAccountType = 'platform' | 'seller' | 'other'; export type AccountId = string; export type SplitAmountMinor = number; export type SplitFixedAmount = {type: 'fixed' | 'percentage' | 'remaining', amount: SplitAmountMinor}; export type SplitPercentageAmountRequest = {type: 'fixed' | 'percentage' | 'remaining', percentage: number}; export type SplitRemainingAmountRequest = {type: 'fixed' | 'percentage' | 'remaining'}; export type ProcessingFeeRequest = {percentage: number}; export type SplitsRequest = {type: SplitsType, accounts: Array<{type: SplitsAccountType, id: AccountId, split: SplitFixedAmount | SplitPercentageAmountRequest | SplitRemainingAmountRequest, processing_fee?: ProcessingFeeRequest | undefined}>}; export type StandingInstructionId = string; export type SubsequentStandingInstructionRequest = {id: StandingInstructionId}; export type PaymentWebhooksRequest = {url: string}; export type CreatePaymentRequest = {reference: Reference, amount: AmountMinor, currency: Currency, method?: PaymentMethodRequest | undefined, splits: SplitsRequest, standing_instruction: SubsequentStandingInstructionRequest, webhooks?: PaymentWebhooksRequest | undefined}; export type CreateApiV2PaymentsArgs = {'Getpaid-Idempotency-Key': string, body: CreatePaymentRequest}; export type CreateApiV2PaymentsOptions = {baseUrl?: string | undefined, OAuth?: string | undefined, fetch?: typeof fetch | undefined}; export const paymentId = z.string().min(30).max(50); export const paymentStatus = z.enum(["initiated", "action_required", "submitted", "authorized", "captured", "declined", "failed", "partially_refunded", "refunded"]); export const paymentReason = z.object({code: z.string(), description: z.string()}); export const reference = z.string().min(1).max(50); export const createdAt = z.string().min(24).max(24); export const updatedAt = z.string().min(24).max(24); export const statementDescriptorResponse = z.string().min(1).max(140); export const paymentMethodType = z.enum(["card", "sepa_direct_debit"]); export const paymentMethodDisplayHint = z.string().min(1).max(50); export const cardDynamicDescriptor = z.string().min(1).max(18); export const cardScheme = z.enum(["visa", "mastercard"]); export const cardPaymentMethodDetails = z.object({type: paymentMethodType, display_hint: paymentMethodDisplayHint, dynamic_descriptor: cardDynamicDescriptor, card: z.object({scheme: cardScheme, holder_name: z.string().min(1).max(26), first_digits: z.string().min(4).max(6), last_digits: z.string().min(4).max(4), expiry_month: z.number().int().gte(1).lte(12), expiry_year: z.number().int().gte(2021), kind: z.enum(["credit", "debit"]).optional(), segment: z.enum(["consumer", "corporate"]).optional()})}); export const sddRemittanceInfo = z.string().min(1).max(140); export const sddPaymentMethodDetails = z.object({type: paymentMethodType, display_hint: paymentMethodDisplayHint, remittance_info: sddRemittanceInfo, bank_account: z.object({type: z.enum(["business", "consumer"]), number: z.object({type: z.literal("iban"), value: z.string().min(15).max(33)})})}); export const paymentMethodDetails = z.discriminatedUnion("type", [cardPaymentMethodDetails, sddPaymentMethodDetails]); export const splitsType = z.literal("per_transaction"); export const splitsAccountType = z.enum(["platform", "seller", "other"]); export const accountId = z.string().min(30).max(50); export const accountVersion = z.string().min(1).max(50); export const accountLegalName = z.string().min(1).max(250); export const splitAmountMinor = z.number().int(); export const splitFixedAmount = z.object({type: z.enum(["fixed", "percentage", "remaining"]), amount: splitAmountMinor}); export const splitPercentageAmountCalculated = z.object({type: z.enum(["fixed", "percentage", "remaining"]), percentage: z.number().gte(0).lte(100).multipleOf(0.1), amount: splitAmountMinor}); export const splitRemainingAmountCalculated = z.object({type: z.enum(["fixed", "percentage", "remaining"]), amount: splitAmountMinor}); export const processingFeeAmount = z.number().int(); export const processingFeeCalculating = z.object({percentage: z.number().gte(0).lte(100).multipleOf(0.1), amount: processingFeeAmount.optional()}); export const splitsCalculatedAndCalculatingFees = z.object({type: splitsType, accounts: z.array(z.object({type: splitsAccountType, id: accountId, version: accountVersion, legal_name: accountLegalName, split: z.discriminatedUnion("type", [splitFixedAmount, splitPercentageAmountCalculated, splitRemainingAmountCalculated]), processing_fee: processingFeeCalculating}))}); export const paymentAuthentication = z.object({type: z.literal("3ds"), status: z.enum(["succeeded", "failed"]), result: z.object({code: z.enum(["Y", "A", "N", "U", "R"]).min(1).max(1), eci: z.enum(["05", "06", "07", "01", "02", "03"]).min(2).max(2)}).optional()}); export const paymentTotals = z.object({net: z.number().int(), captured: z.number().int(), refunded: z.number().int(), refundable: z.number().int(), processing_fees: z.number().int(), accounts: z.array(z.object({type: z.enum(["seller", "platform"]), id: accountId, version: accountVersion, legal_name: accountLegalName, net: z.number().int(), captured: z.number().int(), refunded: z.number().int(), refundable: z.number().int(), processing_fees: z.number().int()}))}); export const createPaymentResponse = z.object({id: paymentId, status: paymentStatus, reason: paymentReason.optional(), reference: reference, created_at: createdAt, updated_at: updatedAt, statement_descriptor: statementDescriptorResponse.optional(), method: paymentMethodDetails, splits: splitsCalculatedAndCalculatingFees, authentication: paymentAuthentication.optional(), totals: paymentTotals}); 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 payment * * POST /v2/payments */ export const createApiV2Payments = async (args: CreateApiV2PaymentsArgs, options?: CreateApiV2PaymentsOptions) => { const {baseUrl = "https://api.getpaid.io", fetch: fetchFn = fetch, OAuth} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (args['Getpaid-Idempotency-Key'] != null) headers.set("Getpaid-Idempotency-Key", String(args['Getpaid-Idempotency-Key'])) if (OAuth !== undefined) headers.set('authorization', `Bearer ${OAuth}`) const url = `${baseUrl.replace(/\/+$/, '')}/v2/payments${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(createPaymentResponse, await res.json()) };