Implement Lipa na M-Pesa using NodeJs

  • Web
  • Backend
  • NodeJS
  • API
  • Daraja
Cover Image for Implement Lipa na M-Pesa using NodeJs
Aloys Aboge Jr.
Aloys Aboge Jr.

This article is a documentation of how I implemented a lightweight Lipa na M-Pesa endpoint using Safaricom's Daraja API for my final year project. The project is a bus fare collection mobile app built on React Native for the client and Firebase as the Backend. I had to implement an additional endpoint for the payment gateway. That's where NodeJs with Express and Daraja API come in. Enjoy.

Getting Started

Visit the Safaricom Developer Portal and register for an account.

Create a new app under the My Apps Tab

From there you will be provided with a Consumer Key and a Consumer Secret. These credentials wil be necessary to interact with Safaricom's Daraja API. Keep them securely.

In your terminal window or text editor of choice, initialize a new nodejs project.

npm init -ycopy

Install necessary dependencies

npm i express axios dotenv nodemoncopy

Create a new file

touch index.jscopy

This is where all the code for our Lipa na M-Pesa endpoint will reside.

index.js
const express = require("express"); //For the REST API
const router = express.Router(); //For creating modular routes
const axios = require("axios"); //For making http requests to the daraja API
const port = 5000; //Arbitrary port where the server will listen on
const app = express(); //initialize the express app
app.use(express.json()); //middleware
 
// Daraja API credentials acquired from your app on the developer portal
const consumerKey = "[YOUR_CONSUMER_KEY]";
const consumerSecret = "[YOUR CONSUMER SECRET]";
 
/**
 * generateTimestamp - fundtion to generate a timestamp required for payment request as specified in the documentataion.
 * Return - a string of a timestamp formatted for the payment request
*/
const generateTimestamp = () => {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, "0");
  const day = String(now.getDate()).padStart(2, "0");
  const hours = String(now.getHours()).padStart(2, "0");
  const minutes = String(now.getMinutes()).padStart(2, "0");
  const seconds = String(now.getSeconds()).padStart(2, "0");
  return `${year}${month}${day}${hours}${minutes}${seconds}`;
};
 
/**
 * generateAccessToken - fundtion to generate an access token by authenticating you using consumer key and consumer secret.
 * Return - a string of an access token
*/
const generateAccessToken = async (consumerKey, consumerSecret) => {
  const username = consumerKey;
  const password = consumerSecret;
  const authString = `${username}:${password}`;
  const base64AuthString = btoa(authString);
  try {
    const response = await axios.get(
      "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials",
      {
        headers: {
          Authorization: "Basic " + base64AuthString,
        },
      }
    );
    return response.data.access_token;
  } catch (error) {
    throw error;
  }
};
 
/**
 * generatePassword - fundtion to generate a password required for payment request as specified in the documentataion.
 * Return - a string of a timestamp formatted for the payment request
*/
const generatePassword = () => {
  const shortcode = "174379";
  const passkey =
    "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919";
  const password = Buffer.from(`${shortcode}${passkey}${timestamp}`).toString(
    "base64"
  );
  return password;
};
 
/**
 * initiatePayment - fundtion to initiate a payment request
 * Return - a json object containing transaction information
*/
const initiatePayment = async (accessToken, paymentRequest) => {
  try {
    const response = await axios.post(
      "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest",
      paymentRequest,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );
    return response.data;
  } catch (error) {
    throw error;
  }
};
 
// Endpoint to initiate a Lipa Na M-Pesa Online Payment
router.post("/lipa", async (req, res) => {
  try {
    // Generate an access token for authentication
    const accessToken = await generateAccessToken(consumerKey, consumerSecret);
 
    // Create the payment request
    const paymentRequest = {
      BusinessShortCode: "174379",
      Password: generatePassword(),
      Timestamp: generateTimestamp();,
      TransactionType: "CustomerPayBillOnline",
      Amount: req.body.amount,
      PartyA: req.body.phone, // Customer's phone number
      PartyB: 174379,
      PhoneNumber: req.body.phone, // Customer's phone number
      CallBackURL: "PUT_A_HTTPS_CALLBACK_ENDPOINT_HERE", //The request requires a https callback url
      AccountReference: "RideWallet",
      TransactionDesc: "Fare Payment",
    };
    // Make the payment request
    const paymentResponse = await initiatePayment(accessToken, paymentRequest);
    // Handle the payment response as needed
    res.status(200).json({
      message: "Payment initiated successfully",
      data: paymentResponse,
    });
  } catch (error) {
    console.error("Error initiating payment:", error.response.data);
    res.status(500).json({ message: "Payment initiation failed" });
  }
});
 
 
app.use("/", router);
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Make a POST request to the endpoint http://localhost:5000/lipa with the body:

request body
{
  "phone": "254712345678",
  "AMOUNT": "1"
}

On success, you should receive a similar response to:

response
{
  "message": "Payment initiated successfully",
  "data": {
    "MerchantRequestID": "53e3-4aa8-9fe0-8fb5e4092cdd2293840",
    "CheckoutRequestID": "ws_CO_08052024032308969712737135",
    "ResponseCode": "0",
    "ResponseDescription": "Success. Request accepted for processing",
    "CustomerMessage": "Success. Request accepted for processing"
  }
}

A this point you will receive an STK Push on your mobile phone. You can either accept or decline the payment (I personally recommend the latter since the refund process is quite the hassle).

The callback url option in the payment request is useful when handling advanced payment operations on your app such as recording transactions i.e, you could store transaction history for one of your users with recipts and transaction IDs. The callback url has to be htps (http secure). If you haven't deployed your application yet you can use nginx which will give you a temporary https endpont and map it to your localhost at a port of your choosing.

Thanks for reading. Happy Hacking 👍🏾️.


More Articles