Skip to content

Error Handling

Learn how to handle errors gracefully in your Salam Gateway integration.

Error Types

Salam Gateway uses standard HTTP status codes and returns structured error objects.

Common Error Codes

CodeStatusDescription
invalid_api_key401API key is invalid
invalid_request400Request format is invalid
payment_failed400Payment could not be processed
card_declined400Card was declined
insufficient_funds400Insufficient funds
rate_limit_exceeded429Too many requests

See full error reference

Handling Errors in SDK

typescript
import { Salam, ApiError, ValidationError, AuthError } from '@salamgateway/node';

try {
  const payment = await salam.payments.create({
    amount: 10000,
    success_url: 'https://example.com/success',
    cancel_url: 'https://example.com/cancel',
  });
} catch (error) {
  if (error instanceof AuthError) {
    // Invalid API key
    console.error('Authentication failed:', error.message);
  } else if (error instanceof ValidationError) {
    // Validation error
    console.error(`Validation error on ${error.param}:`, error.message);
  } else if (error instanceof ApiError) {
    // Other API error
    console.error(`API error (${error.statusCode}):`, error.message);
  }
}

User-Friendly Error Messages

Don't expose technical errors to users:

typescript
function getUserMessage(error: ApiError): string {
  switch (error.code) {
    case 'card_declined':
      return 'Your payment was declined. Please try a different card.';
    case 'insufficient_funds':
      return 'Insufficient funds. Please use a different payment method.';
    case 'fpx_unavailable':
      return 'Online banking is temporarily unavailable. Please try again later.';
    case 'payment_expired':
      return 'Payment session expired. Please start a new checkout.';
    default:
      return 'Unable to process payment. Please try again.';
  }
}

// Usage
try {
  await createPayment();
} catch (error) {
  showToast(getUserMessage(error));
}

Retry Logic

Implement exponential backoff for retries:

typescript
async function retryRequest<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error instanceof RateLimitError) {
        // Wait before retrying
        await sleep(error.retryAfter * 1000);
      } else if (i === maxRetries - 1) {
        throw error;
      } else {
        // Exponential backoff
        await sleep(Math.pow(2, i) * 1000);
      }
    }
  }
  throw new Error('Max retries exceeded');
}

// Usage
const payment = await retryRequest(() =>
  salam.payments.create(data)
);

Logging Errors

Log errors for debugging:

typescript
import winston from 'winston';

const logger = winston.createLogger({
  level: 'error',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log' }),
  ],
});

try {
  await salam.payments.create(data);
} catch (error) {
  logger.error('Payment creation failed', {
    code: error.code,
    message: error.message,
    statusCode: error.statusCode,
    timestamp: new Date().toISOString(),
  });
  
  throw error;
}

Webhook Error Handling

Handle webhook errors carefully:

typescript
app.post('/webhooks/salam', async (req, res) => {
  try {
    const event = salam.webhooks.constructEvent(
      req.body,
      req.headers['salam-signature']
    );
    
    // Return 200 immediately
    res.json({ received: true });
    
    // Process asynchronously
    try {
      await processWebhook(event);
    } catch (error) {
      // Log but don't fail the webhook
      logger.error('Webhook processing failed', { error, event });
      // Consider adding to retry queue
    }
  } catch (error) {
    // Signature verification failed
    res.status(400).send('Invalid signature');
  }
});

Handling Payment Failures

Show appropriate messages for payment failures:

typescript
app.post('/webhooks/salam', async (req, res) => {
  const event = salam.webhooks.constructEvent(req.body, signature);
  
  if (event.type === 'payment.failed') {
    const payment = event.data;
    
    // Notify customer
    await sendEmail(payment.metadata.customer_email, {
      subject: 'Payment Failed',
      body: `Your payment failed. Please try again or use a different payment method.`,
    });
    
    // Update order
    await db.orders.update(
      { payment_id: payment.id },
      { status: 'payment_failed' }
    );
  }
  
  res.json({ received: true });
});

Monitoring Errors

Set up monitoring for production:

typescript
import * as Sentry from '@sentry/node';

// Initialize Sentry
Sentry.init({
  dsn: process.env.SENTRY_DSN,
});

// Track errors
try {
  await salam.payments.create(data);
} catch (error) {
  Sentry.captureException(error, {
    tags: {
      type: 'payment_creation',
      code: error.code,
    },
  });
  
  throw error;
}

Best Practices

  1. Always catch errors - Never let errors crash your application
  2. Log everything - Log errors with context for debugging
  3. Show user-friendly messages - Don't expose technical details
  4. Implement retries - Retry transient failures automatically
  5. Monitor error rates - Set up alerts for high error rates
  6. Test error scenarios - Test all error paths in development

Testing Errors

Test error handling in development:

typescript
// Test invalid API key
try {
  const salam = new Salam({ apiKey: 'invalid_key' });
  await salam.payments.create(data);
} catch (error) {
  assert(error instanceof AuthError);
}

// Test validation error
try {
  await salam.payments.create({ amount: 50 }); // Too small
} catch (error) {
  assert(error instanceof ValidationError);
  assert(error.param === 'amount');
}

Next Steps

Released under the MIT License.