From 06bb6ac975d452cb3e12376167c4b1e1d31aa225 Mon Sep 17 00:00:00 2001 From: Acho Arnold Date: Sun, 11 Sep 2022 11:46:38 +0300 Subject: [PATCH] Add sync withdrawal reqeust --- .gitignore | 2 +- client.go | 21 +++++++++++ client_test.go | 39 +++++++++++++++++++++ internal/stubs/api_responses.go | 62 +++++++++++++++++++++++++++++++-- transaction.go | 10 ++++++ transaction_service_test.go | 2 +- utilities.go | 18 +++++----- utilities_service_test.go | 51 +++++++++++++++++++++++++++ 8 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 utilities_service_test.go diff --git a/.gitignore b/.gitignore index 6a60688..ddfc425 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,4 @@ vendor/ # Temp file on windows $path -utilities_service_test.go +*e2e_test.go diff --git a/client.go b/client.go index 5c5a998..c0cfc7a 100644 --- a/client.go +++ b/client.go @@ -144,6 +144,27 @@ func (client *Client) Withdraw(ctx context.Context, params *WithdrawParams) (*Wi return &withdrawResponse, response, nil } +// WithdrawSync transfers money to a mobile number and waits for the transaction to be completed. +// POST /withdraw/ +// API Doc: https://documenter.getpostman.com/view/2391374/T1LV8PVA#885dbde0-b0dd-4514-a0f9-f84fc83df12d +func (client *Client) WithdrawSync(ctx context.Context, params *WithdrawParams) (*Transaction, *Response, error) { + transaction, response, err := client.Withdraw(ctx, params) + if err != nil { + return nil, response, err + } + + // wait for completion in 2 minutes + counter := 1 + for { + status, response, err := client.Transaction.Get(ctx, transaction.Reference) + if err != nil || !status.IsPending() || ctx.Err() != nil || counter == 12 { + return status, response, err + } + time.Sleep(10 * time.Second) + counter++ + } +} + // newRequest creates an API request. A relative URL can be provided in uri, // in which case it is resolved relative to the BaseURL of the Client. // URI's should always be specified without a preceding slash. diff --git a/client_test.go b/client_test.go index d150775..72b6686 100644 --- a/client_test.go +++ b/client_test.go @@ -126,3 +126,42 @@ func TestClient_Withdraw(t *testing.T) { // Teardown server.Close() } + +func TestClient_WithdrawSync(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + requests := make([]*http.Request, 0) + responses := [][]byte{stubs.PostTokenResponse(), stubs.PostWithdrawResponse(), stubs.GetPendingTransactionResponse(), stubs.GetSuccessfulTransactionResponse()} + server := helpers.MakeRequestCapturingTestServer(http.StatusOK, responses, &requests) + client := New(WithEnvironment(Environment(server.URL))) + + // Act + transaction, _, err := client.WithdrawSync(context.Background(), &WithdrawParams{ + Amount: 100, + To: "2376XXXXXXXX", + Description: "Test", + ExternalReference: nil, + }) + + // Assert + assert.Nil(t, err) + + assert.GreaterOrEqual(t, len(requests), 4) + assert.Equal(t, &Transaction{ + Reference: "bcedde9b-62a7-4421-96ac-2e6179552a1a", + Status: "SUCCESSFUL", + Amount: 1, + Currency: "XAF", + Operator: "MTN", + Code: "CP201027T00005", + OperatorReference: "1880106956", + }, transaction) + + assert.True(t, transaction.IsSuccessfull()) + assert.False(t, transaction.IsPending()) + + // Teardown + server.Close() +} diff --git a/internal/stubs/api_responses.go b/internal/stubs/api_responses.go index d3f2d38..98512d9 100644 --- a/internal/stubs/api_responses.go +++ b/internal/stubs/api_responses.go @@ -20,8 +20,8 @@ func PostCollectResponse() string { }` } -// GetTransactionResponse is a dummy JSON response for the Transaction Status -func GetTransactionResponse() []byte { +// GetPendingTransactionResponse is a dummy JSON response for the Transaction Status +func GetPendingTransactionResponse() []byte { return []byte(` { "reference": "bcedde9b-62a7-4421-96ac-2e6179552a1a", @@ -34,6 +34,54 @@ func GetTransactionResponse() []byte { }`) } +// GetSuccessfulTransactionResponse is a dummy JSON response for the Transaction Status +func GetSuccessfulTransactionResponse() []byte { + return []byte(` + { + "reference": "bcedde9b-62a7-4421-96ac-2e6179552a1a", + "status": "SUCCESSFUL", + "amount": 1, + "currency": "XAF", + "operator": "MTN", + "code": "CP201027T00005", + "operator_reference": "1880106956" + }`) +} + +// GetPendingAirtimeTransactionResponse is a dummy JSON response for a pending airtime transaction +func GetPendingAirtimeTransactionResponse() []byte { + return []byte(` + { + "reference": "971e32ae-bb5a-420a-a38a-c2931536609f", + "external_reference": "5577006791947779410", + "status": "PENDING", + "amount": 100, + "currency": "XAF", + "operator": "ORANGE_CM", + "code": "CP220804U0649K", + "type": "AIRTIME", + "reason": "" + } +`) +} + +// GetSuccessfullAirtimeTransactionResponse is a dummy JSON response for a successful airtime transaction +func GetSuccessfullAirtimeTransactionResponse() []byte { + return []byte(` + { + "reference": "971e32ae-bb5a-420a-a38a-c2931536609f", + "external_reference": "5577006791947779410", + "status": "SUCCESSFUL", + "amount": 100, + "currency": "XAF", + "operator": "ORANGE_CM", + "code": "CP220804U0649K", + "type": "AIRTIME", + "reason": "" + } +`) +} + // GetBalanceResponse is a dummy JSON response for the transaction balance func GetBalanceResponse() string { return ` @@ -46,6 +94,16 @@ func GetBalanceResponse() string { ` } +// PostTransferResponse is a dummy JSON response for airtime transfer requests +func PostTransferResponse() []byte { + return []byte(` + { + "reference":"26676007-1c31-46d7-9c71-acb031cf0de4", + "status":"PENDING" + } +`) +} + // PostWithdrawResponse is a dummy JSON response for withdraw requests func PostWithdrawResponse() []byte { return []byte(` diff --git a/transaction.go b/transaction.go index 86e5008..77e30c1 100644 --- a/transaction.go +++ b/transaction.go @@ -10,3 +10,13 @@ type Transaction struct { Code string `json:"code"` OperatorReference string `json:"operator_reference"` } + +// IsPending checks if a transaction is pending +func (transaction *Transaction) IsPending() bool { + return transaction.Status == "PENDING" +} + +// IsSuccessfull checks if a transaction is successfull +func (transaction *Transaction) IsSuccessfull() bool { + return transaction.Status == "SUCCESSFUL" +} diff --git a/transaction_service_test.go b/transaction_service_test.go index eb4c9ba..0ccc17f 100644 --- a/transaction_service_test.go +++ b/transaction_service_test.go @@ -18,7 +18,7 @@ func TestTransactionService_Get(t *testing.T) { // Arrange requests := make([]*http.Request, 0) - responses := [][]byte{stubs.PostTokenResponse(), stubs.GetTransactionResponse()} + responses := [][]byte{stubs.PostTokenResponse(), stubs.GetPendingTransactionResponse()} server := helpers.MakeRequestCapturingTestServer(http.StatusOK, responses, &requests) client := New(WithEnvironment(Environment(server.URL))) reference := "bcedde9b-62a7-4421-96ac-2e6179552a1a" diff --git a/utilities.go b/utilities.go index f0bbcb9..801cad7 100644 --- a/utilities.go +++ b/utilities.go @@ -15,15 +15,15 @@ type AirtimeTransferResponse struct { // UtilitiesTransaction represent a utility transaction type UtilitiesTransaction struct { - Reference string `json:"reference"` - ExternalReference string `json:"external_reference"` - Status string `json:"status"` - Amount float64 `json:"amount"` - Currency string `json:"currency"` - Operator string `json:"operator"` - Code string `json:"code"` - Type string `json:"type"` - Reason interface{} `json:"reason"` + Reference string `json:"reference"` + ExternalReference string `json:"external_reference"` + Status string `json:"status"` + Amount float64 `json:"amount"` + Currency string `json:"currency"` + Operator string `json:"operator"` + Code string `json:"code"` + Type string `json:"type"` + Reason string `json:"reason"` } // IsPending checks if a transaction is pending diff --git a/utilities_service_test.go b/utilities_service_test.go new file mode 100644 index 0000000..4a83ae7 --- /dev/null +++ b/utilities_service_test.go @@ -0,0 +1,51 @@ +package campay + +import ( + "context" + "net/http" + "testing" + + "github.com/NdoleStudio/campay-go-sdk/internal/helpers" + "github.com/NdoleStudio/campay-go-sdk/internal/stubs" + "github.com/stretchr/testify/assert" +) + +func TestUtilitiesService_AirtimeTransferSync(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + requests := make([]*http.Request, 0) + responses := [][]byte{stubs.PostTokenResponse(), stubs.PostTransferResponse(), stubs.GetPendingAirtimeTransactionResponse(), stubs.GetSuccessfullAirtimeTransactionResponse()} + server := helpers.MakeRequestCapturingTestServer(http.StatusOK, responses, &requests) + client := New(WithEnvironment(Environment(server.URL))) + + // Act + transaction, _, err := client.Utilities.AirtimeTransferSync(context.Background(), &AirtimeTransferParams{ + Amount: "100", + To: "2376XXXXXXXXX", + ExternalReference: "5577006791947779410", + }) + + // Assert + assert.Nil(t, err) + + assert.GreaterOrEqual(t, len(requests), 4) + assert.Equal(t, &UtilitiesTransaction{ + Reference: "971e32ae-bb5a-420a-a38a-c2931536609f", + ExternalReference: "5577006791947779410", + Status: "SUCCESSFUL", + Amount: 100, + Currency: "XAF", + Operator: "ORANGE_CM", + Code: "CP220804U0649K", + Type: "AIRTIME", + Reason: "", + }, transaction) + + assert.True(t, transaction.IsSuccessfull()) + assert.False(t, transaction.IsPending()) + + // Teardown + server.Close() +}