Local SEO for Kenyan Businesses, Tools and Resources, Web Development Basics, Website Design Tips

M-Pesa Integration for Kenyan E-Commerce Sites: A Developer’s Step-by-Step Guide

M-Pesa Integration for Kenyan E-Commerce Sites: A Developer’s Step-by-Step Guide

It was 3 p.m. on a Friday when my client’s e-commerce site had a conversion issue.

“Arthur, our customers are abandoning carts because the ‘Pay with Card’ option isn’t working for them,” the client groaned. “Half of Kenya uses M-Pesa. Why don’t we?”

He was right. Over 80% of Kenyan adults use mobile money, yet his site relied on card payments. After a weekend of caffeine-fueled coding, we integrated M-Pesa—and saw conversions jump by 40% in a week.

Here’s how you can do it too.

Step 1: Set Up Safaricom Daraja API

1.Register for a Daraja Account
  • Visit Safaricom Developer Portal.
  • Create a test app to access sandbox credentials (Consumer Key, Consumer Secret).
2. Simulate Lipa Na M-Pesa Online

Use the sandbox to test without real money:

				
					# Generate access token  
curl -X GET "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"  
-H "Authorization: Basic YOUR_ENCODED_CREDENTIALS"  
				
			

Step 2: Build the Node.js Backend

Initialize Payment (Express.js)

				
					const axios = require('axios');  
const btoa = require('btoa');  

app.post('/initiate-payment', async (req, res) => {  
  const { phone, amount } = req.body;  

  // 1. Authenticate  
  const auth = btoa(`${DARAJA_CONSUMER_KEY}:${DARAJA_CONSUMER_SECRET}`);  
  const { data: { access_token } } = await axios.get(  
    'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',  
    { headers: { Authorization: `Basic ${auth}` } }  
  );  

  // 2. Trigger STK Push  
  const response = await axios.post(  
    'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest',  
    {  
      BusinessShortCode: '174379',  
      Password: btoa(`174379${DARAJA_PASSKEY}${TIMESTAMP}`),  
      Timestamp: TIMESTAMP,  
      TransactionType: 'CustomerPayBillOnline',  
      Amount: amount,  
      PartyA: `254${phone.slice(-9)}`, // Format to 2547XXXXXXXX  
      PartyB: '174379',  
      PhoneNumber: `254${phone.slice(-9)}`,  
      CallBackURL: 'https://yourdomain.com/mpesa-callback',  
      AccountReference: 'E-Commerce',  
      TransactionDesc: 'Payment'  
    },  
    { headers: { Authorization: `Bearer ${access_token}` } }  
  );  

  res.json(response.data);  
});   
				
			

Handle Callbacks (Webhooks)

				
					app.post('/mpesa-callback', (req, res) => {  
  const { Body: { stkCallback: { ResultCode, ResultDesc, CallbackMetadata } } } = req.body;  

  if (ResultCode === 0) {  
    const amount = CallbackMetadata.Item.find(i => i.Name === 'Amount').Value;  
    const mpesaCode = CallbackMetadata.Item.find(i => i.Name === 'MpesaReceiptNumber').Value;  
    // Update your database here  
    console.log(`Payment ${mpesaCode} of Ksh ${amount} succeeded!`);  
  } else {  
    console.error(`Payment failed: ${ResultDesc}`);  
  }  
  res.sendStatus(200);  
});  
				
			

Step 3: Integrate with React Frontend

Payment Button Component

				
					import { useState } from 'react';  
import axios from 'axios';  

const MpesaButton = () => {  
  const [phone, setPhone] = useState('');  
  const [amount, setAmount] = useState('');  

  const handlePayment = async () => {  
    try {  
      const response = await axios.post('/api/initiate-payment', {  
        phone: phone.replace(/^0/, '7'), // Convert 07... to 7...  
        amount  
      });  
      alert('Check your phone to complete the payment!');  
    } catch (error) {  
      alert('Error initiating payment. Please try again.');  
    }  
  };  

  return (  
    <div>  
      <input  
        type="tel"  
        placeholder="07XX XXX XXX"  
        value={phone}  
        onChange={(e) => setPhone(e.target.value)}  
      />  
      <input  
        type="number"  
        placeholder="Amount (KES)"  
        value={amount}  
        onChange={(e) => setAmount(e.target.value)}  
      />  
      <button onClick={handlePayment}>Pay with M-Pesa</button>  
    </div>  
  );  
};  
				
			

Common Pitfalls & Fixes

1. Invalid Phone Numbers:
  • Always format numbers to 2547XXXXXXXX (e.g., convert 0712345678 → 254712345678).
2. Callback URL HTTPS:
  • Safaricom requires HTTPS in production. Use Ngrok for local testing.
3. Security Best Practices:
  • Encrypt Daraja credentials using environment variables.
  • Validate amounts server-side to prevent tampering.

Results You Can Expect

  • Faster checkouts: Users pay in 3 taps instead of typing card details.

  • Higher trust: Local customers prefer platforms supporting M-Pesa.

  • Lower costs: Avoid third-party payment gateways’ fees.

Final Tip!!

Auto-Detect Safaricom Numbers
Use the Telkom API to check if a number is Safaricom before showing M-Pesa as an option. gateways’ fees.

Leave a Reply

Your email address will not be published. Required fields are marked *