import { kody } from 'kody:runtime'
import { getAccount, getBalance } from './account.ts'
import { listCustomers } from './customers.ts'
import { createRefund } from './payments.ts'
import {
API_KEY_SETUP_URL,
DEFAULT_API_KEY_SECRET,
resolveApiKeySecretName,
secretSetupUrl,
WEBHOOK_SECRET_SETUP_URL,
type StripeAuthOptions,
} from './stripe-core.ts'
import { createTestSignatureHeader, verifyWebhookSignature } from './webhooks.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 helper checks plus an optional live account/balance read.
*
* Always verifies webhook HMAC helpers with a throwaway test secret. When
* `stripeApiKey` (or the selected account secret) exists, also reads the
* Stripe account profile without mutating anything.
*
* @example
* import smokeTest from 'kody:@kody/stripe/smoke-test'
* const result = await smokeTest()
*/
export async function smokeTest(input: StripeAuthOptions = {}) {
const payload = JSON.stringify({
id: 'evt_kody_smoke',
object: 'event',
type: 'charge.succeeded',
livemode: false,
data: { object: { id: 'ch_kody_smoke', object: 'charge' } },
})
const webhookSecret = 'whsec_kody_smoke_test'
const nowSeconds = 1_700_000_000
const signatureHeader = await createTestSignatureHeader({
payload,
webhookSecret,
timestamp: nowSeconds,
})
const verified = await verifyWebhookSignature({
payload,
signatureHeader,
webhookSecret,
nowSeconds,
})
if (!verified.ok || verified.eventId !== 'evt_kody_smoke') {
throw new Error('Local Stripe webhook HMAC self-check failed.')
}
const refundPreview = await createRefund({
chargeId: 'ch_kody_smoke',
amount: 100,
dryRun: true,
})
if (!('dryRun' in refundPreview) || refundPreview.dryRun !== true) {
throw new Error('createRefund dryRun self-check failed.')
}
const secretName = resolveApiKeySecretName(input)
const names = await listUserSecretNames()
const hasApiKey = names.has(secretName)
const hasWebhookSecret = names.has(
secretName === DEFAULT_API_KEY_SECRET
? 'stripeWebhookSecret'
: secretName.replace(/^stripeApiKey/, 'stripeWebhookSecret'),
)
if (!hasApiKey) {
return {
ok: true,
live: false,
selfCheck: { webhookSignature: true, dryRunRefund: true },
secretName,
setup: {
auth: 'api-key',
hosts: ['api.stripe.com', 'files.stripe.com'],
nextSteps: [
'Save a Stripe secret key (sk_...) or restricted key (rk_...) as ' + secretName + '.',
secretSetupUrl(secretName),
'Optional webhook signing secret: ' + WEBHOOK_SECRET_SETUP_URL,
],
},
}
}
const [account, balance, customers] = await Promise.all([
getAccount(input),
getBalance(input),
listCustomers({ ...input, maxItems: 1 }),
])
return {
ok: true,
live: true,
selfCheck: { webhookSignature: true, dryRunRefund: true },
secretName,
account: {
id: account.id,
displayName: account.displayName,
country: account.country,
chargesEnabled: account.chargesEnabled,
livemode: account.livemode,
},
balance: {
livemode: balance.livemode,
availableCurrencies: balance.available.map((entry) => entry.currency),
},
customerSampleCount: customers.customers.length,
setup: {
auth: 'api-key',
hosts: ['api.stripe.com', 'files.stripe.com'],
webhookSecretSaved: hasWebhookSecret,
apiKeyUrl: hasApiKey ? null : API_KEY_SETUP_URL,
},
}
}
export default smokeTest