MPesa APIs Integration in Go
Tags: go, mpesa, tutorial, api
Published: 2021-10-08 10:00:23

**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
- [Go](https://go.dev/) installed on your local computer.
- [Postman](https://www.postman.com/downloads/) installed locally since we'll use it to test the APIs.

The completed code can be found [here](https://github.com/jwambugu/tutorials/tree/main/go/mpesa).

### What we will cover
- [Creating a developer's account](#creating-a-developers-account)
- [Creating an app](#creating-an-app)
- [Project Set Up](#project-set-up)
- [Generating Access Token](#generating-access-token)
- [Integrating Lipa na Mpesa (STK Push)](#integrating-lipa-na-mpesa)

### Creating a developer's account
In order to start consuming the APIs, you will need an account. To create one, please go to [Daraja](https://developer.safaricom.co.ke/) 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.

```bash
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**

```go
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 of `Mpesa`.
- `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](https://developer.safaricom.co.ke/MyApps) tab to list all your apps and copy the **Consumer Key** and **Consumer Secret** keys for the app we created [here](#creating-an-app).

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:

```go
// 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](https://developer.safaricom.co.ke/APIs/Authorization) on the developer's portal. We will then create a new method `generateAccessToken` for generating the access token.

```go
// 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.

```go
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.

```json
  &{AccessToken:TchBwjZgTIq17dVbi3SRo2OsWvNB ExpiresIn:3599 RequestID: ErrorCode: ErrorMessage:}
```

> If you are having any issues, you can refer to the [documentation](https://developer.safaricom.co.ke/APIs/Authorization)
> 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:
```go
// 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](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate).

- `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
```go
// 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 new `Authorization: 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](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate).

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.

```go
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 is `base64` 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.

```text
  &{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.
```text
  &{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](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate).

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:

```go
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 a `ResultCode` of `0`.

> Remember to perform validation of the `MpesaReceiptNumber` against the `CheckoutRequestID` and `MerchantRequestID` 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.
```go
func main() {
    httpServer()
}
```

Open [Postman](https://www.postman.com/downloads/) 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.

```json
{
  "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](https://developer.safaricom.co.ke/APIs/BusinessToCustomer).