Skip to content

Commit

Permalink
implement ValidateAccountHolderStatus and GetBasicUserinfo
Browse files Browse the repository at this point in the history
  • Loading branch information
AchoArnold committed Oct 31, 2024
1 parent 97f3ea8 commit a413d73
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 7 deletions.
29 changes: 29 additions & 0 deletions collection.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
package mtnmomo

import "strings"

type AccountHolderIDType string

const (
// AccountHolderIDTypeMSISDN is the account holder ID type for a mobile number
AccountHolderIDTypeMSISDN AccountHolderIDType = "msisdn"

// AccountHolderIDTypeEMAIL is the account holder ID type for an email address
AccountHolderIDTypeEMAIL AccountHolderIDType = "email"
)

// AccountHolderStatus is the status of an account holder
type AccountHolderStatus struct {
IsActive bool `json:"result"`
}

// BasicUserInfo contains personal information of an account holder
type BasicUserInfo struct {
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Sub string `json:"sub"`
}

// FullName returns the full name of the account holder
func (info *BasicUserInfo) FullName() string {
return strings.TrimSpace(info.FamilyName + " " + info.GivenName)
}

// RequestToPayParams is the set of parameters used when creating a payment request
type RequestToPayParams struct {
Amount string `json:"amount"`
Expand Down
63 changes: 63 additions & 0 deletions collection_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mtnmomo
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
Expand Down Expand Up @@ -129,6 +130,68 @@ func (service *collectionService) GetAccountBalance(ctx context.Context) (*Accou
return balance, response, err
}

// ValidateAccountHolderStatus is used to check if an account holder is registered and active in the system.
//
// API Docs: https://momodeveloper.mtn.com/api-details#api=collection&operation=ValidateAccountHolderStatus
func (service *collectionService) ValidateAccountHolderStatus(ctx context.Context, IDType AccountHolderIDType, ID string) (*AccountHolderStatus, *Response, error) {
err := service.refreshToken(ctx)
if err != nil {
return nil, nil, err
}

uri := fmt.Sprintf("/collection/v1_0/accountholder/%s/%s/active", IDType, ID)
request, err := service.client.newRequest(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, nil, err
}

service.client.addAccessToken(request)
service.client.addTargetEnvironment(request)

response, err := service.client.do(request)
if err != nil {
return nil, response, err
}

status := new(AccountHolderStatus)
if err = json.Unmarshal(*response.Body, status); err != nil {
return nil, response, err
}

return status, response, err
}

// GetBasicUserinfo returns personal information of the account holder.
//
// API Docs: https://momodeveloper.mtn.com/api-details#api=collection&operation=GetBasicUserinfo
func (service *collectionService) GetBasicUserinfo(ctx context.Context, IDType AccountHolderIDType, ID string) (*BasicUserInfo, *Response, error) {
err := service.refreshToken(ctx)
if err != nil {
return nil, nil, err
}

uri := fmt.Sprintf("/collection/v1_0/accountholder/%s/%s/basicuserinfo", IDType, ID)
request, err := service.client.newRequest(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, nil, err
}

service.client.addAccessToken(request)
service.client.addTargetEnvironment(request)

response, err := service.client.do(request)
if err != nil {
return nil, response, err
}

status := new(BasicUserInfo)
if err = json.Unmarshal(*response.Body, status); err != nil {
return nil, response, err
}

return status, response, err
}

func (service *collectionService) tokenIsValid() bool {
return time.Now().UTC().Unix() < service.client.accessTokenExpiresAt
}
Expand Down
104 changes: 104 additions & 0 deletions collection_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,107 @@ func TestCollectionService_GetAccountBalance(t *testing.T) {
// Teardown
server.Close()
}

func TestCollectionService_ValidateAccountHolderStatus(t *testing.T) {
// Setup
t.Parallel()

// Arrange
requests := make([]*http.Request, 0)
responses := [][]byte{stubs.CollectionToken(), stubs.CollectionValidateAccountHolderStatus()}
server := helpers.MakeRequestCapturingTestServer(http.StatusOK, responses, &requests)
client := New(
WithBaseURL(server.URL),
WithSubscriptionKey(testSubscriptionKey),
WithAPIUser(testAPIUser),
WithAPIKey(testAPIKey),
)

// Act
status, response, err := client.Collection.ValidateAccountHolderStatus(context.Background(), AccountHolderIDTypeMSISDN, "23777777777")

// Assert
assert.Nil(t, err)

assert.GreaterOrEqual(t, len(requests), 1)
request := requests[len(requests)-1]
assert.Equal(t, "/collection/v1_0/accountholder/msisdn/23777777777/active", request.URL.Path)
assert.True(t, strings.HasPrefix(request.Header.Get("Authorization"), "Bearer"))
assert.Equal(t, testSubscriptionKey, request.Header.Get(headerKeySubscriptionKey))
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)

assert.True(t, status.IsActive)

// Teardown
server.Close()
}

func TestCollectionService_ValidateAccountHolderStatusWithInternalServerError(t *testing.T) {
// Setup
t.Parallel()

// Arrange
server := helpers.MakeTestServer(http.StatusInternalServerError, nil)
client := New(WithBaseURL(server.URL))

// Act
_, _, err := client.Collection.ValidateAccountHolderStatus(context.Background(), AccountHolderIDTypeMSISDN, "23777777777")

// Assert
assert.NotNil(t, err)

// Teardown
server.Close()
}

func TestCollectionService_GetBasicUserinfo(t *testing.T) {
// Setup
t.Parallel()

// Arrange
requests := make([]*http.Request, 0)
responses := [][]byte{stubs.CollectionToken(), stubs.CollectionGetBasicUserinfo()}
server := helpers.MakeRequestCapturingTestServer(http.StatusOK, responses, &requests)
client := New(
WithBaseURL(server.URL),
WithSubscriptionKey(testSubscriptionKey),
WithAPIUser(testAPIUser),
WithAPIKey(testAPIKey),
)

// Act
userInfo, response, err := client.Collection.GetBasicUserinfo(context.Background(), AccountHolderIDTypeMSISDN, "23777777777")

// Assert
assert.Nil(t, err)

assert.GreaterOrEqual(t, len(requests), 1)
request := requests[len(requests)-1]
assert.Equal(t, "/collection/v1_0/accountholder/msisdn/23777777777/basicuserinfo", request.URL.Path)
assert.True(t, strings.HasPrefix(request.Header.Get("Authorization"), "Bearer"))
assert.Equal(t, testSubscriptionKey, request.Header.Get(headerKeySubscriptionKey))
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)

assert.Equal(t, "JOHN DOE", userInfo.FullName())

// Teardown
server.Close()
}

func TestCollectionService_GetBasicUserinfoWithInternalServerError(t *testing.T) {
// Setup
t.Parallel()

// Arrange
server := helpers.MakeTestServer(http.StatusInternalServerError, nil)
client := New(WithBaseURL(server.URL))

// Act
_, _, err := client.Collection.GetBasicUserinfo(context.Background(), AccountHolderIDTypeMSISDN, "23777777777")

// Assert
assert.NotNil(t, err)

// Teardown
server.Close()
}
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
module github.com/NdoleStudio/mtnmomo-go

go 1.17
go 1.18

require (
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2
github.com/davecgh/go-spew v1.1.1
github.com/dustin/go-humanize v1.0.1
github.com/fatih/color v1.17.0
github.com/fatih/color v1.18.0
github.com/google/uuid v1.3.0
github.com/stretchr/testify v1.8.1
)

require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/sys v0.26.0 // indirect
)

require (
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand All @@ -34,8 +36,11 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8 changes: 4 additions & 4 deletions internal/helpers/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package helpers
import (
"bytes"
"context"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
)
Expand All @@ -26,12 +26,12 @@ func MakeRequestCapturingTestServer(responseCode int, responses [][]byte, reques
clonedRequest := request.Clone(context.Background())

// clone body
body, err := ioutil.ReadAll(request.Body)
body, err := io.ReadAll(request.Body)
if err != nil {
panic(err)
}
request.Body = ioutil.NopCloser(bytes.NewReader(body))
clonedRequest.Body = ioutil.NopCloser(bytes.NewReader(body))
request.Body = io.NopCloser(bytes.NewReader(body))
clonedRequest.Body = io.NopCloser(bytes.NewReader(body))

*requests = append(*requests, clonedRequest)

Expand Down
20 changes: 20 additions & 0 deletions internal/stubs/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,23 @@ func CollectionAccountBalance() []byte {
}
`)
}

// CollectionValidateAccountHolderStatus is a dummy json response for the `/collection/v1_0/accountholder/{accountHolderIdType}/{accountHolderId}/active` endpoint
func CollectionValidateAccountHolderStatus() []byte {
return []byte(`
{
"result": true
}
`)
}

// CollectionGetBasicUserinfo is a dummy json response for the `/collection/v1_0/accountholder/{accountHolderIdType}/{accountHolderId}/basicuserinfo` endpoint
func CollectionGetBasicUserinfo() []byte {
return []byte(`
{
"family_name": "JOHN",
"given_name": "DOE",
"sub": "1111111"
}
`)
}

0 comments on commit a413d73

Please sign in to comment.