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
The HTTP status code (e.g., 400, 404, 500)
The PayNow parseable error code (e.g., bad-request, not-found)
The human-readable error message
A distributed trace ID used for debugging. Include this when contacting support.
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.
internal-server-error (500)
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