Integrating payments with Lipa na M-Pesa online (M-Pesa Express/STK/NI Push) API the right way

Mark Aloo
CodeInfluence
Published in
7 min readDec 2, 2022

--

The Daraja developers portal has been there for a long time now. Althought it allows developers to plug in payment solutions to their applications, Safaricom has not yet made the documentation as clear as should be. It is however worth mentioning that there is current work in progress to improve the docs.

The most commonly used API is the Lipa na M-Pesa Online (M-Pesa Express) API. Today, I will be showing you the steps to follow to get the intergation right.

The implementation of this API takes a two step process. We start by calling the authorization API then calling the STK Push API.

STK (Sim Toolkit) push was recently renamed to NI(Network Initiated) push and the implementation changed behind the hood. However, we will be refering to it as STK since it’s the most common name.

Prerequisites

  1. Create an account on Safaricom developers portal
  2. Create a new app under My Apps section ensuring to check all the fields. You will be able to see consumer key and consumer secret under each app
  3. Have node.js installed (or reuse the implementation in your favorite programming language)
  4. Have ngrok installed. We will use this to simulate https on our localhost server. If you already have node.js then you can install it by running npm install -g ngrok or run it directly on npx . Otherwise, download it from ngrok download page
  5. Have Postman or any other API testing tool installed

Steps

We will be using node.js for the demo, however, you will be able to reuse the implementation in whatever language you will be using. Here is an image of the folder structure we will end up with:

folder structure for the m-pesa stk push demo
folder structure for the whole app

Setup the project in a folder of your choice and install the dependencies we will be using as follows

npm init && npm i axios body-parser cors dotenv express node-datetime

1. The Authorization (OAuth) API

All APIs on Daraja require the authorization API first. It gives you the access_token that you will then use when calling the main API (in this case STK Push). For this, you will need two entities namely consumer secret and consumer key. You got these when you created the app, remember? You will then create a basic auth header with a base64 encoded string of the app’s consumer key and consumer secret. Include the header in a GET request to the provided OAuth API.

https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials

Below is a code snippet of how to do so:

import envVars from './envVars.js';
import axios from 'axios';

const getMpesaCredentials = async () => {
let credential = {};

const config = {
headers: {
Authorization: `Basic ${Buffer.from(
envVars.MPESA_CONSUMER_KEY + ':' + envVars.MPESA_CONSUMER_SECRET
).toString('base64')}`,
},
};

await axios
.get(
'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
config
)
.then(resp => {
credential = resp.data;
})
.catch(error => {
console.log(error.message);
});

return credential;
};

export default getMpesaCredentials;

The envVars contains environment variables that we declare. Some of which we will use later. This is how it looks like:

import dotenv from 'dotenv';

dotenv.config();

const {
MPESA_SHORTCODE,
MPESA_PASSKEY,
MPESA_CONSUMER_KEY,
MPESA_CONSUMER_SECRET,
API_URL,
} = process.env;

const envVars = {
MPESA_SHORTCODE,
MPESA_PASSKEY,
MPESA_CONSUMER_KEY,
MPESA_CONSUMER_SECRET,
API_URL,
};

export default envVars;

This is how the .env looks like:

MPESA_SHORTCODE = 
MPESA_PASSKEY =
MPESA_CONSUMER_KEY =
MPESA_CONSUMER_SECRET =

API_URL =

2. The (STK/NI) Push API

Now that we have a function to generate the credentials, we can proceed to the next step. To call the STK push API, we will need a few values in the body of the request as shown below:

{
BusinessShortCode: '174379', // provided for testing
Password: '',
Timestamp: '',
TransactionType: 'CustomerPayBillOnline',
Amount: '1',
PartyA: 'phone_number', //starting with 254
PartyB: '174379', // provided for testing
PhoneNumber: 'phone_number', //starting with 254
CallBackURL: `${envVars.API_URL}/api/client/mpesa-callback/deposit`,
AccountReference: 'BUSINESS_NAME', // Name of business
TransactionDesc: 'Store Payment', // self explanatory
};

A BusinessShortCode of 174379 is provided on the developers portal for testing

Password

The password is used to encrypting the STK push request sent. It is a base64 encoded string with a combination of Shortcode, Passkey and Timestamp.

When you go live on Daraja, you are provided with a passkey. For testing, we will use this passkey:

bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919.

In case you’re wondering where we get the passkey, it is on the simulator in Daraja portal MpesaExpressSimulate section. On the simulator, select your app then the passkey will show under the prefilled values.

mpesa simulate interface
m-pesa simulate interface

Note that the timestamp used to generate the password should be the same as the one passed in the body of the request. We will therefore create a function to generate the password but also export the timestamp. In terms of code:

import datetime from 'node-datetime';
import envVars from './envVars.js';

const dt = datetime.create();
const formatedDT = dt.format('YmdHMS');

const getMpesaPassword = () => {
const password = Buffer.from(
envVars.MPESA_SHORTCODE + envVars.MPESA_PASSKEY + formatedDT
).toString('base64');

return { password, timestamp: formatedDT };
};

export default getMpesaPassword;

CallbackURL

When a user gets a push on their phone, you will need a way to know if they successfully made the transaction or they cancelled. The callbackURL is a way for M-Pesa to communicate that information to you. Essentially you need to create a webhook to listen for these. The callbackController.js file has this implementation as shown below:

export const depositCallback = async (req, res, next) => {
console.log('-- Mpesa Callback --');
console.log('Req Body: ', req.body);

// Do your logic based on the callback information

res.send({ success: true });
};

Setup routes and the depositController

Phew! We are almost done with the setup. We need to declare the index.js, routes and depositController.js (calls the STK Push API).

NB: I will not be covering how node.js and express.js work in this tutorial

index.js

import bodyParser from 'body-parser';
import cors from 'cors';
import express from 'express';
import callbackRoutes from './routes/callbackRoutes.js';
import depositRoutes from './routes/depositRoutes.js';

const app = express();
const PORT = process.env.PORT || 5000;

app.use(express.json());
app.use(bodyParser.json({ extended: true }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());

app.use('/api/client/mpesa', depositRoutes);
app.use('/api/client/mpesa-callback', callbackRoutes);

app.listen(PORT, () => {
console.log(`listening on ${PORT}`);
});

depositRoutes

import express from 'express';
import { deposit } from '../controllers/depositController.js';

const router = express.Router();

router.route('/deposit').post(deposit);

export default router;

callbackRoutes

import express from 'express';
import { depositCallback } from '../controllers/callbackController.js';

const router = express.Router();

router.route('/deposit').post(depositCallback);

export default router;

depositController

We will pass the access_token from getMpesaCredentials, inside a bearer auth header while we POST a request to the STK Push API, together with the request body we discussed earlier. We will also take in phone from the request body.

The URL for STK push:

https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest

The depositController file is shown below:

import axios from 'axios';
import envVars from '../utilities/envVars.js';
import getMpesaCredentials from '../utilities/getMpesaCredentials.js';
import getMpesaPassword from '../utilities/getMpesaPassword.js';

export const deposit = async (req, res, next) => {
const { phone } = req.body;
const { access_token } = await getMpesaCredentials();

const data = {
BusinessShortCode: '174379',
Password: getMpesaPassword().password,
Timestamp: getMpesaPassword().timestamp,
TransactionType: 'CustomerPayBillOnline',
Amount: '1',
PartyA: phone,
PartyB: '174379',
PhoneNumber: phone,
CallBackURL: `${envVars.API_URL}/api/client/mpesa-callback/deposit`,
AccountReference: 'MONARCH LTD',
TransactionDesc: 'MONARCH LTD',
};

const config = {
headers: {
Authorization: `Bearer ${access_token}`,
},
};

axios
.post(
'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest',
data,
config
)
.then(res => {
res.send({ res });
console.log(res);
})
.catch(error => {
res.send(error);
});
};

Testing

That was a lot of explanation accompanied by many code snippets. it has come to a moment of truth, testing our implementation. You can find the full code with the folder structure in this github repo. Be sure to star the repo 😉

PS: Be sure to exclude node_modules before you push to github. Do this by including it in your .gitignore file. Check the file in the repo for reference.

  1. Run ngrok http 5000 in a terminal in the project root
  2. Fill in the .env with the necessary information. Replace the API_URL with the https version of the link provided on the ngrok terminal in step 1
  3. Start the project by running node index.js
  4. In postman, create a POST request with the body containing the phone as shown below
  5. Press send and “fingers crossed” you should receive a push on your phone
  6. On the node.js terminal, you should see a console logging once you either accept or decline the push.
postman interface
interface of postman in step 4
node.js terminal interface
response from the callback URL

You can play with the push to see how the response loopk like when you enter wrong password, cancel the request e.t.c

Conclusion

Feel free to ask questions if you need clarifications on any part. Happy hacking and thanks for stopping by.

--

--

Mark Aloo
CodeInfluence

Tech pro with a broad skillset in frontend development, software engineering, RPA, technical writing, speaking about tech and passion for continuous learning