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. APaymentId 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
| Type | Factory | Example |
|---|---|---|
PaymentId | asPaymentId() | pay_test_abc123 |
MerchantId | asMerchantId() | merch_xyz789 |
InvoiceId | asInvoiceId() | inv_test_abc123 |
SubscriptionId | asSubscriptionId() | sub_abc123 |
InstallmentPlanId | asInstallmentPlanId() | inst_abc123 |
PaymentLinkCode | asPaymentLinkCode() | link_abc123 |
How Branding Works
type Brand<T, B> = T & { __brand: B };
type PaymentId = Brand<string, 'PaymentId'>;
__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 tomax_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;
}
PaymentLink
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;
};
}