Error Handling
Complete guide to handling errors in @joyida/payme.
Error Types
The library provides four error classes:
typescript
import {
PaymeError, // Payme API errors
ValidationError, // Input validation errors
NetworkError, // Network/timeout errors
ParseError // JSON-RPC protocol errors
} from '@joyida/payme';PaymeError
Errors returned by Payme API.
Properties
typescript
class PaymeError extends Error {
code: number; // Error code
message: string; // Error message
data?: string | Record<string, unknown>; // Additional data
}Example
typescript
try {
await payme.createTransaction(params);
} catch (error) {
if (error instanceof PaymeError) {
console.error('Payme error:', error.code);
console.error('Message:', error.message);
console.error('Data:', error.data);
}
}Common Error Codes
Authentication Errors
| Code | Description | Solution |
|---|---|---|
-32504 | Forbidden (invalid credentials) | Check merchantId and secretKey |
System Errors
| Code | Description | Solution |
|---|---|---|
-32400 | System error | Retry later or contact support |
Transaction Errors
| Code | Description | Solution |
|---|---|---|
-31001 | Invalid amount | Check amount is positive integer in tiyin |
-31003 | Transaction not found | Verify transaction ID |
-31007 | Order fulfilled, cannot cancel | Cannot cancel delivered orders |
-31008 | Cannot perform operation | Check transaction state |
Account Errors
| Code | Description | Solution |
|---|---|---|
-31050 | Account not found | Verify account exists in your system |
-31051 | Invalid account | Check account data format |
-31052 | Account blocked | Unblock account or contact support |
-31053 | Insufficient balance | Check account balance |
| ... | Custom account errors | Check your business logic |
-31099 | Last account error code | - |
Handling Specific Errors
typescript
import { PaymeError } from '@joyida/payme';
try {
await payme.createTransaction(params);
} catch (error) {
if (error instanceof PaymeError) {
switch (error.code) {
case -32504:
console.error('Invalid credentials');
// Update credentials
break;
case -31001:
console.error('Invalid amount');
// Validate amount
break;
case -31003:
console.error('Transaction not found');
// Check transaction ID
break;
case -31007:
console.error('Cannot cancel fulfilled order');
// Inform user
break;
case -31008:
console.error('Invalid transaction state');
// Check state before operation
break;
case -31050:
console.error('Account not found');
// Verify account exists
break;
default:
console.error('Unknown error:', error.code);
}
}
}ValidationError
Input validation errors (before API call).
Properties
typescript
class ValidationError extends Error {
message: string; // Validation error message
}Example
typescript
import { ValidationError } from '@joyida/payme';
try {
await payme.createTransaction({
id: 'invalid', // Too short
time: Date.now(),
amount: -100, // Negative
account: {}
});
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation error:', error.message);
// Show error to user
}
}Common Validation Errors
| Error | Description |
|---|---|
| "Amount must be a positive integer" | Amount is negative or not integer |
| "Card number must be 13-19 digits" | Invalid card number length |
| "Invalid card expiry format" | Expiry not in MMYY format |
| "Card has expired" | Expiry date is in the past |
| "Invalid phone number format" | Phone not in 998XXXXXXXXX format |
| "Payme ID must be 24 characters" | Transaction ID wrong length |
| "Timestamp must be 13 digits" | Invalid Unix timestamp |
| "Account must be a non-empty object" | Empty account object |
| "Token must be a non-empty string" | Empty token |
Preventing Validation Errors
typescript
import { Validator } from '@joyida/payme';
// Validate before API call
try {
Validator.validateAmount(500000);
Validator.validateCardNumber('8600069195406311');
Validator.validateCardExpiry('0399');
Validator.validatePhone('998901234567');
Validator.validatePaymeId('5305e3bab097f420a62ced0b');
Validator.validateTimestamp(Date.now());
Validator.validateAccount({ order_id: '123' });
Validator.validateToken('card_token_here');
// All valid, proceed with API call
await payme.createTransaction(params);
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
}
}NetworkError
Network or timeout errors.
Properties
typescript
class NetworkError extends Error {
message: string; // Error message
timeout?: number; // Timeout value (if timeout error)
cause?: unknown; // Original error
}Example
typescript
import { NetworkError } from '@joyida/payme';
try {
await payme.createTransaction(params);
} catch (error) {
if (error instanceof NetworkError) {
console.error('Network error:', error.message);
if (error.timeout) {
console.error(`Request timed out after ${error.timeout}ms`);
// Retry with longer timeout
}
}
}Handling Network Errors
typescript
async function createTransactionWithRetry(
params: CreateTransactionParams,
maxRetries = 3
) {
for (let i = 0; i < maxRetries; i++) {
try {
return await payme.createTransaction(params);
} catch (error) {
if (error instanceof NetworkError) {
console.log(`Retry ${i + 1}/${maxRetries}`);
if (i === maxRetries - 1) {
throw error; // Last retry failed
}
// Wait before retry (exponential backoff)
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
} else {
throw error; // Not a network error, don't retry
}
}
}
}ParseError
JSON-RPC protocol errors.
Properties
typescript
class ParseError extends Error {
message: string; // Parse error message
}Example
typescript
import { ParseError } from '@joyida/payme';
try {
await payme.createTransaction(params);
} catch (error) {
if (error instanceof ParseError) {
console.error('Protocol error:', error.message);
// This usually indicates a bug or API change
}
}Complete Error Handling Example
typescript
import {
PaymeMerchant,
PaymeError,
ValidationError,
NetworkError,
ParseError
} from '@joyida/payme';
const payme = new PaymeMerchant({
merchantId: process.env.PAYME_MERCHANT_ID!,
secretKey: process.env.PAYME_SECRET_KEY!,
});
async function processPayment(orderId: string, amount: number) {
try {
// Create transaction
const result = await payme.createTransaction({
id: generatePaymeId(),
time: Date.now(),
amount,
account: { order_id: orderId }
});
console.log('✅ Transaction created:', result.transaction);
return result;
} catch (error) {
// Handle different error types
if (error instanceof ValidationError) {
// Input validation error
console.error('❌ Validation error:', error.message);
throw new Error(`Invalid input: ${error.message}`);
} else if (error instanceof PaymeError) {
// Payme API error
console.error('❌ Payme error:', error.code, error.message);
switch (error.code) {
case -32504:
throw new Error('Payment system configuration error');
case -31001:
throw new Error('Invalid payment amount');
case -31003:
throw new Error('Transaction not found');
case -31007:
throw new Error('Cannot cancel completed order');
case -31008:
throw new Error('Invalid transaction state');
case -31050:
throw new Error('Order not found');
case -32400:
throw new Error('Payment system temporarily unavailable');
default:
throw new Error(`Payment error: ${error.message}`);
}
} else if (error instanceof NetworkError) {
// Network or timeout error
console.error('❌ Network error:', error.message);
if (error.timeout) {
throw new Error(`Request timed out after ${error.timeout}ms`);
} else {
throw new Error('Network connection error');
}
} else if (error instanceof ParseError) {
// Protocol error
console.error('❌ Protocol error:', error.message);
throw new Error('Payment system protocol error');
} else {
// Unknown error
console.error('❌ Unknown error:', error);
throw new Error('Unexpected error occurred');
}
}
}Error Logging
Basic Logging
typescript
import { PaymeError } from '@joyida/payme';
try {
await payme.createTransaction(params);
} catch (error) {
if (error instanceof PaymeError) {
// Log to console
console.error({
timestamp: new Date().toISOString(),
type: 'PaymeError',
code: error.code,
message: error.message,
data: error.data
});
}
}Advanced Logging
typescript
import { PaymeError, ValidationError, NetworkError } from '@joyida/payme';
function logError(error: unknown, context: Record<string, unknown>) {
const logEntry = {
timestamp: new Date().toISOString(),
context,
error: {
type: error.constructor.name,
message: error.message
}
};
if (error instanceof PaymeError) {
logEntry.error = {
...logEntry.error,
code: error.code,
data: error.data
};
} else if (error instanceof NetworkError) {
logEntry.error = {
...logEntry.error,
timeout: error.timeout
};
}
// Send to logging service
console.error(JSON.stringify(logEntry));
}
// Usage
try {
await payme.createTransaction(params);
} catch (error) {
logError(error, {
operation: 'createTransaction',
orderId: params.account.order_id,
amount: params.amount
});
throw error;
}Best Practices
✅ Do's
- Always catch and handle errors
- Use specific error types for different handling
- Log errors with context
- Show user-friendly messages
- Implement retry logic for network errors
- Validate inputs before API calls
- Monitor error rates
❌ Don'ts
- Don't ignore errors
- Don't expose sensitive data in error messages
- Don't retry on validation errors
- Don't show technical errors to users
- Don't retry indefinitely
- Don't log sensitive data (card numbers, tokens)
Error Monitoring
Sentry Integration
typescript
import * as Sentry from '@sentry/node';
import { PaymeError } from '@joyida/payme';
try {
await payme.createTransaction(params);
} catch (error) {
if (error instanceof PaymeError) {
Sentry.captureException(error, {
tags: {
error_code: error.code,
error_type: 'PaymeError'
},
extra: {
data: error.data
}
});
}
throw error;
}Next Steps
- Learn about Validation
- Check Merchant API Reference
- Explore Subscribe API Reference
- See Complete Examples