Skip to content

Error Handling ​

The Flutterwave SDK provides comprehensive error handling with custom exception classes for different HTTP error scenarios. This makes it easy to handle errors gracefully in your application.

Exception Classes ​

The SDK includes the following exception classes:

BadRequestException ​

Thrown when the API returns a 400 Bad Request status code. This typically indicates invalid request data.

typescript
import { BadRequestException } from 'flutterwave-node-v4';

Common causes:

  • Invalid parameter values
  • Missing required fields
  • Malformed request data
  • Invalid currency codes or country codes

UnauthorizedRequestException ​

Thrown when the API returns a 401 Unauthorized status code. This indicates authentication failure.

typescript
import { UnauthorizedRequestException } from 'flutterwave-node-v4';

Common causes:

  • Invalid client ID or client secret
  • Expired or invalid access token
  • Missing encryption key for card operations

ForbiddenRequestException ​

Thrown when the API returns a 403 Forbidden status code. This indicates lack of permission for the requested operation.

typescript
import { ForbiddenRequestException } from 'flutterwave-node-v4';

Common causes:

  • Insufficient account permissions
  • Attempting operations not enabled for your account
  • IP address restrictions

HttpException ​

The base exception class for all HTTP-related errors. Other exceptions extend this class.

typescript
import { HttpException } from 'flutterwave-node-v4';

Basic Error Handling ​

Try-Catch Pattern ​

The most common way to handle errors is using try-catch blocks:

typescript
import {
  BadRequestException,
  UnauthorizedRequestException,
} from 'flutterwave-node-v4';

try {
  const transfer = await flutterwave.api.transfers.directTransfer({
    action: 'instant',
    type: 'bank',
    reference: `ref-${Date.now()}`,
    payment_instruction: {
      source_currency: 'NGN',
      destination_currency: 'NGN',
      amount: {
        value: 10000,
        applies_to: 'source_currency',
      },
      recipient: {
        bank: {
          account_number: '0690000031',
          code: '044',
        },
      },
      sender: {
        name: {
          first: 'John',
          last: 'Doe',
        },
      },
    },
  });

  console.log('Transfer successful:', transfer.id);
} catch (error) {
  if (error instanceof BadRequestException) {
    console.error('Invalid transfer data:', error.message);
  } else if (error instanceof UnauthorizedRequestException) {
    console.error('Authentication failed:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
}

Error Handling Patterns ​

Specific Exception Handling ​

Handle different exception types with specific recovery strategies:

typescript
async function createTransferWithErrorHandling(transferData: any) {
  try {
    return await flutterwave.api.transfers.directTransfer(transferData);
  } catch (error) {
    if (error instanceof BadRequestException) {
      // Log invalid data and ask user to correct
      console.error('Invalid data provided:', error.message);
      throw new Error('Please check your transfer details and try again');
    } else if (error instanceof UnauthorizedRequestException) {
      // Attempt to refresh credentials
      console.error('Authentication failed, refreshing token...');
      await flutterwave.generateAccessToken();
      // Retry the request
      return await flutterwave.api.transfers.directTransfer(transferData);
    } else if (error instanceof ForbiddenRequestException) {
      // Log permission issue
      console.error('Operation not permitted:', error.message);
      throw new Error('You do not have permission for this operation');
    } else {
      // Handle unexpected errors
      console.error('Unexpected error:', error);
      throw error;
    }
  }
}

Retry Logic ​

Implement retry logic for transient errors:

typescript
async function transferWithRetry(
  transferData: any,
  maxRetries: number = 3,
): Promise<any> {
  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await flutterwave.api.transfers.directTransfer(transferData);
    } catch (error) {
      lastError = error;

      if (error instanceof UnauthorizedRequestException) {
        // Refresh token and retry
        await flutterwave.generateAccessToken();
        continue;
      }

      if (error instanceof BadRequestException) {
        // Don't retry bad requests
        throw error;
      }

      // For other errors, wait before retry
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError;
}

Validation Before API Calls ​

Validate data before making API calls to catch errors early:

typescript
function validateTransferData(data: any): void {
  if (!data.reference) {
    throw new Error('Reference is required');
  }

  if (!data.payment_instruction) {
    throw new Error('Payment instruction is required');
  }

  if (data.payment_instruction.amount.value <= 0) {
    throw new Error('Amount must be greater than zero');
  }

  // Add more validation as needed
}

async function createTransferSafely(transferData: any) {
  try {
    // Validate first
    validateTransferData(transferData);

    // Then make API call
    return await flutterwave.api.transfers.directTransfer(transferData);
  } catch (error) {
    if (error instanceof Error && !error.message.includes('API')) {
      // This is a validation error
      console.error('Validation failed:', error.message);
      throw error;
    }

    // This is an API error
    handleApiError(error);
  }
}

Custom Error Wrapper ​

Create a custom error wrapper for consistent error handling:

typescript
class TransferError extends Error {
  constructor(
    message: string,
    public code: string,
    public originalError?: any,
  ) {
    super(message);
    this.name = 'TransferError';
  }
}

async function safeTransfer(transferData: any): Promise<any> {
  try {
    return await flutterwave.api.transfers.directTransfer(transferData);
  } catch (error) {
    if (error instanceof BadRequestException) {
      throw new TransferError(
        'Invalid transfer details provided',
        'INVALID_DATA',
        error,
      );
    } else if (error instanceof UnauthorizedRequestException) {
      throw new TransferError('Authentication failed', 'AUTH_FAILED', error);
    } else if (error instanceof ForbiddenRequestException) {
      throw new TransferError('Operation not permitted', 'FORBIDDEN', error);
    } else {
      throw new TransferError('Transfer failed', 'UNKNOWN_ERROR', error);
    }
  }
}

Logging Errors ​

Implement proper error logging for debugging and monitoring:

typescript
import { HttpException } from 'flutterwave-node-v4';

function logError(error: any, context: string): void {
  const timestamp = new Date().toISOString();

  if (error instanceof HttpException) {
    console.error(`[${timestamp}] ${context}:`, {
      type: error.constructor.name,
      message: error.message,
      stack: error.stack,
    });
  } else {
    console.error(`[${timestamp}] ${context}:`, {
      message: error.message || 'Unknown error',
      stack: error.stack,
    });
  }
}

async function transferWithLogging(transferData: any) {
  try {
    const result = await flutterwave.api.transfers.directTransfer(transferData);
    console.log('Transfer successful:', result.id);
    return result;
  } catch (error) {
    logError(error, 'Transfer failed');
    throw error;
  }
}

Error Monitoring ​

Set up error monitoring for production environments:

typescript
interface ErrorReport {
  timestamp: string;
  errorType: string;
  message: string;
  context: any;
}

const errorReports: ErrorReport[] = [];

function reportError(error: any, context: any): void {
  const report: ErrorReport = {
    timestamp: new Date().toISOString(),
    errorType: error.constructor.name,
    message: error.message,
    context,
  };

  errorReports.push(report);

  // Send to monitoring service
  if (process.env.NODE_ENV === 'production') {
    sendToMonitoringService(report);
  }
}

async function monitoredTransfer(transferData: any) {
  try {
    return await flutterwave.api.transfers.directTransfer(transferData);
  } catch (error) {
    reportError(error, {
      operation: 'transfer',
      reference: transferData.reference,
      amount: transferData.payment_instruction.amount.value,
    });
    throw error;
  }
}

function sendToMonitoringService(report: ErrorReport): void {
  // Implementation depends on your monitoring service
  // e.g., Sentry, DataDog, CloudWatch, etc.
}

Best Practices ​

1. Always Handle Errors ​

Never leave API calls without error handling:

typescript
// Bad
const transfer = await flutterwave.api.transfers.directTransfer(data);

// Good
try {
  const transfer = await flutterwave.api.transfers.directTransfer(data);
} catch (error) {
  handleError(error);
}

2. Provide User-Friendly Messages ​

Don't expose technical error messages to end users:

typescript
async function createUserFriendlyTransfer(transferData: any) {
  try {
    return await flutterwave.api.transfers.directTransfer(transferData);
  } catch (error) {
    if (error instanceof BadRequestException) {
      return {
        success: false,
        message: 'Please check your transfer details and try again',
      };
    } else if (error instanceof UnauthorizedRequestException) {
      return {
        success: false,
        message: 'Authentication error. Please contact support',
      };
    } else {
      return {
        success: false,
        message: 'Transfer failed. Please try again later',
      };
    }
  }
}

3. Log Errors for Debugging ​

Keep detailed logs for debugging in development:

typescript
async function debugTransfer(transferData: any) {
  try {
    const result = await flutterwave.api.transfers.directTransfer(transferData);

    if (process.env.NODE_ENV === 'development') {
      console.log('Transfer Debug:', {
        id: result.id,
        status: result.status,
        reference: transferData.reference,
      });
    }

    return result;
  } catch (error) {
    if (process.env.NODE_ENV === 'development') {
      console.error('Transfer Error Debug:', {
        error: error.constructor.name,
        message: error.message,
        data: transferData,
      });
    }
    throw error;
  }
}

4. Don't Swallow Errors ​

Always propagate or handle errors appropriately:

typescript
// Bad
async function badTransfer(data: any) {
  try {
    return await flutterwave.api.transfers.directTransfer(data);
  } catch (error) {
    // Silently failing - very bad!
  }
}

// Good
async function goodTransfer(data: any) {
  try {
    return await flutterwave.api.transfers.directTransfer(data);
  } catch (error) {
    logError(error, 'Transfer failed');
    throw error; // Re-throw so caller can handle
  }
}

Common Error Scenarios ​

Invalid Credentials ​

typescript
try {
  const flutterwave = new Flutterwave('wrong_client_id', 'wrong_client_secret');

  await flutterwave.generateAccessToken();
} catch (error) {
  if (error instanceof UnauthorizedRequestException) {
    console.error('Invalid credentials provided');
  }
}

Insufficient Balance ​

typescript
try {
  await flutterwave.api.transfers.directTransfer(largeTransferData);
} catch (error) {
  if (error instanceof BadRequestException) {
    if (error.message.includes('insufficient')) {
      console.error('Insufficient balance for this transfer');
    }
  }
}

Invalid Account Details ​

typescript
try {
  await flutterwave.api.banks.resolve({
    account: {
      code: '044',
      number: 'invalid_number',
    },
    currency: 'NGN',
  });
} catch (error) {
  if (error instanceof BadRequestException) {
    console.error('Invalid account number');
  }
}

Testing Error Handling ​

Write tests to ensure your error handling works correctly:

typescript
import { describe, it, expect } from 'vitest';

describe('Error Handling', () => {
  it('should handle bad request errors', async () => {
    const invalidData = {
      // Missing required fields
    };

    await expect(
      flutterwave.api.transfers.directTransfer(invalidData as any),
    ).rejects.toThrow(BadRequestException);
  });

  it('should handle authentication errors', async () => {
    const badFlutterwave = new Flutterwave('bad_id', 'bad_secret');

    await expect(badFlutterwave.generateAccessToken()).rejects.toThrow(
      UnauthorizedRequestException,
    );
  });
});

Released under the MIT License.