Skip to content

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

CodeDescriptionSolution
-32504Forbidden (invalid credentials)Check merchantId and secretKey

System Errors

CodeDescriptionSolution
-32400System errorRetry later or contact support

Transaction Errors

CodeDescriptionSolution
-31001Invalid amountCheck amount is positive integer in tiyin
-31003Transaction not foundVerify transaction ID
-31007Order fulfilled, cannot cancelCannot cancel delivered orders
-31008Cannot perform operationCheck transaction state

Account Errors

CodeDescriptionSolution
-31050Account not foundVerify account exists in your system
-31051Invalid accountCheck account data format
-31052Account blockedUnblock account or contact support
-31053Insufficient balanceCheck account balance
...Custom account errorsCheck your business logic
-31099Last 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

ErrorDescription
"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

  1. Always catch and handle errors
  2. Use specific error types for different handling
  3. Log errors with context
  4. Show user-friendly messages
  5. Implement retry logic for network errors
  6. Validate inputs before API calls
  7. Monitor error rates

❌ Don'ts

  1. Don't ignore errors
  2. Don't expose sensitive data in error messages
  3. Don't retry on validation errors
  4. Don't show technical errors to users
  5. Don't retry indefinitely
  6. 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

Released under MIT License.