> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zendfi.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Client Reference

> Complete method reference for ZendFiClient.

# 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

```typescript theme={null}
import { ZendFiClient } from '@zendfi/sdk';

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

### Configuration Options

<ParamField body="apiKey" type="string">
  Your ZendFi API key. Falls back to `ZENDFI_API_KEY` env var.
</ParamField>

<ParamField body="baseURL" type="string" default="https://api.zendfi.tech">
  API base URL. Override for self-hosted or proxy setups.
</ParamField>

<ParamField body="environment" type="string" default="auto-detected">
  `development`, `staging`, or `production`. Auto-detected from `NODE_ENV`.
</ParamField>

<ParamField body="mode" type="string" default="auto-detected">
  `test` or `live`. Auto-detected from API key prefix.
</ParamField>

<ParamField body="timeout" type="number" default="30000">
  Request timeout in milliseconds.
</ParamField>

<ParamField body="retries" type="number" default="3">
  Number of retry attempts for failed requests (5xx and network errors).
</ParamField>

<ParamField body="idempotencyEnabled" type="boolean" default="true">
  Automatically generate idempotency keys for POST requests.
</ParamField>

<ParamField body="debug" type="boolean" default="false">
  Log all requests and responses to the console.
</ParamField>

***

## Payments

### createPayment

Creates a new payment and returns a checkout URL.

```typescript theme={null}
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);
```

<ResponseField name="Returns" type="Payment">
  Payment object with `id`, `status`, `payment_url`, `checkout_url`, `qr_code`, `expires_at`, and more.
</ResponseField>

### getPayment

Retrieves a payment by ID.

```typescript theme={null}
const payment = await zendfi.getPayment('pay_test_abc123');
console.log(payment.status); // 'pending' | 'confirmed' | 'failed' | 'expired'
```

***

## Payment Links

### createPaymentLink

Creates a shareable checkout URL.

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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`.

```typescript theme={null}
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`.

```typescript theme={null}
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);
```

```typescript theme={null}
const link = await zendfi.getPaymentLink('link_code_here');
```

### listPaymentLinks

```typescript theme={null}
const links = await zendfi.listPaymentLinks();
```

***

## Sub Accounts

Sub-account methods are documented in detail on [SDK Sub Accounts](/sdk/sub-accounts).

### createSubAccount

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

### listSubAccounts

```typescript theme={null}
const subs = await zendfi.listSubAccounts();
```

### getSubAccount / getSubAccountBalance

```typescript theme={null}
const sub = await zendfi.getSubAccount('sa_xxxxx');
const balance = await zendfi.getSubAccountBalance(sub.id);
```

### mintSubAccountDelegationToken

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

### freezeSubAccount / closeSubAccount

```typescript theme={null}
await zendfi.freezeSubAccount(sub.id, { reason: 'fraud-review' });
await zendfi.closeSubAccount(sub.id);
```

### drainSubAccount / withdrawFromSubAccount

```typescript theme={null}
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.

```typescript theme={null}
const plan = await zendfi.createSubscriptionPlan({
  name: 'Pro Plan',
  amount: 29.99,
  interval: 'monthly',
  trial_days: 14,
});
```

### getSubscriptionPlan

```typescript theme={null}
const plan = await zendfi.getSubscriptionPlan('plan_abc123');
```

### createSubscription

Subscribes a customer to a plan.

```typescript theme={null}
const sub = await zendfi.createSubscription({
  plan_id: 'plan_abc123',
  customer_email: 'customer@example.com',
  customer_wallet: '7xKXt...',
});
```

### getSubscription

```typescript theme={null}
const sub = await zendfi.getSubscription('sub_abc123');
```

### cancelSubscription

```typescript theme={null}
const canceled = await zendfi.cancelSubscription('sub_abc123');
```

***

## Installment Plans

### createInstallmentPlan

Splits a purchase into scheduled payments.

```typescript theme={null}
const plan = await zendfi.createInstallmentPlan({
  customer_wallet: '7xKXt...',
  total_amount: 1200.00,
  installment_count: 4,
  payment_frequency_days: 30,
  description: 'Quarterly payments',
});
```

### getInstallmentPlan

```typescript theme={null}
const plan = await zendfi.getInstallmentPlan('plan_test_abc123');
```

### listInstallmentPlans

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

### listCustomerInstallmentPlans

```typescript theme={null}
const plans = await zendfi.listCustomerInstallmentPlans('7xKXt...');
```

### cancelInstallmentPlan

```typescript theme={null}
const result = await zendfi.cancelInstallmentPlan('plan_test_abc123');
```

***

## Invoices

### createInvoice

Creates a professional invoice.

```typescript theme={null}
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.

```typescript theme={null}
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

```typescript theme={null}
const invoice = await zendfi.getInvoice('inv_test_abc123');
```

### listInvoices

```typescript theme={null}
const invoices = await zendfi.listInvoices();
```

### sendInvoice

Sends the invoice to the customer via email.

```typescript theme={null}
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.

```typescript theme={null}
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' });
}
```

<Note>
  Always pass the **raw request body** as the payload, not the parsed JSON. Parsing and re-serializing can alter whitespace and break the signature.
</Note>

***

## Retry and Idempotency

The client automatically retries failed requests:

* **5xx errors**: Retried up to `retries` times with exponential backoff ($2^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:

```typescript theme={null}
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",...}
```
