From 48274038c12f2c444330738dc049216a01511108 Mon Sep 17 00:00:00 2001 From: alvseven Date: Fri, 10 Apr 2026 17:32:59 -0300 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20rename=20receivers=20=E2=86=92=20cu?= =?UTF-8?q?stomers=20in=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename all receiver commands to customer (listCustomers, createCustomer, etc.) - update API paths from /receivers to /customers - rename command group from 'receivers' to 'customers' - update all user-facing labels and descriptions --- src/commands/resources.ts | 287 ++++---------------------------------- src/index.ts | 70 +++++----- 2 files changed, 59 insertions(+), 298 deletions(-) diff --git a/src/commands/resources.ts b/src/commands/resources.ts index 148b934..ebbbca5 100644 --- a/src/commands/resources.ts +++ b/src/commands/resources.ts @@ -67,11 +67,11 @@ function extractList(res: any): any[] { return [] } -// Receivers -export async function listReceivers(options: { json: boolean }) { +// Customers +export async function listCustomers(options: { json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/receivers`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers`) const list = extractList(res) const display = list.map((r: any) => ({ id: r.id, @@ -91,7 +91,7 @@ export async function listReceivers(options: { json: boolean }) { export async function getReceiver(id: string, options: { json: boolean }) { try { const ctx = resolveContext() - const receiver = await apiGet(ctx, `${instancePath(ctx)}/receivers/${id}`) + const receiver = await apiGet(ctx, `${instancePath(ctx)}/customers/${id}`) printResult(receiver, options.json) } catch (e) { @@ -138,11 +138,11 @@ export async function createReceiver(options: { external_id: options.externalId ?? null, kyc_status: options.kycStatus ?? 'approved', } - const receiver = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/receivers`, body) + const receiver = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers`, body) const displayName = body.type === 'business' ? (body.legal_name || '—') : [body.first_name, body.last_name].filter(Boolean).join(' ').trim() || '—' - clack.log.success(`Created receiver ${receiver.id} (${receiver.type}, ${displayName})`) + clack.log.success(`Created customer ${receiver.id} (${receiver.type}, ${displayName})`) if (options.json) console.log(formatOutput(receiver, true)) } @@ -193,8 +193,8 @@ export async function updateReceiver( if (Object.keys(body).length === 0) { exitWithError('Provide at least one field to update (e.g. --name, --kyc-status)', 1, options.json) } - const receiver = await apiPut>(ctx, `${instancePath(ctx)}/receivers/${id}`, body) - clack.log.success(`Updated receiver ${id}`) + const receiver = await apiPut>(ctx, `${instancePath(ctx)}/customers/${id}`, body) + clack.log.success(`Updated customer ${id}`) if (options.json) console.log(formatOutput(receiver, true)) else @@ -208,8 +208,8 @@ export async function updateReceiver( export async function deleteReceiver(id: string, options: { json?: boolean } = {}) { try { const ctx = resolveContext() - await apiDelete(ctx, `${instancePath(ctx)}/receivers/${id}`) - clack.log.success(`Deleted receiver ${id}`) + await apiDelete(ctx, `${instancePath(ctx)}/customers/${id}`) + clack.log.success(`Deleted customer ${id}`) } catch (e) { handleApiError(e, options.json) @@ -220,7 +220,7 @@ export async function deleteReceiver(id: string, options: { json?: boolean } = { export async function listBankAccounts(options: { receiverId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/bank-accounts`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts`) const list = extractList(res) const display = list.map((a: any) => ({ id: a.id, type: a.type, name: a.name, status: a.status, country: a.country })) printResult(options.json ? list : display, options.json, ['id', 'type', 'name', 'status', 'country']) @@ -233,7 +233,7 @@ export async function listBankAccounts(options: { receiverId: string, json: bool export async function getBankAccount(id: string, options: { receiverId: string, json: boolean }) { try { const ctx = resolveContext() - const account = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/bank-accounts/${id}`) + const account = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts/${id}`) printResult(account, options.json) } catch (e) { @@ -253,7 +253,6 @@ export async function createBankAccount(options: { accountType?: string accountClass?: string country?: string - swiftIfscBranchCode?: string json: boolean }) { try { @@ -269,9 +268,8 @@ export async function createBankAccount(options: { account_type: options.accountType ?? null, account_class: options.accountClass ?? null, country: options.country ?? null, - swift_ifsc_branch_code: options.swiftIfscBranchCode ?? null, } - const ba = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/bank-accounts`, body) + const ba = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts`, body) clack.log.success(`Created bank account ${ba.id} (${ba.type})`) if (options.json) console.log(formatOutput(ba, true)) @@ -284,7 +282,7 @@ export async function createBankAccount(options: { export async function deleteBankAccount(id: string, options: { receiverId: string, json?: boolean }) { try { const ctx = resolveContext() - await apiDelete(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/bank-accounts/${id}`) + await apiDelete(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts/${id}`) clack.log.success(`Deleted bank account ${id}`) } catch (e) { @@ -296,7 +294,7 @@ export async function deleteBankAccount(id: string, options: { receiverId: strin export async function listBlockchainWallets(options: { receiverId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/blockchain-wallets`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets`) const list = extractList(res) const display = list.map((w: any) => ({ id: w.id, address: truncate(w.address, 20), network: w.network })) printResult(options.json ? list : display, options.json, ['id', 'address', 'network']) @@ -309,7 +307,7 @@ export async function listBlockchainWallets(options: { receiverId: string, json: export async function getBlockchainWallet(id: string, options: { receiverId: string, json: boolean }) { try { const ctx = resolveContext() - const wallet = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/blockchain-wallets/${id}`) + const wallet = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets/${id}`) printResult(wallet, options.json) } catch (e) { @@ -333,7 +331,7 @@ export async function createBlockchainWallet(options: { name: options.name || 'CLI Blockchain Wallet', external_id: options.externalId ?? null, } - const wallet = await apiPost<{ id: string, network: string }>(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/blockchain-wallets`, body) + const wallet = await apiPost<{ id: string, network: string }>(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets`, body) clack.log.success(`Created blockchain wallet ${wallet.id} (${wallet.network})`) if (options.json) console.log(formatOutput(wallet, true)) @@ -346,7 +344,7 @@ export async function createBlockchainWallet(options: { export async function deleteBlockchainWallet(id: string, options: { receiverId: string, json?: boolean }) { try { const ctx = resolveContext() - await apiDelete(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/blockchain-wallets/${id}`) + await apiDelete(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets/${id}`) clack.log.success(`Deleted blockchain wallet ${id}`) } catch (e) { @@ -664,7 +662,7 @@ export async function deleteApiKey(id: string, options: { json?: boolean } = {}) export async function listVirtualAccounts(options: { receiverId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/virtual-accounts`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/virtual-accounts`) const list = extractList(res) const display = list.map((a: any) => ({ id: a.id, account_number: a.account_number, routing_number: a.routing_number, kyc_status: a.kyc_status })) printResult(options.json ? list : display, options.json, ['id', 'account_number', 'routing_number', 'kyc_status']) @@ -677,7 +675,7 @@ export async function listVirtualAccounts(options: { receiverId: string, json: b export async function createVirtualAccount(options: { receiverId: string, blockchainWalletId: string, json: boolean }) { try { const ctx = resolveContext() - const account = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/virtual-accounts`, { blockchain_wallet_id: options.blockchainWalletId }) + const account = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/virtual-accounts`, { blockchain_wallet_id: options.blockchainWalletId }) clack.log.success(`Created virtual account ${account.id}`) if (options.json) console.log(formatOutput(account, true)) @@ -691,7 +689,7 @@ export async function createVirtualAccount(options: { receiverId: string, blockc export async function listOfframpWallets(options: { receiverId: string, bankAccountId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/bank-accounts/${options.bankAccountId}/offramp-wallets`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts/${options.bankAccountId}/offramp-wallets`) const list = extractList(res) const display = list.map((w: any) => ({ id: w.id, address: truncate(w.address, 20), network: w.network })) printResult(options.json ? list : display, options.json, ['id', 'address', 'network']) @@ -752,7 +750,7 @@ export async function updateInstance(options: { export async function getReceiverLimits(receiverId: string, options: { json: boolean }) { try { const ctx = resolveContext() - const limits = await apiGet(ctx, `${instancePath(ctx)}/limits/receivers/${receiverId}`) + const limits = await apiGet(ctx, `${instancePath(ctx)}/customers/${receiverId}/limits`) printResult(limits, options.json) } catch (e) { @@ -763,38 +761,9 @@ export async function getReceiverLimits(receiverId: string, options: { json: boo export async function getReceiverLimitsIncreaseRequests(receiverId: string, options: { json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/receivers/${receiverId}/limit-increase`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${receiverId}/limits-increase-requests`) const list = extractList(res) - printResult(list, options.json, ['id', 'status', 'per_transaction', 'daily', 'monthly', 'created_at']) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function createReceiverLimitIncrease( - receiverId: string, - options: { - perTransaction: string - daily: string - monthly: string - supportingDocumentType: string - supportingDocumentFile: string - json: boolean - }, -) { - try { - const ctx = resolveContext() - const body = { - per_transaction: parseAmount(options.perTransaction, 0, options.json), - daily: parseAmount(options.daily, 0, options.json), - monthly: parseAmount(options.monthly, 0, options.json), - supporting_document_type: options.supportingDocumentType, - supporting_document_file: options.supportingDocumentFile, - } - const res = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/receivers/${receiverId}/limit-increase`, body) - clack.log.success(`Created limit increase request ${res.id}`) - printResult(res, options.json) + printResult(list, options.json) } catch (e) { handleApiError(e, options.json) @@ -840,211 +809,3 @@ export async function getAvailableBankDetails(options: { rail: string, json: boo handleApiError(e, options.json) } } - -// Wallets (custodial, distinct from blockchain_wallets) -export async function listWallets(options: { receiverId: string, json: boolean }) { - try { - const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/wallets`) - const list = extractList(res) - printResult(list, options.json, ['id', 'name', 'network', 'address']) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function getWallet(id: string, options: { receiverId: string, json: boolean }) { - try { - const ctx = resolveContext() - const wallet = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/wallets/${id}`) - printResult(wallet, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function getWalletBalance(id: string, options: { receiverId: string, json: boolean }) { - try { - const ctx = resolveContext() - const balance = await apiGet(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/wallets/${id}/balance`) - printResult(balance, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function createWallet(options: { - receiverId: string - network: string - name: string - externalId?: string - json: boolean -}) { - try { - const ctx = resolveContext() - const body: Record = { - network: options.network, - name: options.name, - } - if (options.externalId !== undefined) body.external_id = options.externalId - const wallet = await apiPost<{ id: string, network: string }>(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/wallets`, body) - clack.log.success(`Created wallet ${wallet.id} (${wallet.network})`) - printResult(wallet, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function deleteWallet(id: string, options: { receiverId: string, json?: boolean }) { - try { - const ctx = resolveContext() - await apiDelete(ctx, `${instancePath(ctx)}/receivers/${options.receiverId}/wallets/${id}`) - clack.log.success(`Deleted wallet ${id}`) - } - catch (e) { - handleApiError(e, options.json ?? false) - } -} - -// Transfers -export async function listTransfers(options: { json: boolean }) { - try { - const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/transfers`) - const list = extractList(res) - printResult(list, options.json, ['id', 'status', 'sender_amount', 'receiver_amount', 'created_at']) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function getTransfer(id: string, options: { json: boolean }) { - try { - const ctx = resolveContext() - const transfer = await apiGet(ctx, `${instancePath(ctx)}/transfers/${id}`) - printResult(transfer, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function createTransfer(options: { transferQuoteId: string, json: boolean }) { - try { - const ctx = resolveContext() - const body = { transfer_quote_id: options.transferQuoteId } - const transfer = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/transfers`, body) - clack.log.success(`Created transfer ${transfer.id}`) - printResult(transfer, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -export async function trackTransfer(id: string, options: { json: boolean }) { - try { - const ctx = resolveContext() - const res = await apiGet(ctx, `/v1/e/transfers/${id}`) - printResult(res, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -// Transfer Quotes -export async function createTransferQuote(options: { - walletId: string - senderToken: string - receiverWalletAddress: string - receiverToken: string - receiverNetwork: string - requestAmount: string - amountReference: string - coverFees?: boolean - partnerFeeId?: string - json: boolean -}) { - try { - const ctx = resolveContext() - const body: Record = { - wallet_id: options.walletId, - sender_token: options.senderToken, - receiver_wallet_address: options.receiverWalletAddress, - receiver_token: options.receiverToken, - receiver_network: options.receiverNetwork, - request_amount: parseAmount(options.requestAmount, 0, options.json), - amount_reference: options.amountReference, - cover_fees: options.coverFees ?? false, - } - if (options.partnerFeeId !== undefined) body.partner_fee_id = options.partnerFeeId - const quote = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/transfer-quotes`, body) - clack.log.success(`Created transfer quote ${quote.id}`) - printResult(quote, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -// Fees -export async function getInstanceFees(options: { json: boolean }) { - try { - const ctx = resolveContext() - const fees = await apiGet(ctx, `${instancePath(ctx)}/billing/fees`) - printResult(fees, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -// Terms of Service -export async function initiateTos(options: { idempotencyKey: string, receiverId?: string, redirectUrl?: string, json: boolean }) { - try { - const ctx = resolveContext() - const body: Record = { idempotency_key: options.idempotencyKey } - if (options.receiverId !== undefined) body.receiver_id = options.receiverId - if (options.redirectUrl !== undefined) body.redirect_url = options.redirectUrl - const res = await apiPost<{ url: string }>(ctx, `/v1/e/instances/${ctx.instanceId}/tos`, body) - if (!options.json) clack.log.success(`Open this URL to accept the Terms of Service:\n${res.url}`) - printResult(res, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} - -// Upload -export async function uploadFile(options: { file: string, bucket: string, instanceId?: string, json: boolean }) { - try { - const ctx = resolveContext() - const fileHandle = Bun.file(options.file) - if (!await fileHandle.exists()) - exitWithError(`File not found: ${options.file}`, 1, options.json) - - const form = new FormData() - form.append('file', fileHandle as unknown as Blob, options.file.split('/').pop()) - form.append('bucket', options.bucket) - - const params = options.instanceId ? `?instance_id=${encodeURIComponent(options.instanceId)}` : '' - const url = `${ctx.baseUrl}/v1/upload${params}` - const res = await fetch(url, { method: 'POST', body: form }) - if (!res.ok) { - const text = await res.text().catch(() => '') - throw new Error(`Upload failed (${res.status}): ${text || res.statusText}`) - } - const body = await res.json() as { file_url: string } - if (!options.json) clack.log.success(`Uploaded: ${body.file_url}`) - printResult(body, options.json) - } - catch (e) { - handleApiError(e, options.json) - } -} diff --git a/src/index.ts b/src/index.ts index 735694d..69e4056 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,13 +3,13 @@ import { Command } from 'commander' import * as clack from '@clack/prompts' import { listSchemas, getSchema } from './commands/schema' import { - listReceivers, - getReceiver, - createReceiver, - updateReceiver, - deleteReceiver, - getReceiverLimits, - getReceiverLimitsIncreaseRequests, + listCustomers, + getCustomer, + createCustomer, + updateCustomer, + deleteCustomer, + getCustomerLimits, + getCustomerLimitsIncreaseRequests, listBankAccounts, getBankAccount, createBankAccount, @@ -66,12 +66,12 @@ const program = new Command() program .name('blindpay') - .description('Blindpay CLI — manage receivers, bank accounts, payouts, payins, and more from the terminal.') + .description('Blindpay CLI — manage customers, bank accounts, payouts, payins, and more from the terminal.') .version(CLI_VERSION) .addHelpText('after', ` Examples: $ blindpay config set --api-key --instance-id - $ blindpay receivers list --json + $ blindpay customers list --json $ blindpay payouts list --status processing $ blindpay available rails @@ -136,33 +136,33 @@ configCmd .action(() => console.log(getConfigPath())) // ── Receivers ─────────────────────────────────────────────────────────── -const receivers = program.command('receivers').description('Manage receivers') +const customers = program.command('customers').description('Manage customers') .addHelpText('after', ` Examples: - $ blindpay receivers list - $ blindpay receivers list --json - $ blindpay receivers get - $ blindpay receivers create --email user@example.com --name "John Doe" --country US - $ blindpay receivers create --type business --email biz@co.com --legal-name "Acme Inc" - $ blindpay receivers update --kyc-status approved - $ blindpay receivers delete `) - -receivers + $ blindpay customers list + $ blindpay customers list --json + $ blindpay customers get + $ blindpay customers create --email user@example.com --name "John Doe" --country US + $ blindpay customers create --type business --email biz@co.com --legal-name "Acme Inc" + $ blindpay customers update --kyc-status approved + $ blindpay customers delete `) + +customers .command('list') - .description('List all receivers') + .description('List all customers') .option('--json', 'Output as JSON', false) - .action(opts => listReceivers(opts)) + .action(opts => listCustomers(opts)) -receivers +customers .command('get ') - .description('Get a receiver by ID') + .description('Get a customer by ID') .option('--json', 'Output as JSON', false) .action((id, opts) => getReceiver(id, opts)) -receivers +customers .command('create') - .description('Create a new receiver') - .requiredOption('--email ', 'Receiver email') + .description('Create a new customer') + .requiredOption('--email ', 'Customer email') .option('--type ', 'individual or business', 'individual') .option('--name ', 'Full name (individual); splits into first_name and last_name') .option('--first-name ', 'First name (individual)') @@ -175,32 +175,32 @@ receivers .option('--json', 'Output as JSON', false) .action(opts => createReceiver(opts)) -receivers +customers .command('update ') - .description('Update a receiver') + .description('Update a customer') .option('--name ', 'Full name (individual); splits into first_name and last_name') .option('--first-name ', 'First name (individual)') .option('--last-name ', 'Last name (individual)') .option('--legal-name ', 'Legal name (business)') - .option('--email ', 'Receiver email') + .option('--email ', 'Customer email') .option('--country ', 'ISO 3166 country code') .option('--kyc-status ', 'KYC status (verifying, approved, rejected, deprecated)') .option('--json', 'Output as JSON', false) .action((id, opts) => updateReceiver(id, opts)) -receivers +customers .command('delete ') - .description('Delete a receiver') + .description('Delete a customer') .option('--json', 'Output as JSON', false) .action((id, opts) => deleteReceiver(id, opts)) -receivers +customers .command('limits ') .description('Get receiver limits') .option('--json', 'Output as JSON', false) .action((id, opts) => getReceiverLimits(id, opts)) -receivers +customers .command('limits_increase_requests ') .description('Get receiver limit-increase requests') .option('--json', 'Output as JSON', false) @@ -747,12 +747,12 @@ const schema = program.command('schema').description('Introspect CLI resource sc .addHelpText('after', ` Examples: $ blindpay schema # list all resources - $ blindpay schema receivers # full schema for receivers + $ blindpay schema customers # full schema for receivers $ blindpay schema bank_accounts # schema + available rails $ blindpay schema bank_accounts --rail ach # schema + rail-specific fields`) schema - .argument('[resource]', 'Resource name (e.g. receivers, payouts, bank_accounts)') + .argument('[resource]', 'Resource name (e.g. customers, payouts, bank_accounts)') .option('--rail ', 'Show rail-specific fields (bank_accounts only)') .action((resource, opts) => { if (!resource) { From e8dc2e80d7bcacde708e101b47b47cd2593fc5d6 Mon Sep 17 00:00:00 2001 From: alvseven Date: Mon, 15 Jun 2026 17:29:59 -0300 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20complete=20receivers=20=E2=86=92=20?= =?UTF-8?q?customers=20migration=20in=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename all exported functions: getReceiver→getCustomer, createReceiver→createCustomer, updateReceiver→updateCustomer, deleteReceiver→deleteCustomer, getReceiverLimits→getCustomerLimits, getReceiverLimitsIncreaseRequests→getCustomerLimitIncreaseRequests - Rename option param receiverId→customerId across all sub-resource functions (bank-accounts, blockchain-wallets, virtual-accounts, offramp-wallets, wallets) - Add create_limit_increase subcommand under customers - Restore missing functions removed in prior commit: createCustomerLimitIncrease, listWallets, getWallet, getWalletBalance, createWallet, deleteWallet, listTransfers, getTransfer, createTransfer, trackTransfer, createTransferQuote, getInstanceFees, initiateTos, uploadFile - Update all --receiver-id flags to --customer-id across bank_accounts, blockchain_wallets, virtual_accounts, offramp_wallets, wallets commands - Update schema.ts: receivers→customers resource, receiver_id→customer_id field names - Fix initiateTos to send customer_id body key instead of receiver_id --- src/commands/resources.ts | 315 +++++++++++++++++++++++++++++++++----- src/commands/schema.ts | 14 +- src/index.ts | 110 ++++++------- 3 files changed, 338 insertions(+), 101 deletions(-) diff --git a/src/commands/resources.ts b/src/commands/resources.ts index ebbbca5..02b8352 100644 --- a/src/commands/resources.ts +++ b/src/commands/resources.ts @@ -88,18 +88,18 @@ export async function listCustomers(options: { json: boolean }) { } } -export async function getReceiver(id: string, options: { json: boolean }) { +export async function getCustomer(id: string, options: { json: boolean }) { try { const ctx = resolveContext() - const receiver = await apiGet(ctx, `${instancePath(ctx)}/customers/${id}`) - printResult(receiver, options.json) + const customer = await apiGet(ctx, `${instancePath(ctx)}/customers/${id}`) + printResult(customer, options.json) } catch (e) { handleApiError(e, options.json) } } -export async function createReceiver(options: { +export async function createCustomer(options: { email: string type?: string name?: string @@ -138,20 +138,20 @@ export async function createReceiver(options: { external_id: options.externalId ?? null, kyc_status: options.kycStatus ?? 'approved', } - const receiver = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers`, body) + const customer = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers`, body) const displayName = body.type === 'business' ? (body.legal_name || '—') : [body.first_name, body.last_name].filter(Boolean).join(' ').trim() || '—' - clack.log.success(`Created customer ${receiver.id} (${receiver.type}, ${displayName})`) + clack.log.success(`Created customer ${customer.id} (${customer.type}, ${displayName})`) if (options.json) - console.log(formatOutput(receiver, true)) + console.log(formatOutput(customer, true)) } catch (e) { handleApiError(e, options.json) } } -export async function updateReceiver( +export async function updateCustomer( id: string, options: { name?: string @@ -193,19 +193,19 @@ export async function updateReceiver( if (Object.keys(body).length === 0) { exitWithError('Provide at least one field to update (e.g. --name, --kyc-status)', 1, options.json) } - const receiver = await apiPut>(ctx, `${instancePath(ctx)}/customers/${id}`, body) + const customer = await apiPut>(ctx, `${instancePath(ctx)}/customers/${id}`, body) clack.log.success(`Updated customer ${id}`) if (options.json) - console.log(formatOutput(receiver, true)) + console.log(formatOutput(customer, true)) else - console.log(formatOutput(receiver, false)) + console.log(formatOutput(customer, false)) } catch (e) { handleApiError(e, options.json) } } -export async function deleteReceiver(id: string, options: { json?: boolean } = {}) { +export async function deleteCustomer(id: string, options: { json?: boolean } = {}) { try { const ctx = resolveContext() await apiDelete(ctx, `${instancePath(ctx)}/customers/${id}`) @@ -217,10 +217,10 @@ export async function deleteReceiver(id: string, options: { json?: boolean } = { } // Bank Accounts -export async function listBankAccounts(options: { receiverId: string, json: boolean }) { +export async function listBankAccounts(options: { customerId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/bank-accounts`) const list = extractList(res) const display = list.map((a: any) => ({ id: a.id, type: a.type, name: a.name, status: a.status, country: a.country })) printResult(options.json ? list : display, options.json, ['id', 'type', 'name', 'status', 'country']) @@ -230,10 +230,10 @@ export async function listBankAccounts(options: { receiverId: string, json: bool } } -export async function getBankAccount(id: string, options: { receiverId: string, json: boolean }) { +export async function getBankAccount(id: string, options: { customerId: string, json: boolean }) { try { const ctx = resolveContext() - const account = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts/${id}`) + const account = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/bank-accounts/${id}`) printResult(account, options.json) } catch (e) { @@ -242,7 +242,7 @@ export async function getBankAccount(id: string, options: { receiverId: string, } export async function createBankAccount(options: { - receiverId: string + customerId: string type?: string name?: string recipientRelationship?: string @@ -269,7 +269,7 @@ export async function createBankAccount(options: { account_class: options.accountClass ?? null, country: options.country ?? null, } - const ba = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts`, body) + const ba = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers/${options.customerId}/bank-accounts`, body) clack.log.success(`Created bank account ${ba.id} (${ba.type})`) if (options.json) console.log(formatOutput(ba, true)) @@ -279,10 +279,10 @@ export async function createBankAccount(options: { } } -export async function deleteBankAccount(id: string, options: { receiverId: string, json?: boolean }) { +export async function deleteBankAccount(id: string, options: { customerId: string, json?: boolean }) { try { const ctx = resolveContext() - await apiDelete(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts/${id}`) + await apiDelete(ctx, `${instancePath(ctx)}/customers/${options.customerId}/bank-accounts/${id}`) clack.log.success(`Deleted bank account ${id}`) } catch (e) { @@ -291,10 +291,10 @@ export async function deleteBankAccount(id: string, options: { receiverId: strin } // Blockchain Wallets -export async function listBlockchainWallets(options: { receiverId: string, json: boolean }) { +export async function listBlockchainWallets(options: { customerId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/blockchain-wallets`) const list = extractList(res) const display = list.map((w: any) => ({ id: w.id, address: truncate(w.address, 20), network: w.network })) printResult(options.json ? list : display, options.json, ['id', 'address', 'network']) @@ -304,10 +304,10 @@ export async function listBlockchainWallets(options: { receiverId: string, json: } } -export async function getBlockchainWallet(id: string, options: { receiverId: string, json: boolean }) { +export async function getBlockchainWallet(id: string, options: { customerId: string, json: boolean }) { try { const ctx = resolveContext() - const wallet = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets/${id}`) + const wallet = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/blockchain-wallets/${id}`) printResult(wallet, options.json) } catch (e) { @@ -316,7 +316,7 @@ export async function getBlockchainWallet(id: string, options: { receiverId: str } export async function createBlockchainWallet(options: { - receiverId: string + customerId: string address: string network?: string name?: string @@ -331,7 +331,7 @@ export async function createBlockchainWallet(options: { name: options.name || 'CLI Blockchain Wallet', external_id: options.externalId ?? null, } - const wallet = await apiPost<{ id: string, network: string }>(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets`, body) + const wallet = await apiPost<{ id: string, network: string }>(ctx, `${instancePath(ctx)}/customers/${options.customerId}/blockchain-wallets`, body) clack.log.success(`Created blockchain wallet ${wallet.id} (${wallet.network})`) if (options.json) console.log(formatOutput(wallet, true)) @@ -341,10 +341,10 @@ export async function createBlockchainWallet(options: { } } -export async function deleteBlockchainWallet(id: string, options: { receiverId: string, json?: boolean }) { +export async function deleteBlockchainWallet(id: string, options: { customerId: string, json?: boolean }) { try { const ctx = resolveContext() - await apiDelete(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/blockchain-wallets/${id}`) + await apiDelete(ctx, `${instancePath(ctx)}/customers/${options.customerId}/blockchain-wallets/${id}`) clack.log.success(`Deleted blockchain wallet ${id}`) } catch (e) { @@ -659,10 +659,10 @@ export async function deleteApiKey(id: string, options: { json?: boolean } = {}) } // Virtual Accounts -export async function listVirtualAccounts(options: { receiverId: string, json: boolean }) { +export async function listVirtualAccounts(options: { customerId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/virtual-accounts`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/virtual-accounts`) const list = extractList(res) const display = list.map((a: any) => ({ id: a.id, account_number: a.account_number, routing_number: a.routing_number, kyc_status: a.kyc_status })) printResult(options.json ? list : display, options.json, ['id', 'account_number', 'routing_number', 'kyc_status']) @@ -672,10 +672,10 @@ export async function listVirtualAccounts(options: { receiverId: string, json: b } } -export async function createVirtualAccount(options: { receiverId: string, blockchainWalletId: string, json: boolean }) { +export async function createVirtualAccount(options: { customerId: string, blockchainWalletId: string, json: boolean }) { try { const ctx = resolveContext() - const account = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/virtual-accounts`, { blockchain_wallet_id: options.blockchainWalletId }) + const account = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/customers/${options.customerId}/virtual-accounts`, { blockchain_wallet_id: options.blockchainWalletId }) clack.log.success(`Created virtual account ${account.id}`) if (options.json) console.log(formatOutput(account, true)) @@ -686,10 +686,10 @@ export async function createVirtualAccount(options: { receiverId: string, blockc } // Offramp Wallets -export async function listOfframpWallets(options: { receiverId: string, bankAccountId: string, json: boolean }) { +export async function listOfframpWallets(options: { customerId: string, bankAccountId: string, json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.receiverId}/bank-accounts/${options.bankAccountId}/offramp-wallets`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/bank-accounts/${options.bankAccountId}/offramp-wallets`) const list = extractList(res) const display = list.map((w: any) => ({ id: w.id, address: truncate(w.address, 20), network: w.network })) printResult(options.json ? list : display, options.json, ['id', 'address', 'network']) @@ -746,11 +746,11 @@ export async function updateInstance(options: { } } -// Receiver Limits -export async function getReceiverLimits(receiverId: string, options: { json: boolean }) { +// Customer Limits +export async function getCustomerLimits(customerId: string, options: { json: boolean }) { try { const ctx = resolveContext() - const limits = await apiGet(ctx, `${instancePath(ctx)}/customers/${receiverId}/limits`) + const limits = await apiGet(ctx, `${instancePath(ctx)}/customers/${customerId}/limits`) printResult(limits, options.json) } catch (e) { @@ -758,10 +758,10 @@ export async function getReceiverLimits(receiverId: string, options: { json: boo } } -export async function getReceiverLimitsIncreaseRequests(receiverId: string, options: { json: boolean }) { +export async function getCustomerLimitIncreaseRequests(customerId: string, options: { json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${receiverId}/limits-increase-requests`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${customerId}/limits-increase-requests`) const list = extractList(res) printResult(list, options.json) } @@ -770,6 +770,35 @@ export async function getReceiverLimitsIncreaseRequests(receiverId: string, opti } } +export async function createCustomerLimitIncrease( + customerId: string, + options: { + perTransaction: string + daily: string + monthly: string + supportingDocumentType: string + supportingDocumentFile: string + json: boolean + }, +) { + try { + const ctx = resolveContext() + const body = { + per_transaction: parseAmount(options.perTransaction, 0, options.json), + daily: parseAmount(options.daily, 0, options.json), + monthly: parseAmount(options.monthly, 0, options.json), + supporting_document_type: options.supportingDocumentType, + supporting_document_file: options.supportingDocumentFile, + } + const res = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/customers/${customerId}/limit-increase`, body) + clack.log.success(`Created limit increase request ${res.id}`) + printResult(res, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + // FX Rates export async function getQuoteFxRate(options: { from?: string, to?: string, json: boolean }) { try { @@ -809,3 +838,211 @@ export async function getAvailableBankDetails(options: { rail: string, json: boo handleApiError(e, options.json) } } + +// Wallets (custodial, distinct from blockchain_wallets) +export async function listWallets(options: { customerId: string, json: boolean }) { + try { + const ctx = resolveContext() + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/wallets`) + const list = extractList(res) + printResult(list, options.json, ['id', 'name', 'network', 'address']) + } + catch (e) { + handleApiError(e, options.json) + } +} + +export async function getWallet(id: string, options: { customerId: string, json: boolean }) { + try { + const ctx = resolveContext() + const wallet = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/wallets/${id}`) + printResult(wallet, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +export async function getWalletBalance(id: string, options: { customerId: string, json: boolean }) { + try { + const ctx = resolveContext() + const balance = await apiGet(ctx, `${instancePath(ctx)}/customers/${options.customerId}/wallets/${id}/balance`) + printResult(balance, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +export async function createWallet(options: { + customerId: string + network: string + name: string + externalId?: string + json: boolean +}) { + try { + const ctx = resolveContext() + const body: Record = { + network: options.network, + name: options.name, + } + if (options.externalId !== undefined) body.external_id = options.externalId + const wallet = await apiPost<{ id: string, network: string }>(ctx, `${instancePath(ctx)}/customers/${options.customerId}/wallets`, body) + clack.log.success(`Created wallet ${wallet.id} (${wallet.network})`) + printResult(wallet, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +export async function deleteWallet(id: string, options: { customerId: string, json?: boolean }) { + try { + const ctx = resolveContext() + await apiDelete(ctx, `${instancePath(ctx)}/customers/${options.customerId}/wallets/${id}`) + clack.log.success(`Deleted wallet ${id}`) + } + catch (e) { + handleApiError(e, options.json ?? false) + } +} + +// Transfers +export async function listTransfers(options: { json: boolean }) { + try { + const ctx = resolveContext() + const res = await apiGet(ctx, `${instancePath(ctx)}/transfers`) + const list = extractList(res) + printResult(list, options.json, ['id', 'status', 'sender_amount', 'receiver_amount', 'created_at']) + } + catch (e) { + handleApiError(e, options.json) + } +} + +export async function getTransfer(id: string, options: { json: boolean }) { + try { + const ctx = resolveContext() + const transfer = await apiGet(ctx, `${instancePath(ctx)}/transfers/${id}`) + printResult(transfer, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +export async function createTransfer(options: { transferQuoteId: string, json: boolean }) { + try { + const ctx = resolveContext() + const body = { transfer_quote_id: options.transferQuoteId } + const transfer = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/transfers`, body) + clack.log.success(`Created transfer ${transfer.id}`) + printResult(transfer, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +export async function trackTransfer(id: string, options: { json: boolean }) { + try { + const ctx = resolveContext() + const res = await apiGet(ctx, `/v1/e/transfers/${id}`) + printResult(res, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +// Transfer Quotes +export async function createTransferQuote(options: { + walletId: string + senderToken: string + receiverWalletAddress: string + receiverToken: string + receiverNetwork: string + requestAmount: string + amountReference: string + coverFees?: boolean + partnerFeeId?: string + json: boolean +}) { + try { + const ctx = resolveContext() + const body: Record = { + wallet_id: options.walletId, + sender_token: options.senderToken, + receiver_wallet_address: options.receiverWalletAddress, + receiver_token: options.receiverToken, + receiver_network: options.receiverNetwork, + request_amount: parseAmount(options.requestAmount, 0, options.json), + amount_reference: options.amountReference, + cover_fees: options.coverFees ?? false, + } + if (options.partnerFeeId !== undefined) body.partner_fee_id = options.partnerFeeId + const quote = await apiPost<{ id: string }>(ctx, `${instancePath(ctx)}/transfer-quotes`, body) + clack.log.success(`Created transfer quote ${quote.id}`) + printResult(quote, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +// Fees +export async function getInstanceFees(options: { json: boolean }) { + try { + const ctx = resolveContext() + const fees = await apiGet(ctx, `${instancePath(ctx)}/billing/fees`) + printResult(fees, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +// Terms of Service +export async function initiateTos(options: { idempotencyKey: string, customerId?: string, redirectUrl?: string, json: boolean }) { + try { + const ctx = resolveContext() + const body: Record = { idempotency_key: options.idempotencyKey } + if (options.customerId !== undefined) body.customer_id = options.customerId + if (options.redirectUrl !== undefined) body.redirect_url = options.redirectUrl + const res = await apiPost<{ url: string }>(ctx, `/v1/e/instances/${ctx.instanceId}/tos`, body) + if (!options.json) clack.log.success(`Open this URL to accept the Terms of Service:\n${res.url}`) + printResult(res, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} + +// Upload +export async function uploadFile(options: { file: string, bucket: string, instanceId?: string, json: boolean }) { + try { + const ctx = resolveContext() + const fileHandle = Bun.file(options.file) + if (!await fileHandle.exists()) + exitWithError(`File not found: ${options.file}`, 1, options.json) + + const form = new FormData() + form.append('file', fileHandle as unknown as Blob, options.file.split('/').pop()) + form.append('bucket', options.bucket) + + const params = options.instanceId ? `?instance_id=${encodeURIComponent(options.instanceId)}` : '' + const url = `${ctx.baseUrl}/v1/upload${params}` + const res = await fetch(url, { method: 'POST', body: form }) + if (!res.ok) { + const text = await res.text().catch(() => '') + throw new Error(`Upload failed (${res.status}): ${text || res.statusText}`) + } + const body = await res.json() as { file_url: string } + if (!options.json) clack.log.success(`Uploaded: ${body.file_url}`) + printResult(body, options.json) + } + catch (e) { + handleApiError(e, options.json) + } +} diff --git a/src/commands/schema.ts b/src/commands/schema.ts index 4a2781d..f2da478 100644 --- a/src/commands/schema.ts +++ b/src/commands/schema.ts @@ -19,12 +19,12 @@ interface ResourceSchema { const schemas: ResourceSchema[] = [ { - resource: 'receivers', + resource: 'customers', commands: ['list', 'get', 'create', 'update', 'delete'], create: { fields: [ - { name: 'email', type: 'string', required: true, description: 'Receiver email address' }, - { name: 'type', type: 'string', required: false, description: 'Receiver type', default: 'individual', enum: ['individual', 'business'] }, + { name: 'email', type: 'string', required: true, description: 'Customer email address' }, + { name: 'type', type: 'string', required: false, description: 'Customer type', default: 'individual', enum: ['individual', 'business'] }, { name: 'name', type: 'string', required: false, description: 'Full name (individual); auto-splits into first_name and last_name' }, { name: 'first_name', type: 'string', required: false, description: 'First name (individual)' }, { name: 'last_name', type: 'string', required: false, description: 'Last name (individual)' }, @@ -41,7 +41,7 @@ const schemas: ResourceSchema[] = [ { name: 'first_name', type: 'string', required: false, description: 'First name (individual)' }, { name: 'last_name', type: 'string', required: false, description: 'Last name (individual)' }, { name: 'legal_name', type: 'string', required: false, description: 'Legal name (business)' }, - { name: 'email', type: 'string', required: false, description: 'Receiver email address' }, + { name: 'email', type: 'string', required: false, description: 'Customer email address' }, { name: 'country', type: 'string', required: false, description: 'ISO 3166 country code' }, { name: 'kyc_status', type: 'string', required: false, description: 'KYC verification status', enum: ['verifying', 'approved', 'rejected', 'deprecated'] }, ], @@ -52,7 +52,7 @@ const schemas: ResourceSchema[] = [ commands: ['list', 'get', 'create', 'delete'], create: { fields: [ - { name: 'receiver_id', type: 'string', required: true, description: 'Receiver ID that owns this bank account' }, + { name: 'customer_id', type: 'string', required: true, description: 'Customer ID that owns this bank account' }, { name: 'type', type: 'string', required: false, description: 'Bank account type / payment rail', default: 'ach', enum: Object.keys(bankDetailFields) }, { name: 'name', type: 'string', required: false, description: 'Account display name', default: 'CLI Bank Account' }, { name: 'beneficiary_name', type: 'string', required: false, description: 'Beneficiary name on the account' }, @@ -71,7 +71,7 @@ const schemas: ResourceSchema[] = [ commands: ['list', 'get', 'create', 'delete'], create: { fields: [ - { name: 'receiver_id', type: 'string', required: true, description: 'Receiver ID that owns this wallet' }, + { name: 'customer_id', type: 'string', required: true, description: 'Customer ID that owns this wallet' }, { name: 'address', type: 'string', required: true, description: 'Blockchain wallet address' }, { name: 'network', type: 'string', required: false, description: 'Blockchain network', default: 'base', enum: ['base', 'ethereum', 'polygon', 'solana', 'stellar', 'arbitrum', 'optimism'] }, { name: 'external_id', type: 'string', required: false, description: 'External reference ID' }, @@ -162,7 +162,7 @@ const schemas: ResourceSchema[] = [ commands: ['list', 'create'], create: { fields: [ - { name: 'receiver_id', type: 'string', required: true, description: 'Receiver ID' }, + { name: 'customer_id', type: 'string', required: true, description: 'Customer ID' }, { name: 'blockchain_wallet_id', type: 'string', required: true, description: 'Blockchain wallet ID' }, ], }, diff --git a/src/index.ts b/src/index.ts index 69e4056..c713f8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,8 @@ import { updateCustomer, deleteCustomer, getCustomerLimits, - getCustomerLimitsIncreaseRequests, + getCustomerLimitIncreaseRequests, + createCustomerLimitIncrease, listBankAccounts, getBankAccount, createBankAccount, @@ -44,7 +45,6 @@ import { updateInstance, listAvailableRails, getAvailableBankDetails, - createReceiverLimitIncrease, listWallets, getWallet, getWalletBalance, @@ -135,7 +135,7 @@ configCmd .description('Print config file path') .action(() => console.log(getConfigPath())) -// ── Receivers ─────────────────────────────────────────────────────────── +// ── Customers ─────────────────────────────────────────────────────────── const customers = program.command('customers').description('Manage customers') .addHelpText('after', ` Examples: @@ -157,7 +157,7 @@ customers .command('get ') .description('Get a customer by ID') .option('--json', 'Output as JSON', false) - .action((id, opts) => getReceiver(id, opts)) + .action((id, opts) => getCustomer(id, opts)) customers .command('create') @@ -173,7 +173,7 @@ customers .option('--external-id ', 'External ID') .option('--kyc-status ', 'KYC status (verifying, approved, rejected, deprecated)', 'approved') .option('--json', 'Output as JSON', false) - .action(opts => createReceiver(opts)) + .action(opts => createCustomer(opts)) customers .command('update ') @@ -186,65 +186,65 @@ customers .option('--country ', 'ISO 3166 country code') .option('--kyc-status ', 'KYC status (verifying, approved, rejected, deprecated)') .option('--json', 'Output as JSON', false) - .action((id, opts) => updateReceiver(id, opts)) + .action((id, opts) => updateCustomer(id, opts)) customers .command('delete ') .description('Delete a customer') .option('--json', 'Output as JSON', false) - .action((id, opts) => deleteReceiver(id, opts)) + .action((id, opts) => deleteCustomer(id, opts)) customers .command('limits ') - .description('Get receiver limits') + .description('Get customer limits') .option('--json', 'Output as JSON', false) - .action((id, opts) => getReceiverLimits(id, opts)) + .action((id, opts) => getCustomerLimits(id, opts)) customers .command('limits_increase_requests ') - .description('Get receiver limit-increase requests') + .description('Get customer limit-increase requests') .option('--json', 'Output as JSON', false) - .action((id, opts) => getReceiverLimitsIncreaseRequests(id, opts)) + .action((id, opts) => getCustomerLimitIncreaseRequests(id, opts)) -receivers +customers .command('create_limit_increase ') - .description('Request a limit increase for a receiver') + .description('Request a limit increase for a customer') .requiredOption('--per-transaction ', 'Per-transaction limit in cents') .requiredOption('--daily ', 'Daily limit in cents') .requiredOption('--monthly ', 'Monthly limit in cents') - .requiredOption('--supporting-document-type ', 'Supporting document type (individual_bank_statement, individual_tax_return, individual_proof_of_income, business_bank_statement, business_financial_statements, business_tax_return)') + .requiredOption('--supporting-document-type ', 'Supporting document type') .requiredOption('--supporting-document-file ', 'Supporting document URL (upload via `blindpay upload` first)') .option('--json', 'Output as JSON', false) - .action((id, opts) => createReceiverLimitIncrease(id, opts)) + .action((id, opts) => createCustomerLimitIncrease(id, opts)) // ── Bank Accounts ─────────────────────────────────────────────────────── const bankAccounts = program.command('bank_accounts').description('Manage bank accounts') .addHelpText('after', ` Examples: - $ blindpay bank_accounts list --receiver-id - $ blindpay bank_accounts get --receiver-id - $ blindpay bank_accounts create --receiver-id --type ach --routing-number 021000021 --account-number 123456789 - $ blindpay bank_accounts create --receiver-id --type pix --pix-key user@email.com - $ blindpay bank_accounts delete --receiver-id `) + $ blindpay bank_accounts list --customer-id + $ blindpay bank_accounts get --customer-id + $ blindpay bank_accounts create --customer-id --type ach --routing-number 021000021 --account-number 123456789 + $ blindpay bank_accounts create --customer-id --type pix --pix-key user@email.com + $ blindpay bank_accounts delete --customer-id `) bankAccounts .command('list') - .description('List bank accounts for a receiver') - .requiredOption('--receiver-id ', 'Receiver ID') + .description('List bank accounts for a customer') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action(opts => listBankAccounts(opts)) bankAccounts .command('get ') .description('Get a bank account by ID') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action((id, opts) => getBankAccount(id, opts)) bankAccounts .command('create') .description('Create a new bank account') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--type ', 'Bank account type (ach, wire, pix, etc.)', 'ach') .option('--name ', 'Account name') .option('--beneficiary-name ', 'Beneficiary name') @@ -262,7 +262,7 @@ bankAccounts bankAccounts .command('delete ') .description('Delete a bank account') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action((id, opts) => deleteBankAccount(id, opts)) @@ -270,29 +270,29 @@ bankAccounts const blockchainWallets = program.command('blockchain_wallets').description('Manage blockchain wallets') .addHelpText('after', ` Examples: - $ blindpay blockchain_wallets list --receiver-id - $ blindpay blockchain_wallets get --receiver-id - $ blindpay blockchain_wallets create --receiver-id --address 0x... --network base - $ blindpay blockchain_wallets delete --receiver-id `) + $ blindpay blockchain_wallets list --customer-id + $ blindpay blockchain_wallets get --customer-id + $ blindpay blockchain_wallets create --customer-id --address 0x... --network base + $ blindpay blockchain_wallets delete --customer-id `) blockchainWallets .command('list') - .description('List blockchain wallets for a receiver') - .requiredOption('--receiver-id ', 'Receiver ID') + .description('List blockchain wallets for a customer') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action(opts => listBlockchainWallets(opts)) blockchainWallets .command('get ') .description('Get a blockchain wallet by ID') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action((id, opts) => getBlockchainWallet(id, opts)) blockchainWallets .command('create') .description('Create a new blockchain wallet') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .requiredOption('--address
', 'Wallet address') .option('--network ', 'Blockchain network', 'base') .option('--name ', 'Wallet name') @@ -303,7 +303,7 @@ blockchainWallets blockchainWallets .command('delete ') .description('Delete a blockchain wallet') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action((id, opts) => deleteBlockchainWallet(id, opts)) @@ -508,20 +508,20 @@ apiKeys const virtualAccounts = program.command('virtual_accounts').description('Manage virtual accounts') .addHelpText('after', ` Examples: - $ blindpay virtual_accounts list --receiver-id - $ blindpay virtual_accounts create --receiver-id --blockchain-wallet-id `) + $ blindpay virtual_accounts list --customer-id + $ blindpay virtual_accounts create --customer-id --blockchain-wallet-id `) virtualAccounts .command('list') - .description('List virtual accounts for a receiver') - .requiredOption('--receiver-id ', 'Receiver ID') + .description('List virtual accounts for a customer') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action(opts => listVirtualAccounts(opts)) virtualAccounts .command('create') .description('Create a virtual account') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .requiredOption('--blockchain-wallet-id ', 'Blockchain wallet ID') .option('--json', 'Output as JSON', false) .action(opts => createVirtualAccount(opts)) @@ -530,12 +530,12 @@ virtualAccounts const offrampWallets = program.command('offramp_wallets').description('Manage offramp wallets') .addHelpText('after', ` Examples: - $ blindpay offramp_wallets list --receiver-id --bank-account-id `) + $ blindpay offramp_wallets list --customer-id --bank-account-id `) offrampWallets .command('list') .description('List offramp wallets') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .requiredOption('--bank-account-id ', 'Bank account ID') .option('--json', 'Output as JSON', false) .action(opts => listOfframpWallets(opts)) @@ -596,37 +596,37 @@ available const wallets = program.command('wallets').description('Manage custodial wallets (BlindPay-managed, with balance)') .addHelpText('after', ` Examples: - $ blindpay wallets list --receiver-id - $ blindpay wallets get --receiver-id - $ blindpay wallets balance --receiver-id - $ blindpay wallets create --receiver-id --network polygon --name "Main Wallet" - $ blindpay wallets delete --receiver-id `) + $ blindpay wallets list --customer-id + $ blindpay wallets get --customer-id + $ blindpay wallets balance --customer-id + $ blindpay wallets create --customer-id --network polygon --name "Main Wallet" + $ blindpay wallets delete --customer-id `) wallets .command('list') - .description('List custodial wallets for a receiver') - .requiredOption('--receiver-id ', 'Receiver ID') + .description('List custodial wallets for a customer') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action(opts => listWallets(opts)) wallets .command('get ') .description('Get a custodial wallet by ID') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action((id, opts) => getWallet(id, opts)) wallets .command('balance ') .description('Get a custodial wallet balance (USDC/USDT/USDB)') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action((id, opts) => getWalletBalance(id, opts)) wallets .command('create') .description('Create a new custodial wallet') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .requiredOption('--network ', 'Network') .requiredOption('--name ', 'Wallet name') .option('--external-id ', 'External ID') @@ -636,7 +636,7 @@ wallets wallets .command('delete ') .description('Delete a custodial wallet') - .requiredOption('--receiver-id ', 'Receiver ID') + .requiredOption('--customer-id ', 'Customer ID') .option('--json', 'Output as JSON', false) .action((id, opts) => deleteWallet(id, opts)) @@ -715,13 +715,13 @@ const tos = program.command('tos').description('Initiate Terms of Service flow') .addHelpText('after', ` Examples: $ blindpay tos initiate --idempotency-key - $ blindpay tos initiate --idempotency-key --receiver-id --redirect-url https://example.com`) + $ blindpay tos initiate --idempotency-key --customer-id --redirect-url https://example.com`) tos .command('initiate') .description('Initiate a Terms of Service session and return a hosted URL') .requiredOption('--idempotency-key ', 'Idempotency key') - .option('--receiver-id ', 'Receiver ID') + .option('--customer-id ', 'Customer ID') .option('--redirect-url ', 'Redirect URL after acceptance') .option('--json', 'Output as JSON', false) .action(opts => initiateTos(opts)) @@ -747,7 +747,7 @@ const schema = program.command('schema').description('Introspect CLI resource sc .addHelpText('after', ` Examples: $ blindpay schema # list all resources - $ blindpay schema customers # full schema for receivers + $ blindpay schema customers # full schema for customers $ blindpay schema bank_accounts # schema + available rails $ blindpay schema bank_accounts --rail ach # schema + rail-specific fields`) From 244342f057493212fa930fb5e9c831c8962ac497 Mon Sep 17 00:00:00 2001 From: alvseven Date: Mon, 15 Jun 2026 18:45:36 -0300 Subject: [PATCH 3/8] fix: correct limits and limit-increase-requests endpoint paths for customers --- src/commands/resources.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/resources.ts b/src/commands/resources.ts index 02b8352..8959b8b 100644 --- a/src/commands/resources.ts +++ b/src/commands/resources.ts @@ -750,7 +750,7 @@ export async function updateInstance(options: { export async function getCustomerLimits(customerId: string, options: { json: boolean }) { try { const ctx = resolveContext() - const limits = await apiGet(ctx, `${instancePath(ctx)}/customers/${customerId}/limits`) + const limits = await apiGet(ctx, `${instancePath(ctx)}/limits/customers/${customerId}`) printResult(limits, options.json) } catch (e) { @@ -761,7 +761,7 @@ export async function getCustomerLimits(customerId: string, options: { json: boo export async function getCustomerLimitIncreaseRequests(customerId: string, options: { json: boolean }) { try { const ctx = resolveContext() - const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${customerId}/limits-increase-requests`) + const res = await apiGet(ctx, `${instancePath(ctx)}/customers/${customerId}/limit-increase`) const list = extractList(res) printResult(list, options.json) } From 000eba5dcbf23087324bb54fa1db3c798af44c59 Mon Sep 17 00:00:00 2001 From: alvseven Date: Mon, 15 Jun 2026 22:34:26 -0300 Subject: [PATCH 4/8] feat: add SEPA fields (iban, bic, legal-name) to bank_accounts create command --- src/commands/resources.ts | 8 +++++++- src/index.ts | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/commands/resources.ts b/src/commands/resources.ts index 8959b8b..ce0422b 100644 --- a/src/commands/resources.ts +++ b/src/commands/resources.ts @@ -253,11 +253,14 @@ export async function createBankAccount(options: { accountType?: string accountClass?: string country?: string + sepaIban?: string + sepaBeneficiaryBic?: string + sepaBeneficiaryLegalName?: string json: boolean }) { try { const ctx = resolveContext() - const body = { + const body: Record = { type: options.type || 'ach', name: options.name || 'CLI Bank Account', recipient_relationship: options.recipientRelationship ?? null, @@ -269,6 +272,9 @@ export async function createBankAccount(options: { account_class: options.accountClass ?? null, country: options.country ?? null, } + if (options.sepaIban) body.sepa_iban = options.sepaIban + if (options.sepaBeneficiaryBic) body.sepa_beneficiary_bic = options.sepaBeneficiaryBic + if (options.sepaBeneficiaryLegalName) body.sepa_beneficiary_legal_name = options.sepaBeneficiaryLegalName const ba = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers/${options.customerId}/bank-accounts`, body) clack.log.success(`Created bank account ${ba.id} (${ba.type})`) if (options.json) diff --git a/src/index.ts b/src/index.ts index c713f8e..accd1df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -245,7 +245,7 @@ bankAccounts .command('create') .description('Create a new bank account') .requiredOption('--customer-id ', 'Customer ID') - .option('--type ', 'Bank account type (ach, wire, pix, etc.)', 'ach') + .option('--type ', 'Bank account type (ach, wire, pix, sepa, etc.)', 'ach') .option('--name ', 'Account name') .option('--beneficiary-name ', 'Beneficiary name') .option('--routing-number ', 'Routing number') @@ -255,7 +255,9 @@ bankAccounts .option('--pix-key ', 'PIX key') .option('--recipient-relationship ', 'Recipient relationship') .option('--country ', 'Country code') - .option('--swift-ifsc-branch-code ', 'SWIFT/IFSC branch code (international_swift accounts)') + .option('--sepa-iban ', 'IBAN (sepa accounts)') + .option('--sepa-beneficiary-bic ', 'Beneficiary BIC/SWIFT code (sepa accounts)') + .option('--sepa-beneficiary-legal-name ', 'Beneficiary legal name (sepa accounts)') .option('--json', 'Output as JSON', false) .action(opts => createBankAccount(opts)) From fa38efbd0edc22761af4392ac370f24b3b02a593 Mon Sep 17 00:00:00 2001 From: alvseven Date: Tue, 16 Jun 2026 01:20:29 -0300 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20update=20tests=20=E2=80=94=20receive?= =?UTF-8?q?r*=20=E2=86=92=20customer*,=20receiverId=20=E2=86=92=20customer?= =?UTF-8?q?Id,=20urls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/resources.test.ts | 145 ++++++++++++++++---------------- 1 file changed, 72 insertions(+), 73 deletions(-) diff --git a/src/__tests__/resources.test.ts b/src/__tests__/resources.test.ts index 1441522..d850d39 100644 --- a/src/__tests__/resources.test.ts +++ b/src/__tests__/resources.test.ts @@ -112,29 +112,29 @@ function lastJsonLog(): Record { const BASE = 'https://api.blindpay.com/v1/instances/in_testInstance' -describe('Receivers', () => { +describe('Customers', () => { beforeEach(setupTestEnv) afterEach(teardownTestEnv) - test('lists receivers', async () => { - mockResponse.body = { data: [{ id: 're_1', type: 'individual', email: 'a@b.com' }] } - await resources.listReceivers({ json: true }) + test('lists customers', async () => { + mockResponse.body = [{ id: 'cu_1', type: 'individual', email: 'a@b.com' }] + await resources.listCustomers({ json: true }) expect(lastCall().method).toBe('GET') - expect(lastCall().url).toBe(`${BASE}/receivers`) + expect(lastCall().url).toBe(`${BASE}/customers`) }) - test('fetches a receiver by id', async () => { - mockResponse.body = { id: 're_xyz' } - await resources.getReceiver('re_xyz', { json: true }) + test('fetches a customer by id', async () => { + mockResponse.body = { id: 'cu_xyz' } + await resources.getCustomer('cu_xyz', { json: true }) expect(lastCall().method).toBe('GET') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz`) }) - test('creates a receiver, splitting --name into first_name and last_name and filling defaults', async () => { - mockResponse.body = { id: 're_new', type: 'individual' } - await resources.createReceiver({ email: 'a@b.com', name: 'Jane Doe', json: true }) + test('creates a customer, splitting --name into first_name and last_name and filling defaults', async () => { + mockResponse.body = { id: 'cu_new', type: 'individual' } + await resources.createCustomer({ email: 'a@b.com', name: 'Jane Doe', json: true }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/receivers`) + expect(lastCall().url).toBe(`${BASE}/customers`) expect(lastCall().body).toEqual({ type: 'individual', email: 'a@b.com', @@ -149,35 +149,35 @@ describe('Receivers', () => { }) test('updates only the fields explicitly passed via flags', async () => { - mockResponse.body = { id: 're_xyz' } - await resources.updateReceiver('re_xyz', { email: 'new@b.com', json: true }) + mockResponse.body = { id: 'cu_xyz' } + await resources.updateCustomer('cu_xyz', { email: 'new@b.com', json: true }) expect(lastCall().method).toBe('PUT') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz`) expect(lastCall().body).toEqual({ email: 'new@b.com' }) }) - test('deletes a receiver by id', async () => { + test('deletes a customer by id', async () => { mockResponse.body = { success: true } - await resources.deleteReceiver('re_xyz', { json: true }) + await resources.deleteCustomer('cu_xyz', { json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz`) }) - test('fetches receiver limits', async () => { + test('fetches customer limits', async () => { mockResponse.body = { per_transaction: 100 } - await resources.getReceiverLimits('re_xyz', { json: true }) - expect(lastCall().url).toBe(`${BASE}/limits/receivers/re_xyz`) + await resources.getCustomerLimits('cu_xyz', { json: true }) + expect(lastCall().url).toBe(`${BASE}/limits/customers/cu_xyz`) }) - test('fetches receiver limit-increase requests', async () => { + test('fetches customer limit-increase requests', async () => { mockResponse.body = [] - await resources.getReceiverLimitsIncreaseRequests('re_xyz', { json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/limit-increase`) + await resources.getCustomerLimitIncreaseRequests('cu_xyz', { json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/limit-increase`) }) test('creates a limit-increase request with monetary fields parsed as integers', async () => { mockResponse.body = { id: 'rl_new' } - await resources.createReceiverLimitIncrease('re_xyz', { + await resources.createCustomerLimitIncrease('cu_xyz', { perTransaction: '100000', daily: '200000', monthly: '1000000', @@ -186,7 +186,7 @@ describe('Receivers', () => { json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/limit-increase`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/limit-increase`) expect(lastCall().body).toEqual({ per_transaction: 100000, daily: 200000, @@ -201,22 +201,22 @@ describe('Bank Accounts', () => { beforeEach(setupTestEnv) afterEach(teardownTestEnv) - test('lists bank accounts for a receiver', async () => { + test('lists bank accounts for a customer', async () => { mockResponse.body = [] - await resources.listBankAccounts({ receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts`) + await resources.listBankAccounts({ customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts`) }) test('fetches a bank account by id', async () => { mockResponse.body = { id: 'ba_1' } - await resources.getBankAccount('ba_1', { receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts/ba_1`) + await resources.getBankAccount('ba_1', { customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts/ba_1`) }) test('creates a bank account, snake_casing keys and defaulting unset fields to null', async () => { mockResponse.body = { id: 'ba_new', type: 'ach' } await resources.createBankAccount({ - receiverId: 're_xyz', + customerId: 'cu_xyz', type: 'ach', beneficiaryName: 'Jane', routingNumber: '021000021', @@ -224,7 +224,7 @@ describe('Bank Accounts', () => { json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts`) expect(lastCall().body).toEqual({ type: 'ach', name: 'CLI Bank Account', @@ -236,15 +236,14 @@ describe('Bank Accounts', () => { account_type: null, account_class: null, country: null, - swift_ifsc_branch_code: null, }) }) test('deletes a bank account', async () => { mockResponse.body = { success: true } - await resources.deleteBankAccount('ba_1', { receiverId: 're_xyz', json: true }) + await resources.deleteBankAccount('ba_1', { customerId: 'cu_xyz', json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts/ba_1`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts/ba_1`) }) }) @@ -252,28 +251,28 @@ describe('Blockchain Wallets', () => { beforeEach(setupTestEnv) afterEach(teardownTestEnv) - test('lists blockchain wallets for a receiver', async () => { + test('lists blockchain wallets for a customer', async () => { mockResponse.body = [] - await resources.listBlockchainWallets({ receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets`) + await resources.listBlockchainWallets({ customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets`) }) test('fetches a blockchain wallet by id', async () => { mockResponse.body = { id: 'bw_1' } - await resources.getBlockchainWallet('bw_1', { receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets/bw_1`) + await resources.getBlockchainWallet('bw_1', { customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets/bw_1`) }) - test('creates a blockchain wallet for a receiver', async () => { + test('creates a blockchain wallet for a customer', async () => { mockResponse.body = { id: 'bw_new', network: 'base' } await resources.createBlockchainWallet({ - receiverId: 're_xyz', + customerId: 'cu_xyz', address: '0xabc', network: 'base', json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets`) expect(lastCall().body).toEqual({ address: '0xabc', network: 'base', @@ -284,9 +283,9 @@ describe('Blockchain Wallets', () => { test('deletes a blockchain wallet', async () => { mockResponse.body = { success: true } - await resources.deleteBlockchainWallet('bw_1', { receiverId: 're_xyz', json: true }) + await resources.deleteBlockchainWallet('bw_1', { customerId: 'cu_xyz', json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets/bw_1`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets/bw_1`) }) }) @@ -515,17 +514,17 @@ describe('Virtual Accounts', () => { beforeEach(setupTestEnv) afterEach(teardownTestEnv) - test('lists virtual accounts for a receiver', async () => { + test('lists virtual accounts for a customer', async () => { mockResponse.body = [] - await resources.listVirtualAccounts({ receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/virtual-accounts`) + await resources.listVirtualAccounts({ customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/virtual-accounts`) }) - test('creates a virtual account for a receiver', async () => { + test('creates a virtual account for a customer', async () => { mockResponse.body = { id: 'va_new' } - await resources.createVirtualAccount({ receiverId: 're_xyz', blockchainWalletId: 'bw_1', json: true }) + await resources.createVirtualAccount({ customerId: 'cu_xyz', blockchainWalletId: 'bw_1', json: true }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/virtual-accounts`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/virtual-accounts`) expect(lastCall().body).toEqual({ blockchain_wallet_id: 'bw_1' }) }) }) @@ -536,8 +535,8 @@ describe('Offramp Wallets', () => { test('lists offramp wallets for a bank account', async () => { mockResponse.body = [] - await resources.listOfframpWallets({ receiverId: 're_xyz', bankAccountId: 'ba_1', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts/ba_1/offramp-wallets`) + await resources.listOfframpWallets({ customerId: 'cu_xyz', bankAccountId: 'ba_1', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts/ba_1/offramp-wallets`) }) }) @@ -587,49 +586,49 @@ describe('Wallets (custodial)', () => { beforeEach(setupTestEnv) afterEach(teardownTestEnv) - test('lists custodial wallets for a receiver', async () => { + test('lists custodial wallets for a customer', async () => { mockResponse.body = [] - await resources.listWallets({ receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets`) + await resources.listWallets({ customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets`) }) test('fetches a custodial wallet by id', async () => { mockResponse.body = { id: 'bl_1' } - await resources.getWallet('bl_1', { receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets/bl_1`) + await resources.getWallet('bl_1', { customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets/bl_1`) }) test('fetches a custodial wallet balance', async () => { mockResponse.body = { USDC: { amount: 0 } } - await resources.getWalletBalance('bl_1', { receiverId: 're_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets/bl_1/balance`) + await resources.getWalletBalance('bl_1', { customerId: 'cu_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets/bl_1/balance`) }) test('creates a custodial wallet with --external-id', async () => { mockResponse.body = { id: 'bl_new', network: 'polygon' } await resources.createWallet({ - receiverId: 're_xyz', + customerId: 'cu_xyz', network: 'polygon', name: 'Main', externalId: 'ext-1', json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets`) expect(lastCall().body).toEqual({ network: 'polygon', name: 'Main', external_id: 'ext-1' }) }) test('omits external_id from the body when --external-id is not passed', async () => { mockResponse.body = { id: 'bl_new', network: 'polygon' } - await resources.createWallet({ receiverId: 're_xyz', network: 'polygon', name: 'Main', json: true }) + await resources.createWallet({ customerId: 'cu_xyz', network: 'polygon', name: 'Main', json: true }) expect(lastCall().body).toEqual({ network: 'polygon', name: 'Main' }) }) test('deletes a custodial wallet', async () => { mockResponse.body = { success: true } - await resources.deleteWallet('bl_1', { receiverId: 're_xyz', json: true }) + await resources.deleteWallet('bl_1', { customerId: 'cu_xyz', json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets/bl_1`) + expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets/bl_1`) }) }) @@ -732,7 +731,7 @@ describe('Terms of Service', () => { mockResponse.body = { url: 'https://app.blindpay.com/e/terms-of-service?...' } await resources.initiateTos({ idempotencyKey: 'idem-1', - receiverId: 're_xyz', + customerId: 'cu_xyz', redirectUrl: 'https://example.com', json: true, }) @@ -740,7 +739,7 @@ describe('Terms of Service', () => { expect(lastCall().url).toBe('https://api.blindpay.com/v1/e/instances/in_testInstance/tos') expect(lastCall().body).toEqual({ idempotency_key: 'idem-1', - receiver_id: 're_xyz', + customer_id: 'cu_xyz', redirect_url: 'https://example.com', }) }) @@ -785,13 +784,13 @@ describe('Error handling', () => { afterEach(teardownTestEnv) test('exits with code 2 and emits a JSON error payload on a non-2xx response', async () => { - mockResponse = { status: 404, body: { message: 'receiver not found', errors: [] } } - await expect(resources.getReceiver('re_xyz', { json: true })).rejects.toThrow('__test_exit__2') + mockResponse = { status: 404, body: { message: 'customer not found', errors: [] } } + await expect(resources.getCustomer('cu_xyz', { json: true })).rejects.toThrow('__test_exit__2') expect(lastJsonLog()).toMatchObject({ error: true, exitCode: 2, statusCode: 404, - message: 'receiver not found', + message: 'customer not found', }) }) @@ -803,7 +802,7 @@ describe('Error handling', () => { errors: [{ path: ['email'], message: 'required' }], }, } - await expect(resources.createReceiver({ email: 'x', json: true })).rejects.toThrow('__test_exit__2') + await expect(resources.createCustomer({ email: 'x', json: true })).rejects.toThrow('__test_exit__2') expect(lastJsonLog()).toMatchObject({ error: true, exitCode: 2, From 724bfc5e20e31ad6046fdbfc2a9ab8edff802b3a Mon Sep 17 00:00:00 2001 From: alvseven Date: Tue, 16 Jun 2026 22:04:01 -0300 Subject: [PATCH 6/8] fix(tos): send receiver_id on the wire for initiate-tos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The receivers→customers rename over-renamed the Terms-of-Service body FK to customer_id, but the /tos endpoint still expects receiver_id — node and the Swift SDK both send receiver_id there, and the changelog lists only payins/payouts as accepting both names. Keep the clean --customer-id flag but map it to receiver_id on the wire; update the test assertion to match. --- src/__tests__/resources.test.ts | 2 +- src/commands/resources.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/__tests__/resources.test.ts b/src/__tests__/resources.test.ts index d850d39..e477815 100644 --- a/src/__tests__/resources.test.ts +++ b/src/__tests__/resources.test.ts @@ -739,7 +739,7 @@ describe('Terms of Service', () => { expect(lastCall().url).toBe('https://api.blindpay.com/v1/e/instances/in_testInstance/tos') expect(lastCall().body).toEqual({ idempotency_key: 'idem-1', - customer_id: 'cu_xyz', + receiver_id: 'cu_xyz', redirect_url: 'https://example.com', }) }) diff --git a/src/commands/resources.ts b/src/commands/resources.ts index ce0422b..d43ba3c 100644 --- a/src/commands/resources.ts +++ b/src/commands/resources.ts @@ -1014,7 +1014,9 @@ export async function initiateTos(options: { idempotencyKey: string, customerId? try { const ctx = resolveContext() const body: Record = { idempotency_key: options.idempotencyKey } - if (options.customerId !== undefined) body.customer_id = options.customerId + // The TOS endpoint's body still uses receiver_id on the wire (matches node + the Swift SDK); + // keep the clean customerId option but map it to receiver_id. Drop once the API accepts customer_id. + if (options.customerId !== undefined) body.receiver_id = options.customerId if (options.redirectUrl !== undefined) body.redirect_url = options.redirectUrl const res = await apiPost<{ url: string }>(ctx, `/v1/e/instances/${ctx.instanceId}/tos`, body) if (!options.json) clack.log.success(`Open this URL to accept the Terms of Service:\n${res.url}`) From f75b3994f67fa3eddc3de35565890ce5b8eade03 Mon Sep 17 00:00:00 2001 From: alvseven Date: Tue, 16 Jun 2026 22:06:41 -0300 Subject: [PATCH 7/8] fix(tests): use re_ customer ID prefix in examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The receivers→customers rename changed the resource path and name but NOT the ID prefix — customer IDs are still re_(nanoid) per the changelog (https://www.blindpay.com/changelog/2026-06-04-customers-rename). Replace the incorrect cu_ example IDs (cu_xyz/cu_1/cu_new) with re_ in the resource tests. --- src/__tests__/resources.test.ts | 104 ++++++++++++++++---------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/__tests__/resources.test.ts b/src/__tests__/resources.test.ts index e477815..9b0c28b 100644 --- a/src/__tests__/resources.test.ts +++ b/src/__tests__/resources.test.ts @@ -117,21 +117,21 @@ describe('Customers', () => { afterEach(teardownTestEnv) test('lists customers', async () => { - mockResponse.body = [{ id: 'cu_1', type: 'individual', email: 'a@b.com' }] + mockResponse.body = [{ id: 're_1', type: 'individual', email: 'a@b.com' }] await resources.listCustomers({ json: true }) expect(lastCall().method).toBe('GET') expect(lastCall().url).toBe(`${BASE}/customers`) }) test('fetches a customer by id', async () => { - mockResponse.body = { id: 'cu_xyz' } - await resources.getCustomer('cu_xyz', { json: true }) + mockResponse.body = { id: 're_xyz' } + await resources.getCustomer('re_xyz', { json: true }) expect(lastCall().method).toBe('GET') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz`) }) test('creates a customer, splitting --name into first_name and last_name and filling defaults', async () => { - mockResponse.body = { id: 'cu_new', type: 'individual' } + mockResponse.body = { id: 're_new', type: 'individual' } await resources.createCustomer({ email: 'a@b.com', name: 'Jane Doe', json: true }) expect(lastCall().method).toBe('POST') expect(lastCall().url).toBe(`${BASE}/customers`) @@ -149,35 +149,35 @@ describe('Customers', () => { }) test('updates only the fields explicitly passed via flags', async () => { - mockResponse.body = { id: 'cu_xyz' } - await resources.updateCustomer('cu_xyz', { email: 'new@b.com', json: true }) + mockResponse.body = { id: 're_xyz' } + await resources.updateCustomer('re_xyz', { email: 'new@b.com', json: true }) expect(lastCall().method).toBe('PUT') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz`) expect(lastCall().body).toEqual({ email: 'new@b.com' }) }) test('deletes a customer by id', async () => { mockResponse.body = { success: true } - await resources.deleteCustomer('cu_xyz', { json: true }) + await resources.deleteCustomer('re_xyz', { json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz`) }) test('fetches customer limits', async () => { mockResponse.body = { per_transaction: 100 } - await resources.getCustomerLimits('cu_xyz', { json: true }) - expect(lastCall().url).toBe(`${BASE}/limits/customers/cu_xyz`) + await resources.getCustomerLimits('re_xyz', { json: true }) + expect(lastCall().url).toBe(`${BASE}/limits/customers/re_xyz`) }) test('fetches customer limit-increase requests', async () => { mockResponse.body = [] - await resources.getCustomerLimitIncreaseRequests('cu_xyz', { json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/limit-increase`) + await resources.getCustomerLimitIncreaseRequests('re_xyz', { json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/limit-increase`) }) test('creates a limit-increase request with monetary fields parsed as integers', async () => { mockResponse.body = { id: 'rl_new' } - await resources.createCustomerLimitIncrease('cu_xyz', { + await resources.createCustomerLimitIncrease('re_xyz', { perTransaction: '100000', daily: '200000', monthly: '1000000', @@ -186,7 +186,7 @@ describe('Customers', () => { json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/limit-increase`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/limit-increase`) expect(lastCall().body).toEqual({ per_transaction: 100000, daily: 200000, @@ -203,20 +203,20 @@ describe('Bank Accounts', () => { test('lists bank accounts for a customer', async () => { mockResponse.body = [] - await resources.listBankAccounts({ customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts`) + await resources.listBankAccounts({ customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts`) }) test('fetches a bank account by id', async () => { mockResponse.body = { id: 'ba_1' } - await resources.getBankAccount('ba_1', { customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts/ba_1`) + await resources.getBankAccount('ba_1', { customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts/ba_1`) }) test('creates a bank account, snake_casing keys and defaulting unset fields to null', async () => { mockResponse.body = { id: 'ba_new', type: 'ach' } await resources.createBankAccount({ - customerId: 'cu_xyz', + customerId: 're_xyz', type: 'ach', beneficiaryName: 'Jane', routingNumber: '021000021', @@ -224,7 +224,7 @@ describe('Bank Accounts', () => { json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts`) expect(lastCall().body).toEqual({ type: 'ach', name: 'CLI Bank Account', @@ -241,9 +241,9 @@ describe('Bank Accounts', () => { test('deletes a bank account', async () => { mockResponse.body = { success: true } - await resources.deleteBankAccount('ba_1', { customerId: 'cu_xyz', json: true }) + await resources.deleteBankAccount('ba_1', { customerId: 're_xyz', json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts/ba_1`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts/ba_1`) }) }) @@ -253,26 +253,26 @@ describe('Blockchain Wallets', () => { test('lists blockchain wallets for a customer', async () => { mockResponse.body = [] - await resources.listBlockchainWallets({ customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets`) + await resources.listBlockchainWallets({ customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets`) }) test('fetches a blockchain wallet by id', async () => { mockResponse.body = { id: 'bw_1' } - await resources.getBlockchainWallet('bw_1', { customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets/bw_1`) + await resources.getBlockchainWallet('bw_1', { customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets/bw_1`) }) test('creates a blockchain wallet for a customer', async () => { mockResponse.body = { id: 'bw_new', network: 'base' } await resources.createBlockchainWallet({ - customerId: 'cu_xyz', + customerId: 're_xyz', address: '0xabc', network: 'base', json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets`) expect(lastCall().body).toEqual({ address: '0xabc', network: 'base', @@ -283,9 +283,9 @@ describe('Blockchain Wallets', () => { test('deletes a blockchain wallet', async () => { mockResponse.body = { success: true } - await resources.deleteBlockchainWallet('bw_1', { customerId: 'cu_xyz', json: true }) + await resources.deleteBlockchainWallet('bw_1', { customerId: 're_xyz', json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/blockchain-wallets/bw_1`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets/bw_1`) }) }) @@ -516,15 +516,15 @@ describe('Virtual Accounts', () => { test('lists virtual accounts for a customer', async () => { mockResponse.body = [] - await resources.listVirtualAccounts({ customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/virtual-accounts`) + await resources.listVirtualAccounts({ customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/virtual-accounts`) }) test('creates a virtual account for a customer', async () => { mockResponse.body = { id: 'va_new' } - await resources.createVirtualAccount({ customerId: 'cu_xyz', blockchainWalletId: 'bw_1', json: true }) + await resources.createVirtualAccount({ customerId: 're_xyz', blockchainWalletId: 'bw_1', json: true }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/virtual-accounts`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/virtual-accounts`) expect(lastCall().body).toEqual({ blockchain_wallet_id: 'bw_1' }) }) }) @@ -535,8 +535,8 @@ describe('Offramp Wallets', () => { test('lists offramp wallets for a bank account', async () => { mockResponse.body = [] - await resources.listOfframpWallets({ customerId: 'cu_xyz', bankAccountId: 'ba_1', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/bank-accounts/ba_1/offramp-wallets`) + await resources.listOfframpWallets({ customerId: 're_xyz', bankAccountId: 'ba_1', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts/ba_1/offramp-wallets`) }) }) @@ -588,47 +588,47 @@ describe('Wallets (custodial)', () => { test('lists custodial wallets for a customer', async () => { mockResponse.body = [] - await resources.listWallets({ customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets`) + await resources.listWallets({ customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets`) }) test('fetches a custodial wallet by id', async () => { mockResponse.body = { id: 'bl_1' } - await resources.getWallet('bl_1', { customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets/bl_1`) + await resources.getWallet('bl_1', { customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets/bl_1`) }) test('fetches a custodial wallet balance', async () => { mockResponse.body = { USDC: { amount: 0 } } - await resources.getWalletBalance('bl_1', { customerId: 'cu_xyz', json: true }) - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets/bl_1/balance`) + await resources.getWalletBalance('bl_1', { customerId: 're_xyz', json: true }) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets/bl_1/balance`) }) test('creates a custodial wallet with --external-id', async () => { mockResponse.body = { id: 'bl_new', network: 'polygon' } await resources.createWallet({ - customerId: 'cu_xyz', + customerId: 're_xyz', network: 'polygon', name: 'Main', externalId: 'ext-1', json: true, }) expect(lastCall().method).toBe('POST') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets`) expect(lastCall().body).toEqual({ network: 'polygon', name: 'Main', external_id: 'ext-1' }) }) test('omits external_id from the body when --external-id is not passed', async () => { mockResponse.body = { id: 'bl_new', network: 'polygon' } - await resources.createWallet({ customerId: 'cu_xyz', network: 'polygon', name: 'Main', json: true }) + await resources.createWallet({ customerId: 're_xyz', network: 'polygon', name: 'Main', json: true }) expect(lastCall().body).toEqual({ network: 'polygon', name: 'Main' }) }) test('deletes a custodial wallet', async () => { mockResponse.body = { success: true } - await resources.deleteWallet('bl_1', { customerId: 'cu_xyz', json: true }) + await resources.deleteWallet('bl_1', { customerId: 're_xyz', json: true }) expect(lastCall().method).toBe('DELETE') - expect(lastCall().url).toBe(`${BASE}/customers/cu_xyz/wallets/bl_1`) + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets/bl_1`) }) }) @@ -731,7 +731,7 @@ describe('Terms of Service', () => { mockResponse.body = { url: 'https://app.blindpay.com/e/terms-of-service?...' } await resources.initiateTos({ idempotencyKey: 'idem-1', - customerId: 'cu_xyz', + customerId: 're_xyz', redirectUrl: 'https://example.com', json: true, }) @@ -739,7 +739,7 @@ describe('Terms of Service', () => { expect(lastCall().url).toBe('https://api.blindpay.com/v1/e/instances/in_testInstance/tos') expect(lastCall().body).toEqual({ idempotency_key: 'idem-1', - receiver_id: 'cu_xyz', + receiver_id: 're_xyz', redirect_url: 'https://example.com', }) }) @@ -785,7 +785,7 @@ describe('Error handling', () => { test('exits with code 2 and emits a JSON error payload on a non-2xx response', async () => { mockResponse = { status: 404, body: { message: 'customer not found', errors: [] } } - await expect(resources.getCustomer('cu_xyz', { json: true })).rejects.toThrow('__test_exit__2') + await expect(resources.getCustomer('re_xyz', { json: true })).rejects.toThrow('__test_exit__2') expect(lastJsonLog()).toMatchObject({ error: true, exitCode: 2, From 3294c3d49b71c1cb6a3c134a71db9d0a21b0a874 Mon Sep 17 00:00:00 2001 From: alvseven Date: Tue, 16 Jun 2026 22:21:14 -0300 Subject: [PATCH 8/8] feat(bank-accounts): complete the SEPA field set on create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SEPA bank-account create only wired up 3 of node's 9 sepa_beneficiary_* fields (iban, bic, legal-name), leaving the required address_line_1, city, postal_code and country — plus optional address_line_2 and state_province_region — unreachable from the CLI. Add the missing flags + body mapping and a test covering the full field set (mirrors blindpay-node #50). --- src/__tests__/resources.test.ts | 42 +++++++++++++++++++++++++++++++++ src/commands/resources.ts | 12 ++++++++++ src/index.ts | 6 +++++ 3 files changed, 60 insertions(+) diff --git a/src/__tests__/resources.test.ts b/src/__tests__/resources.test.ts index 9b0c28b..9b925cf 100644 --- a/src/__tests__/resources.test.ts +++ b/src/__tests__/resources.test.ts @@ -239,6 +239,48 @@ describe('Bank Accounts', () => { }) }) + test('creates a sepa bank account with the full sepa_beneficiary_* field set', async () => { + mockResponse.body = { id: 'ba_sepa', type: 'sepa' } + await resources.createBankAccount({ + customerId: 're_xyz', + type: 'sepa', + accountClass: 'individual', + sepaIban: 'DE89370400440532013000', + sepaBeneficiaryBic: 'COBADEFFXXX', + sepaBeneficiaryLegalName: 'Jane Doe', + sepaBeneficiaryAddressLine1: 'Hauptstrasse 1', + sepaBeneficiaryAddressLine2: 'Apt 2', + sepaBeneficiaryCity: 'Berlin', + sepaBeneficiaryStateProvinceRegion: 'BE', + sepaBeneficiaryPostalCode: '10115', + sepaBeneficiaryCountry: 'DE', + json: true, + }) + expect(lastCall().method).toBe('POST') + expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts`) + expect(lastCall().body).toEqual({ + type: 'sepa', + name: 'CLI Bank Account', + recipient_relationship: null, + pix_key: null, + beneficiary_name: null, + routing_number: null, + account_number: null, + account_type: null, + account_class: 'individual', + country: null, + sepa_iban: 'DE89370400440532013000', + sepa_beneficiary_bic: 'COBADEFFXXX', + sepa_beneficiary_legal_name: 'Jane Doe', + sepa_beneficiary_address_line_1: 'Hauptstrasse 1', + sepa_beneficiary_address_line_2: 'Apt 2', + sepa_beneficiary_city: 'Berlin', + sepa_beneficiary_state_province_region: 'BE', + sepa_beneficiary_postal_code: '10115', + sepa_beneficiary_country: 'DE', + }) + }) + test('deletes a bank account', async () => { mockResponse.body = { success: true } await resources.deleteBankAccount('ba_1', { customerId: 're_xyz', json: true }) diff --git a/src/commands/resources.ts b/src/commands/resources.ts index d43ba3c..4572f20 100644 --- a/src/commands/resources.ts +++ b/src/commands/resources.ts @@ -256,6 +256,12 @@ export async function createBankAccount(options: { sepaIban?: string sepaBeneficiaryBic?: string sepaBeneficiaryLegalName?: string + sepaBeneficiaryAddressLine1?: string + sepaBeneficiaryAddressLine2?: string + sepaBeneficiaryCity?: string + sepaBeneficiaryStateProvinceRegion?: string + sepaBeneficiaryPostalCode?: string + sepaBeneficiaryCountry?: string json: boolean }) { try { @@ -275,6 +281,12 @@ export async function createBankAccount(options: { if (options.sepaIban) body.sepa_iban = options.sepaIban if (options.sepaBeneficiaryBic) body.sepa_beneficiary_bic = options.sepaBeneficiaryBic if (options.sepaBeneficiaryLegalName) body.sepa_beneficiary_legal_name = options.sepaBeneficiaryLegalName + if (options.sepaBeneficiaryAddressLine1) body.sepa_beneficiary_address_line_1 = options.sepaBeneficiaryAddressLine1 + if (options.sepaBeneficiaryAddressLine2) body.sepa_beneficiary_address_line_2 = options.sepaBeneficiaryAddressLine2 + if (options.sepaBeneficiaryCity) body.sepa_beneficiary_city = options.sepaBeneficiaryCity + if (options.sepaBeneficiaryStateProvinceRegion) body.sepa_beneficiary_state_province_region = options.sepaBeneficiaryStateProvinceRegion + if (options.sepaBeneficiaryPostalCode) body.sepa_beneficiary_postal_code = options.sepaBeneficiaryPostalCode + if (options.sepaBeneficiaryCountry) body.sepa_beneficiary_country = options.sepaBeneficiaryCountry const ba = await apiPost<{ id: string, type: string }>(ctx, `${instancePath(ctx)}/customers/${options.customerId}/bank-accounts`, body) clack.log.success(`Created bank account ${ba.id} (${ba.type})`) if (options.json) diff --git a/src/index.ts b/src/index.ts index accd1df..f78a8fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -258,6 +258,12 @@ bankAccounts .option('--sepa-iban ', 'IBAN (sepa accounts)') .option('--sepa-beneficiary-bic ', 'Beneficiary BIC/SWIFT code (sepa accounts)') .option('--sepa-beneficiary-legal-name ', 'Beneficiary legal name (sepa accounts)') + .option('--sepa-beneficiary-address-line-1 ', 'Beneficiary address line 1 (sepa accounts)') + .option('--sepa-beneficiary-address-line-2 ', 'Beneficiary address line 2 (sepa accounts)') + .option('--sepa-beneficiary-city ', 'Beneficiary city (sepa accounts)') + .option('--sepa-beneficiary-state-province-region ', 'Beneficiary state/province/region (sepa accounts)') + .option('--sepa-beneficiary-postal-code ', 'Beneficiary postal code (sepa accounts)') + .option('--sepa-beneficiary-country ', 'Beneficiary country code (sepa accounts)') .option('--json', 'Output as JSON', false) .action(opts => createBankAccount(opts))