Transfers And Virtual Accounts
Collect by bank transfer and send payouts on both rails.
Three namespaces: one for collecting into virtual accounts, two for paying out.
kyshi.virtualAccounts
| Method | Endpoint |
|---|---|
create(params, options?) | POST /v1/wallets |
createCorporate(params, options?) | POST /v1/wallets/corporate |
list(params?, options?) | GET /v1/wallets |
retrieve(accountNumberOrAccountId, options?) | GET /v1/wallets/{id} |
getBalance(currency?, options?) | GET /v1/wallets/balance |
getDetails(businessId, options?) | Wallet detail lookup |
verifyPayment(params, options?) | GET /v1/wallets/verify |
simulateCredit(params, options?) | POST /v1/wallets/credit |
const account = await kyshi.virtualAccounts.create({
customer: { email: '[email protected]', firstName: 'Ada', lastName: 'Okafor' },
currency: 'NGN',
accountCategory: 'VIRTUAL_ACCOUNT',
amount: 5000,
reference: 'ORDER-10001',
feeBearer: 'CUSTOMER',
expiresAt: '60',
});
// Show payableAmount, never requestedAmount.
display(account.accountNumber, account.bankName, account.payableAmount);Showing requestedAmount instead of payableAmount is the most common cause of PARTIAL collections. See Virtual Accounts.
const result = await kyshi.virtualAccounts.verifyPayment({
reference: 'ORDER-10001',
});
switch (result.collectionStatus) {
case 'COMPLETED': return fulfilOnce('ORDER-10001');
case 'OVERPAID': return fulfilThenRefundExcess('ORDER-10001');
case 'PARTIAL': return notifyShortfall('ORDER-10001');
}getBalance takes the currency positionally, not as an object:
const balance = await kyshi.virtualAccounts.getBalance('NGN');In test mode, stand in for the customer:
await kyshi.virtualAccounts.simulateCredit({
accountNumber: account.accountNumber,
amount: '5070',
});kyshi.transfers
| Method | Endpoint |
|---|---|
listBanks(currency?, options?) | GET /v1/transfer/banks |
nameEnquiry(params, options?) | GET /v1/transfer/name-enquiry |
createBeneficiary(params, options?) | POST /v1/transfer/beneficiaries |
listBeneficiaries(params?, options?) | GET /v1/transfer/beneficiaries |
retrieveBeneficiary(id, options?) | GET /v1/transfer/beneficiaries/{id} |
create(params, options?) | POST /v1/transfer |
list(params?, options?) | GET /v1/transfer |
retrieve(id, options?) | GET /v1/transfer/{id} |
const banks = await kyshi.transfers.listBanks('NGN');
const resolved = await kyshi.transfers.nameEnquiry({
bankCode: '000013',
accountNumber: '1234567890',
});
// Show resolved.accountName and have someone confirm it before sending.
const payout = await kyshi.transfers.create(
{ beneficiaryId: 'bnf_abc123', amount: 50000, currency: 'NGN', narration: 'Payout ORDER-10001' },
{ idempotencyKey: 'PAYOUT-10001' },
);Always pass an idempotencyKey on payouts. Without one the SDK will not retry, and a lost response leaves you unsure whether the money moved. See Configuration.
kyshi.virtualAccountTransfers
Same methods as transfers, on the /v1/va/transfer rail, plus bulk:
| Method | Endpoint |
|---|---|
createBulk(params, options?) | POST /v1/va/transfer/bulk |
retrieveBulk(id, options?) | GET /v1/va/transfer/bulk/{id} |
const batch = await kyshi.virtualAccountTransfers.createBulk(
{ beneficiaryId: 'bnf_abc123', amount: 100_000_000, currency: 'NGN', narration: 'Supplier payout' },
{ idempotencyKey: 'BULK-10001' },
);Bulk is one payout to one beneficiary, split because the rail caps at 5,000,000 NGN. It is not a way to pay many recipients. A batch can complete partially, so reconcile its children individually. See Virtual Account Transfers.
Beneficiary IDs are not shared between the two rails.
Next
- Virtual Accounts · Transfers · Virtual Account Transfers
- Balances — what you can actually send
Updated 5 days ago
