Skip to main content

Types

The SDK provides full TypeScript type coverage for every API request and response. It also uses branded types to prevent accidentally mixing up different ID strings.

Branded IDs

Branded types add compile-time safety to string IDs. A PaymentId cannot be accidentally passed where a MerchantId is expected, even though both are strings at runtime.
import { asPaymentId, asMerchantId } from '@zendfi/sdk';

const paymentId = asPaymentId('pay_test_abc123');
const merchantId = asMerchantId('merch_xyz789');

// These are type-safe at compile time
await zendfi.getPayment(paymentId);   // works
await zendfi.getPayment(merchantId);  // type error

Available Branded Types

TypeFactoryExample
PaymentIdasPaymentId()pay_test_abc123
MerchantIdasMerchantId()merch_xyz789
InvoiceIdasInvoiceId()inv_test_abc123
SubscriptionIdasSubscriptionId()sub_abc123
InstallmentPlanIdasInstallmentPlanId()inst_abc123
PaymentLinkCodeasPaymentLinkCode()link_abc123

How Branding Works

type Brand<T, B> = T & { __brand: B };
type PaymentId = Brand<string, 'PaymentId'>;
The __brand property is a phantom type — it exists only in the type system and has zero runtime overhead. You can still use branded IDs as regular strings:
const id = asPaymentId('pay_test_abc123');
console.log(id);           // "pay_test_abc123"
console.log(id.length);    // 17
console.log(`ID: ${id}`);  // "ID: pay_test_abc123"

Enums

Environment

type Environment = 'development' | 'staging' | 'production';

ApiKeyMode

type ApiKeyMode = 'test' | 'live';

Currency

type Currency = 'USD' | 'EUR' | 'GBP';

PaymentToken

type PaymentToken = 'SOL' | 'USDC' | 'USDT';

Status Types

type PaymentStatus = 'pending' | 'confirmed' | 'failed' | 'expired';
type SubscriptionStatus = 'active' | 'canceled' | 'past_due' | 'paused';
type InstallmentPlanStatus = 'active' | 'completed' | 'defaulted' | 'cancelled';
type InvoiceStatus = 'draft' | 'sent' | 'paid';
type SplitStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'refunded';

Configuration

ZendFiConfig

interface ZendFiConfig {
  apiKey?: string;
  baseURL?: string;
  environment?: Environment;
  mode?: ApiKeyMode;
  timeout?: number;        // default: 30000
  retries?: number;        // default: 3
  idempotencyEnabled?: boolean;  // default: true
  debug?: boolean;         // default: false
}

Request Types

CreatePaymentRequest

interface CreatePaymentRequest {
  amount: number;
  currency?: Currency;          // default: 'USD'
  token?: PaymentToken;         // default: 'USDC'
  description?: string;
  customer_email?: string;
  redirect_url?: string;
  metadata?: Record<string, any>;
  split_recipients?: SplitRecipient[];
}

CustomerObject

Pre-filled customer info attached to a payment link at creation time. When present, the checkout page skips the email / customer-info collection step and shows a “Continue to Pay” button instead. The link is automatically forced to max_uses = 1.
interface CustomerObject {
  email: string;
  name?: string;
  phone?: string;
  company?: string;
  billing_address_line1?: string;
  billing_address_line2?: string;
  billing_city?: string;
  billing_state?: string;
  billing_postal_code?: string;
  billing_country?: string;    // ISO 3166-1 alpha-2
}

CreatePaymentLinkRequest

interface CreatePaymentLinkRequest {
  amount: number;
  currency?: string;
  token?: string;
  description?: string;
  max_uses?: number;
  expires_at?: string;              // ISO 8601
  metadata?: Record<string, any>;
  onramp?: boolean;                 // enable fiat on-ramp
  amount_ngn?: number;              // NGN amount for exact conversion
  payer_service_charge?: boolean;   // add service charge to payer
  collect_customer_info?: boolean;  // show customer details form on checkout
  /**
   * Optional pre-filled customer.  When set:
   *   - checkout skips the info-collection step ("Continue to Pay" CTA)
   *   - max_uses is forced to 1 regardless of any supplied value
   *   - customer data is forwarded straight into the onramp / payment flow
   */
  customer?: CustomerObject;
  /**
   * Optional split recipients for this link. When present, all payment link features
   * (onramp, customer collection, etc.) are inherited by payments created from this link.
   */
  split_recipients?: SplitRecipient[];
}

CreateSubscriptionPlanRequest

interface CreateSubscriptionPlanRequest {
  name: string;
  description?: string;
  amount: number;
  currency?: Currency;
  interval: 'daily' | 'weekly' | 'monthly' | 'yearly';
  interval_count?: number;      // default: 1
  trial_days?: number;          // default: 0
  metadata?: Record<string, any>;
}

CreateSubscriptionRequest

interface CreateSubscriptionRequest {
  plan_id: string;
  customer_email: string;
  customer_wallet?: string;
  metadata?: Record<string, any>;
}

CreateInstallmentPlanRequest

interface CreateInstallmentPlanRequest {
  customer_wallet: string;
  customer_email?: string;
  total_amount: number;
  installment_count: number;
  first_payment_date?: string;  // ISO 8601, default: tomorrow
  payment_frequency_days: number;
  description?: string;
  late_fee_amount?: number;
  grace_period_days?: number;   // default: 7
  metadata?: Record<string, any>;
}

CreateInvoiceRequest

interface CreateInvoiceRequest {
  customer_email: string;
  customer_name?: string;
  amount: number;
  token?: PaymentToken;
  description: string;
  line_items?: InvoiceLineItem[];
  due_date?: string;                // ISO 8601
  metadata?: Record<string, any>;
  /** Enable PAJ onramp (NGN bank transfer) on the payment link generated when this invoice is sent. */
  onramp?: boolean;
  /** Max times the generated payment link can be used. Defaults to 1 (single-customer invoice). */
  payment_link_max_uses?: number;
  /** Show a customer-details form on the checkout page before the payment step. */
  collect_customer_info?: boolean;
  /** Original NGN amount for exact PAJ onramp conversion. */
  amount_ngn?: number;
  /** When true, the PAJ service charge is billed to the payer instead of the merchant. */
  payer_service_charge?: boolean;
}

SplitRecipient

Discriminated union supporting two settlement paths: direct wallet transfers and bank account deposits via PAJ.
// Recipient type discriminator
type RecipientType = 'wallet' | 'bank_account';

// Base split configuration (shared across both types)
interface SplitRecipientBase {
  recipient_type: RecipientType;
  recipient_name?: string;
  percentage?: number;          // 0-100, sum of all percentages must equal 100
  fixed_amount_usd?: number;    // alternative to percentage (not summed)
  split_order?: number;         // processing order (default: 0)
}

// Wallet recipient: direct blockchain transfer
interface WalletSplitRecipient extends SplitRecipientBase {
  recipient_type: 'wallet';
  recipient_wallet?: string;      // Solana wallet address
  sub_account_id?: string;        // sub-account external_id or UUID
  recipient_sub_account?: string; // alias for sub_account_id
}

// Bank account recipient: PAJ offramp (USDC → NGN → bank deposit)
interface BankAccountSplitRecipient extends SplitRecipientBase {
  recipient_type: 'bank_account';
  recipient_account_name: string;   // Account holder name
  recipient_bank_account: string;   // Account number
  recipient_bank_id?: string;       // Bank identifier (PAJ id, bank code, or bank name)
  recipient_bank?: string;          // alias for recipient_bank_id
  bank_identifier?: string;         // alias for recipient_bank_id
  bank_code?: string;               // alias for recipient_bank_id
  recipient_email: string;          // For OTP verification
}

type SplitRecipient = WalletSplitRecipient | BankAccountSplitRecipient;

Response Types

Payment

interface Payment {
  id: string;
  merchant_id: string;
  amount_usd?: number;
  amount?: number;
  currency?: string;
  payment_token?: PaymentToken;
  status: PaymentStatus;
  customer_wallet?: string;
  customer_email?: string;
  description?: string;
  checkout_url?: string;
  payment_url?: string;
  qr_code?: string;
  expires_at: string;
  confirmed_at?: string;
  transaction_signature?: string;
  metadata?: Record<string, any>;
  /** Split recipient IDs and statuses (if splits were applied to this payment). */
  split_statuses?: Array<{
    split_id: string;
    recipient_type: 'wallet' | 'bank_account';
    amount_usd: number;
    status: 'pending' | 'processing' | 'completed' | 'failed';
    transaction_signature?: string;
  }>;
  created_at?: string;
  updated_at?: string;
}
interface PaymentLink {
  id: string;
  link_code: string;
  merchant_id: string;
  description?: string;
  amount: number;
  currency: string;
  token: string;
  payment_url: string;
  hosted_page_url: string;
  url: string;                  // alias for hosted_page_url
  max_uses?: number;
  uses_count: number;
  expires_at?: string;
  is_active: boolean;
  onramp: boolean;
  payer_service_charge: boolean;
  /** Whether the checkout page collects full customer details before payment. */
  collect_customer_info: boolean;
  /**
   * Present only on customer-scoped links (created with a `customer` object).
   * Forwarded to the checkout and onramp flow so no manual input is needed.
   */
  customer_data?: CustomerObject;
  /**
   * Split recipients associated with this payment link.
   * When split_recipients are present, all payments created from this link
   * will automatically apply the splits. Supports both wallet (direct blockchain transfer)
   * and bank_account (PAJ offramp: USDC → NGN → bank) recipient types.
   */
  split_recipients?: SplitRecipient[];
  metadata?: Record<string, any>;
  created_at: string;
  updated_at: string;
}

Invoice

interface Invoice {
  id: string;
  invoice_number: string;
  customer_email: string;
  customer_name?: string;
  amount_usd: number;
  currency: string;
  token: PaymentToken;
  description: string;
  line_items: InvoiceLineItem[];
  status: InvoiceStatus;
  payment_url?: string;
  due_date?: string;
  sent_at?: string;
  paid_at?: string;
  created_at: string;
  /** Whether the payment link uses the PAJ onramp flow. */
  onramp: boolean;
  /** Max-use cap on the generated payment link. */
  payment_link_max_uses?: number;
  /** Whether the checkout page collects full customer details before payment. */
  collect_customer_info: boolean;
  /** Whether the payer bears the PAJ service charge. */
  payer_service_charge: boolean;
  /** Original NGN amount set by the merchant for exact PAJ conversion. */
  amount_ngn?: number;
}

Webhook Types

type WebhookEvent =
  | 'payment.created'
  | 'payment.confirmed'
  | 'payment.failed'
  | 'payment.expired'
  | 'subscription.created'
  | 'subscription.activated'
  | 'subscription.canceled'
  | 'subscription.payment_failed'
  | 'split.completed'
  | 'split.failed'
  | 'installment.due'
  | 'installment.paid'
  | 'installment.late'
  | 'invoice.sent'
  | 'invoice.paid';

interface WebhookPayload {
  event: WebhookEvent;
  timestamp: string;
  merchant_id: string;
  data: Payment | Subscription;
}

interface VerifyWebhookRequest {
  payload: string | object;
  signature: string;
  secret: string;
}

Pagination

interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    total_pages: number;
  };
}