UNPKG

daraja-javascript-sdk

Version:
628 lines (486 loc) 15 kB
# 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 ```bash 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: ```env # 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: ```javascript const Daraja = require('daraja-javascript-sdk'); const daraja = new Daraja(); // Will use environment variables ``` ### Manual Configuration You can also provide configuration directly: ```javascript 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. ```javascript // 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: ```javascript 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: ```javascript 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 ```javascript 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** ```javascript const daraja = new Daraja({ callbackUrl: 'https://your-domain.com/mpesa/callback' }); ``` **Option 2: Using Ngrok for Development** 1. Install ngrok: ```bash npm install -g ngrok ``` 2. Start your Express server: ```bash node server.js # Running on port 3000 ``` 3. Start ngrok: ```bash ngrok http 3000 ``` 4. Use the ngrok URL as your callback: ```javascript 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): ```javascript 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)** ```bash # 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 ``` 2. **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) 2. **Invalid Response** - Always respond with: ```javascript res.json({ ResultCode: 0, ResultDesc: "Success" }); ``` 3. **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** ```javascript 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" }); }); ``` 2. **Handle Duplicate Callbacks** ```javascript 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... }); ``` 3. **Add Timeout Handling** ```javascript // 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: ```javascript 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: ```javascript 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: ```javascript 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): ```javascript // 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: ```javascript 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: ```javascript 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: ```javascript 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.