PayGate

Errors

Understanding and handling PayGate API errors.

Errors

PayGate uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the 2xx range indicate success, 4xx codes indicate an error from the information provided, and 5xx codes indicate a server error.

Error Response Format

All errors follow this format:

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "The amount must be a positive integer",
    "param": "amount",
    "doc_url": "https://docs.paygate.com.gh/errors/parameter_invalid"
  }
}

Error Object

FieldTypeDescription
typestringThe type of error
codestringSpecific error code
messagestringHuman-readable message
paramstringParameter that caused the error (if applicable)
doc_urlstringLink to documentation about this error

HTTP Status Codes

StatusDescription
200OK - Request succeeded
201Created - Resource created successfully
400Bad Request - Invalid parameters
401Unauthorized - Invalid API key
403Forbidden - Not allowed to access resource
404Not Found - Resource doesn't exist
409Conflict - Request conflicts with current state
422Unprocessable Entity - Request understood but cannot be processed
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Something went wrong on our end

Error Types

api_error

Server-side errors. These are rare and indicate something went wrong on PayGate's end.

{
  "error": {
    "type": "api_error",
    "message": "An unexpected error occurred. Please try again."
  }
}

authentication_error

Invalid or missing API key.

{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "Invalid API key provided."
  }
}

invalid_request_error

The request has invalid parameters.

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "Missing required parameter: amount",
    "param": "amount"
  }
}

rate_limit_error

Too many requests in a short period.

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please retry after 60 seconds."
  }
}

payment_error

Payment-specific errors.

{
  "error": {
    "type": "payment_error",
    "code": "insufficient_funds",
    "message": "The customer's account has insufficient funds."
  }
}

Error Codes

Authentication Errors

CodeDescription
invalid_api_keyThe API key is invalid
expired_api_keyThe API key has expired
revoked_api_keyThe API key was revoked
test_key_on_liveTest key used on live endpoint
live_key_on_testLive key used on test endpoint

Request Errors

CodeDescription
parameter_missingRequired parameter is missing
parameter_invalidParameter value is invalid
parameter_unknownUnknown parameter provided
resource_not_foundRequested resource doesn't exist
idempotency_errorConflicting idempotency key

Payment Errors

CodeDescription
insufficient_fundsAccount has insufficient funds
transaction_declinedTransaction was declined
phone_invalidPhone number is invalid
phone_unreachablePhone is off or no network
provider_unavailablePayment provider is unavailable
timeoutTransaction timed out
invalid_pinCustomer entered wrong PIN
daily_limit_exceededDaily transaction limit exceeded

Amount Errors

CodeDescription
amount_too_smallBelow minimum (GHS 1.00)
amount_too_largeAbove maximum allowed
invalid_currencyCurrency not supported

Rate Limit Errors

CodeDescription
rate_limit_exceededToo many requests

Handling Errors

import PayGate, { PayGateError } from '@paygate/node'

const paygate = new PayGate('sk_test_...')

try {
  const payment = await paygate.payments.create({
    amount: 5000,
    currency: 'GHS',
    payment_method: 'mobile_money',
    phone: '0241234567'
  })
} catch (error) {
  if (error instanceof PayGateError) {
    switch (error.type) {
      case 'authentication_error':
        console.error('Check your API key')
        break

      case 'invalid_request_error':
        console.error(`Invalid parameter: ${error.param}`)
        break

      case 'payment_error':
        switch (error.code) {
          case 'insufficient_funds':
            // Tell customer to top up
            break
          case 'phone_unreachable':
            // Tell customer to check their phone
            break
          default:
            console.error(error.message)
        }
        break

      case 'rate_limit_error':
        // Wait and retry
        await sleep(60000)
        break

      case 'api_error':
        // Log and alert
        console.error('PayGate server error:', error.message)
        break
    }
  } else {
    // Network error or other
    console.error('Unexpected error:', error)
  }
}
import paygate
from paygate.errors import (
    PayGateError,
    AuthenticationError,
    InvalidRequestError,
    PaymentError,
    RateLimitError,
    APIError
)

client = paygate.Client('sk_test_...')

try:
    payment = client.payments.create(
        amount=5000,
        currency='GHS',
        payment_method='mobile_money',
        phone='0241234567'
    )
except AuthenticationError as e:
    print('Check your API key')

except InvalidRequestError as e:
    print(f'Invalid parameter: {e.param}')

except PaymentError as e:
    if e.code == 'insufficient_funds':
        print('Please top up your account')
    elif e.code == 'phone_unreachable':
        print('Please check your phone')
    else:
        print(e.message)

except RateLimitError:
    import time
    time.sleep(60)
    # Retry

except APIError as e:
    print(f'PayGate server error: {e.message}')

except PayGateError as e:
    print(f'Error: {e.message}')
use PayGate\PayGate;
use PayGate\Exception\PayGateException;
use PayGate\Exception\AuthenticationException;
use PayGate\Exception\InvalidRequestException;
use PayGate\Exception\PaymentException;
use PayGate\Exception\RateLimitException;
use PayGate\Exception\ApiException;

$paygate = new PayGate('sk_test_...');

try {
    $payment = $paygate->payments->create([
        'amount' => 5000,
        'currency' => 'GHS',
        'payment_method' => 'mobile_money',
        'phone' => '0241234567'
    ]);
} catch (AuthenticationException $e) {
    echo 'Check your API key';

} catch (InvalidRequestException $e) {
    echo 'Invalid parameter: ' . $e->getParam();

} catch (PaymentException $e) {
    switch ($e->getCode()) {
        case 'insufficient_funds':
            echo 'Please top up your account';
            break;
        case 'phone_unreachable':
            echo 'Please check your phone';
            break;
        default:
            echo $e->getMessage();
    }

} catch (RateLimitException $e) {
    sleep(60);
    // Retry

} catch (ApiException $e) {
    error_log('PayGate server error: ' . $e->getMessage());

} catch (PayGateException $e) {
    echo 'Error: ' . $e->getMessage();
}

Best Practices

1. Always Handle Errors

Never assume API calls will succeed:

// Bad - no error handling
const payment = await paygate.payments.create({...})

// Good - proper error handling
try {
  const payment = await paygate.payments.create({...})
} catch (error) {
  // Handle appropriately
}

2. Show Helpful Messages

Map error codes to user-friendly messages:

const errorMessages = {
  insufficient_funds: 'Your mobile money balance is too low. Please top up and try again.',
  phone_unreachable: 'We couldn\'t reach your phone. Make sure it\'s on and try again.',
  timeout: 'The request timed out. Please try again.',
  daily_limit_exceeded: 'You\'ve reached your daily limit. Try again tomorrow.'
}

function getErrorMessage(error) {
  return errorMessages[error.code] || 'Something went wrong. Please try again.'
}

3. Log Errors for Debugging

Log errors with context for debugging:

catch (error) {
  console.error('Payment failed', {
    type: error.type,
    code: error.code,
    message: error.message,
    param: error.param,
    requestId: error.requestId,
    orderId: order.id
  })
}

4. Retry Intelligently

Implement retries with exponential backoff for transient errors:

async function createPaymentWithRetry(params, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await paygate.payments.create(params)
    } catch (error) {
      if (error.type === 'api_error' && i < maxRetries - 1) {
        await sleep(Math.pow(2, i) * 1000) // 1s, 2s, 4s
        continue
      }
      throw error
    }
  }
}

Only retry on transient errors like api_error or rate_limit_error. Don't retry on invalid_request_error or payment_error.