Skip to content

Commit 9315868

Browse files
committed
Add endpoints for bill payments and validation
1 parent 57c3b21 commit 9315868

File tree

10 files changed

+344
-10
lines changed

10 files changed

+344
-10
lines changed

README.md

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,15 @@ import "github.com/NdoleStudio/flutterwave-go"
2727

2828
## Implemented
2929

30-
- [Token](#token)
31-
- `POST /token` - Get access token
32-
- [Collect](#collect)
33-
- `POST /collect` - Request Payment
34-
- [Transaction](#transaction)
35-
- `POST /transaction/(reference)/` - Transaction Status
30+
- [BILLS](#bills)
31+
- `POST /bills/`: Create a bill payment
32+
- `GET /bill-items/{item_code}/validate`: validate services like DSTV smartcard number, Meter number etc.
3633

3734
## Usage
3835

3936
### Initializing the Client
4037

41-
An instance of the `campay` client can be created using `New()`. The `http.Client` supplied will be used to make requests to the API.
38+
An instance of the `flutterwave` client can be created using `New()`.
4239

4340
```go
4441
package main
@@ -59,7 +56,7 @@ func main() {
5956
All API calls return an `error` as the last return object. All successful calls will return a `nil` error.
6057

6158
```go
62-
data, httpResponse, err := campayClient.Bills.Create(context.Background(), request)
59+
data, httpResponse, err := flutterwaveClient.Bills.Create(context.Background(), request)
6360
if err != nil {
6461
//handle error
6562
}
@@ -72,7 +69,30 @@ if err != nil {
7269
`POST /bills/`: Create a bill payment
7370

7471
```go
75-
response, _, err := flutterwaveClient.Bills.Create(context.Background(), request)
72+
response, _, err := flutterwaveClient.Bills.CreatePayment(context.Background(), &BillsCreatePaymentRequest{
73+
Country: "NG",
74+
Customer: "7034504232",
75+
Amount: 100,
76+
Recurrence: "ONCE",
77+
Type: "DSTV",
78+
Reference: uuid.New().String(),
79+
BillerName: "DSTV",
80+
})
81+
82+
if err != nil {
83+
log.Fatal(err)
84+
}
85+
86+
log.Println(response.Status) // success
87+
```
88+
89+
90+
#### Create a bill payment
91+
92+
`GET /bill-items/{item_code}/validate`: validate services like DSTV smartcard number, Meter number etc.
93+
94+
```go
95+
response, _, err := flutterwaveClient.Bills.Validate(context.Background(), "CB177", "BIL099", "08038291822")
7696

7797
if err != nil {
7898
log.Fatal(err)

bills_service.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package flutterwave
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
)
8+
9+
// billsService is the API client for the `/gateway` endpoint
10+
type billsService service
11+
12+
// CreatePayment creates bill payments.
13+
//
14+
// API Docs: https://developer.flutterwave.com/reference/create-a-bill-payment
15+
func (service *billsService) CreatePayment(ctx context.Context, payload *BillsCreatePaymentRequest) (*BillsCreatePaymentResponse, *Response, error) {
16+
request, err := service.client.newRequest(ctx, http.MethodPost, "/v3/bills", payload)
17+
if err != nil {
18+
return nil, nil, err
19+
}
20+
21+
response, err := service.client.do(request)
22+
if err != nil {
23+
return nil, response, err
24+
}
25+
26+
var data BillsCreatePaymentResponse
27+
if err = json.Unmarshal(*response.Body, &data); err != nil {
28+
return nil, response, err
29+
}
30+
31+
return &data, response, nil
32+
}
33+
34+
// Validate validates services like DSTV smartcard number, Meter number etc.
35+
//
36+
// API Docs: https://developer.flutterwave.com/reference/validate-bill-service
37+
func (service *billsService) Validate(ctx context.Context, itemCode string, billerCode string, customer string) (*BillsValidateResponse, *Response, error) {
38+
request, err := service.client.newRequest(ctx, http.MethodGet, "/v3/bill-items/"+itemCode+"/validate", nil)
39+
if err != nil {
40+
return nil, nil, err
41+
}
42+
43+
request = service.client.addURLParams(request, map[string]string{
44+
"code": billerCode,
45+
"customer": customer,
46+
})
47+
48+
response, err := service.client.do(request)
49+
if err != nil {
50+
return nil, response, err
51+
}
52+
53+
var data BillsValidateResponse
54+
if err = json.Unmarshal(*response.Body, &data); err != nil {
55+
return nil, response, err
56+
}
57+
58+
return &data, response, nil
59+
}

bills_service_models.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package flutterwave
2+
3+
// BillsCreatePaymentRequest is data needed to creat a payment
4+
type BillsCreatePaymentRequest struct {
5+
Country string `json:"country"`
6+
Customer string `json:"customer"`
7+
Amount int `json:"amount"`
8+
Recurrence string `json:"recurrence,omitempty"`
9+
Type string `json:"type"`
10+
Reference string `json:"reference,omitempty"`
11+
BillerName string `json:"biller_name,omitempty"`
12+
}
13+
14+
// BillsCreatePaymentResponse is the data returned after creating a payment
15+
type BillsCreatePaymentResponse struct {
16+
Status string `json:"status"`
17+
Message string `json:"message"`
18+
Data struct {
19+
PhoneNumber string `json:"phone_number"`
20+
Amount int `json:"amount"`
21+
Network string `json:"network"`
22+
FlwRef string `json:"flw_ref"`
23+
TxRef string `json:"tx_ref"`
24+
} `json:"data"`
25+
}
26+
27+
// BillsValidateResponse is the response after validating a bill service
28+
type BillsValidateResponse struct {
29+
Status string `json:"status"`
30+
Message string `json:"message"`
31+
Data struct {
32+
ResponseCode string `json:"response_code"`
33+
Address interface{} `json:"address"`
34+
ResponseMessage string `json:"response_message"`
35+
Name string `json:"name"`
36+
BillerCode string `json:"biller_code"`
37+
Customer string `json:"customer"`
38+
ProductCode string `json:"product_code"`
39+
Email interface{} `json:"email"`
40+
Fee int `json:"fee"`
41+
Maximum int `json:"maximum"`
42+
Minimum int `json:"minimum"`
43+
} `json:"data"`
44+
}

bills_service_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package flutterwave
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"testing"
7+
8+
"github.com/NdoleStudio/flutterwave-go/internal/helpers"
9+
"github.com/NdoleStudio/flutterwave-go/internal/stubs"
10+
"github.com/google/uuid"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestBillsService_CreatePayment(t *testing.T) {
15+
// Setup
16+
t.Parallel()
17+
18+
// Arrange
19+
server := helpers.MakeTestServer(http.StatusOK, stubs.BillsCreateDStvPaymentResponse())
20+
client := New(WithBaseURL(server.URL))
21+
22+
// Act
23+
data, response, err := client.Bills.CreatePayment(context.Background(), &BillsCreatePaymentRequest{
24+
Country: "NG",
25+
Customer: "7034504232",
26+
Amount: 100,
27+
Recurrence: "ONCE",
28+
Type: "DSTV",
29+
Reference: uuid.New().String(),
30+
BillerName: "DSTV",
31+
})
32+
33+
// Assert
34+
assert.Nil(t, err)
35+
36+
assert.Equal(t, &BillsCreatePaymentResponse{
37+
Status: "success",
38+
Message: "Bill payment successful",
39+
Data: struct {
40+
PhoneNumber string `json:"phone_number"`
41+
Amount int `json:"amount"`
42+
Network string `json:"network"`
43+
FlwRef string `json:"flw_ref"`
44+
TxRef string `json:"tx_ref"`
45+
}{
46+
"+23490803840303",
47+
500,
48+
"9MOBILE",
49+
"CF-FLYAPI-20200311081921359990",
50+
"BPUSSD1583957963415840",
51+
},
52+
}, data)
53+
54+
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)
55+
56+
// Teardown
57+
server.Close()
58+
}
59+
60+
func TestBillsService_Validate(t *testing.T) {
61+
// Setup
62+
t.Parallel()
63+
64+
// Arrange
65+
server := helpers.MakeTestServer(http.StatusOK, stubs.BillsValidateDstvResponse())
66+
client := New(WithBaseURL(server.URL))
67+
68+
// Act
69+
data, response, err := client.Bills.Validate(context.Background(), "CB177", "BIL099", "08038291822")
70+
71+
// Assert
72+
assert.Nil(t, err)
73+
74+
assert.Equal(t, &BillsValidateResponse{
75+
Status: "success",
76+
Message: "Item validated successfully",
77+
Data: struct {
78+
ResponseCode string `json:"response_code"`
79+
Address interface{} `json:"address"`
80+
ResponseMessage string `json:"response_message"`
81+
Name string `json:"name"`
82+
BillerCode string `json:"biller_code"`
83+
Customer string `json:"customer"`
84+
ProductCode string `json:"product_code"`
85+
Email interface{} `json:"email"`
86+
Fee int `json:"fee"`
87+
Maximum int `json:"maximum"`
88+
Minimum int `json:"minimum"`
89+
}{
90+
"00",
91+
nil,
92+
"Successful",
93+
"MTN",
94+
"BIL099",
95+
"08038291822",
96+
"AT099",
97+
nil,
98+
100,
99+
0,
100+
0,
101+
},
102+
}, data)
103+
104+
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)
105+
106+
// Teardown
107+
server.Close()
108+
}

client.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type Client struct {
2121
common service
2222
secretKey string
2323
baseURL string
24+
25+
Bills *billsService
2426
}
2527

2628
// New creates and returns a new campay.Client from a slice of campay.ClientOption.
@@ -38,6 +40,7 @@ func New(options ...ClientOption) *Client {
3840
}
3941

4042
client.common.client = client
43+
client.Bills = (*billsService)(&client.common)
4144
return client
4245
}
4346

@@ -67,6 +70,16 @@ func (client *Client) newRequest(ctx context.Context, method, uri string, body i
6770
return req, nil
6871
}
6972

73+
// addURLParams adds urls parameters to an *http.Request
74+
func (client *Client) addURLParams(request *http.Request, params map[string]string) *http.Request {
75+
q := request.URL.Query()
76+
for key, value := range params {
77+
q.Add(key, value)
78+
}
79+
request.URL.RawQuery = q.Encode()
80+
return request
81+
}
82+
7083
// do carries out an HTTP request and returns a Response
7184
func (client *Client) do(req *http.Request) (*Response, error) {
7285
if req == nil {

client_option_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99

1010
func TestWithHTTPClient(t *testing.T) {
1111
t.Run("httpClient is not set when the httpClient is nil", func(t *testing.T) {
12+
// Setup
13+
t.Parallel()
14+
1215
// Arrange
1316
config := defaultClientConfig()
1417

@@ -20,6 +23,9 @@ func TestWithHTTPClient(t *testing.T) {
2023
})
2124

2225
t.Run("httpClient is set when the httpClient is not nil", func(t *testing.T) {
26+
// Setup
27+
t.Parallel()
28+
2329
// Arrange
2430
config := defaultClientConfig()
2531
newClient := &http.Client{Timeout: 300}
@@ -35,6 +41,9 @@ func TestWithHTTPClient(t *testing.T) {
3541

3642
func TestWithBaseURL(t *testing.T) {
3743
t.Run("baseURL is set successfully", func(t *testing.T) {
44+
// Setup
45+
t.Parallel()
46+
3847
// Arrange
3948
baseURL := "https://example.com"
4049
config := defaultClientConfig()
@@ -47,6 +56,9 @@ func TestWithBaseURL(t *testing.T) {
4756
})
4857

4958
t.Run("tailing / is trimmed from baseURL", func(t *testing.T) {
59+
// Setup
60+
t.Parallel()
61+
5062
// Arrange
5163
baseURL := "https://example.com/"
5264
config := defaultClientConfig()
@@ -61,6 +73,9 @@ func TestWithBaseURL(t *testing.T) {
6173

6274
func TestWithSecretKey(t *testing.T) {
6375
t.Run("secretKey is set successfully", func(t *testing.T) {
76+
// Setup
77+
t.Parallel()
78+
6479
// Arrange
6580
config := defaultClientConfig()
6681
secretKey := "secretKey"

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ module github.com/NdoleStudio/flutterwave-go
22

33
go 1.17
44

5-
require github.com/stretchr/testify v1.7.0
5+
require (
6+
github.com/google/uuid v1.3.0
7+
github.com/stretchr/testify v1.7.0
8+
)
69

710
require (
811
github.com/davecgh/go-spew v1.1.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
22
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
4+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
35
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
46
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
57
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

0 commit comments

Comments
 (0)