TypeScript Guide
Complete guide to using ZendFi with TypeScript for maximum type safety and developer experience.
Why TypeScript?
The ZendFi SDK is built with TypeScript and provides:
Full type coverage - Every method, parameter, and response is typed
Autocomplete - IntelliSense shows available methods and parameters
Compile-time errors - Catch mistakes before runtime
Better refactoring - Rename and update with confidence
Documentation - Types serve as inline documentation
Installation
npm install @zendfi/sdk
# TypeScript should be installed in your project
npm install -D typescript @types/node
Basic Setup
TypeScript Configuration
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
}
}
Import the SDK
import { ZendFiClient } from '@zendfi/sdk';
import type { Payment, CreatePaymentRequest } from '@zendfi/sdk';
const zendfi = new ZendFiClient({
apiKey: process.env.ZENDFI_API_KEY,
});
Core Types
Payment Types
import type {
Payment,
CreatePaymentRequest,
PaymentStatus,
ListPaymentsRequest
} from '@zendfi/sdk';
// Payment object returned from API (snake_case fields)
const payment: Payment = {
id: 'pay_abc123',
merchant_id: 'merch_xyz789',
amount: 99.99,
currency: 'USD',
status: 'pending',
payment_url: 'https://pay.zendfi.tech/pay_abc123',
expires_at: '2025-12-22T10:00:00Z',
metadata: {}
};
// Parameters for creating a payment
const params: CreatePaymentRequest = {
amount: 99.99,
currency: 'USD',
description: 'Pro Plan Subscription',
metadata: {
order_id: 'order_123'
}
};
// Payment status type
const status: PaymentStatus = 'confirmed'; // 'pending' | 'confirmed' | 'failed' | 'expired'
Subscription Types
import type {
SubscriptionPlan,
CreateSubscriptionPlanRequest,
Subscription,
CreateSubscriptionRequest,
SubscriptionStatus
} from '@zendfi/sdk';
// Plan object (snake_case fields)
const plan: SubscriptionPlan = {
id: 'plan_abc123',
merchant_id: 'merch_xyz789',
name: 'Pro Plan',
amount: 29.99,
currency: 'USD',
interval: 'monthly',
interval_count: 1,
trial_days: 0,
is_active: true,
created_at: '2025-12-22T10:00:00Z',
updated_at: '2025-12-22T10:00:00Z'
};
// Subscription object (snake_case fields)
const subscription: Subscription = {
id: 'sub_abc123',
merchant_id: 'merch_xyz789',
plan_id: 'plan_abc123',
customer_email: 'user@example.com',
customer_wallet: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
status: 'active',
current_period_start: '2025-12-22T10:00:00Z',
current_period_end: '2026-01-22T10:00:00Z',
created_at: '2025-12-22T10:00:00Z',
updated_at: '2025-12-22T10:00:00Z'
};
Payment Link Types
import type {
PaymentLink,
CreatePaymentLinkRequest
} from '@zendfi/sdk';
const link: PaymentLink = {
link_code: 'plink_abc123',
merchant_id: 'merch_xyz789',
description: 'Pro Plan Upgrade',
amount: 99.99,
currency: 'USD',
payment_url: 'https://pay.zendfi.tech/plink_abc123',
hosted_page_url: 'https://pay.zendfi.tech/plink_abc123',
url: 'https://pay.zendfi.tech/plink_abc123', // Alias for hosted_page_url
is_active: true,
uses_count: 47,
created_at: '2025-12-22T10:00:00Z',
updated_at: '2025-12-22T10:00:00Z'
};
Invoice Types
import type {
Invoice,
CreateInvoiceRequest,
InvoiceLineItem,
InvoiceStatus
} from '@zendfi/sdk';
const lineItem: InvoiceLineItem = {
description: 'Website Development',
quantity: 40,
unit_amount: 150,
amount: 6000
};
const invoice: Invoice = {
id: 'inv_abc123',
merchant_id: 'merch_xyz789',
invoice_number: 'INV-2025-001',
customer_name: 'Acme Corp',
customer_email: 'billing@acme.com',
line_items: [lineItem],
subtotal: 6000,
tax: 510,
total: 6510,
amount_paid: 0,
amount_due: 6510,
status: 'sent',
due_date: '2025-12-31',
created_at: '2025-12-22T10:00:00Z',
updated_at: '2025-12-22T10:00:00Z'
};
Webhook Types
import type {
WebhookPayload,
WebhookEvent
} from '@zendfi/sdk';
const payload: WebhookPayload = {
event: 'payment.confirmed',
timestamp: '2025-12-22T10:00:00Z',
merchant_id: 'merch_test_123',
data: {
id: 'pay_abc123',
amount: 99.99,
status: 'confirmed'
} as Payment
};
// Event type union
const eventType: WebhookEvent =
'payment.created' |
'payment.confirmed' |
'payment.failed' |
'subscription.created' |
'subscription.canceled';
Type-Safe API Calls
Payments
import { ZendFiClient } from '@zendfi/sdk';
import type { Payment, CreatePaymentRequest } from '@zendfi/sdk';
const zendfi = new ZendFiClient({
apiKey: process.env.ZENDFI_API_KEY,
});
async function createPayment(): Promise<Payment> {
const params: CreatePaymentRequest = {
amount: 99.99,
currency: 'USD',
description: 'Pro Plan',
metadata: {
order_id: 'order_123',
user_id: 'user_456'
}
};
// TypeScript knows the return type is Payment
const payment = await zendfi.createPayment(params);
// Autocomplete available for all properties (snake_case)
console.log(payment.id);
console.log(payment.payment_url); // Note: snake_case
console.log(payment.status);
return payment;
}
async function getPayment(id: string): Promise<Payment> {
return await zendfi.getPayment(id);
}
async function listPayments(limit: number = 20): Promise<Payment[]> {
const result = await zendfi.listPayments({ limit });
return result.data;
}
Subscriptions
import type {
SubscriptionPlan,
CreateSubscriptionPlanRequest,
Subscription,
CreateSubscriptionRequest
} from '@zendfi/sdk';
async function createPlan(): Promise<SubscriptionPlan> {
const params: CreateSubscriptionPlanRequest = {
name: 'Pro Plan',
amount: 29.99,
currency: 'USD',
interval: 'monthly', // Type-checked: 'daily' | 'weekly' | 'monthly' | 'yearly'
trial_days: 14
};
return await zendfi.createSubscriptionPlan(params);
}
async function subscribe(
planId: string,
email: string
): Promise<Subscription> {
const params: CreateSubscriptionRequest = {
plan_id: planId,
customer_email: email,
metadata: {
source: 'website'
}
};
return await zendfi.createSubscription(params);
}
Payment Links
import type {
PaymentLink,
CreatePaymentLinkRequest
} from '@zendfi/sdk';
async function createPaymentLink(): Promise<PaymentLink> {
const params: CreatePaymentLinkRequest = {
amount: 99.99,
currency: 'USD',
description: 'Pro Plan Upgrade',
metadata: {
campaign: 'summer_sale'
}
};
const link = await zendfi.createPaymentLink(params);
// Use snake_case fields
console.log(link.payment_url);
console.log(link.link_code);
return link;
}
Custom Type Guards
Create type guards for runtime validation:
import type { Payment, PaymentStatus } from '@zendfi/sdk';
// Type guard for payment status
function isConfirmedPayment(payment: Payment): boolean {
return payment.status === 'confirmed';
}
// Type guard with type predicate
function isValidPayment(value: unknown): value is Payment {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'amount' in value &&
'status' in value
);
}
// Usage
const payment = await zendfi.getPayment('pay_abc123');
if (isConfirmedPayment(payment)) {
// TypeScript knows payment is confirmed here
console.log('Payment confirmed:', payment.id);
}
if (isValidPayment(unknownData)) {
// TypeScript knows unknownData is Payment here
console.log(unknownData.payment_url);
}
Generic Helper Functions
import type { Payment, Subscription, Invoice } from '@zendfi/sdk';
// Generic function for handling metadata
function addMetadata<T extends { metadata?: Record<string, any> }>(
item: T,
key: string,
value: any
): T {
return {
...item,
metadata: {
...item.metadata,
[key]: value
}
};
}
// Usage with different types
const payment: Payment = await zendfi.getPayment('pay_123');
const updatedPayment = addMetadata(payment, 'processed', true);
const invoice: Invoice = await zendfi.getInvoice('inv_123');
const updatedInvoice = addMetadata(invoice, 'sent_via', 'email');
Type-Safe Webhook Handlers
import { ZendFiClient } from '@zendfi/sdk';
import type { WebhookPayload, Payment, Subscription } from '@zendfi/sdk';
const zendfi = new ZendFiClient({
apiKey: process.env.ZENDFI_API_KEY,
});
// Webhook handler with type narrowing
async function handleWebhook(
body: string,
signature: string
): Promise<void> {
// Verify webhook signature (returns boolean)
const isValid = zendfi.verifyWebhook({
payload: body,
signature,
secret: process.env.ZENDFI_WEBHOOK_SECRET!
});
if (!isValid) {
throw new Error('Invalid webhook signature');
}
// Parse payload
const payload: WebhookPayload = JSON.parse(body);
// Type narrowing based on event type
switch (payload.event) {
case 'payment.confirmed':
await handlePaymentConfirmed(payload.data as Payment);
break;
case 'subscription.created':
await handleSubscriptionCreated(payload.data as Subscription);
break;
case 'subscription.canceled':
await handleSubscriptionCanceled(payload.data as Subscription);
break;
default:
console.log('Unhandled event:', payload.event);
}
}
async function handlePaymentConfirmed(payment: Payment): Promise<void> {
console.log('Payment confirmed:', payment.id);
// TypeScript knows payment has all Payment properties
console.log('Amount:', payment.amount);
console.log('Status:', payment.status);
}
async function handleSubscriptionCreated(subscription: Subscription): Promise<void> {
console.log('Subscription created:', subscription.id);
// TypeScript knows subscription has all Subscription properties (snake_case)
console.log('Plan:', subscription.plan_id);
console.log('Customer:', subscription.customer_email);
}
async function handleSubscriptionCanceled(subscription: Subscription): Promise<void> {
console.log('Subscription canceled:', subscription.id);
}
Environment Variables with Types
// env.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
ZENDFI_API_KEY: string;
ZENDFI_WEBHOOK_SECRET: string;
NODE_ENV: 'development' | 'production' | 'test';
}
}
}
export {};
// config/zendfi.ts
import { ZendFiClient } from '@zendfi/sdk';
// TypeScript ensures env vars are defined
const apiKey = process.env.ZENDFI_API_KEY;
const webhookSecret = process.env.ZENDFI_WEBHOOK_SECRET;
if (!apiKey) {
throw new Error('ZENDFI_API_KEY is required');
}
if (!webhookSecret) {
throw new Error('ZENDFI_WEBHOOK_SECRET is required');
}
export const zendfi = new ZendFiClient({
apiKey,
debug: process.env.NODE_ENV === 'development'
});
export const config = {
webhookSecret
};
Extending Types
Add custom properties to metadata:
import type { Payment, CreatePaymentRequest } from '@zendfi/sdk';
// Define custom metadata interface
interface OrderMetadata {
order_id: string;
user_id: string;
product_sku: string;
coupon_code?: string;
}
// Extend CreatePaymentRequest with typed metadata
interface TypedPaymentRequest extends Omit<CreatePaymentRequest, 'metadata'> {
metadata: OrderMetadata;
}
// Type-safe payment creation
async function createOrderPayment(
params: TypedPaymentRequest
): Promise<Payment> {
return await zendfi.createPayment(params);
}
// Usage - TypeScript enforces metadata structure
const payment = await createOrderPayment({
amount: 99.99,
currency: 'USD',
description: 'Order #12345',
metadata: {
order_id: 'order_12345',
user_id: 'user_789',
product_sku: 'PROD-001'
// TypeScript error if required fields missing!
}
});
Error Handling with Types
import { ZendFiClient, ZendFiError } from '@zendfi/sdk';
import type { Payment } from '@zendfi/sdk';
const zendfi = new ZendFiClient({
apiKey: process.env.ZENDFI_API_KEY,
});
async function createPaymentSafely(
amount: number
): Promise<Payment | null> {
try {
return await zendfi.createPayment({
amount,
currency: 'USD'
});
} catch (error) {
// Type guard for ZendFiError
if (error instanceof ZendFiError) {
console.error('ZendFi error:', {
code: error.code,
message: error.message,
statusCode: error.statusCode
});
} else if (error instanceof Error) {
console.error('Unexpected error:', error.message);
} else {
console.error('Unknown error:', error);
}
return null;
}
}
// Usage with type narrowing
const payment = await createPaymentSafely(99.99);
if (payment) {
// TypeScript knows payment is Payment here (not null)
console.log('Payment created:', payment.id);
console.log('Payment URL:', payment.payment_url); // snake_case
} else {
// Handle null case
console.log('Payment creation failed');
}
Utility Types
import type { Payment, Subscription } from '@zendfi/sdk';
// Extract specific properties
type PaymentSummary = Pick<Payment, 'id' | 'amount' | 'status'>;
const summary: PaymentSummary = {
id: 'pay_123',
amount: 99.99,
status: 'confirmed'
};
// Make all properties optional
type PartialPayment = Partial<Payment>;
// Make all properties required
type RequiredSubscription = Required<Subscription>;
// Exclude properties
type PaymentWithoutMetadata = Omit<Payment, 'metadata'>;
// Create union type from status values
type PaymentStatusUnion = Payment['status']; // 'pending' | 'confirmed' | 'failed' | 'expired'
Best Practices
1. Always Import Types
// Good: Import types separately
import { ZendFiClient } from '@zendfi/sdk';
import type { Payment, CreatePaymentRequest } from '@zendfi/sdk';
// Avoid: Importing everything without type keyword
import { ZendFiClient, Payment, CreatePaymentRequest } from '@zendfi/sdk';
2. Use Strict TypeScript
tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true
}
}
3. Define Return Types
// Good: Explicit return type
async function getPayment(id: string): Promise<Payment> {
return await zendfi.getPayment(id);
}
// Avoid: Implicit return type
async function getPayment(id: string) {
return await zendfi.getPayment(id);
}
4. Use Type Narrowing
import type { Payment } from '@zendfi/sdk';
function processPayment(payment: Payment): void {
// Type narrowing with switch
switch (payment.status) {
case 'confirmed':
console.log('Payment confirmed');
break;
case 'pending':
console.log('Payment pending');
break;
case 'failed':
console.log('Payment failed');
break;
case 'expired':
console.log('Payment expired');
break;
}
}
5. Remember snake_case Fields
import type { Payment } from '@zendfi/sdk';
// Correct: snake_case
const url = payment.payment_url;
const merchant = payment.merchant_id;
const createdAt = payment.created_at;
// Wrong: camelCase (TypeScript error)
const url = payment.paymentUrl;
const merchant = payment.merchantId;
const createdAt = payment.createdAt;
6. Validate at Runtime
import { z } from 'zod';
import type { CreatePaymentRequest } from '@zendfi/sdk';
// Define validation schema
const PaymentParamsSchema = z.object({
amount: z.number().positive().max(1000000),
currency: z.literal('USD'),
description: z.string().min(1).max(500),
metadata: z.record(z.any()).optional()
});
// Validate and infer type
async function createValidatedPayment(data: unknown): Promise<Payment> {
// Validates at runtime AND provides TypeScript types
const params = PaymentParamsSchema.parse(data);
return await zendfi.createPayment(params);
}
Framework-Specific Patterns
Next.js with TypeScript
// app/api/payments/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ZendFiClient } from '@zendfi/sdk';
import type { Payment } from '@zendfi/sdk';
const zendfi = new ZendFiClient({
apiKey: process.env.ZENDFI_API_KEY,
});
export async function POST(request: NextRequest): Promise<NextResponse> {
try {
const body = await request.json();
const payment: Payment = await zendfi.createPayment({
amount: body.amount,
currency: 'USD',
description: body.description
});
return NextResponse.json({
success: true,
payment_url: payment.payment_url // snake_case
});
} catch (error) {
return NextResponse.json(
{ success: false, error: 'Payment creation failed' },
{ status: 500 }
);
}
}
Express with TypeScript
// routes/payments.ts
import express, { Request, Response } from 'express';
import { ZendFiClient } from '@zendfi/sdk';
import type { Payment, CreatePaymentRequest } from '@zendfi/sdk';
const router = express.Router();
const zendfi = new ZendFiClient({
apiKey: process.env.ZENDFI_API_KEY,
});
interface CreatePaymentBody {
amount: number;
description: string;
}
router.post('/payments', async (
req: Request<{}, {}, CreatePaymentBody>,
res: Response
) => {
try {
const params: CreatePaymentRequest = {
amount: req.body.amount,
currency: 'USD',
description: req.body.description
};
const payment: Payment = await zendfi.createPayment(params);
res.json({
success: true,
payment_url: payment.payment_url // snake_case
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Payment creation failed'
});
}
});
export default router;
Next Steps
Learn More:
- Testing & Debugging - Test your integration
- Best Practices - Production patterns
- API Reference - Complete API documentation
Need Help?