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();
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',
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.
try {
const response = await daraja.stkPush({
phoneNumber: '254712345678',
amount: 1,
accountReference: 'TEST',
transactionDesc: 'Test Payment'
});
console.log('STK Push Response:', response);
} 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();
app.use(express.json());
app.post('/mpesa/callback', (req, res) => {
const { Body } = req.body;
if (Body.stkCallback) {
if (Body.stkCallback.ResultCode === 0) {
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);
} else {
console.log('Payment failed:', Body.stkCallback.ResultDesc);
}
}
res.json({ ResultCode: 0, ResultDesc: "Success" });
});
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();
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:
- 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());
const payments = [];
const daraja = new Daraja();
app.post('/pay', async (req, res) => {
try {
const { phone, amount } = req.body;
const result = await daraja.stkPush({
phoneNumber: phone,
amount: amount,
accountReference: 'Test',
transactionDesc: 'Test Payment'
});
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 });
}
});
app.post('/mpesa/callback', (req, res) => {
const { Body } = req.body;
if (Body.stkCallback) {
const { ResultCode, ResultDesc, CheckoutRequestID } = Body.stkCallback;
const payment = payments.find(p => p.checkoutRequestId === CheckoutRequestID);
if (payment) {
if (ResultCode === 0) {
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.status = 'failed';
payment.error = ResultDesc;
console.log('Payment failed:', ResultDesc);
}
}
}
res.json({ ResultCode: 0, ResultDesc: "Success" });
});
app.get('/status/:checkoutRequestId', async (req, res) => {
const { checkoutRequestId } = req.params;
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:
- 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
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
npm install -g ngrok
- Start your Express server:
node server.js
ngrok http 3000
- 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
- Create your server file (server.js):
const express = require('express');
const Daraja = require('daraja-javascript-sdk');
const app = express();
app.use(express.json());
const daraja = new Daraja({
callbackUrl: 'https://your-domain.com/mpesa/callback'
});
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 });
}
});
app.post('/mpesa/callback', (req, res) => {
const { Body } = req.body;
if (Body.stkCallback) {
if (Body.stkCallback.ResultCode === 0) {
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}`);
}
}
res.json({ ResultCode: 0, ResultDesc: "Success" });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Testing Callbacks
- Local Development (using ngrok)
node server.js
ngrok http 3000
- 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
- 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)
res.json({ ResultCode: 0, ResultDesc: "Success" });
- M-Pesa requires valid SSL certificates
- Self-signed certificates won't work
- Use Let's Encrypt for free SSL certificates
Best Practices for Callbacks
app.post('/mpesa/callback', (req, res) => {
console.log('Received callback:', JSON.stringify(req.body));
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);
});
const result = await daraja.stkPush({...});
setTimeout(async () => {
const status = await daraja.stkPushQuery({
checkoutRequestId: result.CheckoutRequestID
});
if (status.ResultCode !== 0) {
console.log('Payment timed out');
}
}, 60000);
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',
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):
try {
const response = await daraja.c2bRegisterUrl({
shortCode: '123456',
responseType: 'Completed',
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);
}
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',
remarks: 'Supplier Payment'
});
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'
});
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:
- Clone the repository
- Install dependencies:
npm install
- Create a
.env file with your test credentials
- Run tests:
npm test
License
MIT
Support
For support, please raise an issue in the GitHub repository or contact the maintainers.