import {z} from 'zod' export type GetApiRestAccountsArgs = {'filter[person.id]'?: string | undefined, 'filter[account_type]'?: ('EARNINGS_BALANCE' | 'DEPOSITORY' | 'CARD') | undefined, 'filter[subtype]'?: string | undefined, filter?: string | undefined}; export type GetApiRestAccountsOptions = {baseUrl?: string | undefined, oauthClientCredentialsToken?: string | undefined, fetch?: typeof fetch | undefined}; export const currency = z.string(); export const transferDestinationCapability = z.object({schedule: z.enum(["WITHIN_THIRTY_MINUTES", "NEXT_BUSINESS_DAY"]), fee: z.number().int(), currency: currency}); export const accountAttributes = z.union([z.object({verification_status: z.enum(["VERIFICATION_PENDING", "VERIFICATION_FAILED", "VERIFIED"]), balances: z.object({available: z.number().int().gte(0).nullable(), current: z.number().int().gte(0).nullable(), currency: z.string()}), capabilities: z.object({transfer_destination: z.array(transferDestinationCapability)}), name: z.string(), account_type: z.literal("CARD"), subtype: z.enum(["DEBIT", "DAILYPAY"]), details: z.object({last_four: z.string(), issuer: z.string(), first_name: z.string(), last_name: z.string(), expiration_month: z.string(), expiration_year: z.string()})}), z.object({verification_status: z.enum(["VERIFICATION_PENDING", "VERIFICATION_FAILED", "VERIFIED"]), balances: z.object({available: z.number().int().gte(0).nullable(), current: z.number().int().gte(0).nullable(), currency: z.string()}), capabilities: z.object({transfer_destination: z.array(transferDestinationCapability)}), name: z.string(), account_type: z.literal("EARNINGS_BALANCE"), subtype: z.literal("ODP"), details: z.object({})}), z.object({verification_status: z.enum(["VERIFICATION_PENDING", "VERIFICATION_FAILED", "VERIFIED"]), balances: z.object({available: z.number().int().gte(0).nullable(), current: z.number().int().gte(0).nullable(), currency: z.string()}), capabilities: z.object({transfer_destination: z.array(transferDestinationCapability)}), name: z.string(), account_type: z.literal("DEPOSITORY"), subtype: z.enum(["SAVINGS", "CHECKING"]), details: z.object({first_name: z.string(), last_name: z.string(), routing_number: z.string(), account_number: z.string()})})]); export const accountLink = z.string(); export const accountLinks = z.object({self: accountLink}); export const personIdentifier = z.object({type: z.literal("people"), id: z.string()}); export const personRelationship = z.object({data: personIdentifier}); export const accountRelationships = z.object({person: personRelationship}); export const accountResource = z.object({id: z.string(), type: z.literal("accounts"), attributes: accountAttributes, links: accountLinks, relationships: accountRelationships}); export const accountsData = z.object({data: z.array(accountResource)}); 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 }; /** * Get a list of Account objects * * GET /rest/accounts * * Also accepts auth: oauth_user_token */ export const getApiRestAccounts = async (args: GetApiRestAccountsArgs, options?: GetApiRestAccountsOptions) => { const {baseUrl = "https://api.dailypay.com", fetch: fetchFn = fetch, oauthClientCredentialsToken} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/vnd.api+json") if (args['filter[person.id]'] != null) query.append("filter[person.id]", String(args['filter[person.id]'])) if (args['filter[account_type]'] != null) query.append("filter[account_type]", String(args['filter[account_type]'])) if (args['filter[subtype]'] != null) query.append("filter[subtype]", String(args['filter[subtype]'])) if (args.filter != null) query.append("filter", String(args.filter)) if (oauthClientCredentialsToken !== undefined) headers.set('authorization', `Bearer ${oauthClientCredentialsToken}`) const url = `${baseUrl.replace(/\/+$/, '')}/rest/accounts${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(accountsData, await res.json()) };