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
| Code | Status | Description |
|---|---|---|
invalid_api_key | 401 | API key is invalid |
invalid_request | 400 | Request format is invalid |
payment_failed | 400 | Payment could not be processed |
card_declined | 400 | Card was declined |
insufficient_funds | 400 | Insufficient funds |
rate_limit_exceeded | 429 | Too many requests |
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
- Always catch errors - Never let errors crash your application
- Log everything - Log errors with context for debugging
- Show user-friendly messages - Don't expose technical details
- Implement retries - Retry transient failures automatically
- Monitor error rates - Set up alerts for high error rates
- 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
- API Errors Reference - Full error list
- Webhooks - Handle webhook errors
- Going Live - Production checklist