Skip to content

onesmuskipchumba0/daraja-javascript-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Daraja JavaScript SDK

A lightweight and easy-to-use JavaScript SDK for Safaricom's M-Pesa Daraja API.

Features

  • STK Push (Lipa Na M-Pesa Online)
  • B2C Payment
  • Transaction Status
  • Account Balance Query
  • Easy configuration via environment variables
  • TypeScript support (coming soon)

Installation

npm install daraja-javascript-sdk

Configuration

The SDK can be configured either through environment variables or through the constructor. Using environment variables is recommended for better security and ease of use.

Using Environment Variables (Recommended)

Create a .env file in your project root:

# Required configurations
CONSUMER_KEY=your_consumer_key
CONSUMER_SECRET=your_consumer_secret
BUSINESS_SHORT_CODE=174379
PASS_KEY=your_pass_key
CALLBACK_URL=your_callback_url

# Optional configurations
ENVIRONMENT=sandbox  # or 'production'
TIMEOUT_URL=your_timeout_url
RESULT_URL=your_result_url
INITIATOR_NAME=your_initiator_name
SECURITY_CREDENTIAL=your_security_credential

Then initialize the SDK:

const Daraja = require('daraja-javascript-sdk');
const daraja = new Daraja();  // Will use environment variables

Manual Configuration

You can also provide configuration directly:

const Daraja = require('daraja-javascript-sdk');

const daraja = new Daraja({
  consumerKey: 'your_consumer_key',
  consumerSecret: 'your_consumer_secret',
  environment: 'sandbox',  // or 'production'
  businessShortCode: '174379',
  passKey: 'your_pass_key',
  callbackUrl: 'your_callback_url'
});

Usage

STK Push

STK Push is a service that allows you to initiate a payment prompt on the customer's phone. The service will send a request to the user's phone, prompting them to enter their M-Pesa PIN to authorize the transaction.

// Basic STK Push
try {
  const response = await daraja.stkPush({
    phoneNumber: '254712345678',  // Phone number to prompt for payment
    amount: 1,                    // Amount to charge
    accountReference: 'TEST',     // Your reference for the transaction
    transactionDesc: 'Test Payment'
  });
  
  console.log('STK Push Response:', response);
  // Response includes CheckoutRequestID for tracking the transaction
} catch (error) {
  console.error('STK Push Error:', error);
}

Handling M-Pesa Callbacks

Basic Callback Setup

Here's a simple Express.js server to handle M-Pesa payment notifications:

const express = require('express');
const app = express();

// Allow JSON requests
app.use(express.json());

// Handle M-Pesa payments
app.post('/mpesa/callback', (req, res) => {
  // Get the payment details
  const { Body } = req.body;
  
  if (Body.stkCallback) {
    // Check if payment was successful
    if (Body.stkCallback.ResultCode === 0) {
      // Get payment details
      const items = Body.stkCallback.CallbackMetadata.Item;
      const amount = items.find(item => item.Name === 'Amount').Value;
      const mpesaReceipt = items.find(item => item.Name === 'MpesaReceiptNumber').Value;
      const phoneNumber = items.find(item => item.Name === 'PhoneNumber').Value;
      
      console.log('Payment Received!');
      console.log('Amount:', amount);
      console.log('Receipt Number:', mpesaReceipt);
      console.log('Phone Number:', phoneNumber);
      
      // TODO: Update your database
      // TODO: Send confirmation to customer
      
    } else {
      // Payment failed
      console.log('Payment failed:', Body.stkCallback.ResultDesc);
    }
  }
  
  // Always respond to M-Pesa
  res.json({ ResultCode: 0, ResultDesc: "Success" });
});

// Start server
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Checking Payment Status

Simple way to check if a payment was successful:

const Daraja = require('daraja-javascript-sdk');
const daraja = new Daraja();

// Check payment status
async function checkPayment(checkoutRequestId) {
  try {
    const result = await daraja.stkPushQuery({
      checkoutRequestId: checkoutRequestId
    });
    
    if (result.ResultCode === 0) {
      console.log('Payment was successful!');
    } else {
      console.log('Payment failed or pending');
    }
  } catch (error) {
    console.log('Error checking payment:', error.message);
  }
}

Complete Example

Here's a complete example showing how to:

  1. Start a payment
  2. Save payment details
  3. Handle the callback
const express = require('express');
const Daraja = require('daraja-javascript-sdk');

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

// Store payments in memory (use a database in production)
const payments = [];

// Initialize Daraja
const daraja = new Daraja();

// Start payment
app.post('/pay', async (req, res) => {
  try {
    const { phone, amount } = req.body;
    
    // Start STK Push
    const result = await daraja.stkPush({
      phoneNumber: phone,
      amount: amount,
      accountReference: 'Test',
      transactionDesc: 'Test Payment'
    });
    
    // Save payment details
    payments.push({
      checkoutRequestId: result.CheckoutRequestID,
      amount: amount,
      phone: phone,
      status: 'pending'
    });
    
    res.json({ 
      message: 'Payment started',
      checkoutRequestId: result.CheckoutRequestID
    });
    
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Handle M-Pesa callback
app.post('/mpesa/callback', (req, res) => {
  const { Body } = req.body;
  
  if (Body.stkCallback) {
    const { ResultCode, ResultDesc, CheckoutRequestID } = Body.stkCallback;
    
    // Find the payment
    const payment = payments.find(p => p.checkoutRequestId === CheckoutRequestID);
    if (payment) {
      if (ResultCode === 0) {
        // Payment successful
        const items = Body.stkCallback.CallbackMetadata.Item;
        payment.status = 'completed';
        payment.mpesaReceipt = items.find(item => item.Name === 'MpesaReceiptNumber').Value;
        
        console.log('Payment completed:', payment);
      } else {
        // Payment failed
        payment.status = 'failed';
        payment.error = ResultDesc;
        
        console.log('Payment failed:', ResultDesc);
      }
    }
  }
  
  res.json({ ResultCode: 0, ResultDesc: "Success" });
});

// Check payment status
app.get('/status/:checkoutRequestId', async (req, res) => {
  const { checkoutRequestId } = req.params;
  
  // Find payment in our records
  const payment = payments.find(p => p.checkoutRequestId === checkoutRequestId);
  if (!payment) {
    return res.status(404).json({ error: 'Payment not found' });
  }
  
  res.json({ payment });
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Understanding M-Pesa Callbacks

How Callbacks Work

When a customer makes a payment:

  1. The customer receives an STK push on their phone
  2. After they enter their PIN, M-Pesa processes the payment
  3. M-Pesa sends the result to your callback URL
  4. Your server processes this callback and updates your system

Setting Up Your Callback URL

1. Requirements for Callback URLs

  • Must be a public HTTPS URL (M-Pesa doesn't accept HTTP)
  • Must be accessible from the internet
  • Standard ports (443 for HTTPS)

2. Options for Callback URLs

Option 1: Using a Domain

const daraja = new Daraja({
  callbackUrl: 'https://your-domain.com/mpesa/callback'
});

Option 2: Using Ngrok for Development

  1. Install ngrok:
npm install -g ngrok
  1. Start your Express server:
node server.js  # Running on port 3000
  1. Start ngrok:
ngrok http 3000
  1. Use the ngrok URL as your callback:
const daraja = new Daraja({
  callbackUrl: 'https://your-ngrok-url.ngrok.io/mpesa/callback'
});

3. Example with Domain Setup

  1. Create your server file (server.js):
const express = require('express');
const Daraja = require('daraja-javascript-sdk');
const app = express();

// Parse JSON bodies
app.use(express.json());

// Initialize Daraja with your domain
const daraja = new Daraja({
  callbackUrl: 'https://your-domain.com/mpesa/callback'  // Your domain
});

// Endpoint to start payment
app.post('/start-payment', async (req, res) => {
  try {
    const result = await daraja.stkPush({
      phoneNumber: '254712345678',
      amount: 1,
      accountReference: 'TEST',
      transactionDesc: 'Test Payment'
    });
    res.json(result);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Callback endpoint that M-Pesa will call
app.post('/mpesa/callback', (req, res) => {
  const { Body } = req.body;
  
  if (Body.stkCallback) {
    if (Body.stkCallback.ResultCode === 0) {
      // Payment successful
      const items = Body.stkCallback.CallbackMetadata.Item;
      const amount = items.find(item => item.Name === 'Amount').Value;
      const receipt = items.find(item => item.Name === 'MpesaReceiptNumber').Value;
      
      console.log(`Received payment of ${amount} KSH, receipt: ${receipt}`);
      
      // TODO: Update your database
      // TODO: Notify your customer
    }
  }
  
  // Always respond to M-Pesa
  res.json({ ResultCode: 0, ResultDesc: "Success" });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Testing Callbacks

  1. Local Development (using ngrok)
# Terminal 1: Start your server
node server.js

# Terminal 2: Start ngrok
ngrok http 3000

# Copy the HTTPS URL from ngrok output
# Example: https://abc123.ngrok.io
  1. Production Domain
  • Point your domain to your server
  • Set up SSL certificate (required by M-Pesa)
  • Update your callback URL in the Daraja configuration

Common Callback Issues

  1. Callback Not Received
  • Check if URL is publicly accessible
  • Verify HTTPS is properly set up
  • Ensure correct port is open
  • Check ngrok tunnel is running (if using ngrok)
  1. Invalid Response
  • Always respond with:
res.json({ ResultCode: 0, ResultDesc: "Success" });
  1. HTTPS Issues
  • M-Pesa requires valid SSL certificates
  • Self-signed certificates won't work
  • Use Let's Encrypt for free SSL certificates

Best Practices for Callbacks

  1. Log Everything
app.post('/mpesa/callback', (req, res) => {
  // Log incoming callback
  console.log('Received callback:', JSON.stringify(req.body));
  
  // Process callback
  // ...
  
  // Log response
  console.log('Sending response to M-Pesa');
  res.json({ ResultCode: 0, ResultDesc: "Success" });
});
  1. Handle Duplicate Callbacks
const processedTransactions = new Set();

app.post('/mpesa/callback', (req, res) => {
  const transactionId = req.body.Body.stkCallback.CheckoutRequestID;
  
  if (processedTransactions.has(transactionId)) {
    console.log('Duplicate transaction:', transactionId);
    return res.json({ ResultCode: 0, ResultDesc: "Success" });
  }
  
  processedTransactions.add(transactionId);
  // Process the payment...
});
  1. Add Timeout Handling
// When starting payment
const result = await daraja.stkPush({...});

// Set timeout to check status
setTimeout(async () => {
  const status = await daraja.stkPushQuery({
    checkoutRequestId: result.CheckoutRequestID
  });
  
  if (status.ResultCode !== 0) {
    // Handle timeout
    console.log('Payment timed out');
  }
}, 60000); // Check after 1 minute

Remember:

  • Always use HTTPS in production
  • Keep your callback endpoint simple and fast
  • Log all requests and responses
  • Handle errors gracefully
  • Store transaction details in a database
  • Implement proper security measures

B2C Payment

Send money from your business to customers:

try {
  const response = await daraja.b2c({
    amount: 100,
    phoneNumber: '254712345678',
    commandID: 'BusinessPayment',  // or 'SalaryPayment', 'PromotionPayment'
    remarks: 'Refund'
  });
  
  console.log('B2C Response:', response);
} catch (error) {
  console.error('B2C Error:', error);
}

Transaction Status

Check the status of a transaction:

try {
  const response = await daraja.transactionStatus({
    transactionID: 'TRANSACTION_ID'
  });
  
  console.log('Status:', response);
} catch (error) {
  console.error('Status Check Error:', error);
}

Account Balance

Query your M-Pesa account balance:

try {
  const response = await daraja.accountBalance();
  console.log('Balance:', response);
} catch (error) {
  console.error('Balance Query Error:', error);
}

C2B (Customer to Business)

Register URLs for C2B transactions and simulate payments (simulation only works in sandbox):

// Register URLs for C2B
try {
  const response = await daraja.c2bRegisterUrl({
    shortCode: '123456',  // Optional, uses businessShortCode by default
    responseType: 'Completed',  // Optional
    confirmationUrl: 'https://your-domain.com/mpesa/confirmation',
    validationUrl: 'https://your-domain.com/mpesa/validation'
  });
  
  console.log('C2B URLs registered:', response);
} catch (error) {
  console.error('C2B URL registration failed:', error);
}

// Simulate C2B payment (sandbox only)
try {
  const response = await daraja.c2bSimulate({
    amount: 100,
    phoneNumber: '254712345678',
    billRefNumber: 'TEST123'
  });
  
  console.log('C2B Simulation:', response);
} catch (error) {
  console.error('C2B Simulation failed:', error);
}

B2B (Business to Business)

Transfer money between businesses:

try {
  const response = await daraja.b2b({
    amount: 1000,
    receiverShortCode: '987654',
    commandID: 'BusinessToBusinessTransfer',  // Optional
    remarks: 'Supplier Payment'  // Optional
  });
  
  console.log('B2B Response:', response);
} catch (error) {
  console.error('B2B Transfer failed:', error);
}

Transaction Reversal

Reverse an M-Pesa transaction:

try {
  const response = await daraja.reversal({
    transactionID: 'ABCD1234',
    amount: 100,
    remarks: 'Wrong payment'  // Optional
  });
  
  console.log('Reversal Response:', response);
} catch (error) {
  console.error('Reversal failed:', error);
}

STK Push Status Query

Check the status of an STK push request:

try {
  const response = await daraja.stkPushQuery({
    checkoutRequestId: 'ws_CO_123456789'
  });
  
  console.log('STK Query Response:', response);
} catch (error) {
  console.error('STK Query failed:', error);
}

Testing

For testing purposes, use these sandbox credentials:

  • Business Shortcode: 174379
  • Pass Key: bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919

Development

To contribute or modify the SDK:

  1. Clone the repository
  2. Install dependencies: npm install
  3. Create a .env file with your test credentials
  4. Run tests: npm test

License

MIT

Support

For support, please raise an issue in the GitHub repository or contact the maintainers.

About

A lightweight and easy-to-use JavaScript SDK for Safaricom's M-Pesa Daraja API.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published