A lightweight and easy-to-use JavaScript SDK for Safaricom's M-Pesa Daraja API.
- STK Push (Lipa Na M-Pesa Online)
- B2C Payment
- Transaction Status
- Account Balance Query
- Easy configuration via environment variables
- TypeScript support (coming soon)
npm install daraja-javascript-sdk
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.
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
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'
});
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);
}
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');
});
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);
}
}
Here's a complete example showing how to:
- Start a payment
- Save payment details
- 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');
});
When a customer makes a payment:
- The customer receives an STK push on their phone
- After they enter their PIN, M-Pesa processes the payment
- M-Pesa sends the result to your callback URL
- Your server processes this callback and updates your system
- Must be a public HTTPS URL (M-Pesa doesn't accept HTTP)
- Must be accessible from the internet
- Standard ports (443 for HTTPS)
Option 1: Using a Domain
const daraja = new Daraja({
callbackUrl: 'https://your-domain.com/mpesa/callback'
});
Option 2: Using Ngrok for Development
- Install ngrok:
npm install -g ngrok
- Start your Express server:
node server.js # Running on port 3000
- Start ngrok:
ngrok http 3000
- Use the ngrok URL as your callback:
const daraja = new Daraja({
callbackUrl: 'https://your-ngrok-url.ngrok.io/mpesa/callback'
});
- 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');
});
- 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
- Production Domain
- Point your domain to your server
- Set up SSL certificate (required by M-Pesa)
- Update your callback URL in the Daraja configuration
- 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)
- Invalid Response
- Always respond with:
res.json({ ResultCode: 0, ResultDesc: "Success" });
- HTTPS Issues
- M-Pesa requires valid SSL certificates
- Self-signed certificates won't work
- Use Let's Encrypt for free SSL certificates
- 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" });
});
- 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...
});
- 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
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);
}
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);
}
Query your M-Pesa account balance:
try {
const response = await daraja.accountBalance();
console.log('Balance:', response);
} catch (error) {
console.error('Balance Query Error:', error);
}
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);
}
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);
}
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);
}
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);
}
For testing purposes, use these sandbox credentials:
- Business Shortcode: 174379
- Pass Key: bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919
To contribute or modify the SDK:
- Clone the repository
- Install dependencies:
npm install
- Create a
.env
file with your test credentials - Run tests:
npm test
MIT
For support, please raise an issue in the GitHub repository or contact the maintainers.