Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unmarshal B2CCallback and refactor structs #18

Merged
merged 1 commit into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions mpesa.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,10 @@ func (m *Mpesa) STKPush(ctx context.Context, passkey string, stkReq STKPushReque
}

// UnmarshalSTKPushCallback decodes the provided value to STKPushCallback.
func UnmarshalSTKPushCallback(in interface{}) (out *STKPushCallback, err error) {
var b []byte

switch in := in.(type) {
case string:
b = []byte(in)
default:
if b, err = json.Marshal(in); err != nil {
return nil, err
}
func UnmarshalSTKPushCallback(in interface{}) (*STKPushCallback, error) {
b, err := toBytes(in)
if err != nil {
return nil, fmt.Errorf("mpesa: error unmarshing input - %v", err)
}

var callback STKPushCallback
Expand Down Expand Up @@ -288,3 +282,18 @@ func (m *Mpesa) B2C(ctx context.Context, initiatorPwd string, b2cReq B2CRequest)

return &resp, nil
}

// UnmarshalB2CCallback decodes the provided value to B2CCallback
func UnmarshalB2CCallback(in interface{}) (*B2CCallback, error) {
b, err := toBytes(in)
if err != nil {
return nil, fmt.Errorf("mpesa: error unmarshing input - %v", err)
}

var callback B2CCallback
if err := json.Unmarshal(b, &callback); err != nil {
return nil, fmt.Errorf("mpesa: error unmarshling stk push callback - %v", err)
}

return &callback, nil
}
111 changes: 99 additions & 12 deletions mpesa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,8 @@ func TestMpesa_LipaNaMpesaOnline(t *testing.T) {

func TestUnmarshalSTKPushCallback(t *testing.T) {
tests := []struct {
name string
input interface{}
wantError bool
name string
input interface{}
}{
{
name: "it can unmarshal a successful transaction callback string",
Expand Down Expand Up @@ -277,10 +276,10 @@ func TestUnmarshalSTKPushCallback(t *testing.T) {
}`,
},
{
name: "it can unmarshal a unsuccessful transaction callback struct",
name: "it can unmarshal an unsuccessful transaction callback struct",
input: STKPushCallback{
Body: STKPushCallbackBody{
StkCallback: StkCallback{
STKCallback: STKCallback{
MerchantRequestID: "29115-34620561-1",
CheckoutRequestID: "ws_CO_191220191020363925",
ResultCode: 1032,
Expand All @@ -297,15 +296,9 @@ func TestUnmarshalSTKPushCallback(t *testing.T) {
t.Parallel()

callback, err := UnmarshalSTKPushCallback(tc.input)
if tc.wantError {
require.Error(t, err)
require.Nil(t, callback)
return
}

require.NoError(t, err)
require.NotNil(t, callback)
require.Equal(t, "ws_CO_191220191020363925", callback.Body.StkCallback.CheckoutRequestID)
require.Equal(t, "ws_CO_191220191020363925", callback.Body.STKCallback.CheckoutRequestID)
})
}
}
Expand Down Expand Up @@ -455,3 +448,97 @@ func TestMpesa_B2C(t *testing.T) {
})
}
}

func TestUnmarshalB2CCallback(t *testing.T) {
tests := []struct {
name string
input interface{}
}{
{
name: "it can unmarshal a successful transaction callback string",
input: `
{
"Result": {
"ResultType": 0,
"ResultCode": 0,
"ResultDesc": "The service request is processed successfully.",
"OriginatorConversationID": "10571-7910404-1",
"ConversationID": "AG_20191219_00004e48cf7e3533f581",
"TransactionID": "NLJ41HAY6Q",
"ResultParameters": {
"ResultParameter": [
{
"Key": "TransactionAmount",
"Value": 10
},
{
"Key": "TransactionReceipt",
"Value": "NLJ41HAY6Q"
},
{
"Key": "B2CRecipientIsRegisteredCustomer",
"Value": "Y"
},
{
"Key": "B2CChargesPaidAccountAvailableFunds",
"Value": -4510.00
},
{
"Key": "ReceiverPartyPublicName",
"Value": "254708374149 - John Doe"
},
{
"Key": "TransactionCompletedDateTime",
"Value": "19.12.2019 11:45:50"
},
{
"Key": "B2CUtilityAccountAvailableFunds",
"Value": 10116.00
},
{
"Key": "B2CWorkingAccountAvailableFunds",
"Value": 900000.00
}
]
},
"ReferenceData": {
"ReferenceItem": {
"Key": "QueueTimeoutURL",
"Value": "https:\/\/internalsandbox.safaricom.co.ke\/mpesa\/b2cresults\/v1\/submit"
}
}
}
}`,
},
{
name: "it can unmarshal an unsuccessful transaction callback struct",
input: B2CCallback{
Result: B2CCallbackResult{
ResultType: 0,
ResultCode: 0,
ResultDesc: "The initiator information is invalid.",
OriginatorConversationID: "29112-34801843-1",
ConversationID: "AG_20191219_00004e48cf7e3533f581",
TransactionID: "NLJ41HAY6Q",
ReferenceData: B2CReferenceData{
ReferenceItem: B2CReferenceItem{
Key: "QueueTimeoutURL",
Value: "https:\\/\\/internalsandbox.safaricom.co.ke\\/mpesa\\/b2cresults\\/v1\\/submit",
},
},
},
},
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
callback, err := UnmarshalB2CCallback(tc.input)
require.NoError(t, err)
require.NotNil(t, callback)
require.Equal(t, "AG_20191219_00004e48cf7e3533f581", callback.Result.ConversationID)
require.Equal(t, "QueueTimeoutURL", callback.Result.ReferenceData.ReferenceItem.Key)
})
}
}
84 changes: 73 additions & 11 deletions structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,18 @@ type (
ErrorMessage string `json:"errorMessage,omitempty"`
}

StkCallback struct {
STKCallbackItem struct {
Name string `json:"Name"`
Value interface{} `json:"Value,omitempty"`
}

STKCallbackMetadata struct {
// Item is a JSON Array, within the CallbackMetadata, that holds additional transaction details in
// JSON objects. It is only returned for Successful transaction as part of CallbackMetadata
Item []STKCallbackItem `json:"Item"`
}

STKCallback struct {
// MerchantRequestID is a global unique Identifier for any submitted payment request. It is the same
// value returned to the acknowledgement message on the STKPushRequestResponse.
MerchantRequestID string `json:"MerchantRequestID"`
Expand Down Expand Up @@ -136,19 +147,12 @@ type (
// }
// }
//
CallbackMetadata struct {
// Item is a JSON Array, within the CallbackMetadata, that holds additional transaction details in
// JSON objects. It is only returned for Successful transaction as part of CallbackMetadata
Item []struct {
Name string `json:"Name"`
Value interface{} `json:"Value,omitempty"`
} `json:"Item"`
} `json:"CallbackMetadata"`
CallbackMetadata STKCallbackMetadata `json:"CallbackMetadata"`
}

STKPushCallbackBody struct {
// StkCallback stores the data related to the request.
StkCallback StkCallback `json:"stkCallback"`
// STKCallback stores the data related to the request.
STKCallback STKCallback `json:"stkCallback"`
}

// STKPushCallback is the response sent back sent to the callback URL after making the STKPushRequest
Expand Down Expand Up @@ -223,4 +227,62 @@ type (
// ErrorMessage is a short descriptive message of the failure reason.
ErrorMessage string `json:"errorMessage"`
}

// ResultParameter holds additional transaction details.
// Details available:
// 1:
ResultParameter struct {
Key string `json:"Key"`
Value interface{} `json:"Value"`
}

B2CResultParameters struct {
// ResultParameter is a JSON array within the B2CResultParameters.
ResultParameter []ResultParameter `json:"ResultParameter"`
}

B2CReferenceItem struct {
Key string `json:"Key"`
Value string `json:"Value"`
}

B2CReferenceData struct {
ReferenceItem B2CReferenceItem `json:"ReferenceItem"`
}

B2CCallbackResult struct {
// ConversationID is a global unique identifier for the transaction request returned by the M-Pesa
// upon successful request submission.
ConversationID string `json:"ConversationID"`

// OriginatorConversationID is a global unique identifier for the transaction request returned by the API
// proxy upon successful request submission.
OriginatorConversationID string `json:"OriginatorConversationID"`

ReferenceData B2CReferenceData `json:"ReferenceData"`

// ResultCode is a numeric status code that indicates the status of the transaction processing.
// 0 means success and any other code means an error occurred or the transaction failed.
ResultCode int `json:"ResultCode"`

// ResultDesc is a message from the API that gives the status of the request processing and usually maps to
// a specific ResultCode value.
ResultDesc string `json:"ResultDesc"`

// ResultParameters is a JSON object that holds more details for the transaction.
ResultParameters B2CResultParameters `json:"ResultParameters"`

// ResultType is a status code that indicates whether the transaction was already sent to your listener.
// Usual value is 0.
ResultType int `json:"ResultType"`

// TransactionID is a unique M-PESA transaction ID for every payment request. Same value is sent to customer
// over SMS upon successful processing.
TransactionID string `json:"TransactionID"`
}

B2CCallback struct {
// Result is the root parameter that encloses the entire result message.
Result B2CCallbackResult `json:"Result"`
}
)
16 changes: 16 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mpesa

import "encoding/json"

// toBytes decodes the provided value to bytes
func toBytes(in interface{}) (out []byte, err error) {
switch v := in.(type) {
case string:
return []byte(v), nil
default:
if out, err = json.Marshal(v); err != nil {
return nil, err
}
return out, nil
}
}