Skip to content

Commit

Permalink
Add Business Pay Bill
Browse files Browse the repository at this point in the history
  • Loading branch information
jwambugu committed Jan 30, 2024
1 parent 0315163 commit 817795f
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 0 deletions.
1 change: 1 addition & 0 deletions mpesa.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ func (m *Mpesa) BusinessPayBill(ctx context.Context, initiatorPwd string, req Bu
}

req.SecurityCredential = securityCredential
req.CommandID = BusinessPayBill
req.RecieverIdentifierType = ShortcodeIdentifierType
req.SenderIdentifierType = ShortcodeIdentifierType

Expand Down
177 changes: 177 additions & 0 deletions mpesa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1338,3 +1338,180 @@ func TestMpesa_GetAccountBalance(t *testing.T) {
})
}
}

func TestMpesa_BusinessPayBill(t *testing.T) {
var (
ctx = context.Background()
initatorPassword = "random-string"
businesPaybillReq = BusinessPayBillRequest{
AccountReference: "600992",
Amount: 10,
Initiator: "testapi",
Occasion: "Test",
PartyA: 600992,
PartyB: 600992,
QueueTimeOutURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
Remarks: "Test remarks",
Requester: 254700000000,
ResultURL: "https://webhook.site/62daf156-31dc-4b07-ac41-698dbfadaa4b",
}
)

tests := []struct {
name string
businesPaybillReq BusinessPayBillRequest
env Environment
mock func(t *testing.T, app *Mpesa, c *mockHttpClient, businesPaybillReq BusinessPayBillRequest)
requestsCount int
}{
{
name: "generates valid security credentials and makes the request successfully on sandbox",
env: EnvironmentSandbox,
businesPaybillReq: businesPaybillReq,
mock: func(t *testing.T, app *Mpesa, c *mockHttpClient, businesPaybillReq BusinessPayBillRequest) {
c.MockRequest(app.endpointBusinessPayBill(), func() (status int, body string) {
req := c.requests[1]

require.Equal(t, "application/json", req.Header.Get("Content-Type"))
wantAuthorizationHeader := `Bearer ` + app.cache[testConsumerKey].AccessToken
require.Equal(t, wantAuthorizationHeader, req.Header.Get("Authorization"))

var reqParams BusinessPayBillRequest

err := json.NewDecoder(req.Body).Decode(&reqParams)
require.NoError(t, err)
require.NotEmpty(t, reqParams.SecurityCredential) // TODO: verify the security credential
require.Equal(t, ShortcodeIdentifierType, reqParams.RecieverIdentifierType)
require.Equal(t, ShortcodeIdentifierType, reqParams.SenderIdentifierType)
require.Equal(t, BusinessPayBill, reqParams.CommandID)

return http.StatusOK, `{
"OriginatorConversationID": "2ba8-4165-beca-292db11f9ef878061",
"ConversationID": "AG_20240122_2010332bae9191b3d522",
"ResponseCode": "0",
"ResponseDescription": "Accept the service request successfully."
}`
})

res, err := app.BusinessPayBill(ctx, initatorPassword, businesPaybillReq)
require.NoError(t, err)
require.NotNil(t, res)
require.Contains(t, res.ResponseDescription, "Accept the service request successfully")
},
requestsCount: 2,
},
{
name: "generates valid security credentials and makes the request successfully on production",
env: EnvironmentProduction,
businesPaybillReq: businesPaybillReq,
mock: func(t *testing.T, app *Mpesa, c *mockHttpClient, businesPaybillReq BusinessPayBillRequest) {
c.MockRequest(app.endpointBusinessPayBill(), func() (status int, body string) {
req := c.requests[1]

require.Equal(t, "application/json", req.Header.Get("Content-Type"))
wantAuthorizationHeader := `Bearer ` + app.cache[testConsumerKey].AccessToken
require.Equal(t, wantAuthorizationHeader, req.Header.Get("Authorization"))

var reqParams BusinessPayBillRequest

err := json.NewDecoder(req.Body).Decode(&reqParams)
require.NoError(t, err)
require.NotEmpty(t, reqParams.SecurityCredential) // TODO: verify the security credential

return http.StatusOK, `{
"OriginatorConversationID": "2ba8-4165-beca-292db11f9ef878061",
"ConversationID": "AG_20240122_2010332bae9191b3d522",
"ResponseCode": "0",
"ResponseDescription": "Accept the service request successfully."
}`
})

res, err := app.BusinessPayBill(ctx, initatorPassword, businesPaybillReq)
require.NoError(t, err)
require.NotNil(t, res)
require.Contains(t, res.ResponseDescription, "Accept the service request successfully")
},
requestsCount: 2,
},
{
name: "request fails if no initiator password is provided",
mock: func(t *testing.T, app *Mpesa, c *mockHttpClient, businesPaybillReq BusinessPayBillRequest) {
res, err := app.BusinessPayBill(ctx, "", businesPaybillReq)
require.NotNil(t, err)
require.EqualError(t, err, ErrInvalidInitiatorPassword.Error())
require.Nil(t, res)
},
requestsCount: 1,
},
{
name: "request fails if invalid queue timeout URL is passed",
businesPaybillReq: BusinessPayBillRequest{QueueTimeOutURL: "http://example.com"},
mock: func(t *testing.T, app *Mpesa, c *mockHttpClient, businesPaybillReq BusinessPayBillRequest) {
res, err := app.BusinessPayBill(ctx, initatorPassword, businesPaybillReq)
require.NotNil(t, err)
require.Contains(t, err.Error(), "must use \"https\"")
require.Nil(t, res)
},
requestsCount: 1,
},
{
name: "request fails if invalid result URL is passed",
businesPaybillReq: BusinessPayBillRequest{
QueueTimeOutURL: "https://example.com",
ResultURL: "http://example.com",
},
mock: func(t *testing.T, app *Mpesa, c *mockHttpClient, businesPaybillReq BusinessPayBillRequest) {
res, err := app.BusinessPayBill(ctx, initatorPassword, businesPaybillReq)
require.NotNil(t, err)
require.Contains(t, err.Error(), "must use \"https\"")
require.Nil(t, res)
},
requestsCount: 1,
},
{
name: "request fails with an error code",
businesPaybillReq: businesPaybillReq,
mock: func(t *testing.T, app *Mpesa, c *mockHttpClient, businesPaybillReq BusinessPayBillRequest) {
c.MockRequest(app.endpointBusinessPayBill(), func() (status int, body string) {
return http.StatusBadRequest, `
{
"requestId": "11728-2929992-1",
"errorCode": "401.002.01",
"errorMessage": "Error Occurred - Invalid Access Token - BJGFGOXv5aZnw90KkA4TDtu4Xdyf"
}`
})

res, err := app.BusinessPayBill(ctx, initatorPassword, businesPaybillReq)
require.NotNil(t, err)
require.Nil(t, res)
require.Contains(t, err.Error(), "401.002.01")
},
requestsCount: 2,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

var (
cl = newMockHttpClient()
app = NewApp(cl, testConsumerKey, testConsumerSecret, tc.env)
)

cl.MockRequest(app.endpointAuth(), func() (status int, body string) {
return http.StatusOK, `
{
"access_token": "0A0v8OgxqqoocblflR58m9chMdnU",
"expires_in": "3599"
}`
})

tc.mock(t, app, cl, tc.businesPaybillReq)
_, err := app.GenerateAccessToken(ctx)
require.NoError(t, err)
require.Len(t, cl.requests, tc.requestsCount)
})
}
}

0 comments on commit 817795f

Please sign in to comment.