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
| Field | Type | Description |
|---|---|---|
type | string | The type of error |
code | string | Specific error code |
message | string | Human-readable message |
param | string | Parameter that caused the error (if applicable) |
doc_url | string | Link to documentation about this error |
HTTP Status Codes
| Status | Description |
|---|---|
200 | OK - Request succeeded |
201 | Created - Resource created successfully |
400 | Bad Request - Invalid parameters |
401 | Unauthorized - Invalid API key |
403 | Forbidden - Not allowed to access resource |
404 | Not Found - Resource doesn't exist |
409 | Conflict - Request conflicts with current state |
422 | Unprocessable Entity - Request understood but cannot be processed |
429 | Too Many Requests - Rate limit exceeded |
500 | Internal 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
| Code | Description |
|---|---|
invalid_api_key | The API key is invalid |
expired_api_key | The API key has expired |
revoked_api_key | The API key was revoked |
test_key_on_live | Test key used on live endpoint |
live_key_on_test | Live key used on test endpoint |
Request Errors
| Code | Description |
|---|---|
parameter_missing | Required parameter is missing |
parameter_invalid | Parameter value is invalid |
parameter_unknown | Unknown parameter provided |
resource_not_found | Requested resource doesn't exist |
idempotency_error | Conflicting idempotency key |
Payment Errors
| Code | Description |
|---|---|
insufficient_funds | Account has insufficient funds |
transaction_declined | Transaction was declined |
phone_invalid | Phone number is invalid |
phone_unreachable | Phone is off or no network |
provider_unavailable | Payment provider is unavailable |
timeout | Transaction timed out |
invalid_pin | Customer entered wrong PIN |
daily_limit_exceeded | Daily transaction limit exceeded |
Amount Errors
| Code | Description |
|---|---|
amount_too_small | Below minimum (GHS 1.00) |
amount_too_large | Above maximum allowed |
invalid_currency | Currency not supported |
Rate Limit Errors
| Code | Description |
|---|---|
rate_limit_exceeded | Too 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.