Skip to main content
The PayNow SDK provides robust error handling with typed error responses and helper functions for error detection.

PayNowError Type

All API errors from PayNow are returned as PayNowError objects, which extend Axios errors with PayNow-specific error information:
import type { PayNowError } from 'paynow';
import { AxiosError, type AxiosResponse } from 'axios';

export type PayNowError = AxiosError<{
  status: number;
  code: string;
  message: string;
  trace_id?: string | null;
  errors?: ValidationError[] | null;
}> & {
  response: AxiosResponse<{
    status: number;
    code: string;
    message: string;
    trace_id?: string | null;
    errors?: ValidationError[] | null;
  }>;
};

Error Response Structure

status
number
required
The HTTP status code (e.g., 400, 404, 500)
code
string
required
The PayNow parseable error code (e.g., bad-request, not-found)
message
string
required
The human-readable error message
trace_id
string | null
A distributed trace ID used for debugging. Include this when contacting support.
errors
ValidationError[] | null
An array of validation errors (only present for validation failures)

isPayNowError Function

The SDK exports an isPayNowError type guard function to check if an error is a PayNow API error:
import { isPayNowError } from 'paynow';

export function isPayNowError(error: unknown): error is PayNowError {
  return (
    error instanceof AxiosError &&
    !!error.response &&
    typeof error.response.data === 'object' &&
    error.response.data !== null &&
    'status' in error.response.data &&
    'code' in error.response.data &&
    'message' in error.response.data
  );
}

Basic Error Handling

Use isPayNowError to handle API errors:
import { createManagementClient, isPayNowError } from 'paynow';

const client = createManagementClient({
  storeId: 'your-store-id',
  apiKey: 'your-api-key'
});

try {
  const product = await client.products.get('invalid-product-id');
} catch (error) {
  if (isPayNowError(error)) {
    console.error('PayNow API Error:');
    console.error('Status:', error.response.data.status);
    console.error('Code:', error.response.data.code);
    console.error('Message:', error.response.data.message);
    console.error('Trace ID:', error.response.data.trace_id);
  } else {
    console.error('Unexpected error:', error);
  }
}

Handling Specific Error Codes

Check the error code to handle specific error types:
import { createStorefrontClient, isPayNowError } from 'paynow';

const client = createStorefrontClient({
  storeId: 'your-store-id',
  customerToken: 'customer-token'
});

try {
  await client.cart.addLine({
    product_id: 'product-id',
    quantity: 1
  });
} catch (error) {
  if (isPayNowError(error)) {
    const { code, message } = error.response.data;
    
    switch (code) {
      case 'not-found':
        console.error('Product not found:', message);
        break;
        
      case 'bad-request':
        console.error('Invalid request:', message);
        break;
        
      case 'unauthorized':
        console.error('Authentication failed:', message);
        break;
        
      case 'forbidden':
        console.error('Access denied:', message);
        break;
        
      case 'rate-limited':
        console.error('Rate limit exceeded:', message);
        break;
        
      case 'internal-server-error':
        console.error('Server error:', message);
        console.error('Trace ID:', error.response.data.trace_id);
        break;
        
      default:
        console.error('Error:', message);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

Validation Errors

For validation errors (status 400 with validation failures), the errors array contains detailed field-level errors:
import { createManagementClient, isPayNowError } from 'paynow';

const client = createManagementClient({
  storeId: 'your-store-id',
  apiKey: 'your-api-key'
});

try {
  await client.products.create({
    name: '', // Invalid: empty name
    slug: 'test product', // Invalid: slug contains spaces
    price: -100, // Invalid: negative price
    currency: 'usd'
  });
} catch (error) {
  if (isPayNowError(error)) {
    const { code, message, errors } = error.response.data;
    
    if (code === 'bad-request' && errors) {
      console.error('Validation errors:');
      errors.forEach((err) => {
        console.error(`- ${err.field}: ${err.message}`);
      });
    } else {
      console.error('Error:', message);
    }
  }
}

Creating Error Helpers

Create reusable error handling utilities:
import { isPayNowError } from 'paynow';
import type { PayNowError } from 'paynow';

class PayNowErrorHandler {
  static handle(error: unknown): never {
    if (isPayNowError(error)) {
      const { status, code, message, trace_id, errors } = error.response.data;
      
      // Log for debugging
      console.error('PayNow API Error:', {
        status,
        code,
        message,
        trace_id,
        errors
      });
      
      // Throw a custom error
      throw new Error(
        `PayNow API Error [${code}]: ${message}` +
        (trace_id ? ` (trace: ${trace_id})` : '')
      );
    }
    
    // Re-throw unexpected errors
    throw error;
  }
  
  static isNotFound(error: unknown): boolean {
    return isPayNowError(error) && error.response.data.code === 'not-found';
  }
  
  static isValidationError(error: unknown): boolean {
    return (
      isPayNowError(error) &&
      error.response.data.code === 'bad-request' &&
      !!error.response.data.errors
    );
  }
  
  static isRateLimited(error: unknown): boolean {
    return isPayNowError(error) && error.response.data.code === 'rate-limited';
  }
  
  static getTraceId(error: unknown): string | null {
    return isPayNowError(error) ? error.response.data.trace_id || null : null;
  }
}

// Usage
try {
  await client.products.get('product-id');
} catch (error) {
  if (PayNowErrorHandler.isNotFound(error)) {
    console.log('Product not found');
  } else {
    PayNowErrorHandler.handle(error);
  }
}

Retry Logic with Exponential Backoff

Implement retry logic for transient errors:
import { isPayNowError } from 'paynow';

async function withRetry<T>(
  fn: () => Promise<T>,
  options = {
    maxRetries: 3,
    initialDelay: 1000,
    maxDelay: 10000,
    backoffMultiplier: 2
  }
): Promise<T> {
  let lastError: unknown;
  let delay = options.initialDelay;
  
  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      
      // Don't retry on client errors (4xx) except rate limiting
      if (isPayNowError(error)) {
        const status = error.response.data.status;
        const code = error.response.data.code;
        
        if (status >= 400 && status < 500 && code !== 'rate-limited') {
          throw error; // Don't retry client errors
        }
      }
      
      // Last attempt, throw error
      if (attempt === options.maxRetries) {
        throw error;
      }
      
      // Wait before retrying
      await new Promise(resolve => setTimeout(resolve, delay));
      
      // Exponential backoff
      delay = Math.min(delay * options.backoffMultiplier, options.maxDelay);
    }
  }
  
  throw lastError;
}

// Usage
const product = await withRetry(() => 
  client.products.get('product-id')
);

Common Error Codes

The request was invalid. Check the errors array for validation details.
Authentication failed. Check your API key or customer token.
You don’t have permission to access this resource.
The requested resource was not found.
Too many requests. Implement exponential backoff and retry.
Server error. Contact support with the trace ID if the issue persists.

Error Logging Best Practices

Always Log Trace IDs

Include the trace_id in your error logs to help PayNow support diagnose issues:
if (isPayNowError(error)) {
  logger.error('PayNow API Error', {
    code: error.response.data.code,
    message: error.response.data.message,
    trace_id: error.response.data.trace_id,
    request: {
      method: error.config?.method,
      url: error.config?.url
    }
  });
}

Management API

Learn about Management API methods

Storefront API

Learn about Storefront API methods

Management Schemas

Type definitions including PayNowError

Build docs developers (and LLMs) love