What is MPesa?
Mpesa is a mobile banking service launched by Safaricom, the largest mobile phone network in Kenya, in 2007 that allows users to send or save money through their mobile phones.
Since the service is so much popular, Safaricom introduced a REST API that allows developers to integrate their apps with MPesa.
Requirements
The completed code can be found here.
What we will cover
- Creating a developer's account
- Creating an app
- Project Set Up
- Generating Access Token
- Integrating Lipa na Mpesa (STK Push)
Creating a developer's account
In order to start consuming the APIs, you will need an account. To create one, please go to Daraja and click on the Login/Sign Up button then choose Go to Sign Up
Please fill all the fields and select Individual for the Account Type since we will be using it for learning purposes. Once done, a confirmation email will be sent, and you can now access the portal.
Creating an app
All the MPesa APIs require a developer to have an app so let's create one.
- Step 1: Click on MY APPS tab
- Step 2: Click on CREATE NEW APP button
- Step 3: Fill in the App Name and select Lipa Na MPesa Sandbox and Mpesa Sandbox products
- Step 4: Finish by clicking CREATE APP button.
Once done, you will see the newly added app on one of the cards with its name, consumer key and consumer secret.
Project Set Up
Create a new project then open it using your favorite IDE or editor then run the command below to initialize new module in current directory.
go mod init mpesa-golang
Once done, create a new file main.go
and add the snippet below.
This project is for learning purposes. You should not store any credentials on your code
package main
import (
"io"
"net/http"
"time"
)
// Mpesa is an application that will be making a transaction
type Mpesa struct {
consumerKey string
consumerSecret string
baseURL string
client *http.Client
}
// MpesaOpts stores all the configuration keys we need to set up a Mpesa app,
type MpesaOpts struct {
ConsumerKey string
ConsumerSecret string
BaseURL string
}
// NewMpesa sets up and returns an instance of Mpesa
func NewMpesa(m *MpesaOpts) *Mpesa {
client := &http.Client{
Timeout: 30 * time.Second,
}
return &Mpesa{
consumerKey: m.ConsumerKey,
consumerSecret: m.ConsumerSecret,
baseURL: m.BaseURL,
client: client,
}
}
// makeRequest performs all the http requests for the specific app
func (m *Mpesa) makeRequest(req *http.Request) ([]byte, error) {
resp, err := m.client.Do(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
The
Mpesa
struct will store the details about an app and the base url to be used since it changes based on the environment. The fields are unexported since we will only be using them within the package.The
NewMpesa
function is a constructor that we'll set up and return an instance ofMpesa
.makeRequest
will handle the sending of http requests for the app.
Generating Access Token
In order to interact with the MPesa APIs, we will need to provide an access token to authenticate our requests which is generated using Consumer Key and Consumer Secret generated when creating a new app.
To get these credentials, click on MY APPS tab to list all your apps and copy the Consumer Key and Consumer Secret keys for the app we created here.
In order to generate the access token, we will add a new struct to handle the processing of the response so add the snippet below:
// MpesaAccessTokenResponse is the response sent back by Safaricom when we make a request to generate a token
type MpesaAccessTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn string `json:"expires_in"`
RequestID string `json:"requestId"`
ErrorCode string `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
}
The above fields found can be found here on the developer's portal. We will then create a new method generateAccessToken
for generating the access token.
// generateAccessToken sends a http request to generate new access token
func (m *Mpesa) generateAccessToken() (*MpesaAccessTokenResponse, error) {
url := fmt.Sprintf("%s/oauth/v1/generate?grant_type=client_credentials", m.baseURL)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(m.consumerKey, m.consumerSecret)
req.Header.Set("Content-Type", "application/json")
resp, err := m.makeRequest(req)
if err != nil {
return nil, err
}
accessTokenResponse := new(MpesaAccessTokenResponse)
if err := json.Unmarshal(resp, &accessTokenResponse); err != nil {
return nil, err
}
return accessTokenResponse, nil
}
- The above method creates a new http request then set uses
SetBasicAuth
method to set the request's Authorization header to use HTTP Basic Authentication with the provided consumer_key and consumer_secret. - We then make a http request and unmarshal the response to
MpesaAccessTokenResponse
struct.
We can then test if we can be able to generate an access token by adding the following block to our main method.
func main() {
mpesa := NewMpesa(&MpesaOpts{
ConsumerKey: "your-consumer-key-goes-here",
ConsumerSecret: "your-consumer-secret-goes-here",
BaseURL: "https://sandbox.safaricom.co.ke",
})
accessTokenResponse, err := mpesa.generateAccessToken()
if err != nil {
log.Fatalln(err)
}
fmt.Printf("%+v\n", accessTokenResponse)
}
If the credentials are valid, you should get a response similar to this one.
&{AccessToken:TchBwjZgTIq17dVbi3SRo2OsWvNB ExpiresIn:3599 RequestID: ErrorCode: ErrorMessage:}
If you are having any issues, you can refer to the documentation
for more details.
Integrating Lipa na Mpesa
Lipa na M-Pesa Online Payment API is used to initiate an MPesa transaction on behalf of a customer using STK Push. This API simplifies the checkout process since the customer doesn't need to enter the account number or the paybill (shortcode) they're paying to.
In order to make the request, we will add two new structs as follows:
// STKPushRequestBody is the body with the parameters to be used to initiate an STK push request
type STKPushRequestBody struct {
BusinessShortCode string `json:"BusinessShortCode"`
Password string `json:"Password"`
Timestamp string `json:"Timestamp"`
TransactionType string `json:"TransactionType"`
Amount string `json:"Amount"`
PartyA string `json:"PartyA"`
PartyB string `json:"PartyB"`
PhoneNumber string `json:"PhoneNumber"`
CallBackURL string `json:"CallBackURL"`
AccountReference string `json:"AccountReference"`
TransactionDesc string `json:"TransactionDesc"`
}
// STKPushRequestResponse is the response sent back after initiating an STK push request.
type STKPushRequestResponse struct {
MerchantRequestID string `json:"MerchantRequestID"`
CheckoutRequestID string `json:"CheckoutRequestID"`
ResponseCode string `json:"ResponseCode"`
ResponseDescription string `json:"ResponseDescription"`
CustomerMessage string `json:"CustomerMessage"`
RequestID string `json:"requestId"`
ErrorCode string `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
}
STKPushRequestBody
struct has the values required to build the request body. To learn more about the parameters,
please check out this link.STKPushRequestResponse
struct is used to process the response after initiating a request.
To make the request, we will create a new method InitiateSTKPushRequest
as follows
// InitiateSTKPushRequest makes a http request performing an STK push request
func (m *Mpesa) InitiateSTKPushRequest(body *STKPushRequestBody) (*STKPushRequestResponse, error) {
url := fmt.Sprintf("%s/mpesa/stkpush/v1/processrequest", m.baseURL)
requestBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(requestBody))
if err != nil {
return nil, err
}
accessTokenResponse, err := m.generateAccessToken()
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessTokenResponse.AccessToken))
resp, err := m.makeRequest(req)
if err != nil {
return nil, err
}
stkPushResponse := new(STKPushRequestResponse)
if err := json.Unmarshal(resp, &stkPushResponse); err != nil {
return nil, err
}
return stkPushResponse, nil
}
- The above method handles the task of making the request to initiate the STK push request. After building the request, we call
generateAccessToken
method to attempt to generate an access token before making the request then set a newAuthorization: Bearer
header with our access token. - After making the request, we unmarshal our response body to create a new
STKPushRequestResponse
.
In order to test if we can make the request successfully and receive the STK push on our phone, we will update our main method as follows. The test credentials can be found here.
Select an app on the simulator, then scroll down and click on the icon on the bottom left to open a modal with the test credentials.
func main() {
mpesa := NewMpesa(&MpesaOpts{
ConsumerKey: "your-consumer-key-goes-here",
ConsumerSecret: "your-consumer-secret-goes-here",
BaseURL: "https://sandbox.safaricom.co.ke",
})
// The expected format is YYYYMMDDHHmmss
timestamp := time.Now().Format("20060102150405")
shortcode, passkey := "your-business-short-code-goes-here", "your-pass-key-goes-here"
// base64 encoding of the shortcode + passkey + timestamp
passwordToEncode := fmt.Sprintf("%s%s%s", shortcode, passkey, timestamp)
password := base64.StdEncoding.EncodeToString([]byte(passwordToEncode))
response, err := mpesa.InitiateSTKPushRequest(&STKPushRequestBody{
BusinessShortCode: shortcode,
Password: password,
Timestamp: timestamp,
TransactionType: "CustomerPayBillOnline",
Amount: "10", // Amount to be charged when checking out
PartyA: "your-phone-number-goes-here", // 2547XXXXXXXX
PartyB: shortcode,
PhoneNumber: "your-phone-number-goes-here", // 2547XXXXXXXX
CallBackURL: "your-endpoint-to-receive-the-callback-on", // https://
AccountReference: "TEST",
TransactionDesc: "Payment via STK push.",
})
if err != nil {
log.Fatalln(err)
}
fmt.Printf("%+v\n", response)
}
- We are getting the current timestamp then we concatenate the
shortcode + passkey + timestamp
to create the password used for encrypting the request sent which isbase64
encoded string. - We then build our request body then make the request. If the request was successfully, you will receive an output similar to the one below.
&{MerchantRequestID:32690-4496353-2 CheckoutRequestID:ws_CO_081020211030225587 ResponseCode:0 ResponseDescription:Success. Request accepted for processing CustomerMessage:Success. Request accepted for processing RequestID: ErrorCode: ErrorMessage:}
If the request failed, you will receive the response similar to this.
&{MerchantRequestID: CheckoutRequestID: ResponseCode: ResponseDescription: CustomerMessage: RequestID:1469-4474264-2 ErrorCode:400.002.02 ErrorMessage:Bad Request - Invalid BusinessShortCode}
To get more insights on the error messages, please refer to the documentation.
Since the request is asynchronous, we pass the CallBackURL
on the request body where we will receive the results of the transaction showing if it was processed successfully or not. Let's see how we can process the results on our callback URL.
We will use the default http server to process the payment response so let's create one. Add a new function called httpServer
and update is as follows:
func httpServer() {
stkPushCallbackHandler := func(w http.ResponseWriter, req *http.Request) {
payload := new(STKPushCallbackResponse)
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
log.Fatalln(err)
}
fmt.Printf("%+v\n", payload)
fmt.Printf("Result Code: %d\n", payload.Body.StkCallback.ResultCode)
fmt.Printf("Result Description: %s\n", payload.Body.StkCallback.ResultDesc)
// TODO PROCESS THE RESPONSE
}
addr := ":8080"
http.HandleFunc("/stk-push-callback", stkPushCallbackHandler)
log.Printf("[*] Server started and running on port %s", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
- The above function creates and starts a http server on port
8080
. - We then add
stkPushCallbackHandler
which attempts to read and decode the request body. Any successful transaction will have aResultCode
of0
.
Remember to perform validation of the
MpesaReceiptNumber
against theCheckoutRequestID
andMerchantRequestID
which you can store after making the STK push request.
To test if we can make process the callback, update the main method to the code below then run the application.
func main() {
httpServer()
}
Open Postman and create a new POST
request to http://localhost:8080/stk-push-callback
. Click on the BODY
tab then select the raw
option then select JSON
from the dropdown and paste the payload below then click on the SEND button then check your terminal output.
{
"Body":{
"stkCallback":{
"ResultCode":0,
"ResultDesc":"The service request is processed successfully.",
"CallbackMetadata":{
"Item":[
{
"Name":"Amount",
"Value":1
},
{
"Name":"MpesaReceiptNumber",
"Value":"PJ14NE27PI"
},
{
"Name":"Balance"
},
{
"Name":"TransactionDate",
"Value":20211009115859
},
{
"Name":"PhoneNumber",
"Value":254700000000
}
]
},
"CheckoutRequestID":"ws_CO_01102021191752608272",
"MerchantRequestID":"96131-41939755-1"
}
}
}
Thank you for following along. Next we will implement the B2C API.
Top comments (0)