The Paystack SDK uses a unique error handling pattern that combines TypeScript’s type safety with Zod’s runtime validation. Unlike traditional try-catch approaches, methods return a result object that contains either data or an error.
The Result Pattern
All SDK methods return a result object with two properties:
type Result < T > = {
data : T | undefined ;
error : ZodError | undefined ;
}
Either data or error will be defined, never both. This pattern eliminates the need for try-catch blocks in most cases.
Basic Error Handling
Here’s how to handle responses from the SDK:
import { Paystack } from '@efobi/paystack' ;
const paystack = new Paystack ( process . env . PAYSTACK_SECRET_KEY ! );
const result = await paystack . transaction . verify ( 'invalid_reference' );
if ( result . error ) {
// Handle validation or API error
console . error ( 'Error:' , result . error . message );
console . error ( 'Issues:' , result . error . issues );
} else if ( result . data ) {
// Success - use the data
console . log ( 'Transaction:' , result . data . data );
console . log ( 'Status:' , result . data . status );
}
Types of Errors
The SDK handles three main categories of errors:
Validation Errors Input data fails Zod schema validation before the API request
API Errors Paystack API returns an error response (4xx, 5xx status codes)
Network Errors Connection issues, timeouts, or network failures
1. Validation Errors
Zod validates all inputs before making API requests. If validation fails, you’ll receive a detailed error:
const result = await paystack . transaction . initialize ({
email: 'invalid-email' , // ❌ Not a valid email
amount: '50000'
});
if ( result . error ) {
console . log ( result . error . issues );
// [
// {
// code: 'invalid_string',
// validation: 'email',
// path: ['email'],
// message: 'Invalid email'
// }
// ]
}
Validation Error Structure
Zod errors provide detailed information:
interface ZodIssue {
code : string ; // Error type (e.g., 'invalid_string', 'too_small')
path : ( string | number )[]; // Path to the invalid field
message : string ; // Human-readable error message
validation ?: string ; // Validation type (e.g., 'email', 'url')
expected ?: string ; // Expected value/type
received ?: string ; // Actual value/type received
}
Flatten Errors
Format Errors
Extract Messages
// Get user-friendly error messages
const result = await paystack . transaction . initialize ( invalidData );
if ( result . error ) {
const flatErrors = result . error . flatten ();
console . log ( flatErrors . fieldErrors );
// {
// email: ['Invalid email'],
// amount: ['Required']
// }
}
2. API Errors
When Paystack returns an error response, the SDK parses it and returns the error in the result. Here’s the implementation from src/main/transaction.ts:119-124:
if ( ! response . ok ) {
const { data , error } = await genericResponse . safeParseAsync ( raw );
return { data , error };
}
const { data , error } = await txnInitializeSuccess . safeParseAsync ( raw );
return { data , error };
Common API Errors
The request contains invalid parameters. const result = await paystack . transaction . initialize ({
email: 'customer@example.com' ,
amount: '-1000' // ❌ Negative amount
});
if ( result . error ) {
// API validation failed
console . error ( 'Bad request:' , result . error . message );
}
Invalid or missing API key. // Using invalid key
const paystack = new Paystack ( 'sk_test_invalid_key' );
const result = await paystack . transaction . list ();
if ( result . error ) {
// Unauthorized - check your API key
console . error ( 'Authentication failed' );
}
The requested resource doesn’t exist. const result = await paystack . transaction . verify ( 'non_existent_ref' );
if ( result . error ) {
// Transaction not found
console . error ( 'Resource not found' );
}
Rate limit exceeded. const result = await paystack . transaction . list ();
if ( result . error ) {
// Too many requests - implement backoff
console . error ( 'Rate limited - retry after delay' );
}
500 Internal Server Error
Paystack server error (rare). const result = await paystack . transaction . initialize ( data );
if ( result . error ) {
// Server error - retry or contact support
console . error ( 'Server error - please try again' );
}
3. Network Errors
Network errors occur outside the SDK’s control and should be caught with try-catch:
try {
const result = await paystack . transaction . verify ( reference );
if ( result . error ) {
// Validation or API error
handleApiError ( result . error );
} else if ( result . data ) {
// Success
processTransaction ( result . data );
}
} catch ( error ) {
// Network error: connection timeout, DNS failure, etc.
console . error ( 'Network error:' , error . message );
// Implement retry logic or show user-friendly message
}
Error Handling Patterns
Pattern 1: Simple Check
For basic operations where you just need to know if it succeeded:
const result = await paystack . transaction . initialize ({
email: 'customer@example.com' ,
amount: '50000'
});
if ( result . error ) {
return { success: false , message: 'Failed to initialize transaction' };
}
return { success: true , url: result . data . data . authorization_url };
Pattern 2: Detailed Error Handling
When you need granular control:
const result = await paystack . transaction . verify ( reference );
if ( result . error ) {
// Log full error for debugging
console . error ( 'Verification failed:' , result . error );
// Extract user-friendly messages
const errorMessages = result . error . issues . map ( issue => ({
field: issue . path . join ( '.' ),
message: issue . message
}));
return {
success: false ,
errors: errorMessages
};
}
// Success path
const transaction = result . data . data ;
return {
success: true ,
status: transaction . status ,
amount: transaction . amount
};
Pattern 3: Type Guards
Use TypeScript type guards for safer code:
function isError < T >( result : { data : T | undefined ; error : any }) : result is { error : any } {
return result . error !== undefined ;
}
const result = await paystack . transaction . initialize ( data );
if ( isError ( result )) {
// TypeScript knows result.error exists
throw new Error ( `Transaction failed: ${ result . error . message } ` );
}
// TypeScript knows result.data exists here
const { authorization_url } = result . data . data ;
Pattern 4: Centralized Error Handler
Create a reusable error handler:
function handlePaystackError ( error : any ) {
const issues = error . issues || [];
return {
message: 'Operation failed' ,
errors: issues . map (( issue : any ) => ({
field: issue . path . join ( '.' ) || 'general' ,
message: issue . message ,
code: issue . code
}))
};
}
// Usage
const result = await paystack . transaction . list ();
if ( result . error ) {
const formatted = handlePaystackError ( result . error );
return res . status ( 400 ). json ( formatted );
}
return res . json ( result . data );
Framework Integration
Express.js
Next.js App Router
Hono
import express from 'express' ;
import { Paystack } from '@efobi/paystack' ;
const app = express ();
const paystack = new Paystack ( process . env . PAYSTACK_SECRET_KEY ! );
app . post ( '/api/transactions/verify/:reference' , async ( req , res ) => {
try {
const result = await paystack . transaction . verify ( req . params . reference );
if ( result . error ) {
return res . status ( 400 ). json ({
success: false ,
message: 'Verification failed' ,
errors: result . error . flatten (). fieldErrors
});
}
return res . json ({
success: true ,
data: result . data . data
});
} catch ( error ) {
console . error ( 'Network error:' , error );
return res . status ( 500 ). json ({
success: false ,
message: 'Internal server error'
});
}
});
// app/api/transactions/verify/[reference]/route.ts
import { NextResponse } from 'next/server' ;
import { Paystack } from '@efobi/paystack' ;
const paystack = new Paystack ( process . env . PAYSTACK_SECRET_KEY ! );
export async function GET (
request : Request ,
{ params } : { params : { reference : string } }
) {
try {
const result = await paystack . transaction . verify ( params . reference );
if ( result . error ) {
return NextResponse . json (
{
success: false ,
message: 'Verification failed' ,
errors: result . error . issues
},
{ status: 400 }
);
}
return NextResponse . json ({
success: true ,
data: result . data . data
});
} catch ( error ) {
console . error ( 'Network error:' , error );
return NextResponse . json (
{ success: false , message: 'Internal server error' },
{ status: 500 }
);
}
}
import { Hono } from 'hono' ;
import { Paystack } from '@efobi/paystack' ;
const app = new Hono ();
const paystack = new Paystack ( process . env . PAYSTACK_SECRET_KEY ! );
app . get ( '/api/transactions/verify/:reference' , async ( c ) => {
try {
const reference = c . req . param ( 'reference' );
const result = await paystack . transaction . verify ( reference );
if ( result . error ) {
return c . json (
{
success: false ,
message: 'Verification failed' ,
errors: result . error . issues
},
400
);
}
return c . json ({
success: true ,
data: result . data . data
});
} catch ( error ) {
console . error ( 'Network error:' , error );
return c . json (
{ success: false , message: 'Internal server error' },
500
);
}
});
Best Practices
Always Check Errors First Check for result.error before accessing result.data to avoid undefined errors. // ✅ Good
if ( result . error ) return ;
console . log ( result . data );
// ❌ Bad
console . log ( result . data ); // May be undefined
Log Errors Completely Log the full error object for debugging, but show user-friendly messages to users. if ( result . error ) {
console . error ( 'Full error:' , result . error );
return 'Transaction failed. Please try again.' ;
}
Use Try-Catch for Network Wrap SDK calls in try-catch to handle network errors. try {
const result = await paystack . transaction . list ();
// Handle result
} catch ( error ) {
// Handle network error
}
Don't Swallow Errors Always handle or propagate errors. Never silently ignore them. // ❌ Bad
if ( result . error ) {
// Do nothing
}
// ✅ Good
if ( result . error ) {
throw new Error ( 'Transaction failed' );
}
Debugging Errors
The SDK logs API error responses to the console automatically. Check src/main/fetcher.ts:65-67: if ( ! response . ok ) {
console . error ( "API Error Response:" , raw );
}
This helps you debug issues during development.
Enable Detailed Logging
const result = await paystack . transaction . verify ( reference );
if ( result . error ) {
// Log everything for debugging
console . log ( 'Error name:' , result . error . name );
console . log ( 'Error message:' , result . error . message );
console . log ( 'Issues:' , JSON . stringify ( result . error . issues , null , 2 ));
console . log ( 'Formatted:' , result . error . format ());
console . log ( 'Flattened:' , result . error . flatten ());
}
Next Steps