Skip to content

ADAMSmugwe/mpesa-API-wrapper-for-node.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚀 Adams MPesa SDK for Node.js

Modern, Production-Ready MPesa API Integration for Node.js & TypeScript

npm version npm downloads npm bundle size Node.js Version TypeScript License: MIT Build Status Tests

FeaturesInstallationQuick StartDocumentationExamplesContributing


📖 Description

Adams MPesa SDK is a comprehensive, developer-friendly Node.js library for integrating Safaricom's MPesa Daraja API into your applications. Built with TypeScript and designed for production use, it provides a clean, intuitive interface for all MPesa payment operations.

Whether you're building an e-commerce platform, a SaaS application, or a mobile money service in Kenya, this SDK handles the complexity of MPesa integration so you can focus on building your product.

Why Choose This SDK?

  • Type-Safe: Full TypeScript support with comprehensive type definitions
  • Production-Ready: Robust error handling, automatic retries, and token caching
  • Developer-Friendly: Clean API, extensive documentation, and working examples
  • Well-Tested: 49 passing unit tests with comprehensive coverage
  • Modern: Uses latest Node.js features and best practices
  • Zero Config: Sensible defaults that work out of the box

✨ Features

Core Functionality

  • 🔐 OAuth Token Management - Automatic generation, caching, and refresh
  • 💳 STK Push (Lipa Na M-Pesa Online) - Initiate and query customer payments
  • 💰 C2B Payments - URL registration and payment simulation
  • 💸 B2C Payments - Send money to customers (salary, refunds, etc.)
  • 📊 Transaction Status - Query any transaction status

Developer Experience

  • Full TypeScript Support - Complete type definitions for all APIs
  • 🔄 Automatic Retries - Exponential backoff for failed requests
  • Input Validation - Phone numbers, amounts, URLs validated automatically
  • 🎯 Custom Error Types - Detailed, actionable error messages
  • 📱 Phone Number Formatting - Supports multiple formats (0712..., +254..., 254...)
  • 🌍 Environment Support - Seamless switching between sandbox and production

Production Features

  • Token Caching - Minimizes unnecessary API calls
  • 🔒 Secure - Follows security best practices
  • 📝 Comprehensive Logging - Debug-friendly error messages
  • 🧪 Fully Tested - 49 unit tests ensuring reliability
  • 📦 Dual Module System - Supports both CommonJS and ES Modules

📦 Installation

npm install adams-mpesa-sdk

Or using yarn:

yarn add adams-mpesa-sdk

Requirements

  • Node.js >= 16.0.0
  • npm or yarn
  • Safaricom Daraja API credentials (Get them here)

🚀 Quick Start

1. Get Your Credentials

Before you begin, sign up at Safaricom Developer Portal and create an app to get:

  • Consumer Key
  • Consumer Secret
  • Business Shortcode
  • Lipa Na M-Pesa Online Passkey

2. Set Up Environment Variables

Create a .env file in your project root:

MPESA_CONSUMER_KEY=your_consumer_key_here
MPESA_CONSUMER_SECRET=your_consumer_secret_here
MPESA_SHORTCODE=174379
MPESA_PASSKEY=your_passkey_here
MPESA_ENVIRONMENT=sandbox

3. Initialize the SDK

TypeScript

import Mpesa from 'adams-mpesa-sdk';
import 'dotenv/config';

const mpesa = new Mpesa({
  consumerKey: process.env.MPESA_CONSUMER_KEY!,
  consumerSecret: process.env.MPESA_CONSUMER_SECRET!,
  shortcode: process.env.MPESA_SHORTCODE!,
  passkey: process.env.MPESA_PASSKEY!,
  environment: 'sandbox', // or 'production'
});

JavaScript (CommonJS)

const Mpesa = require('adams-mpesa-sdk').default;
require('dotenv/config');

const mpesa = new Mpesa({
  consumerKey: process.env.MPESA_CONSUMER_KEY,
  consumerSecret: process.env.MPESA_CONSUMER_SECRET,
  shortcode: process.env.MPESA_SHORTCODE,
  passkey: process.env.MPESA_PASSKEY,
  environment: 'sandbox',
});

💡 Usage Examples

STK Push (Lipa Na M-Pesa Online)

Prompt customers to pay via MPesa:

async function initiatePayment() {
  try {
    const response = await mpesa.stkPush({
      amount: 100,
      phone: '254712345678', // Supports multiple formats
      accountReference: 'ORDER-123',
      transactionDesc: 'Payment for Order #123',
      callbackUrl: 'https://yourdomain.com/api/mpesa/callback',
    });

    console.log('Payment initiated:', response.CheckoutRequestID);
    // Customer will receive MPesa prompt on their phone
  } catch (error) {
    console.error('Payment failed:', error.message);
  }
}

Query STK Push Status

Check the status of a payment request:

async function checkPaymentStatus(checkoutRequestId: string) {
  try {
    const response = await mpesa.stkQuery({
      checkoutRequestId: checkoutRequestId,
    });

    if (response.ResultCode === '0') {
      console.log('Payment successful!');
    } else {
      console.log('Payment failed:', response.ResultDesc);
    }
  } catch (error) {
    console.error('Query failed:', error.message);
  }
}

B2C Payment (Send Money to Customers)

Send money for salaries, refunds, or promotions:

async function sendMoney() {
  try {
    const response = await mpesa.b2c({
      amount: 500,
      phone: '254712345678',
      commandId: 'BusinessPayment', // or 'SalaryPayment', 'PromotionPayment'
      occasion: 'Salary Payment',
      remarks: 'Monthly salary',
      resultUrl: 'https://yourdomain.com/api/mpesa/b2c/result',
      timeoutUrl: 'https://yourdomain.com/api/mpesa/b2c/timeout',
    });

    console.log('Transfer initiated:', response.ConversationID);
  } catch (error) {
    console.error('Transfer failed:', error.message);
  }
}

C2B URL Registration

Register URLs for receiving customer payments:

async function registerC2BUrls() {
  try {
    const response = await mpesa.c2bRegister({
      validationUrl: 'https://yourdomain.com/api/mpesa/validation',
      confirmationUrl: 'https://yourdomain.com/api/mpesa/confirmation',
      responseType: 'Completed', // or 'Cancelled'
    });

    console.log('URLs registered successfully');
  } catch (error) {
    console.error('Registration failed:', error.message);
  }
}

For more examples, see the examples.ts file.


⚙️ Configuration

Required Configuration

Parameter Type Description
consumerKey string Consumer Key from Daraja Portal
consumerSecret string Consumer Secret from Daraja Portal
shortcode string Your business shortcode (Paybill or Till)
passkey string Lipa Na M-Pesa Online Passkey
environment 'sandbox' | 'production' API environment

Optional Configuration

Parameter Type Default Description
initiatorName string - Required for B2C and Transaction Status
securityCredential string - Required for B2C and Transaction Status
autoRefreshToken boolean true Automatically refresh expired tokens
maxRetries number 3 Maximum retry attempts for failed requests
timeout number 30000 Request timeout in milliseconds

✅ Input Validation

The SDK provides comprehensive input validation to ensure data integrity before making API calls:

Phone Number Validation

import { validateKenyanPhoneNumber, formatPhoneNumber } from 'adams-mpesa-sdk';

const isValid = validateKenyanPhoneNumber('254712345678');

const formattedPhone = formatPhoneNumber('0712345678');

Supported Phone Formats:

  • 254712345678
  • 0712345678
  • 712345678
  • +254712345678
  • 254-712-345-678

Amount Validation

import { validatePositiveAmount } from 'adams-mpesa-sdk';

validatePositiveAmount(100, 'payment amount');

Rules:

  • Must be a positive number
  • Must be a whole number (no decimals)
  • Must be greater than 0

Shortcode Validation

import { validateShortcode, isValidShortcode } from 'adams-mpesa-sdk';

validateShortcode('174379');

const valid = isValidShortcode('174379');

Rules:

  • Must be 5-7 digits
  • Must contain only numbers

Required Fields Validation

import { validateRequiredFields } from 'adams-mpesa-sdk';

const paymentData = {
  amount: 100,
  phone: '254712345678',
  accountReference: 'ORDER-123',
};

validateRequiredFields(paymentData, ['amount', 'phone', 'accountReference']);

All validation errors throw descriptive ValidationError with clear messages about what went wrong.


🔄 Retry Logic Explained

The SDK includes intelligent retry logic for handling Safaricom API downtime and network issues:

How It Works

  1. Automatic Retries: Failed requests are automatically retried up to 3 times (configurable)
  2. Exponential Backoff: Delays between retries increase exponentially to avoid overwhelming the API
  3. Smart Retry: Only retries on errors that might succeed on retry (network errors, 5xx errors, rate limits)

Retry Sequence

Request 1 → Fail → Wait 1000ms (1s)
Request 2 → Fail → Wait 2000ms (2s)
Request 3 → Fail → Wait 4000ms (4s)
Request 4 → Fail → Throw MaxRetriesExceededError

Configuration

const mpesa = new Mpesa({
  consumerKey: 'your-key',
  consumerSecret: 'your-secret',
  shortcode: '174379',
  passkey: 'your-passkey',
  environment: 'sandbox',
  maxRetries: 3,
  timeout: 30000,
});

Errors That Trigger Retries

  • Network errors (ECONNREFUSED, ETIMEDOUT)
  • HTTP 5xx errors (server errors)
  • HTTP 429 (rate limiting)
  • Timeout errors

Errors That Don't Trigger Retries

  • HTTP 4xx errors (client errors like invalid credentials)
  • Validation errors
  • Authentication failures

🎯 Using the Callback Middleware

The SDK includes built-in Express middleware to handle MPesa callbacks easily:

Basic Usage

import express from 'express';
import { mpesaCallbackMiddleware, MpesaRequest } from 'adams-mpesa-sdk';

const app = express();
app.use(express.json());

app.post(
  '/api/mpesa/callback',
  mpesaCallbackMiddleware(),
  (req: MpesaRequest, res) => {
    const { mpesa } = req;
    
    if (mpesa?.ResultCode === 0) {
      console.log('✅ Payment successful!');
      console.log('Amount:', mpesa.metadata?.Amount);
      console.log('Receipt:', mpesa.metadata?.MpesaReceiptNumber);
      console.log('Phone:', mpesa.metadata?.PhoneNumber);
    } else {
      console.log('❌ Payment failed:', mpesa?.ResultDesc);
    }
    
    res.json({ ResultCode: 0, ResultDesc: 'Success' });
  }
);

app.listen(3000);

With Signature Verification (Recommended for Production)

app.post(
  '/api/mpesa/callback',
  mpesaCallbackMiddleware({
    verifySignature: true,
    secretKey: process.env.MPESA_SECRET_KEY!,
    signatureHeader: 'x-mpesa-signature',
  }),
  (req: MpesaRequest, res) => {
    res.json({ ResultCode: 0, ResultDesc: 'Success' });
  }
);

C2B Callback Middleware

import { c2bCallbackMiddleware, MpesaRequest } from 'adams-mpesa-sdk';

app.post(
  '/api/mpesa/c2b-confirmation',
  c2bCallbackMiddleware(),
  (req: MpesaRequest, res) => {
    const { metadata } = req.mpesa!;
    
    console.log('Transaction ID:', metadata?.TransID);
    console.log('Amount:', metadata?.TransAmount);
    console.log('Phone:', metadata?.MSISDN);
    
    res.json({ ResultCode: 0, ResultDesc: 'Success' });
  }
);

Available Callback Data

The middleware attaches req.mpesa with:

{
  MerchantRequestID: string;
  CheckoutRequestID: string;
  ResultCode: number;
  ResultDesc: string;
  CallbackMetadata?: {
    Item: Array<{ Name: string; Value: string | number }>;
  };
  metadata?: {
    Amount: number;
    MpesaReceiptNumber: string;
    PhoneNumber: string;
    TransactionDate: string;
  };
}

🔐 Security Best Practices

1. Callback Signature Verification

Always verify callbacks in production to prevent unauthorized requests:

import { verifyCallbackSignature } from 'adams-mpesa-sdk';

const payload = JSON.stringify(req.body);
const signature = req.headers['x-mpesa-signature'];
const secretKey = process.env.MPESA_SECRET_KEY!;

const isValid = verifyCallbackSignature(payload, signature, secretKey);

if (!isValid) {
  return res.status(401).json({ error: 'Invalid signature' });
}

2. Environment Variables

Never hardcode credentials. Always use environment variables:

// ❌ Bad
const mpesa = new Mpesa({
  consumerKey: 'abc123',
  consumerSecret: 'xyz789',
  ...
});

// ✅ Good
const mpesa = new Mpesa({
  consumerKey: process.env.MPESA_CONSUMER_KEY!,
  consumerSecret: process.env.MPESA_CONSUMER_SECRET!,
  ...
});

3. HTTPS for Callbacks

MPesa requires HTTPS URLs for production callbacks:

const callbackUrl = process.env.NODE_ENV === 'production'
  ? 'https://yourdomain.com/callback'
  : 'https://your-ngrok-url.ngrok.io/callback';

4. Rate Limiting

Implement rate limiting on your callback endpoints:

import rateLimit from 'express-rate-limit';

const callbackLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
});

app.post('/api/mpesa/callback', callbackLimiter, ...);

5. Input Sanitization

Always validate and sanitize user inputs:

import { validateRequiredFields, validatePositiveAmount } from 'adams-mpesa-sdk';

validateRequiredFields(req.body, ['amount', 'phone', 'reference']);
validatePositiveAmount(req.body.amount);

6. Error Logging

Log errors securely without exposing sensitive data:

import { sanitizeForLogging } from 'adams-mpesa-sdk';

const safeData = sanitizeForLogging({
  consumerKey: 'secret-key',
  amount: 100,
  phone: '254712345678',
});

console.log(safeData);

7. Token Security

The SDK handles token caching securely in memory. Never store tokens in:

  • Local storage (browser)
  • Plain text files
  • Version control (git)
  • Database without encryption

🎯 Callback / Webhook Handling

MPesa sends callbacks to your server for async operations. Here's a complete Express.js example:

import express from 'express';
import Mpesa from 'adams-mpesa-sdk';

const app = express();
app.use(express.json());

// STK Push Callback
app.post('/api/mpesa/callback', (req, res) => {
  const { Body } = req.body;
  const { stkCallback } = Body;

  if (stkCallback.ResultCode === 0) {
    // Payment successful
    const { CallbackMetadata } = stkCallback;
    const amount = CallbackMetadata.Item.find((item: any) => item.Name === 'Amount')?.Value;
    const mpesaRef = CallbackMetadata.Item.find((item: any) => item.Name === 'MpesaReceiptNumber')?.Value;
    
    console.log(`Payment of ${amount}. Ref: ${mpesaRef}`);
  } else {
    console.log('Payment failed:', stkCallback.ResultDesc);
  }

  res.json({ ResultCode: 0, ResultDesc: 'Success' });
});

app.listen(3000, () => console.log('Server running'));

🚨 Error Handling

The SDK provides detailed error types:

import {
  MpesaError,
  MpesaAuthError,
  MpesaResponseError,
  ValidationError,
  InvalidPhoneNumberError,
} from 'adams-mpesa-sdk';

try {
  await mpesa.stkPush({...});
} catch (error) {
  if (error instanceof InvalidPhoneNumberError) {
    console.error('Invalid phone number format');
  } else if (error instanceof MpesaAuthError) {
    console.error('Authentication failed');
  } else if (error instanceof MpesaResponseError) {
    console.error('MPesa API error:', error.message);
  }
}

Common Errors and Solutions

Error Cause Solution
Invalid Access Token App not subscribed to API Subscribe to the API product in Daraja Portal
Invalid Shortcode Wrong business shortcode Verify your shortcode in Daraja Portal
Invalid Phone Number Wrong phone format Use format: 254XXXXXXXXX

📚 Documentation

For more detailed information:


🧪 Testing Guide

Testing With Safaricom Sandbox

The SDK works seamlessly with both sandbox and production environments. Here's how to test without a real phone:

1. Get Sandbox Credentials

  1. Visit Safaricom Developer Portal
  2. Create an account and log in
  3. Create a new app
  4. Subscribe to the following APIs:
    • Lipa Na M-Pesa Online (for STK Push)
    • M-Pesa C2B (for Customer to Business)
    • M-Pesa B2C (for Business to Customer)
  5. Get your credentials from the app dashboard:
    • Consumer Key
    • Consumer Secret
    • Test Credentials (shortcode, passkey, etc.)

2. Sandbox Test Credentials

Safaricom provides these test credentials for sandbox:

MPESA_CONSUMER_KEY=your_app_consumer_key
MPESA_CONSUMER_SECRET=your_app_consumer_secret
MPESA_SHORTCODE=174379
MPESA_PASSKEY=bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919
MPESA_ENVIRONMENT=sandbox

3. Testing STK Push

import Mpesa from 'adams-mpesa-sdk';

const mpesa = new Mpesa({
  consumerKey: process.env.MPESA_CONSUMER_KEY!,
  consumerSecret: process.env.MPESA_CONSUMER_SECRET!,
  shortcode: '174379', // Sandbox shortcode
  passkey: 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',
  environment: 'sandbox',
});

// Test STK Push
const response = await mpesa.stkPush({
  amount: 1,
  phone: '254708374149', // Use any Kenyan number in sandbox
  accountReference: 'TEST-001',
  transactionDesc: 'Test Payment',
});

console.log('Checkout Request ID:', response.CheckoutRequestID);

4. Simulating Payment in Sandbox

In sandbox mode, you can simulate payment responses without a real phone:

Option A: Use the C2B Simulator

  1. Go to Safaricom Developer Portal
  2. Navigate to APIs → M-Pesa Sandbox
  3. Use the "C2B Simulate Transaction" tool

Option B: Programmatically simulate

// Simulate C2B payment
await mpesa.c2bSimulate({
  amount: 100,
  phone: '254708374149',
  billRefNumber: 'TEST-REF',
  commandId: 'CustomerPayBillOnline',
});

5. Testing Callbacks Locally

Use ngrok to expose your local server for callback testing:

# Install ngrok
npm install -g ngrok

# Start your local server
npm start

# In another terminal, start ngrok
ngrok http 3000

Update your .env with the ngrok URL:

MPESA_CALLBACK_URL=https://your-ngrok-url.ngrok.io/api/mpesa/callback

6. Unit Testing Your Integration

Example test using Jest:

import Mpesa from 'adams-mpesa-sdk';

describe('MPesa Integration', () => {
  let mpesa: Mpesa;

  beforeAll(() => {
    mpesa = new Mpesa({
      consumerKey: process.env.MPESA_CONSUMER_KEY!,
      consumerSecret: process.env.MPESA_CONSUMER_SECRET!,
      shortcode: process.env.MPESA_SHORTCODE!,
      passkey: process.env.MPESA_PASSKEY!,
      environment: 'sandbox',
    });
  });

  it('should generate access token', async () => {
    const token = await mpesa.getAccessToken();
    expect(token).toBeDefined();
    expect(token.length).toBeGreaterThan(0);
  });

  it('should initiate STK push', async () => {
    const response = await mpesa.stkPush({
      amount: 1,
      phone: '254708374149',
      accountReference: 'TEST',
      transactionDesc: 'Test',
    });

    expect(response.ResponseCode).toBe('0');
    expect(response.CheckoutRequestID).toBeDefined();
  });

  it('should query STK push status', async () => {
    const stkResponse = await mpesa.stkPush({
      amount: 1,
      phone: '254708374149',
      accountReference: 'TEST',
      transactionDesc: 'Test',
    });

    const queryResponse = await mpesa.stkQuery({
      checkoutRequestId: stkResponse.CheckoutRequestID,
    });

    expect(queryResponse.ResultCode).toBeDefined();
  });
});

7. Moving to Production

When ready for production:

  1. Update credentials:

    MPESA_ENVIRONMENT=production
    MPESA_SHORTCODE=your_production_shortcode
    MPESA_PASSKEY=your_production_passkey
  2. Use HTTPS for callbacks:

    • Safaricom requires HTTPS URLs in production
    • Ensure your callback URLs are publicly accessible
    • Use a valid SSL certificate
  3. Update callback URLs:

    MPESA_CALLBACK_URL=https://yourdomain.com/api/mpesa/callback
    MPESA_RESULT_URL=https://yourdomain.com/api/mpesa/result
  4. Test with real transactions:

    • Start with small amounts (KES 1-10)
    • Test with your own phone number first
    • Verify callbacks are received correctly

8. Common Sandbox Issues

Issue Solution
"Invalid Access Token" Ensure you've subscribed to the API in the developer portal
"Invalid Shortcode" Use 174379 for sandbox STK Push
"Request failed" Check your consumer key and secret are correct
"Callback not received" Ensure your callback URL is publicly accessible (use ngrok)

9. Testing Checklist

Before going live, test these scenarios:

  • ✅ OAuth token generation
  • ✅ STK Push initiation
  • ✅ STK Push query
  • ✅ Successful payment callback
  • ✅ Failed payment callback
  • ✅ Timeout handling
  • ✅ Network error handling
  • ✅ Invalid phone number handling
  • ✅ Invalid amount handling
  • ✅ C2B URL registration (if using C2B)
  • ✅ B2C payment (if using B2C)

📚 Examples

The /examples folder contains complete working examples:

1. Express STK Push Server

File: examples/express-stk-push.ts

A complete Express.js server demonstrating:

  • STK Push payment initiation
  • Payment status queries
  • Error handling
  • RESTful API endpoints
npx ts-node examples/express-stk-push.ts

2. Callback Handling

File: examples/callback-handling.ts

Complete callback handling examples:

  • STK Push callbacks with middleware
  • Secure callbacks with signature verification
  • C2B validation and confirmation
  • Extracting payment metadata
npx ts-node examples/callback-handling.ts

3. Error Handling

File: examples/error-handling.ts

Comprehensive error handling strategies:

  • Handling specific error types
  • Graceful degradation
  • User-friendly error messages
  • Error logging for monitoring
  • Retry logic demonstration
npx ts-node examples/error-handling.ts

🧪 Testing

# Run unit tests
npm test

# Run tests with coverage
npm run test:coverage

# Run live API tests (requires credentials)
npm run test:live

Live Testing Scripts

# Test OAuth token generation
npx ts-node test-token-only.ts

# Test STK Push
npx ts-node test-stk-push.ts

🤝 Contributing

We welcome contributions! Here's how you can help:

Reporting Issues

  • Use the GitHub Issues page
  • Provide a clear description and steps to reproduce
  • Include error messages and stack traces

Pull Requests

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Development Guidelines

  • Write tests for new features
  • Follow the existing code style
  • Update documentation for API changes
  • Ensure all tests pass before submitting

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


💬 Support & Contact


🙏 Acknowledgments

  • Safaricom Daraja API - Official MPesa API
  • All contributors and users of this SDK
  • The Node.js and TypeScript communities

🌟 Show Your Support

If this SDK helped you build something awesome, please:

  • ⭐ Star this repository
  • 🐛 Report issues
  • 💡 Suggest new features
  • 🤝 Contribute code
  • 📢 Share with other developers

🎉 Happy Coding! 🎉

Built with ❤️ for the Kenyan developer community

Made by Adams MugweRepository

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published