Logo
Examples

Error Handling Example

Complete example of handling errors when using One2Pays API.

Overview

This example shows how to:

  1. Handle different error types
  2. Implement retry logic for transient errors
  3. Provide user-friendly error messages

Error Handling Pattern

import crypto from 'crypto';

type ApiError = {
  success: false;
  error: {
    code: string;
    message: string;
    metadata?: Record<string, unknown>;
  };
};

type ApiSuccess<T> = {
  success: true;
  data: T;
};

type ApiResponse<T> = ApiSuccess<T> | ApiError;

// Error handling wrapper
async function handleApiCall<T>(
  apiCall: () => Promise<Response>
): Promise<ApiResponse<T>> {
  try {
    const response = await apiCall();
    const result = await response.json();

    if (!result.success) {
      return handleError(result.error);
    }

    return result as ApiSuccess<T>;
  } catch (error) {
    return {
      success: false,
      error: {
        code: 'NETWORK_ERROR',
        message: 'Network error occurred',
      },
    };
  }
}

// Handle different error types
function handleError(error: any): ApiError {
  switch (error.code) {
    case 'VALIDATION_ERROR':
    case 'INVALID_AMOUNT':
      return {
        success: false,
        error: {
          code: error.code,
          message: 'Invalid amount format. Please check your amount.',
          metadata: error.metadata,
        },
      };

    case 'INSUFFICIENT_BALANCE':
      return {
        success: false,
        error: {
          code: error.code,
          message: 'Insufficient wallet balance. Please top up your wallet.',
          metadata: error.metadata,
        },
      };

    case 'INVALID_CREDENTIALS':
      return {
        success: false,
        error: {
          code: error.code,
          message: 'Authentication failed. Please check your API credentials.',
          metadata: error.metadata,
        },
      };

    case 'RATE_LIMIT_EXCEEDED':
      return {
        success: false,
        error: {
          code: error.code,
          message: 'Too many requests. Please wait a moment and try again.',
          metadata: error.metadata,
        },
      };

    default:
      return {
        success: false,
        error: {
          code: error.code || 'UNKNOWN_ERROR',
          message: error.message || 'An unexpected error occurred',
          metadata: error.metadata,
        },
      };
  }
}

// Retry logic with exponential backoff
async function retryApiCall<T>(
  apiCall: () => Promise<ApiResponse<T>>,
  maxRetries: number = 3
): Promise<ApiResponse<T>> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const result = await apiCall();

    if (result.success) {
      return result;
    }

    // Don't retry on client errors (4xx)
    if (
      result.error.code === 'VALIDATION_ERROR' ||
      result.error.code === 'INVALID_CREDENTIALS' ||
      result.error.code === 'FORBIDDEN' ||
      result.error.code === 'RESOURCE_NOT_FOUND'
    ) {
      return result;
    }

    // Retry on server errors (5xx) or rate limits
    if (attempt < maxRetries - 1) {
      const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
      const retryAfter = (result.error.metadata as any)?.retryAfter || delay / 1000;
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
    }
  }

  return {
    success: false,
    error: {
      code: 'RETRY_EXHAUSTED',
      message: 'Request failed after multiple retries',
    },
  };
}

// Usage example
async function createPaymentWithErrorHandling(
  amount: string,
  referenceId: string,
  customerBankAccount: {
    name: string;
    number: string;
    code: string;
  }
) {
  const result = await retryApiCall(async () => {
    return handleApiCall(async () => {
      // Create payment API call
      const method = 'POST';
      const path = '/api/v1/payments';
      const body = JSON.stringify({
        amount,
        currency: 'THB',
        referenceId,
        paymentMethod: 'promptpay',
        customerBankAccountName: customerBankAccount.name,
        customerBankAccountNumber: customerBankAccount.number,
        customerBankCode: customerBankAccount.code,
      });
      const timestamp = Date.now().toString(); // milliseconds

      // Signature format: HMAC-SHA256(timestamp + "." + rawBody, secretKey)
      const message = `${timestamp}.${body}`;
      const signature = crypto
        .createHmac('sha256', API_SECRET)
        .update(message)
        .digest('hex');

      return fetch(`https://api.example.com${path}`, {
        method,
        headers: {
          'X-API-Key': API_KEY,
          'X-Timestamp': timestamp,
          'X-Signature': `sha256=${signature}`,
          'Content-Type': 'application/json',
        },
        body,
      });
    });
  });

  if (!result.success) {
    // Show user-friendly error message
    console.error('Payment creation failed:', result.error.message);
    return null;
  }

  return result.data;
}

User-Friendly Error Messages

Always provide user-friendly error messages:

function getUserFriendlyMessage(error: ApiError): string {
  const messages: Record<string, string> = {
    VALIDATION_ERROR: 'Please check your input and try again',
    INVALID_AMOUNT: 'Please enter a valid amount',
    INSUFFICIENT_BALANCE: 'Your wallet balance is insufficient. Please top up.',
    INVALID_CREDENTIALS: 'Authentication error. Please contact support.',
    RATE_LIMIT_EXCEEDED: 'Too many requests. Please try again in a moment.',
    RESOURCE_NOT_FOUND: 'Payment not found',
    NETWORK_ERROR: 'Network error. Please check your connection.',
  };

  return messages[error.error.code] || 'An error occurred. Please try again.';
}

On this page