Skip to content

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

Package listing

@kody/paypal

src/smoke-test.ts

124 lines · 3.7 KB · TypeScript
import { kody } from 'kody:runtime'
import { listInvoices } from './invoices.ts'
import { createPayout } from './payouts.ts'
import {
	CLIENT_ID_SETUP_URL,
	CLIENT_SECRET_SETUP_URL,
	DASHBOARD_APPS_URL,
	LIVE_API_HOST,
	SANDBOX_API_HOST,
	clientIdSetupUrl,
	clientSecretSetupUrl,
	paypalListBalances,
	resolveClientIdSecretName,
	resolveClientSecretSecretName,
	type PayPalAuthInput,
} from './paypal-core.ts'
import { sendInvoice } from './invoices.ts'

function secretEntries(result: unknown): Array<{ name?: string; scope?: string }> {
	if (result && typeof result === 'object' && Array.isArray((result as { secrets?: unknown }).secrets)) {
		return (result as { secrets: Array<{ name?: string; scope?: string }> }).secrets
	}
	return Array.isArray(result) ? result : []
}

async function listUserSecretNames(): Promise<Set<string>> {
	try {
		const listed = await kody.secret_list({ scope: 'user' })
		const names = new Set<string>()
		for (const entry of secretEntries(listed)) {
			if (entry?.name && (entry.scope === 'user' || !entry.scope)) names.add(entry.name)
		}
		return names
	} catch {
		return new Set()
	}
}

/**
 * Local dry-run self-check plus an optional live invoice/balance read.
 *
 * Without saved client credentials this still returns `{ ok: true, live: false }`
 * and the prefilled setup URLs. It never sends invoices or money.
 *
 * @example
 * import paypal from 'kody:@kody/paypal'
 * const result = await paypal({ action: 'smoke-test' })
 */
export async function smokeTest(input: PayPalAuthInput = {}) {
	const sendPreview = await sendInvoice({
		id: 'INV2-KODY-SMOKE',
		dryRun: true,
	})
	if (!('dryRun' in sendPreview) || sendPreview.dryRun !== true) {
		throw new Error('sendInvoice dryRun self-check failed.')
	}

	const payoutPreview = await createPayout({
		receiver: 'ada@example.com',
		amount: '1.00',
		currencyCode: 'USD',
		note: 'kody paypal smoke',
		dryRun: true,
	})
	if (!('dryRun' in payoutPreview) || payoutPreview.dryRun !== true) {
		throw new Error('createPayout dryRun self-check failed.')
	}

	const clientIdSecret = resolveClientIdSecretName(input)
	const clientSecretSecret = resolveClientSecretSecretName(input)
	const names = await listUserSecretNames()
	const hasClientId = names.has(clientIdSecret)
	const hasClientSecret = names.has(clientSecretSecret)

	if (!hasClientId || !hasClientSecret) {
		return {
			ok: true,
			live: false,
			selfCheck: { dryRunSendInvoice: true, dryRunCreatePayout: true },
			clientIdSecret,
			clientSecretSecret,
			setup: {
				auth: 'client-credentials',
				hosts: [LIVE_API_HOST, SANDBOX_API_HOST],
				dashboard: DASHBOARD_APPS_URL,
				nextSteps: [
					'Create a REST app at ' + DASHBOARD_APPS_URL + ' and copy the client ID and secret.',
					'Save the client ID: ' + clientIdSetupUrl(clientIdSecret),
					'Save the client secret: ' + clientSecretSetupUrl(clientSecretSecret),
					'Approve hosts ' + LIVE_API_HOST + ' and ' + SANDBOX_API_HOST + ' on both secrets.',
				],
			},
		}
	}

	const [invoices, balances] = await Promise.all([
		listInvoices({ ...input, pageSize: 3 }),
		paypalListBalances(input).catch((error: unknown) => ({
			skipped: true,
			reason: error instanceof Error ? error.message : String(error),
		})),
	])

	return {
		ok: true,
		live: true,
		selfCheck: { dryRunSendInvoice: true, dryRunCreatePayout: true },
		clientIdSecret,
		invoiceList: {
			total_items: invoices.total_items,
			sampleCount: invoices.items.length,
			sample: invoices.items,
		},
		balances,
		setup: {
			auth: 'client-credentials',
			hosts: [LIVE_API_HOST, SANDBOX_API_HOST],
			clientIdUrl: hasClientId ? null : CLIENT_ID_SETUP_URL,
			clientSecretUrl: hasClientSecret ? null : CLIENT_SECRET_SETUP_URL,
		},
	}
}

export default smokeTest