Part Two: Integrating MPesa B2C API
Tags: go, mpesa, tutorial, api
Published: 2021-10-14 08:08:07

In the last [tutorial](https://dev.to/jwambugu/mpesa-apis-integration-in-go-180c), we learnt how to [set up a developers](https://dev.to/jwambugu/mpesa-apis-integration-in-go-180c#creating-a-developers-account), [generate an access token](https://dev.to/jwambugu/mpesa-apis-integration-in-go-180c#generating-access-token) and finally [integrated lipa na mpesa API](https://dev.to/jwambugu/mpesa-apis-integration-in-go-180c#integrating-lipa-na-mpesa).
In this part, we will learn how we can integrate the [B2C API](https://developer.safaricom.co.ke/APIs/BusinessToCustomer). This API allows you to make MPesa transactions between a paybill (shortcode) and a customer's phone number.

> This a continuation which assumes you have already set up your project as per the last tutorial.


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

### What we will cover
 - [Understanding the requirements](#understanding-the-requirements)
 - [Request setup](#request-setup)
 - [Generating security credentials](#generating-security-credentials)
 - [Making the B2C API request](#making-the-b2c-api-request)

### Understanding the requirements
There are the requirements for production app. You will need to access [MPesa Portal](https://org.ke.m-pesa.com/) which is now accessible without the need of the MPesa certificate 🎉🎉
 
- **Initiator username** which is the API operator's username set on the [portal](https://org.ke.m-pesa.com/).
- **Initiator Password** is the password for the API operator.

> To understand more about these requirements, please check the [documentation](https://developer.safaricom.co.ke/Documentation) under the B2C API.

### Request setup
In order to make the request, we will add two new structs as follows:

```go

// B2CRequestBody is the body with the parameters to be used to initiate a B2C request
type B2CRequestBody struct {
    InitiatorName      string `json:"InitiatorName"`
    SecurityCredential string `json:"SecurityCredential"`
    CommandID          string `json:"CommandID"`
    Amount             string `json:"Amount"`
    PartyA             string `json:"PartyA"`
    PartyB             string `json:"PartyB"`
    Remarks            string `json:"Remarks"`
    QueueTimeOutURL    string `json:"QueueTimeOutURL"`
    ResultURL          string `json:"ResultURL"`
    Occassion          string `json:"Occassion"`
}

// B2CRequestResponse is the response sent back after initiating a B2C request.
type B2CRequestResponse struct {
    ConversationID           string `json:"ConversationID"`
    OriginatorConversationID string `json:"OriginatorConversationID"`
    ResponseCode             string `json:"ResponseCode"`
    ResponseDescription      string `json:"ResponseDescription"`
    RequestID                string `json:"requestId"`
    ErrorCode                string `json:"errorCode"`
    ErrorMessage             string `json:"errorMessage"`
}
```

- `B2CRequestBody` 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/BusinessToCustomer).

- `B2CRequestResponse` struct is used to process the response after initiating a request.

### Generating security credentials
The B2C API requires us to provide `SecurityCredential` in the request body which can be generated by encrypting the API initiator password using the public key certificate provided by Safaricom.

**Please note that at the time of writing, the provided links to download the certificates were broken. If you encounter the same issue, please download them [here](https://github.com/jwambugu/tutorials/tree/main/go/mpesa).**

There are two sets of certificates, the [sandbox](https://developer.safaricom.co.ke/sites/default/files/cert/cert_sandbox/cert.cer) and [production](https://developer.safaricom.co.ke/sites/default/files/cert/cert_prod/cert.cer) used when your application has been approved to go live.
Go ahead and [download sandbox certificate](https://developer.safaricom.co.ke/sites/default/files/cert/cert_sandbox/cert.cer), then create `certificates` directory and move the downloaded certificate there.

To generate the `SecurityCredential`, we will create a helper function `GenerateSecurityCredentials` as follows:
```go
// getSecurityCredentials returns the encrypted password using the public key of the specified environment
func GenerateSecurityCredentials(password string, isOnProduction bool) (string, error) {
    path := "./certificates/production.cer"
    
    if !isOnProduction {
        path = "./certificates/sandbox.cer"
    }
    
    f, err := os.Open(path)
    if err != nil {
        return "", err
    }
    
    defer func(f *os.File) {
        _ = f.Close()
    }(f)
    
    contents, err := io.ReadAll(f)
    if err != nil {
        return "", err
    }
    
    block, _ := pem.Decode(contents)
    
    var cert *x509.Certificate
    
    cert, err = x509.ParseCertificate(block.Bytes)
    if err != nil {
        return "", err
    }
    
    rsaPublicKey := cert.PublicKey.(*rsa.PublicKey)
    reader := rand.Reader

    encryptedPayload, err := rsa.EncryptPKCS1v15(reader, rsaPublicKey, []byte(password))
    if err != nil {
        return "", err
    }
    
    securityCredentials := base64.StdEncoding.EncodeToString(encryptedPayload)
    return securityCredentials, nil
}
```
 - The function takes in a `password` which is the API initiator password and `isOnProduction` which is used to determine the certificate to use.
 - We attempt to read and parse the contents certificate to return an encrypted `password`.

We can test if the code works by updating our `main` method to the snippet below.
```go
func main() {
    securityCredentials, err := GenerateSecurityCredentials("test-password", false)
    if err != nil {
        log.Fatalln(err)
    }

    fmt.Printf("%+v\n", securityCredentials)
}
```

If we run our code, an output similar to the one below will be printed.
```json
NCV4uID/dA5OpNmgtwk2UJj1NXyEa0KNyhxm/8fb82Cojo6UQKaw1Lk2MHzD+kX3NXQppwaoTFjkPGNjlWWBO2VDu15IGZGWSOWdS6Wuqc8U8BqVWXcTLCMA5UujQBEfSJsIYROGdbeqUSLE88I/3NNH1nINDgI5A91LX/Fwouewy4S5aogcb9M+KrcVDcoNSkmCY+S4UFlUGMUPAIMxIzZh6z39LCXhM/ARItbJJXK6Nj31cmIimXE3UWTLq8uqHNwafDAidjgUenwTOeO2/pJ/x3rjbM3RyOHd2JTlM3eazBqSSIFh2YaVxrKtCvOtowMXPDQ//1ZKzErnFZtViw==
```

#### Making the B2C API request

For us to make the request, we will create two new methods `setupHttpRequestWithAuth` and `InitiateB2CRequest`.
```go
// setupHttpRequestWithAuth is a helper method aimed to create a http request adding
// the Authorization Bearer header with the access token for the Mpesa app.
func (m *Mpesa) setupHttpRequestWithAuth(method, url string, body []byte) (*http.Request, error) {
    req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
    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))
    
    return req, nil
}

// InitiateB2CRequest makes a http request performing a B2C payment request.
func (m *Mpesa) InitiateB2CRequest(body *B2CRequestBody) (*B2CRequestResponse, error) {
    url := fmt.Sprintf("%s/mpesa/b2c/v1/paymentrequest", m.baseURL)
    
    requestBody, err := json.Marshal(body)
    if err != nil {
        return nil, err
    }
    
    req, err := m.setupHttpRequestWithAuth(http.MethodPost, url, requestBody)
    if err != nil {
        return nil, err
    }
    
    resp, err := m.makeRequest(req)
    if err != nil {
        return nil, err
    }
    
    b2cResponse := new(B2CRequestResponse)
    if err := json.Unmarshal(resp, &b2cResponse); err != nil {
        return nil, err
    }
    
    return b2cResponse, nil
}
```
- `setupHttpRequestWithAuth` set ups and returns a http request with the authorization set up.
- `InitiateB2CRequest` makes a http request with the generated access token. After a successful request is made, we unmarshal the response body to create a new `B2CRequestResponse`

In order to test if we can make the request successfully, we will update our main method as follows. The test credentials can be found [here](https://developer.safaricom.co.ke/APIs/BusinessToCustomer).

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",
    })

    securityCredentials, err := GenerateSecurityCredentials("your-initiator-password", false)
    if err != nil {
        log.Fatalln(err)
    }

    response, err := mpesa.InitiateB2CRequest(&B2CRequestBody{
        InitiatorName:      "your-initiator-name-goes-here",
        SecurityCredential: securityCredentials,
        CommandID:          "BusinessPayment",
        Amount:             "10",
        PartyA:             "your-business-short-code-goes-here",
        PartyB:             "your-phone-number-goes-here",
        Remarks:            "your-remarks-go-here",
        QueueTimeOutURL:    "your-endpoint-to-receive-notifications-in-case-request-times-out",
        ResultURL:          "your-endpoint-to-receive-the-notifications",
        Occassion:          "your-occasion-goes-here",
    })

    if err != nil {
        log.Fatalln(err)
    }

    fmt.Printf("%+v\n", response)
}
```
 - We are using the `GenerateSecurityCredentials` to get the security credentials using the **InitiatorPassword** from the test credentials. The second parameter `isOnProduction` is then set to false since we are on sandbox.
 - 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.

```json
  &{ConversationID:AG_20211014_00007410b1727ef09241 OriginatorConversationID:46629-16123460-2 ResponseCode:0 ResponseDescription:Accept the service request successfully. RequestID: ErrorCode: ErrorMessage:}
```

To get more insights on the error messages, please refer to the [documentation](https://developer.safaricom.co.ke/APIs/BusinessToCustomer).

Since the request is asynchronous, we pass `ResultURL` webhook for receiving notifications upon processing of the payment request. The `QueueTimeOutURL` is a webhook used when the payment request has timed out while awaiting processing.

To test if we can process the callback, we will create a new handler on the `httpServer` function and add a new struct `B2CCallbackResponse`  as follows:

```go
// B2CCallbackResponse has the results of the callback data sent once we successfully make a B2C request.
type B2CCallbackResponse struct {
	Result struct {
        ResultType               int    `json:"ResultType"`
        ResultCode               int    `json:"ResultCode"`
        ResultDesc               string `json:"ResultDesc"`
        OriginatorConversationID string `json:"OriginatorConversationID"`
        ConversationID           string `json:"ConversationID"`
        TransactionID            string `json:"TransactionID"`
        ResultParameters         struct {
            ResultParameter []struct {
                Key   string      `json:"Key"`
                Value interface{} `json:"Value"`
            } `json:"ResultParameter"`
        } `json:"ResultParameters"`
        ReferenceData struct {
            ReferenceItem struct {
                Key   string `json:"Key"`
                Value string `json:"Value"`
            } `json:"ReferenceItem"`
        } `json:"ReferenceData"`
    } `json:"Result"`
}
```

```go
func httpServer() {
	b2cRequestCallbackHandler := func(w http.ResponseWriter, req *http.Request) {
        payload := new(B2CCallbackResponse)
        
        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.Result.ResultCode)
        fmt.Printf("Result Description: %s\n", payload.Result.ResultDesc)
	}

	addr := ":8080"
	http.HandleFunc("/b2c-callback", b2cRequestCallbackHandler)

	log.Printf("[*] Server started and running on port %s", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}
```

- `httpServer` creates and starts a http server on port `8080`. 
- `b2cRequestCallbackHandler` attempts to read and decode the request body. Any successful transaction will have a `ResultCode` of `0`.

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/b2c-callback`.
- Click on the `BODY` tab then select the `raw` option then select `JSON` from the dropdown and paste the payload below.
- Click on the **SEND** button then check your terminal output for the printed response body.

```json
{
   "Result":{
      "ResultType":0,
      "ResultCode":0,
      "ResultDesc":"The service request is processed successfully.",
      "OriginatorConversationID":"46629-16123460-2",
      "ConversationID":"AG_20211014_00007410b1727ef09241",
      "TransactionID":"PJ14NE27PI",
      "ResultParameters":{
         "ResultParameter":[
            {
               "Key":"TransactionAmount",
               "Value":10
            },
            {
               "Key":"TransactionReceipt",
               "Value":"PJ14NE27PI"
            },
            {
               "Key":"B2CRecipientIsRegisteredCustomer",
               "Value":"Y"
            },
            {
               "Key":"B2CChargesPaidAccountAvailableFunds",
               "Value":120.00
            },
            {
               "Key":"ReceiverPartyPublicName",
               "Value":"254700000000 - Go Gopher"
            },
            {
               "Key":"TransactionCompletedDateTime",
               "Value":"14.10.2021 10:26:50"
            },
            {
               "Key":"B2CUtilityAccountAvailableFunds",
               "Value":120.00
            },
            {
               "Key":"B2CWorkingAccountAvailableFunds",
               "Value":120.00
            }
         ]
      },
      "ReferenceData":{
         "ReferenceItem":{
            "Key":"QueueTimeoutURL",
            "Value":"https:\/\/internalsandbox.safaricom.co.ke\/mpesa\/b2cresults\/v1\/submit"
         }
      }
   }
}
```

That's all for this part. Thank you for following along.