Error Handling in mpesajs
The mpesajs SDK implements a comprehensive error handling system designed to help developers handle various error scenarios that may occur during M-Pesa API interactions. This guide explains the error handling architecture and provides examples of how to handle different types of errors effectively.
Error Hierarchy
All errors in the SDK inherit from the base MpesaError
class, which extends the native JavaScript Error
class. Here’s the error hierarchy:
MpesaError
├── AuthenticationError
├── NetworkError
├── ValidationError
├── StkPushError
├── PayoutError
└── RegisterUrlError
Error Types in Detail
MpesaError
The base error class that all other SDK errors extend from. Use this to catch any M-Pesa related error.
class MpesaError extends Error {
name: 'MpesaError';
message: string;
}
AuthenticationError
Thrown during authentication failures with the M-Pesa API. Common when there are issues with credentials or token generation.
class AuthenticationError extends MpesaError {
name: 'AuthenticationError';
errorCode?: string;
}
Common error codes and their meanings:
999991
: Invalid client ID - Check your consumer key999996
: Invalid authentication type - Ensure Basic Auth is selected999997
: Invalid authorization header - Check your consumer secret999998
: Invalid grant type - Must be “client_credentials”
NetworkError
Represents network-related issues when communicating with the M-Pesa API.
class NetworkError extends MpesaError {
name: 'NetworkError';
}
Common scenarios:
- API timeout
- No internet connection
- DNS resolution failures
- CORS issues in browser environments
- SSL/TLS handshake failures
ValidationError
Occurs when input validation fails for API requests. Includes field-specific validation details.
class ValidationError extends MpesaError {
name: 'ValidationError';
field?: string;
}
Common validation rules:
- Phone numbers must match pattern: ^251[7-9][0-9]8$
- Amount must be greater than 0
- Callback URLs must use HTTPS
- Reference must not exceed 12 characters
- Description must not exceed 13 characters
StkPushError
Specific to STK Push transaction failures. Includes detailed transaction identifiers.
class StkPushError extends MpesaError {
name: 'StkPushError';
responseCode?: string;
merchantRequestId?: string;
checkoutRequestId?: string;
}
Common error codes:
TP40087
: Wrong M-PESA PIN entered17
: M-PESA system internal error1037
: Timeout waiting for user input1032
: Transaction canceled by user1001
: Insufficient funds
PayoutError
Handles B2C payment-related errors. Includes comprehensive transaction tracking details.
class PayoutError extends MpesaError {
name: 'PayoutError';
errorCode?: string;
requestId?: string;
responseCode?: string;
conversationId?: string;
}
Common scenarios:
- Insufficient funds in business account
- Invalid recipient number
- Daily transaction limit exceeded
- Invalid initiator credentials
- System downtime
RegisterUrlError
Specific to URL registration failures. Includes shortcode and response details.
class RegisterUrlError extends MpesaError {
name: 'RegisterUrlError';
responseCode?: string;
shortCode?: string;
}
Common issues:
- Invalid URL format
- Non-HTTPS URLs
- Unauthorized shortcode
- Invalid security credentials
Comprehensive Error Handling Strategies
1. Input Validation
Always validate input before making API calls to prevent unnecessary network requests:
function validatePaymentDetails(details: PaymentDetails): void {
// Validate amount
if (details.amount <= 0) {
throw new ValidationError('Amount must be greater than 0', 'amount');
}
// Validate phone number format
if (!/^251[7-9][0-9]{8}$/.test(details.phoneNumber)) {
throw new ValidationError(
'Invalid phone number format. Must start with 251 and be 12 digits long',
'phoneNumber'
);
}
// Validate callback URL
if (!details.callbackUrl.startsWith('https://')) {
throw new ValidationError('Callback URL must use HTTPS protocol', 'callbackUrl');
}
// Validate reference and description length
if (details.reference.length > 12) {
throw new ValidationError('Reference must not exceed 12 characters', 'reference');
}
if (details.description.length > 13) {
throw new ValidationError('Description must not exceed 13 characters', 'description');
}
}
2. Type-Specific Error Handling
Implement comprehensive error handling for different scenarios:
try {
await mpesa.stkPush.sendStkPush({
// ... parameters
});
} catch (error) {
// Handle authentication errors
if (error instanceof AuthenticationError) {
console.error('Authentication failed:', {
name: error.name,
message: error.message,
errorCode: error.errorCode
});
// Implement token refresh or credential validation
}
// Handle STK Push specific errors
else if (error instanceof StkPushError) {
console.error('STK Push failed:', {
message: error.message,
responseCode: error.responseCode,
merchantRequestId: error.merchantRequestId,
checkoutRequestId: error.checkoutRequestId
});
// Implement user feedback and retry logic
}
// Handle network errors with retry
else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
await retryWithExponentialBackoff(operation);
}
// Handle validation errors
else if (error instanceof ValidationError) {
console.error('Validation failed:', {
field: error.field,
message: error.message
});
// Update UI to show validation errors
}
// Handle unexpected errors
else {
console.error('Unexpected error:', error);
// Log to monitoring service
}
}
3. Retry Mechanism with Exponential Backoff
Implement smart retry logic for transient failures:
async function retryWithExponentialBackoff<T>(
operation: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
let attempts = 0;
while (attempts < maxRetries) {
try {
return await operation();
} catch (error) {
attempts++;
if (attempts === maxRetries) {
throw error;
}
if (error instanceof NetworkError ||
(error instanceof StkPushError && error.responseCode === '17')) {
const delay = baseDelay * Math.pow(2, attempts - 1);
const jitter = Math.random() * 100;
await new Promise(resolve => setTimeout(resolve, delay + jitter));
continue;
}
throw error;
}
}
throw new Error('Maximum retry attempts reached');
}
4. Structured Error Logging
Implement detailed error logging for debugging and monitoring:
interface ErrorLog {
timestamp: string;
errorType: string;
message: string;
details: Record<string, any>;
stack?: string;
}
function logError(error: Error): ErrorLog {
const log: ErrorLog = {
timestamp: new Date().toISOString(),
errorType: error.constructor.name,
message: error.message,
details: {},
};
if (error instanceof StkPushError) {
log.details = {
responseCode: error.responseCode,
merchantRequestId: error.merchantRequestId,
checkoutRequestId: error.checkoutRequestId
};
} else if (error instanceof PayoutError) {
log.details = {
errorCode: error.errorCode,
requestId: error.requestId,
responseCode: error.responseCode,
conversationId: error.conversationId
};
} else if (error instanceof ValidationError) {
log.details = {
field: error.field
};
}
if (process.env.NODE_ENV === 'development') {
log.stack = error.stack;
}
// Log to your preferred logging service
console.error('Error occurred:', log);
return log;
}
Real-World Implementation Examples
1. STK Push Implementation
async function initiatePayment(paymentDetails: PaymentDetails): Promise<void> {
try {
// Validate input first
validatePaymentDetails(paymentDetails);
const auth = new Auth();
const mpesa = new StkPush(auth);
const response = await mpesa.sendStkPush(
config.businessShortCode,
config.passkey,
paymentDetails.amount,
paymentDetails.phoneNumber,
paymentDetails.callbackUrl,
paymentDetails.reference,
paymentDetails.description
);
console.log('Payment initiated:', {
MerchantRequestID: response.MerchantRequestID,
CheckoutRequestID: response.CheckoutRequestID,
ResponseDescription: response.ResponseDescription
});
} catch (error) {
logError(error);
handlePaymentError(error);
}
}
2. B2C Payout Implementation
async function processPayout(amount: number, phone: string): Promise<void> {
try {
const auth = new Auth();
const payout = new Payout(auth);
const response = await payout.send(amount, phone);
console.log('Payout successful:', {
ConversationID: response.ConversationID,
ResponseDescription: response.ResponseDescription
});
} catch (error) {
if (error instanceof PayoutError) {
switch(error.responseCode) {
case 'A001':
console.error('Insufficient funds in business account');
// Notify finance team
break;
case 'A002':
console.error('Invalid recipient number');
// Notify user to check number
break;
default:
console.error('Payout failed:', error.message);
}
}
logError(error);
throw error;
}
}
3. URL Registration Implementation
async function setupCallbackUrls(): Promise<void> {
try {
const auth = new Auth();
const register = new RegisterUrl();
const response = await register.register();
console.log('URLs registered:', {
responseCode: response.responseCode,
responseMessage: response.responseMessage
});
} catch (error) {
if (error instanceof RegisterUrlError) {
console.error('URL registration failed:', {
responseCode: error.responseCode,
shortCode: error.shortCode
});
// Implement specific handling based on response code
}
logError(error);
throw error;
}
}
Error Prevention Best Practices
-
Input Validation
- Validate all input data before making API calls
- Use strong typing with TypeScript
- Implement comprehensive validation rules
-
Security
- Keep credentials secure and never expose them in client-side code
- Use environment variables for sensitive data
- Implement proper access control
-
Monitoring and Logging
- Implement comprehensive error logging
- Use structured logging format
- Monitor error patterns and rates
- Set up alerts for critical errors
-
Resilience
- Implement retry mechanisms for transient failures
- Use circuit breakers for dependent services
- Handle rate limiting appropriately
- Implement proper timeout handling
-
Testing
- Write unit tests for error scenarios
- Implement integration tests
- Test error handling logic
- Simulate various error conditions
-
Documentation
- Document error handling procedures
- Maintain error code reference
- Update documentation regularly
- Include examples and solutions
Conclusion
The mpesajs SDK’s error handling system is designed to help you build robust applications that can gracefully handle various error scenarios. By following these best practices and implementing proper error handling, you can create a better user experience and maintain a more stable application.
Remember to:
- Always validate input data
- Implement proper error handling for each error type
- Use structured logging
- Implement retry mechanisms where appropriate
- Keep security in mind
- Monitor and analyze errors
- Test error handling thoroughly
For more examples and detailed implementation guides, check out the examples directory in the repository.