Skip to main content

Client Reference

The ZendFiClient class is the core of the SDK. It provides methods for every ZendFi API operation with built-in retries, idempotency, and typed responses.

Constructor

import { ZendFiClient } from '@zendfi/sdk';

const zendfi = new ZendFiClient(options?: Partial<ZendFiConfig>);

Configuration Options

apiKey
string
Your ZendFi API key. Falls back to ZENDFI_API_KEY env var.
baseURL
string
default:"https://api.zendfi.tech"
API base URL. Override for self-hosted or proxy setups.
environment
string
default:"auto-detected"
development, staging, or production. Auto-detected from NODE_ENV.
mode
string
default:"auto-detected"
test or live. Auto-detected from API key prefix.
timeout
number
default:"30000"
Request timeout in milliseconds.
retries
number
default:"3"
Number of retry attempts for failed requests (5xx and network errors).
idempotencyEnabled
boolean
default:"true"
Automatically generate idempotency keys for POST requests.
debug
boolean
default:"false"
Log all requests and responses to the console.

Payments

createPayment

Creates a new payment and returns a checkout URL.
const payment = await zendfi.createPayment({
  amount: 49.99,
  description: 'Pro Plan',
  currency: 'USD',       // default
  token: 'USDC',         // default
  customer_email: 'jane@example.com',
  metadata: { order_id: '12345' },
  split_recipients: [    // optional
    { recipient_wallet: '7xKXt...', percentage: 80 },
    { recipient_wallet: '8yLYu...', percentage: 20 },
  ],
});

console.log(payment.payment_url);
Returns
Payment
Payment object with id, status, payment_url, checkout_url, qr_code, expires_at, and more.

getPayment

Retrieves a payment by ID.
const payment = await zendfi.getPayment('pay_test_abc123');
console.log(payment.status); // 'pending' | 'confirmed' | 'failed' | 'expired'

Creates a shareable checkout URL.
const link = await zendfi.createPaymentLink({
  amount: 99.00,
  description: 'Premium License',
  onramp: true,            // enable fiat (NGN bank transfer) payment option
  max_uses: 100,
  expires_at: '2026-12-31T23:59:59Z',
  collect_customer_info: true,  // show customer details form on checkout
});

console.log(link.url);             // convenience alias
console.log(link.hosted_page_url); // same URL
console.log(link.collect_customer_info); // true

With payment splits (wallet recipients)

Automatically distribute payments across multiple wallets:
const link = await zendfi.createPaymentLink({
  amount: 100.00,
  description: 'Marketplace Purchase – 3-way split',
  split_recipients: [
    { recipient_wallet: '7xKXt...', percentage: 60, recipient_name: 'Seller' },
    { recipient_wallet: '8yLYu...', percentage: 30, recipient_name: 'Platform' },
    { recipient_wallet: '9zMZv...', percentage: 10, recipient_name: 'Referrer' },
  ],
});

console.log(link.url);
console.log(link.split_recipients); // array with full split details

With payment splits (bank account recipients)

Enable NGN bank deposits via PAJ bank account integration:
const link = await zendfi.createPaymentLink({
  amount: 500.00,
  description: 'Contractor Payment – Direct to Bank',
  onramp: true,
  split_recipients: [
    {
      recipient_type: 'bank_account',
      percentage: 70,
      recipient_account_name: 'John Okonkwo',
      recipient_bank_account: '0123456789',
      recipient_bank_id: 'GTB',   // bank identifier (id/code/name)
      recipient_email: 'john@contractor.com',
    },
    {
      recipient_type: 'wallet',
      recipient_wallet: '7xKXt...',
      percentage: 30,
      recipient_name: 'Agent',
    },
  ],
});

console.log(link.url);
// When paid:
// – 70% (₦...) USDC → PAJ → John's GTB account
// – 30% direct blockchain transfer to agent wallet
For bank recipients, the SDK accepts recipient_bank_id plus aliases recipient_bank, bank_identifier, and bank_code. For wallet recipients, you can target a sub-account directly with sub_account_id (or alias recipient_sub_account) instead of recipient_wallet.
const link = await zendfi.createPaymentLink({
  amount: 100,
  description: 'Fund operations sub-account',
  split_recipients: [
    {
      recipient_type: 'wallet',
      sub_account_id: 'sa_7b1w9j2k4m8p',
      // percentage optional for a single recipient (defaults to 100%)
    },
  ],
});

With a pre-filled customer object

When you supply a customer object the checkout page skips the email / info collection step entirely and shows a single “Continue to Pay” button instead. The customer data is passed directly into the payment flow (including the onramp initiation). The link is automatically locked to max_uses = 1.
const link = await zendfi.createPaymentLink({
  amount: 49.00,
  description: 'Order #1042 – Wireless Headphones',
  onramp: true,
  payer_service_charge: true,
  // Pre-fill customer — checkout skips the info form, max_uses forced to 1
  customer: {
    email: 'ada@example.com',
    name: 'Ada Lovelace',
    phone: '+2348012345678',
    billing_city: 'Lagos',
    billing_country: 'NG',
  },
});

console.log(link.max_uses);     // 1  (forced automatically)
console.log(link.customer_data); // { email: 'ada@example.com', name: 'Ada Lovelace', … }
console.log(link.url);
const link = await zendfi.getPaymentLink('link_code_here');
const links = await zendfi.listPaymentLinks();

Sub Accounts

Sub-account methods are documented in detail on SDK Sub Accounts.

createSubAccount

const sub = await zendfi.createSubAccount({
  label: 'user_paschal_001',
  spend_limit_usdc: 500,
  access_mode: 'delegated',
  yield_enabled: false,
});

listSubAccounts

const subs = await zendfi.listSubAccounts();

getSubAccount / getSubAccountBalance

const sub = await zendfi.getSubAccount('sa_xxxxx');
const balance = await zendfi.getSubAccountBalance(sub.id);

mintSubAccountDelegationToken

const token = await zendfi.mintSubAccountDelegationToken(sub.id, {
  scope: 'withdraw_only',
  spend_limit_usdc: 100,
  expires_in_seconds: 900,
  single_use: true,
});

freezeSubAccount / closeSubAccount

await zendfi.freezeSubAccount(sub.id, { reason: 'fraud-review' });
await zendfi.closeSubAccount(sub.id);

drainSubAccount / withdrawFromSubAccount

await zendfi.drainSubAccount(sub.id, {
  token: 'Usdc',
  amount: 25,
  mode: 'live',
  passkey_signature,
});

await zendfi.withdrawFromSubAccount(sub.id, {
  to_address: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
  amount: 10,
  token: 'Usdc',
  delegation_token: token.delegation_token,
  passkey_signature,
});

Subscriptions

createSubscriptionPlan

Defines a recurring billing plan.
const plan = await zendfi.createSubscriptionPlan({
  name: 'Pro Plan',
  amount: 29.99,
  interval: 'monthly',
  trial_days: 14,
});

getSubscriptionPlan

const plan = await zendfi.getSubscriptionPlan('plan_abc123');

createSubscription

Subscribes a customer to a plan.
const sub = await zendfi.createSubscription({
  plan_id: 'plan_abc123',
  customer_email: 'customer@example.com',
  customer_wallet: '7xKXt...',
});

getSubscription

const sub = await zendfi.getSubscription('sub_abc123');

cancelSubscription

const canceled = await zendfi.cancelSubscription('sub_abc123');

Installment Plans

createInstallmentPlan

Splits a purchase into scheduled payments.
const plan = await zendfi.createInstallmentPlan({
  customer_wallet: '7xKXt...',
  total_amount: 1200.00,
  installment_count: 4,
  payment_frequency_days: 30,
  description: 'Quarterly payments',
});

getInstallmentPlan

const plan = await zendfi.getInstallmentPlan('plan_test_abc123');

listInstallmentPlans

const plans = await zendfi.listInstallmentPlans({ limit: 20, offset: 0 });

listCustomerInstallmentPlans

const plans = await zendfi.listCustomerInstallmentPlans('7xKXt...');

cancelInstallmentPlan

const result = await zendfi.cancelInstallmentPlan('plan_test_abc123');

Invoices

createInvoice

Creates a professional invoice.
const invoice = await zendfi.createInvoice({
  customer_email: 'client@company.com',
  customer_name: 'Acme Corp',
  amount: 2500.00,
  description: 'March consulting',
  line_items: [
    { description: 'Strategy session', quantity: 5, unit_price: 500 },
  ],
  due_date: '2026-03-15T00:00:00Z',
});

console.log(invoice.line_items);   // always returned (not just on create)
console.log(invoice.status);       // 'draft'
console.log(invoice.sent_at);      // null until sendInvoice() is called

With onramp enabled

When onramp: true, the payment link generated by sendInvoice() will use the PAJ onramp flow (NGN bank transfer → USDC settlement) instead of a direct crypto payment.
const invoice = await zendfi.createInvoice({
  customer_email: 'client@company.com',
  customer_name: 'Acme Corp',
  amount: 25.00,
  description: 'Product purchase – March 2026',
  onramp: true,               // payment link will use NGN bank transfer
  amount_ngn: 42500,          // exact NGN amount (bypasses live FX re-quote)
  payer_service_charge: true, // PAJ fee added on top, billed to the customer
  payment_link_max_uses: 1,   // default for invoices; set higher if needed
  collect_customer_info: false,
  due_date: '2026-03-15T00:00:00Z',
});

// When ready, send it — the generated payment link will have onramp=true
const result = await zendfi.sendInvoice(invoice.id);
console.log(result.payment_url);  // https://checkout.zendfi.tech/checkout/…
console.log(result.onramp);       // true
console.log(result.max_uses);     // 1

getInvoice

const invoice = await zendfi.getInvoice('inv_test_abc123');

listInvoices

const invoices = await zendfi.listInvoices();

sendInvoice

Sends the invoice to the customer via email.
const result = await zendfi.sendInvoice('inv_test_abc123');
console.log(result.payment_url); // URL included in the email
console.log(result.sent_to);     // verified recipient

Webhooks

verifyWebhook

Verifies the HMAC-SHA256 signature of an incoming webhook.
const isValid = zendfi.verifyWebhook({
  payload: rawBody,                    // string or object
  signature: req.headers['x-zendfi-signature'],
  secret: process.env.ZENDFI_WEBHOOK_SECRET!,
});

if (!isValid) {
  return res.status(401).json({ error: 'Invalid signature' });
}
Always pass the raw request body as the payload, not the parsed JSON. Parsing and re-serializing can alter whitespace and break the signature.

Retry and Idempotency

The client automatically retries failed requests:
  • 5xx errors: Retried up to retries times with exponential backoff (2n2^n seconds)
  • Network errors: Retried with the same backoff logic
  • 4xx errors: Never retried (these indicate client-side issues)
Idempotency keys are generated automatically for all POST requests when idempotencyEnabled is true (the default). This prevents duplicate charges from retried requests.

Debug Mode

Enable debug logging to see all requests and responses:
const zendfi = new ZendFiClient({
  apiKey: 'zfi_test_...',
  debug: true,
});
Output:
[ZendFi] POST /api/v1/payments
[ZendFi] Request: {"amount":49.99,"description":"Pro Plan"}
[ZendFi] 200 OK (142ms)
[ZendFi] Response: {"id":"pay_test_abc123","status":"pending",...}