Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/paypal

src/openapi-client.ts

186 lines · 7.1 KB · TypeScript
/**
 * Scaffolded PayPal OpenAPI clients (dependency-free ESM).
 *
 * Sources (openapi_client_scaffold, auth kind: none — bearer injected by package auth layer):
 * - https://raw.githubusercontent.com/paypal/paypal-rest-api-specifications/main/openapi/invoicing_v2.json
 *   slugs: invoices_list, invoices_get, invoices_search_invoices, invoices_create, invoices_send
 * - https://raw.githubusercontent.com/paypal/paypal-rest-api-specifications/main/openapi/reporting_transactions_v1.json
 *   slugs: search_get, balances_get
 * - https://raw.githubusercontent.com/paypal/paypal-rest-api-specifications/main/openapi/payments_payouts_batch_v1.json
 *   slugs: payouts_create, payouts_get, payouts_item_get
 *
 * Host approval stays in the account security UI; the OpenAPI spec never widens it.
 */

export type QueryValue = string | number | boolean | null | undefined
export type QueryInput = Record<string, QueryValue | QueryValue[]>

export type ScaffoldInput = {
	params?: Record<string, unknown>
	query?: QueryInput
	headers?: Record<string, string>
	body?: unknown
}

export type ScaffoldOptions = {
	fetchImpl?: typeof fetch
	/** Override the default live API base (e.g. sandbox). */
	apiBaseUrl?: string
}

const DEFAULT_API_BASE_URL = 'https://api-m.paypal.com'

function resolveApiBaseUrl(options: ScaffoldOptions = {}): string {
	const base = options.apiBaseUrl ?? DEFAULT_API_BASE_URL
	return String(base).replace(/\/+$/, '')
}

export function buildUrl(
	pathTemplate: string,
	params: Record<string, unknown> = {},
	apiBaseUrl: string = DEFAULT_API_BASE_URL,
): string {
	return apiBaseUrl + pathTemplate.replace(/\{([^}]+)\}/g, (_match, name: string) => {
		const value = params[name]
		if (value === undefined || value === null) {
			throw new Error(`Missing required path parameter: ${name}`)
		}
		return encodeURIComponent(String(value))
	})
}

export function appendQuery(url: string, query: QueryInput = {}): string {
	const search = new URLSearchParams()
	for (const [key, value] of Object.entries(query)) {
		if (value === undefined || value === null || value === '') continue
		if (Array.isArray(value)) {
			for (const item of value) {
				if (item === undefined || item === null || item === '') continue
				search.append(key, String(item))
			}
			continue
		}
		search.append(key, String(value))
	}
	const qs = search.toString()
	return qs ? `${url}?${qs}` : url
}

function mergeHeaders(
	userHeaders: Record<string, string> | undefined,
	authHeaders: Record<string, string>,
): Record<string, string> {
	const merged: Record<string, string> = { ...(userHeaders ?? {}) }
	for (const [key, value] of Object.entries(authHeaders)) {
		const lower = key.toLowerCase()
		for (const existing of Object.keys(merged)) {
			if (existing.toLowerCase() === lower) delete merged[existing]
		}
		merged[key] = value
	}
	return merged
}

function hasHeader(headers: Record<string, string>, name: string): boolean {
	const lower = name.toLowerCase()
	return Object.keys(headers).some((key) => key.toLowerCase() === lower)
}

async function resolveFetch(fetchImpl?: typeof fetch): Promise<typeof fetch> {
	return fetchImpl ?? fetch
}

/** Scaffold auth kind was `none`; callers inject Authorization via input.headers. */
function authHeaders(): Record<string, string> {
	return {}
}

async function request(
	method: string,
	pathTemplate: string,
	input: ScaffoldInput = {},
	options: ScaffoldOptions = {},
): Promise<Response> {
	const params = input.params ?? {}
	const apiBaseUrl = resolveApiBaseUrl(options)
	const url = appendQuery(buildUrl(pathTemplate, params, apiBaseUrl), input.query)
	let body: string | undefined
	const headers = mergeHeaders(input.headers, authHeaders())
	if (input.body !== undefined) {
		body = JSON.stringify(input.body)
		if (!hasHeader(headers, 'content-type')) {
			headers['content-type'] = 'application/json'
		}
	}
	const fetchImpl = await resolveFetch(options.fetchImpl)
	return fetchImpl(url, { method, headers, body })
}

/** GET /v2/invoicing/invoices — List invoices */
export async function invoicesList(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	return request('GET', '/v2/invoicing/invoices', input, options)
}

/** GET /v2/invoicing/invoices/{invoice_id} — Show invoice details */
export async function invoicesGet(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	const params = input.params ?? {}
	if (params.invoice_id === undefined || params.invoice_id === null) {
		throw new Error('Missing required path parameter: invoice_id')
	}
	return request('GET', '/v2/invoicing/invoices/{invoice_id}', input, options)
}

/** POST /v2/invoicing/search-invoices — Search for invoices (read-only search) */
export async function invoicesSearchInvoices(
	input: ScaffoldInput = {},
	options: ScaffoldOptions = {},
): Promise<Response> {
	return request('POST', '/v2/invoicing/search-invoices', input, options)
}

/** POST /v2/invoicing/invoices — Create draft invoice */
export async function invoicesCreate(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	return request('POST', '/v2/invoicing/invoices', input, options)
}

/** POST /v2/invoicing/invoices/{invoice_id}/send — Send an invoice */
export async function invoicesSend(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	const params = input.params ?? {}
	if (params.invoice_id === undefined || params.invoice_id === null) {
		throw new Error('Missing required path parameter: invoice_id')
	}
	return request('POST', '/v2/invoicing/invoices/{invoice_id}/send', input, options)
}

/** GET /v1/reporting/transactions — List transactions */
export async function searchGet(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	return request('GET', '/v1/reporting/transactions', input, options)
}

/** GET /v1/reporting/balances — List all balances */
export async function balancesGet(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	return request('GET', '/v1/reporting/balances', input, options)
}

/** POST /v1/payments/payouts — Create a batch payout */
export async function payoutsCreate(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	return request('POST', '/v1/payments/payouts', input, options)
}

/** GET /v1/payments/payouts/{payout_batch_id} — Show payout batch details */
export async function payoutsGet(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	const params = input.params ?? {}
	if (params.payout_batch_id === undefined || params.payout_batch_id === null) {
		throw new Error('Missing required path parameter: payout_batch_id')
	}
	return request('GET', '/v1/payments/payouts/{payout_batch_id}', input, options)
}

/** GET /v1/payments/payouts-item/{payout_item_id} — Show payout item details */
export async function payoutsItemGet(input: ScaffoldInput = {}, options: ScaffoldOptions = {}): Promise<Response> {
	const params = input.params ?? {}
	if (params.payout_item_id === undefined || params.payout_item_id === null) {
		throw new Error('Missing required path parameter: payout_item_id')
	}
	return request('GET', '/v1/payments/payouts-item/{payout_item_id}', input, options)
}