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