├── .gitignore ├── .travis.yml ├── Dockerfile.test ├── LICENSE ├── Makefile ├── README.md ├── bank.go ├── bank_test.go ├── bulkcharge.go ├── charge.go ├── charge_test.go ├── customer.go ├── customer_test.go ├── doc.go ├── error.go ├── glide.lock ├── glide.yaml ├── go.mod ├── go.sum ├── page.go ├── page_test.go ├── paystack.go ├── paystack_test.go ├── plan.go ├── plan_test.go ├── runtests.sh ├── settlement.go ├── settlement_test.go ├── subaccount.go ├── subaccount_test.go ├── subscription.go ├── subscription_test.go ├── transaction.go ├── transaction_test.go ├── transfer.go └── transfer_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | NOTES 2 | testoutput.txt 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.14.x 4 | - 1.15.x 5 | - 1.16.x 6 | - master 7 | matrix: 8 | allow_failures: 9 | - go: master 10 | fast_finish: true 11 | install: 12 | - go get golang.org/x/lint/golint 13 | script: 14 | - make 15 | env: 16 | global: 17 | secure: o1hnzfdu7AnfB/CGwKXByAUIbr53TDpVgX5mLVVR35f/YsGarhpjUpmG4skKoFvGkItJLBIyJN5MkH9CiSGWdHTmQcnBKi55Wd5OFHxcQmuj58KdF+uBGgFQ5uYesXa6FfiE+n1NHT4eQkMkR7y1I5uucr13ie81vstvbeg21LDWvBBS+ySyy8UOlxIuQ9RTCIcN6tpGRO4hAncqiHduEifUc5TyU8Kv80++LC5HUgw1DRcaZ6rS5fhBlElyJgBI8tap4d/eUtLp6Baec9IQszJ7lC7SNWwTBH5MdjkQhZ0SpGCLjguWb6KR1+FsweosTDMdNXdwLLxdjCdUnRCdJo2HOIj4+6tIV8nf8MTy58HOtg2G+B77SpIPqhespqAdMwXq1DwgNDlVrcgZTJ8WxrAMeYVoozpt8OQzn9ZX/cQx1CxaD85wVeNXdyeSiF6k3eyEl6Jg1kUWTfdZglnfBMljhhuNWfyfP1yMYHpdgy+0OJSkfnOiyWoRRsXK+SakcllYuQT10oA/JqiQU0VqcHl+xXWfhaoXFTbThQ4+8fWFjJfdbricUWN7wFtcrBv4jK21BGLLQ8SsdeabtuD8eaFzvsGFypHII64WnOcJBBGph80jViJI6It6snfqoEnA9G+HTAabBvlXP0sLAxd5KcVlp4UKNYpctWzX239O1O0= -------------------------------------------------------------------------------- /Dockerfile.test: -------------------------------------------------------------------------------- 1 | FROM golang:1.15 2 | 3 | RUN mkdir -p /go/src/github.com/rpip/paystack-go 4 | WORKDIR /go/src/github.com/rpip/paystack-go 5 | ENV GOPATH /go 6 | 7 | RUN go get golang.org/x/tools/cover 8 | RUN go get golang.org/x/lint/golint 9 | 10 | COPY . /go/src/github.com/rpip/paystack-go 11 | 12 | CMD ["./runtests.sh"] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2017] [Yao Adzaku] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Project name 2 | PROJECT = paystack 3 | 4 | # Set an output prefix, which is the local directory if not specified 5 | PREFIX?=$(shell pwd) 6 | BUILDTAGS= 7 | GLIDE = $(shell which glide) 8 | 9 | .PHONY: clean all fmt vet lint build test static deps docker 10 | .DEFAULT: default 11 | 12 | all: clean build fmt lint test vet 13 | 14 | build: 15 | @echo "+ $@" 16 | @go build -tags "$(BUILDTAGS) cgo" . 17 | 18 | static: 19 | @echo "+ $@" 20 | CGO_ENABLED=1 go build -tags "$(BUILDTAGS) cgo static_build" -ldflags "-w -extldflags -static" -o reg . 21 | 22 | fmt: 23 | @echo "+ $@" 24 | @gofmt -s -l . | grep -v vendor | tee /dev/stderr 25 | 26 | lint: 27 | @echo "+ $@" 28 | @golint ./... | grep -v vendor | tee /dev/stderr 29 | 30 | test: fmt lint vet 31 | @echo "+ $@" 32 | @PAYSTACK_KEY=$(PAYSTACK_KEY) go test -v -tags "$(BUILDTAGS) cgo" $(shell go list ./... | grep -v vendor) 33 | 34 | vet: 35 | @echo "+ $@" 36 | @go vet $(shell go list ./... | grep -v vendor) 37 | 38 | clean: 39 | @echo "+ $@" 40 | @rm -rf reg 41 | 42 | deps: 43 | @echo "Installing dependencies..." 44 | @$(GLIDE) install 45 | 46 | docker: 47 | @docker build . -t $(PROJECT) -f Dockerfile.test 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/rpip/paystack-go) [![Build Status](https://travis-ci.org/rpip/paystack-go.svg?branch=master)](https://travis-ci.org/rpip/paystack-go) 2 | 3 | # Go library for the Paystack API. 4 | 5 | paystack-go is a Go client library for accessing the Paystack API. 6 | 7 | Where possible, the services available on the client groups the API into logical chunks and correspond to the structure of the Paystack API documentation at https://developers.paystack.co/v1.0/reference. 8 | 9 | ## Usage 10 | 11 | ``` go 12 | import "github.com/rpip/paystack-go" 13 | 14 | apiKey := "sk_test_b748a89ad84f35c2f1a8b81681f956274de048bb" 15 | 16 | // second param is an optional http client, allowing overriding of the HTTP client to use. 17 | // This is useful if you're running in a Google AppEngine environment 18 | // where the http.DefaultClient is not available. 19 | client := paystack.NewClient(apiKey) 20 | 21 | recipient := &TransferRecipient{ 22 | Type: "Nuban", 23 | Name: "Customer 1", 24 | Description: "Demo customer", 25 | AccountNumber: "0100000010", 26 | BankCode: "044", 27 | Currency: "NGN", 28 | Metadata: map[string]interface{}{"job": "Plumber"}, 29 | } 30 | 31 | recipient1, err := client.Transfer.CreateRecipient(recipient) 32 | 33 | req := &TransferRequest{ 34 | Source: "balance", 35 | Reason: "Delivery pickup", 36 | Amount: 30, 37 | Recipient: recipient1.RecipientCode, 38 | } 39 | 40 | transfer, err := client.Transfer.Initiate(req) 41 | if err != nil { 42 | // do something with error 43 | } 44 | 45 | // retrieve list of plans 46 | plans, err := client.Plan.List() 47 | 48 | for i, plan := range plans.Values { 49 | fmt.Printf("%+v", plan) 50 | } 51 | 52 | cust := &Customer{ 53 | FirstName: "User123", 54 | LastName: "AdminUser", 55 | Email: "user123@gmail.com", 56 | Phone: "+23400000000000000", 57 | } 58 | // create the customer 59 | customer, err := client.Customer.Create(cust) 60 | if err != nil { 61 | // do something with error 62 | } 63 | 64 | // Get customer by ID 65 | customer, err := client.Customers.Get(customer.ID) 66 | ``` 67 | 68 | See the test files for more examples. 69 | 70 | ## Docker 71 | 72 | Test this library in a docker container: 73 | 74 | ```bash 75 | # PAYSTACK_KEY is an environment variable that should be added to your rc file. i.e .bashrc 76 | $ make docker && docker run -e PAYSTACK_KEY -i -t paystack:latest 77 | ``` 78 | 79 | ## TODO 80 | - [ ] Maybe support request context? 81 | - [ ] Test on App Engine 82 | 83 | ## CONTRIBUTING 84 | Contributions are of course always welcome. The calling pattern is pretty well established, so adding new methods is relatively straightforward. Please make sure the build succeeds and the test suite passes. 85 | -------------------------------------------------------------------------------- /bank.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "fmt" 4 | 5 | // BankService handles operations related to the bank 6 | // For more details see https://developers.paystack.co/v1.0/reference#bank 7 | type BankService service 8 | 9 | // Bank represents a Paystack bank 10 | type Bank struct { 11 | ID int `json:"id,omitempty"` 12 | CreatedAt string `json:"createdAt,omitempty"` 13 | UpdatedAt string `json:"updatedAt,omitempty"` 14 | Name string `json:"name,omitempty"` 15 | Slug string `json:"slug,omitempty"` 16 | Code string `json:"code,omitempty"` 17 | LongCode string `json:"long_code,omitempty"` 18 | Gateway string `json:"gateway,omitempty"` 19 | Active bool `json:"active,omitempty"` 20 | IsDeleted bool `json:"is_deleted,omitempty"` 21 | } 22 | 23 | // BankList is a list object for banks. 24 | type BankList struct { 25 | Meta ListMeta 26 | Values []Bank `json:"data,omitempty"` 27 | } 28 | 29 | // BVNResponse represents response from resolve_bvn endpoint 30 | type BVNResponse struct { 31 | Meta struct { 32 | CallsThisMonth int `json:"calls_this_month,omitempty"` 33 | FreeCallsLeft int `json:"free_calls_left,omitempty"` 34 | } 35 | BVN string 36 | } 37 | 38 | // List returns a list of all the banks. 39 | // For more details see https://developers.paystack.co/v1.0/reference#list-banks 40 | func (s *BankService) List() (*BankList, error) { 41 | banks := &BankList{} 42 | err := s.client.Call("GET", "/bank", nil, banks) 43 | return banks, err 44 | } 45 | 46 | // ResolveBVN docs https://developers.paystack.co/v1.0/reference#resolve-bvn 47 | func (s *BankService) ResolveBVN(bvn int) (*BVNResponse, error) { 48 | u := fmt.Sprintf("/bank/resolve_bvn/%d", bvn) 49 | resp := &BVNResponse{} 50 | err := s.client.Call("GET", u, nil, resp) 51 | return resp, err 52 | } 53 | 54 | // ResolveAccountNumber docs https://developers.paystack.co/v1.0/reference#resolve-account-number 55 | func (s *BankService) ResolveAccountNumber(accountNumber, bankCode string) (Response, error) { 56 | u := fmt.Sprintf("/bank/resolve?account_number=%s&bank_code=%s", accountNumber, bankCode) 57 | resp := Response{} 58 | err := s.client.Call("GET", u, nil, &resp) 59 | return resp, err 60 | } 61 | -------------------------------------------------------------------------------- /bank_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "testing" 4 | 5 | func TestBankList(t *testing.T) { 6 | // retrieve the bank list 7 | banks, err := c.Bank.List() 8 | 9 | if err != nil || !(len(banks.Values) > 0) { 10 | t.Errorf("Expected Bank list, got %d, returned error %v", len(banks.Values), err) 11 | } 12 | } 13 | 14 | func TestResolveBVN(t *testing.T) { 15 | // Test invlaid BVN. 16 | // Err not nill. Resp status code is 400 17 | resp, err := c.Bank.ResolveBVN(21212917) 18 | if err == nil { 19 | t.Errorf("Expected error for invalid BVN, got %+v'", resp) 20 | } 21 | 22 | // Test free calls limit 23 | // Error is nil 24 | // &{Meta:{CallsThisMonth:0 FreeCallsLeft:0} BVN:cZ+MKrsLAqJCUi+hxIdQqw==}’ 25 | resp, err = c.Bank.ResolveBVN(21212917741) 26 | if resp.Meta.FreeCallsLeft != 0 { 27 | t.Errorf("Expected free calls limit exceeded, got %+v'", resp) 28 | } 29 | // TODO(yao): Reproduce error: Your balance is not enough to fulfill this request 30 | } 31 | 32 | func TestResolveAccountNumber(t *testing.T) { 33 | resp, err := c.Bank.ResolveAccountNumber("0022728151", "063") 34 | if err == nil { 35 | t.Errorf("Expected error, got %+v'", resp) 36 | } 37 | 38 | /* 39 | if _, ok := resp["account_number"]; !ok { 40 | t.Errorf("Expected response to contain 'account_number'") 41 | } 42 | 43 | if _, ok := resp["account_name"]; !ok { 44 | t.Errorf("Expected response to contain 'account_name'") 45 | } 46 | */ 47 | } 48 | -------------------------------------------------------------------------------- /bulkcharge.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "fmt" 4 | 5 | // BulkChargeService handles operations related to the bulkcharge 6 | // For more details see https://developers.paystack.co/v1.0/reference#initiate-bulk-charge 7 | type BulkChargeService service 8 | 9 | // BulkChargeBatch represents a bulk charge batch object 10 | // For more details see https://developers.paystack.co/v1.0/reference#initiate-bulk-charge 11 | type BulkChargeBatch struct { 12 | ID int `json:"id,omitempty"` 13 | CreatedAt string `json:"createdAt,omitempty"` 14 | UpdatedAt string `json:"updatedAt,omitempty"` 15 | BatchCode string `json:"batch_code,omitempty"` 16 | Status string `json:"status,omitempty"` 17 | Integration int `json:"integration,omitempty"` 18 | Domain string `json:"domain,omitempty"` 19 | TotalCharges string `json:"total_charges,omitempty"` 20 | PendingCharge string `json:"pending_charge,omitempty"` 21 | } 22 | 23 | // BulkChargeRequest is an array of objects with authorization codes and amount 24 | type BulkChargeRequest struct { 25 | Items []BulkItem 26 | } 27 | 28 | // BulkItem represents a single bulk charge request item 29 | type BulkItem struct { 30 | Authorization string `json:"authorization,omitempty"` 31 | Amount float32 `json:"amount,omitempty"` 32 | } 33 | 34 | // BulkChargeBatchList is a list object for bulkcharges. 35 | type BulkChargeBatchList struct { 36 | Meta ListMeta 37 | Values []BulkChargeBatch `json:"data,omitempty"` 38 | } 39 | 40 | // Initiate initiates a new bulkcharge 41 | // For more details see https://developers.paystack.co/v1.0/reference#initiate-bulk-charge 42 | func (s *BulkChargeService) Initiate(req *BulkChargeRequest) (*BulkChargeBatch, error) { 43 | bulkcharge := &BulkChargeBatch{} 44 | err := s.client.Call("POST", "/bulkcharge", req.Items, bulkcharge) 45 | return bulkcharge, err 46 | } 47 | 48 | // List returns a list of bulkcharges. 49 | // For more details see https://developers.paystack.co/v1.0/reference#list-bulkcharges 50 | func (s *BulkChargeService) List() (*BulkChargeBatchList, error) { 51 | return s.ListN(10, 0) 52 | } 53 | 54 | // ListN returns a list of bulkcharges 55 | // For more details see https://developers.paystack.co/v1.0/reference#list-bulkcharges 56 | func (s *BulkChargeService) ListN(count, offset int) (*BulkChargeBatchList, error) { 57 | u := paginateURL("/bulkcharge", count, offset) 58 | bulkcharges := &BulkChargeBatchList{} 59 | err := s.client.Call("GET", u, nil, bulkcharges) 60 | return bulkcharges, err 61 | } 62 | 63 | // Get returns a bulk charge batch 64 | // This endpoint retrieves a specific batch code. 65 | // It also returns useful information on its progress by way of 66 | // the total_charges and pending_charges attributes. 67 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-bulk-charge-batch 68 | func (s *BulkChargeService) Get(idCode string) (*BulkChargeBatch, error) { 69 | u := fmt.Sprintf("/bulkcharge/%s", idCode) 70 | bulkcharge := &BulkChargeBatch{} 71 | err := s.client.Call("GET", u, nil, bulkcharge) 72 | return bulkcharge, err 73 | } 74 | 75 | // GetBatchCharges returns charges in a batch 76 | // This endpoint retrieves the charges associated with a specified batch code. 77 | // Pagination parameters are available. You can also filter by status. 78 | // Charge statuses can be pending, success or failed. 79 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-charges-in-a-batch 80 | func (s *BulkChargeService) GetBatchCharges(idCode string) (Response, error) { 81 | u := fmt.Sprintf("/bulkcharge/%s/charges", idCode) 82 | resp := Response{} 83 | err := s.client.Call("GET", u, nil, &resp) 84 | return resp, err 85 | } 86 | 87 | // PauseBulkCharge stops processing a batch 88 | // For more details see https://developers.paystack.co/v1.0/reference#pause-bulk-charge-batch 89 | func (s *BulkChargeService) PauseBulkCharge(batchCode string) (Response, error) { 90 | u := fmt.Sprintf("/bulkcharge/pause/%s", batchCode) 91 | resp := Response{} 92 | err := s.client.Call("GET", u, nil, &resp) 93 | 94 | return resp, err 95 | } 96 | 97 | // ResumeBulkCharge stops processing a batch 98 | // For more details see https://developers.paystack.co/v1.0/reference#resume-bulk-charge-batch 99 | func (s *BulkChargeService) ResumeBulkCharge(batchCode string) (Response, error) { 100 | u := fmt.Sprintf("/bulkcharge/resume/%s", batchCode) 101 | resp := Response{} 102 | err := s.client.Call("GET", u, nil, &resp) 103 | 104 | return resp, err 105 | } 106 | -------------------------------------------------------------------------------- /charge.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | // ChargeService handles operations related to bulk charges 9 | // For more details see https://developers.paystack.co/v1.0/reference#charge-tokenize 10 | type ChargeService service 11 | 12 | // Card represents a Card object 13 | type Card struct { 14 | Number string `json:"card_number,omitempty"` 15 | CVV string `json:"card_cvc,omitempty"` 16 | ExpirtyMonth string `json:"expiry_month,omitempty"` 17 | ExpiryYear string `json:"expiry_year,omitempty"` 18 | AddressLine1 string `json:"address_line1,omitempty"` 19 | AddressLine2 string `json:"address_line2,omitempty"` 20 | AddressLine3 string `json:"address_line3,omitempty"` 21 | AddressCountry string `json:"address_country,omitempty"` 22 | AddressPostalCode string `json:"address_postal_code,omitempty"` 23 | Country string `json:"country,omitempty"` 24 | } 25 | 26 | // BankAccount is used as bank in a charge request 27 | type BankAccount struct { 28 | Code string `json:"code,omitempty"` 29 | AccountNumber string `json:"account_number,omitempty"` 30 | } 31 | 32 | // ChargeRequest represents a Paystack charge request 33 | type ChargeRequest struct { 34 | Email string `json:"email,omitempty"` 35 | Amount float32 `json:"amount,omitempty"` 36 | Birthday string `json:"birthday,omitempty"` 37 | Card *Card `json:"card,omitempty"` 38 | Bank *BankAccount `json:"bank,omitempty"` 39 | AuthorizationCode string `json:"authorization_code,omitempty"` 40 | Pin string `json:"pin,omitempty"` 41 | Metadata *Metadata `json:"metadata,omitempty"` 42 | } 43 | 44 | // Create submits a charge request using card details or bank details or authorization code 45 | // For more details see https://developers.paystack.co/v1.0/reference#charge 46 | func (s *ChargeService) Create(req *ChargeRequest) (Response, error) { 47 | resp := Response{} 48 | err := s.client.Call("POST", "/charge", req, &resp) 49 | return resp, err 50 | } 51 | 52 | // Tokenize tokenizes payment instrument before a charge 53 | // For more details see https://developers.paystack.co/v1.0/reference#charge-tokenize 54 | func (s *ChargeService) Tokenize(req *ChargeRequest) (Response, error) { 55 | resp := Response{} 56 | err := s.client.Call("POST", "/charge/tokenize", req, &resp) 57 | return resp, err 58 | } 59 | 60 | // SubmitPIN submits PIN to continue a charge 61 | // For more details see https://developers.paystack.co/v1.0/reference#submit-pin 62 | func (s *ChargeService) SubmitPIN(pin, reference string) (Response, error) { 63 | data := url.Values{} 64 | data.Add("pin", pin) 65 | data.Add("reference", reference) 66 | resp := Response{} 67 | err := s.client.Call("POST", "/charge/submit_pin", data, &resp) 68 | return resp, err 69 | } 70 | 71 | // SubmitOTP submits OTP to continue a charge 72 | // For more details see https://developers.paystack.co/v1.0/reference#submit-pin 73 | func (s *ChargeService) SubmitOTP(otp, reference string) (Response, error) { 74 | data := url.Values{} 75 | data.Add("pin", otp) 76 | data.Add("reference", reference) 77 | resp := Response{} 78 | err := s.client.Call("POST", "/charge/submit_otp", data, &resp) 79 | return resp, err 80 | } 81 | 82 | // SubmitPhone submits Phone when requested 83 | // For more details see https://developers.paystack.co/v1.0/reference#submit-pin 84 | func (s *ChargeService) SubmitPhone(phone, reference string) (Response, error) { 85 | data := url.Values{} 86 | data.Add("pin", phone) 87 | data.Add("reference", reference) 88 | resp := Response{} 89 | err := s.client.Call("POST", "/charge/submit_phone", data, &resp) 90 | return resp, err 91 | } 92 | 93 | // SubmitBirthday submits Birthday when requested 94 | // For more details see https://developers.paystack.co/v1.0/reference#submit-pin 95 | func (s *ChargeService) SubmitBirthday(birthday, reference string) (Response, error) { 96 | data := url.Values{} 97 | data.Add("pin", birthday) 98 | data.Add("reference", reference) 99 | resp := Response{} 100 | err := s.client.Call("POST", "/charge/submit_birthday", data, &resp) 101 | return resp, err 102 | } 103 | 104 | // CheckPending returns pending charges 105 | // When you get "pending" as a charge status, wait 30 seconds or more, 106 | // then make a check to see if its status has changed. Don't call too early as you may get a lot more pending than you should. 107 | // For more details see https://developers.paystack.co/v1.0/reference#check-pending-charge 108 | func (s *ChargeService) CheckPending(reference string) (Response, error) { 109 | u := fmt.Sprintf("/charge/%s", reference) 110 | resp := Response{} 111 | err := s.client.Call("GET", u, nil, &resp) 112 | return resp, err 113 | } 114 | -------------------------------------------------------------------------------- /charge_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestChargeServiceCreate(t *testing.T) { 8 | bankAccount := BankAccount{ 9 | Code: "057", 10 | AccountNumber: "0000000000", 11 | } 12 | 13 | charge := ChargeRequest{ 14 | Email: "your_own_email_here@gmail.com", 15 | Amount: 10000, 16 | Bank: &bankAccount, 17 | Birthday: "1999-12-31", 18 | } 19 | 20 | resp, err := c.Charge.Create(&charge) 21 | if err != nil { 22 | t.Errorf("Create Charge returned error: %v", err) 23 | } 24 | 25 | if resp["reference"] == "" { 26 | t.Error("Missing transaction reference") 27 | } 28 | } 29 | 30 | func TestChargeServiceCheckPending(t *testing.T) { 31 | bankAccount := BankAccount{ 32 | Code: "057", 33 | AccountNumber: "0000000000", 34 | } 35 | 36 | charge := ChargeRequest{ 37 | Email: "your_own_email_here@gmail.com", 38 | Amount: 10000, 39 | Bank: &bankAccount, 40 | Birthday: "1999-12-31", 41 | } 42 | 43 | resp, err := c.Charge.Create(&charge) 44 | if err != nil { 45 | t.Errorf("Create charge returned error: %v", err) 46 | } 47 | 48 | if resp["reference"] == "" { 49 | t.Error("Missing charge reference") 50 | } 51 | 52 | resp2, err := c.Charge.CheckPending(resp["reference"].(string)) 53 | if err != nil { 54 | t.Errorf("Check pending charge returned error: %v", err) 55 | } 56 | 57 | if resp2["status"] == "" { 58 | t.Error("Missing charge pending status") 59 | } 60 | 61 | if resp2["reference"] == "" { 62 | t.Error("Missing charge pending reference") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /customer.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | // CustomerService handles operations related to the customer 9 | // For more details see https://developers.paystack.co/v1.0/reference#create-customer 10 | type CustomerService service 11 | 12 | // Customer is the resource representing your Paystack customer. 13 | // For more details see https://developers.paystack.co/v1.0/reference#create-customer 14 | type Customer struct { 15 | ID int `json:"id,omitempty"` 16 | CreatedAt string `json:"createdAt,omitempty"` 17 | UpdatedAt string `json:"updatedAt,omitempty"` 18 | Domain string `json:"domain,omitempty"` 19 | Integration int `json:"integration,omitempty"` 20 | FirstName string `json:"first_name,omitempty"` 21 | LastName string `json:"last_name,omitempty"` 22 | Email string `json:"email,omitempty"` 23 | Phone string `json:"phone,omitempty"` 24 | Metadata Metadata `json:"metadata,omitempty"` 25 | CustomerCode string `json:"customer_code,omitempty"` 26 | Subscriptions []Subscription `json:"subscriptions,omitempty"` 27 | Authorizations []interface{} `json:"authorizations,omitempty"` 28 | RiskAction string `json:"risk_action"` 29 | } 30 | 31 | // CustomerList is a list object for customers. 32 | type CustomerList struct { 33 | Meta ListMeta 34 | Values []Customer `json:"data"` 35 | } 36 | 37 | // Create creates a new customer 38 | // For more details see https://developers.paystack.co/v1.0/reference#create-customer 39 | func (s *CustomerService) Create(customer *Customer) (*Customer, error) { 40 | u := fmt.Sprintf("/customer") 41 | cust := &Customer{} 42 | err := s.client.Call("POST", u, customer, cust) 43 | 44 | return cust, err 45 | } 46 | 47 | // Update updates a customer's properties. 48 | // For more details see https://developers.paystack.co/v1.0/reference#update-customer 49 | func (s *CustomerService) Update(customer *Customer) (*Customer, error) { 50 | u := fmt.Sprintf("customer/%d", customer.ID) 51 | cust := &Customer{} 52 | err := s.client.Call("PUT", u, customer, cust) 53 | 54 | return cust, err 55 | } 56 | 57 | // Get returns the details of a customer. 58 | // For more details see https://paystack.com/docs/api/#customer-fetch 59 | func (s *CustomerService) Get(customerCode string) (*Customer, error) { 60 | u := fmt.Sprintf("/customer/%s", customerCode) 61 | cust := &Customer{} 62 | err := s.client.Call("GET", u, nil, cust) 63 | 64 | return cust, err 65 | } 66 | 67 | // List returns a list of customers. 68 | // For more details see https://developers.paystack.co/v1.0/reference#list-customers 69 | func (s *CustomerService) List() (*CustomerList, error) { 70 | return s.ListN(10, 0) 71 | } 72 | 73 | // ListN returns a list of customers 74 | // For more details see https://developers.paystack.co/v1.0/reference#list-customers 75 | func (s *CustomerService) ListN(count, offset int) (*CustomerList, error) { 76 | u := paginateURL("/customer", count, offset) 77 | cust := &CustomerList{} 78 | err := s.client.Call("GET", u, nil, cust) 79 | return cust, err 80 | } 81 | 82 | // SetRiskAction can be used to either whitelist or blacklist a customer 83 | // For more details see https://developers.paystack.co/v1.0/reference#whiteblacklist-customer 84 | func (s *CustomerService) SetRiskAction(customerCode, riskAction string) (*Customer, error) { 85 | reqBody := struct { 86 | Customer string `json:"customer"` 87 | Risk_action string `json:"risk_action"` 88 | }{ 89 | Customer: customerCode, 90 | Risk_action: riskAction, 91 | } 92 | cust := &Customer{} 93 | err := s.client.Call("POST", "/customer/set_risk_action", reqBody, cust) 94 | 95 | return cust, err 96 | } 97 | 98 | // DeactivateAuthorization deactivates an authorization 99 | // For more details see https://developers.paystack.co/v1.0/reference#deactivate-authorization 100 | func (s *CustomerService) DeactivateAuthorization(authorizationCode string) (*Response, error) { 101 | params := url.Values{} 102 | params.Add("authorization_code", authorizationCode) 103 | 104 | resp := &Response{} 105 | err := s.client.Call("POST", "/customer/deactivate_authorization", params, resp) 106 | 107 | return resp, err 108 | } 109 | -------------------------------------------------------------------------------- /customer_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCustomerCRUD(t *testing.T) { 8 | cust := &Customer{ 9 | FirstName: "User123", 10 | LastName: "AdminUser", 11 | Email: "user123@gmail.com", 12 | Phone: "+23400000000000000", 13 | } 14 | // create the customer 15 | customer, err := c.Customer.Create(cust) 16 | if err != nil { 17 | t.Errorf("CREATE Customer returned error: %v", err) 18 | } 19 | 20 | // retrieve the customer 21 | customer, err = c.Customer.Get(customer.CustomerCode) 22 | if err != nil { 23 | t.Errorf("GET Customer returned error: %v", err) 24 | } 25 | 26 | if customer.Email != cust.Email { 27 | t.Errorf("Expected Customer email %v, got %v", cust.Email, customer.Email) 28 | } 29 | 30 | if customer.FirstName != cust.FirstName { 31 | t.Errorf("Expected Customer first name %v, got %v", cust.FirstName, customer.FirstName) 32 | } 33 | 34 | if customer.LastName != cust.LastName { 35 | t.Errorf("Expected Customer last name %v, got %v", cust.FirstName, customer.LastName) 36 | } 37 | 38 | if customer.Phone != cust.Phone { 39 | t.Errorf("Expected Customer phone %v, got %v", cust.Phone, customer.Phone) 40 | } 41 | 42 | // retrieve the customer list 43 | customers, err := c.Customer.List() 44 | if err != nil || !(len(customers.Values) > 0) || !(customers.Meta.Total > 0) { 45 | t.Errorf("Expected Customer list, got %d, returned error %v", len(customers.Values), err) 46 | } 47 | } 48 | 49 | func TestCustomerRiskAction(t *testing.T) { 50 | cust := &Customer{ 51 | FirstName: "User123", 52 | LastName: "AdminUser", 53 | Email: "user1-deny@gmail.com", 54 | Phone: "+2341000000000000", 55 | } 56 | customer1, _ := c.Customer.Create(cust) 57 | 58 | //TODO: investigate why 'allow' returns: 403 You cannot whitelist customers on this integration 59 | customer, err := c.Customer.SetRiskAction(customer1.CustomerCode, "deny") 60 | if err != nil { 61 | t.Errorf("Customer risk action returned error %v", err) 62 | } 63 | 64 | if customer.Email != customer1.Email { 65 | t.Errorf("Expected Customer email %v, got %v", cust.Email, customer.Email) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package paystack provides the binding for Paystack REST APIs. 3 | Where possible, the services available on the client groups the API into 4 | logical chunks and correspond to the structure of the Paystack API 5 | documentation at https://developers.paystack.co/v1.0/reference. 6 | 7 | Usage: 8 | 9 | import "github.com/rpip/paystack-go" 10 | 11 | apiKey := "sk_test_b748a89ad84f35c2f1a8b81681f956274de048bb" 12 | 13 | // second param is an optional http client, allowing overriding of the HTTP client to use. 14 | // This is useful if you're running in a Google AppEngine environment 15 | // where the http.DefaultClient is not available. 16 | client := paystack.NewClient(apiKey) 17 | 18 | recipient := &TransferRecipient{ 19 | Type: "Nuban", 20 | Name: "Customer 1", 21 | Description: "Demo customer", 22 | AccountNumber: "0100000010", 23 | BankCode: "044", 24 | Currency: "NGN", 25 | Metadata: map[string]interface{}{"job": "Plumber"}, 26 | } 27 | 28 | recipient1, err := client.Transfer.CreateRecipient(recipient) 29 | 30 | req := &TransferRequest{ 31 | Source: "balance", 32 | Reason: "Delivery pickup", 33 | Amount: 30, 34 | Recipient: recipient1.RecipientCode, 35 | } 36 | 37 | transfer, err := client.Transfer.Initiate(req) 38 | 39 | // retrieve list of plans 40 | plans, err := client.Plan.List() 41 | 42 | for i, plan := range plans.Values { 43 | fmt.Printf("%+v", plan) 44 | } 45 | 46 | */ 47 | package paystack 48 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | // APIError includes the response from the Paystack API and some HTTP request info 11 | type APIError struct { 12 | Message string `json:"message,omitempty"` 13 | HTTPStatusCode int `json:"code,omitempty"` 14 | Details ErrorResponse `json:"details,omitempty"` 15 | URL *url.URL `json:"url,omitempty"` 16 | Header http.Header `json:"header,omitempty"` 17 | } 18 | 19 | // APIError supports the error interface 20 | func (aerr *APIError) Error() string { 21 | ret, _ := json.Marshal(aerr) 22 | return string(ret) 23 | } 24 | 25 | // ErrorResponse represents an error response from the Paystack API server 26 | type ErrorResponse struct { 27 | Status bool `json:"status,omitempty"` 28 | Message string `json:"message,omitempty"` 29 | Errors map[string]interface{} `json:"errors,omitempty"` 30 | } 31 | 32 | func newAPIError(resp *http.Response) *APIError { 33 | p, _ := ioutil.ReadAll(resp.Body) 34 | 35 | var paystackErrorResp ErrorResponse 36 | _ = json.Unmarshal(p, &paystackErrorResp) 37 | return &APIError{ 38 | HTTPStatusCode: resp.StatusCode, 39 | Header: resp.Header, 40 | Details: paystackErrorResp, 41 | URL: resp.Request.URL, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: fe24a4f9ac3638e26670f07c4209aefa47efc3cf2f8b6fb323fbedb144ab36b2 2 | updated: 2017-08-23T22:48:08.57725539Z 3 | imports: 4 | - name: github.com/mitchellh/mapstructure 5 | version: db1efb556f84b25a0a13a04aad883943538ad2e0 6 | testImports: [] 7 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/rpip/paystack-go 2 | import: 3 | - package: github.com/mitchellh/mapstructure 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rpip/paystack-go 2 | 3 | go 1.16 4 | 5 | require github.com/mitchellh/mapstructure v0.0.0-20170125051937-db1efb556f84 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mitchellh/mapstructure v0.0.0-20170125051937-db1efb556f84 h1:rrg06yhhsqEELubsnYWqadxdi0CYJ97s899oUXDIrkY= 2 | github.com/mitchellh/mapstructure v0.0.0-20170125051937-db1efb556f84/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 3 | -------------------------------------------------------------------------------- /page.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "fmt" 4 | 5 | // PageService handles operations related to the page 6 | // For more details see https://developers.paystack.co/v1.0/reference#create-page 7 | type PageService service 8 | 9 | // Page represents a Paystack page 10 | // For more details see https://developers.paystack.co/v1.0/reference#create-page 11 | type Page struct { 12 | ID int `json:"id,omitempty"` 13 | CreatedAt string `json:"createdAt,omitempty"` 14 | UpdatedAt string `json:"updatedAt,omitempty"` 15 | Domain string `json:"domain,omitempty"` 16 | Integration int `json:"integration,omitempty"` 17 | Name string `json:"name,omitempty"` 18 | Slug string `json:"slug,omitempty"` 19 | Description string `json:"description,omitempty"` 20 | Amount float32 `json:"amount,omitempty"` 21 | Currency string `json:"currency,omitempty"` 22 | Active bool `json:"active,omitempty"` 23 | RedirectURL string `json:"redirect_url,omitempty"` 24 | CustomFields []map[string]string `json:"custom_fields,omitempty"` 25 | } 26 | 27 | // PageList is a list object for pages. 28 | type PageList struct { 29 | Meta ListMeta 30 | Values []Page `json:"data,omitempty"` 31 | } 32 | 33 | // Create creates a new page 34 | // For more details see https://developers.paystack.co/v1.0/reference#create-page 35 | func (s *PageService) Create(page *Page) (*Page, error) { 36 | u := fmt.Sprintf("/page") 37 | pg := &Page{} 38 | err := s.client.Call("POST", u, page, pg) 39 | 40 | return pg, err 41 | } 42 | 43 | // Update updates a page's properties. 44 | // For more details see https://developers.paystack.co/v1.0/reference#update-page 45 | func (s *PageService) Update(page *Page) (*Page, error) { 46 | u := fmt.Sprintf("page/%d", page.ID) 47 | pg := &Page{} 48 | err := s.client.Call("PUT", u, page, pg) 49 | 50 | return pg, err 51 | } 52 | 53 | // Get returns the details of a page. 54 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-page 55 | func (s *PageService) Get(id int) (*Page, error) { 56 | u := fmt.Sprintf("/page/%d", id) 57 | pg := &Page{} 58 | err := s.client.Call("GET", u, nil, pg) 59 | 60 | return pg, err 61 | } 62 | 63 | // List returns a list of pages. 64 | // For more details see https://developers.paystack.co/v1.0/reference#list-pages 65 | func (s *PageService) List() (*PageList, error) { 66 | return s.ListN(10, 0) 67 | } 68 | 69 | // ListN returns a list of pages 70 | // For more details see https://developers.paystack.co/v1.0/reference#list-pages 71 | func (s *PageService) ListN(count, offset int) (*PageList, error) { 72 | u := paginateURL("/page", count, offset) 73 | pg := &PageList{} 74 | err := s.client.Call("GET", u, nil, pg) 75 | return pg, err 76 | } 77 | -------------------------------------------------------------------------------- /page_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "testing" 4 | 5 | func TestPageCRUD(t *testing.T) { 6 | page1 := &Page{ 7 | Name: "Demo page", 8 | Description: "Paystack Go client test page", 9 | } 10 | 11 | // create the page 12 | page, err := c.Page.Create(page1) 13 | if err != nil { 14 | t.Errorf("CREATE Page returned error: %v", err) 15 | } 16 | 17 | // retrieve the page 18 | page, err = c.Page.Get(page.ID) 19 | if err != nil { 20 | t.Errorf("GET Page returned error: %v", err) 21 | } 22 | 23 | if page.Name != page1.Name { 24 | t.Errorf("Expected Page Name %v, got %v", page.Name, page1.Name) 25 | } 26 | 27 | // retrieve the page list 28 | pages, err := c.Page.List() 29 | if err != nil || !(len(pages.Values) > 0) || !(pages.Meta.Total > 0) { 30 | t.Errorf("Expected Page list, got %d, returned error %v", len(pages.Values), err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /paystack.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "strconv" 14 | "time" 15 | 16 | "github.com/mitchellh/mapstructure" 17 | ) 18 | 19 | const ( 20 | // library version 21 | version = "0.1.0" 22 | 23 | // defaultHTTPTimeout is the default timeout on the http client 24 | defaultHTTPTimeout = 60 * time.Second 25 | 26 | // base URL for all Paystack API requests 27 | baseURL = "https://api.paystack.co" 28 | 29 | // User agent used when communicating with the Paystack API. 30 | // userAgent = "paystack-go/" + version 31 | userAgent = "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1" 32 | ) 33 | 34 | type service struct { 35 | client *Client 36 | } 37 | 38 | // Client manages communication with the Paystack API 39 | type Client struct { 40 | common service // Reuse a single struct instead of allocating one for each service on the heap. 41 | client *http.Client // HTTP client used to communicate with the API. 42 | 43 | // the API Key used to authenticate all Paystack API requests 44 | key string 45 | 46 | baseURL *url.URL 47 | 48 | logger Logger 49 | // Services supported by the Paystack API. 50 | // Miscellaneous actions are directly implemented on the Client object 51 | Customer *CustomerService 52 | Transaction *TransactionService 53 | SubAccount *SubAccountService 54 | Plan *PlanService 55 | Subscription *SubscriptionService 56 | Page *PageService 57 | Settlement *SettlementService 58 | Transfer *TransferService 59 | Charge *ChargeService 60 | Bank *BankService 61 | BulkCharge *BulkChargeService 62 | 63 | LoggingEnabled bool 64 | Log Logger 65 | } 66 | 67 | // Logger interface for custom loggers 68 | type Logger interface { 69 | Printf(format string, v ...interface{}) 70 | } 71 | 72 | // Metadata is an key-value pairs added to Paystack API requests 73 | type Metadata map[string]interface{} 74 | 75 | // Response represents arbitrary response data 76 | type Response map[string]interface{} 77 | 78 | // RequestValues aliased to url.Values as a workaround 79 | type RequestValues url.Values 80 | 81 | // MarshalJSON to handle custom JSON decoding for RequestValues 82 | func (v RequestValues) MarshalJSON() ([]byte, error) { 83 | m := make(map[string]interface{}, 3) 84 | for k, val := range v { 85 | m[k] = val[0] 86 | } 87 | return json.Marshal(m) 88 | } 89 | 90 | // ListMeta is pagination metadata for paginated responses from the Paystack API 91 | type ListMeta struct { 92 | Total int `json:"total"` 93 | Skipped int `json:"skipped"` 94 | PerPage int `json:"perPage"` 95 | Page int `json:"page"` 96 | PageCount int `json:"pageCount"` 97 | } 98 | 99 | // NewClient creates a new Paystack API client with the given API key 100 | // and HTTP client, allowing overriding of the HTTP client to use. 101 | // This is useful if you're running in a Google AppEngine environment 102 | // where the http.DefaultClient is not available. 103 | func NewClient(key string, httpClient *http.Client) *Client { 104 | if httpClient == nil { 105 | httpClient = &http.Client{Timeout: defaultHTTPTimeout} 106 | } 107 | 108 | u, _ := url.Parse(baseURL) 109 | c := &Client{ 110 | client: httpClient, 111 | key: key, 112 | baseURL: u, 113 | LoggingEnabled: true, 114 | Log: log.New(os.Stderr, "", log.LstdFlags), 115 | } 116 | 117 | c.common.client = c 118 | c.Customer = (*CustomerService)(&c.common) 119 | c.Transaction = (*TransactionService)(&c.common) 120 | c.SubAccount = (*SubAccountService)(&c.common) 121 | c.Plan = (*PlanService)(&c.common) 122 | c.Subscription = (*SubscriptionService)(&c.common) 123 | c.Page = (*PageService)(&c.common) 124 | c.Settlement = (*SettlementService)(&c.common) 125 | c.Transfer = (*TransferService)(&c.common) 126 | c.Charge = (*ChargeService)(&c.common) 127 | c.Bank = (*BankService)(&c.common) 128 | c.BulkCharge = (*BulkChargeService)(&c.common) 129 | 130 | return c 131 | } 132 | 133 | // Call actually does the HTTP request to Paystack API 134 | func (c *Client) Call(method, path string, body, v interface{}) error { 135 | var buf io.ReadWriter 136 | if body != nil { 137 | buf = new(bytes.Buffer) 138 | err := json.NewEncoder(buf).Encode(body) 139 | if err != nil { 140 | return err 141 | } 142 | } 143 | u, _ := c.baseURL.Parse(path) 144 | req, err := http.NewRequest(method, u.String(), buf) 145 | 146 | if err != nil { 147 | if c.LoggingEnabled { 148 | c.Log.Printf("Cannot create Paystack request: %v\n", err) 149 | } 150 | return err 151 | } 152 | 153 | if body != nil { 154 | req.Header.Set("Content-Type", "application/json") 155 | } 156 | req.Header.Set("Authorization", "Bearer "+c.key) 157 | req.Header.Set("User-Agent", userAgent) 158 | 159 | if c.LoggingEnabled { 160 | c.Log.Printf("Requesting %v %v%v\n", req.Method, req.URL.Host, req.URL.Path) 161 | c.Log.Printf("POST request data %v\n", buf) 162 | } 163 | 164 | start := time.Now() 165 | 166 | resp, err := c.client.Do(req) 167 | if err != nil { 168 | return err 169 | } 170 | 171 | if c.LoggingEnabled { 172 | c.Log.Printf("Completed in %v\n", time.Since(start)) 173 | } 174 | 175 | defer resp.Body.Close() 176 | return c.decodeResponse(resp, v) 177 | } 178 | 179 | // ResolveCardBIN docs https://developers.paystack.co/v1.0/reference#resolve-card-bin 180 | func (c *Client) ResolveCardBIN(bin int) (Response, error) { 181 | u := fmt.Sprintf("/decision/bin/%d", bin) 182 | resp := Response{} 183 | err := c.Call("GET", u, nil, &resp) 184 | 185 | return resp, err 186 | } 187 | 188 | // CheckBalance docs https://developers.paystack.co/v1.0/reference#resolve-card-bin 189 | func (c *Client) CheckBalance() (Response, error) { 190 | resp := Response{} 191 | err := c.Call("GET", "balance", nil, &resp) 192 | // check balance 'data' node is an array 193 | resp2 := resp["data"].([]interface{})[0].(map[string]interface{}) 194 | return resp2, err 195 | } 196 | 197 | // GetSessionTimeout fetches payment session timeout 198 | func (c *Client) GetSessionTimeout() (Response, error) { 199 | resp := Response{} 200 | err := c.Call("GET", "/integration/payment_session_timeout", nil, &resp) 201 | return resp, err 202 | } 203 | 204 | // UpdateSessionTimeout updates payment session timeout 205 | func (c *Client) UpdateSessionTimeout(timeout int) (Response, error) { 206 | data := url.Values{} 207 | data.Add("timeout", strconv.Itoa(timeout)) 208 | resp := Response{} 209 | u := "/integration/payment_session_timeout" 210 | err := c.Call("PUT", u, data, &resp) 211 | return resp, err 212 | } 213 | 214 | // INTERNALS 215 | func paginateURL(path string, count, offset int) string { 216 | return fmt.Sprintf("%s?perPage=%d&page=%d", path, count, offset) 217 | } 218 | 219 | func mapstruct(data interface{}, v interface{}) error { 220 | config := &mapstructure.DecoderConfig{ 221 | Result: v, 222 | TagName: "json", 223 | WeaklyTypedInput: true, 224 | } 225 | decoder, err := mapstructure.NewDecoder(config) 226 | if err != nil { 227 | return err 228 | } 229 | err = decoder.Decode(data) 230 | return err 231 | } 232 | 233 | func mustGetTestKey() string { 234 | key := os.Getenv("PAYSTACK_KEY") 235 | 236 | if len(key) == 0 { 237 | panic("PAYSTACK_KEY environment variable is not set\n") 238 | } 239 | 240 | return key 241 | } 242 | 243 | // decodeResponse decodes the JSON response from the Twitter API. 244 | // The actual response will be written to the `v` parameter 245 | func (c *Client) decodeResponse(httpResp *http.Response, v interface{}) error { 246 | var resp Response 247 | respBody, err := ioutil.ReadAll(httpResp.Body) 248 | json.Unmarshal(respBody, &resp) 249 | 250 | if status, _ := resp["status"].(bool); !status || httpResp.StatusCode >= 400 { 251 | if c.LoggingEnabled { 252 | c.Log.Printf("Paystack error: %+v", err) 253 | c.Log.Printf("HTTP Response: %+v", resp) 254 | } 255 | return newAPIError(httpResp) 256 | } 257 | 258 | if c.LoggingEnabled { 259 | c.Log.Printf("Paystack response: %v\n", resp) 260 | } 261 | 262 | if data, ok := resp["data"]; ok { 263 | switch t := resp["data"].(type) { 264 | case map[string]interface{}: 265 | return mapstruct(data, v) 266 | default: 267 | _ = t 268 | return mapstruct(resp, v) 269 | } 270 | } 271 | // if response data does not contain data key, map entire response to v 272 | return mapstruct(resp, v) 273 | } 274 | -------------------------------------------------------------------------------- /paystack_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "testing" 4 | 5 | var c *Client 6 | 7 | func init() { 8 | apiKey := mustGetTestKey() 9 | c = NewClient(apiKey, nil) 10 | } 11 | 12 | func TestResolveCardBIN(t *testing.T) { 13 | resp, err := c.ResolveCardBIN(59983) 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | if _, ok := resp["bin"]; !ok { 18 | t.Errorf("Expected response to contain bin") 19 | } 20 | } 21 | 22 | func TestCheckBalance(t *testing.T) { 23 | resp, err := c.CheckBalance() 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | if _, ok := resp["currency"]; !ok { 28 | t.Errorf("Expected response to contain currency") 29 | } 30 | 31 | if _, ok := resp["balance"]; !ok { 32 | t.Errorf("Expected response to contain balance") 33 | } 34 | } 35 | 36 | func TestSessionTimeout(t *testing.T) { 37 | resp, err := c.GetSessionTimeout() 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | if _, ok := resp["payment_session_timeout"]; !ok { 42 | t.Errorf("Expected response to contain payment_session_timeout") 43 | } 44 | 45 | /* 46 | // actual tests in the Paystack API console also fails. Likely a server error 47 | resp, err = c.UpdateSessionTimeout(30) 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | 52 | if _, ok := resp["payment_session_timeout"]; !ok { 53 | t.Errorf("Expected response to contain payment_session_timeout") 54 | } 55 | */ 56 | } 57 | -------------------------------------------------------------------------------- /plan.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "fmt" 4 | 5 | // PlanService handles operations related to the plan 6 | // For more details see https://developers.paystack.co/v1.0/reference#create-plan 7 | type PlanService service 8 | 9 | // Plan represents a 10 | // For more details see https://developers.paystack.co/v1.0/reference#create-plan 11 | type Plan struct { 12 | ID int `json:"id,omitempty"` 13 | CreatedAt string `json:"createdAt,omitempty"` 14 | UpdatedAt string `json:"updatedAt,omitempty"` 15 | Domain string `json:"domain,omitempty"` 16 | Integration int `json:"integration,omitempty"` 17 | Name string `json:"name,omitempty"` 18 | Description string `json:"description,omitempty"` 19 | PlanCode string `json:"plan_code,omitempty"` 20 | Amount float32 `json:"amount,omitempty"` 21 | Interval string `json:"interval,omitempty"` 22 | SendInvoices bool `json:"send_invoices,omitempty"` 23 | SendSMS bool `json:"send_sms,omitempty"` 24 | Currency string `json:"currency,omitempty"` 25 | InvoiceLimit float32 `json:"invoice_limit,omitempty"` 26 | HostedPage string `json:"hosted_page,omitempty"` 27 | HostedPageURL string `json:"hosted_page_url,omitempty"` 28 | HostedPageSummary string `json:"hosted_page_summary,omitempty"` 29 | } 30 | 31 | // PlanList is a list object for Plans. 32 | type PlanList struct { 33 | Meta ListMeta 34 | Values []Plan `json:"data"` 35 | } 36 | 37 | // Create creates a new plan 38 | // For more details see https://developers.paystack.co/v1.0/reference#create-plan 39 | func (s *PlanService) Create(plan *Plan) (*Plan, error) { 40 | u := fmt.Sprintf("/plan") 41 | plan2 := &Plan{} 42 | err := s.client.Call("POST", u, plan, plan2) 43 | return plan2, err 44 | } 45 | 46 | // Update updates a plan's properties. 47 | // For more details see https://developers.paystack.co/v1.0/reference#update-plan 48 | func (s *PlanService) Update(plan *Plan) (Response, error) { 49 | u := fmt.Sprintf("plan/%d", plan.ID) 50 | resp := Response{} 51 | err := s.client.Call("PUT", u, plan, &resp) 52 | return resp, err 53 | } 54 | 55 | // Get returns the details of a plan. 56 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-plan 57 | func (s *PlanService) Get(id int) (*Plan, error) { 58 | u := fmt.Sprintf("/plan/%d", id) 59 | plan2 := &Plan{} 60 | err := s.client.Call("GET", u, nil, plan2) 61 | return plan2, err 62 | } 63 | 64 | // List returns a list of plans. 65 | // For more details see https://developers.paystack.co/v1.0/reference#list-plans 66 | func (s *PlanService) List() (*PlanList, error) { 67 | return s.ListN(10, 0) 68 | } 69 | 70 | // ListN returns a list of plans 71 | // For more details see https://developers.paystack.co/v1.0/reference#list-plans 72 | func (s *PlanService) ListN(count, offset int) (*PlanList, error) { 73 | u := paginateURL("/plan", count, offset) 74 | plan2 := &PlanList{} 75 | err := s.client.Call("GET", u, nil, plan2) 76 | return plan2, err 77 | } 78 | -------------------------------------------------------------------------------- /plan_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "testing" 4 | 5 | func TestPlanCRUD(t *testing.T) { 6 | plan1 := &Plan{ 7 | Name: "Monthly retainer", 8 | Interval: "monthly", 9 | Amount: 500000, 10 | } 11 | 12 | // create the plan 13 | plan, err := c.Plan.Create(plan1) 14 | if err != nil { 15 | t.Errorf("CREATE Plan returned error: %v", err) 16 | } 17 | 18 | if plan.PlanCode == "" { 19 | t.Errorf("Expected Plan code to be set") 20 | } 21 | 22 | // retrieve the plan 23 | plan, err = c.Plan.Get(plan.ID) 24 | if err != nil { 25 | t.Errorf("GET Plan returned error: %v", err) 26 | } 27 | 28 | if plan.Name != plan1.Name { 29 | t.Errorf("Expected Plan Name %v, got %v", plan.Name, plan1.Name) 30 | } 31 | 32 | // retrieve the plan list 33 | plans, err := c.Plan.List() 34 | if err != nil || !(len(plans.Values) > 0) || !(plans.Meta.Total > 0) { 35 | t.Errorf("Expected Plan list, got %d, returned error %v", len(plans.Values), err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go vet $(go list ./... | grep -v vendor) 4 | test -z "$(golint ./... | grep -v vendor | tee /dev/stderr)" 5 | test -z "$(gofmt -s -l . | grep -v vendor | tee /dev/stderr)" 6 | go test -cover $(go list ./... | grep -v vendor) 7 | go test -race $(go list ./... | grep -v vendor) 8 | go test $(go list ./... | grep -v vendor) 9 | -------------------------------------------------------------------------------- /settlement.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | // SettlementService handles operations related to the settlement 4 | // For more details see https://developers.paystack.co/v1.0/reference#create-settlement 5 | type SettlementService service 6 | 7 | // SettlementList is a list object for settlements. 8 | type SettlementList struct { 9 | Meta ListMeta 10 | Values []Response `json:"data,omitempty"` 11 | } 12 | 13 | // List returns a list of settlements. 14 | // For more details see https://developers.paystack.co/v1.0/reference#settlements 15 | func (s *SettlementService) List() (*SettlementList, error) { 16 | return s.ListN(10, 0) 17 | } 18 | 19 | // ListN returns a list of settlements 20 | // For more details see https://developers.paystack.co/v1.0/reference#settlements 21 | func (s *SettlementService) ListN(count, offset int) (*SettlementList, error) { 22 | u := paginateURL("/settlement", count, offset) 23 | pg := &SettlementList{} 24 | err := s.client.Call("GET", u, nil, pg) 25 | return pg, err 26 | } 27 | -------------------------------------------------------------------------------- /settlement_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSettlementList(t *testing.T) { 9 | // retrieve the settlement list 10 | settlements, err := c.Settlement.List() 11 | 12 | if err != nil { 13 | t.Error(err) 14 | } 15 | 16 | if err == nil { 17 | fmt.Printf("Settlements total: %d", len(settlements.Values)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /subaccount.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "fmt" 4 | 5 | // SubAccountService handles operations related to sub accounts 6 | // For more details see https://developers.paystack.co/v1.0/reference#create-subaccount 7 | type SubAccountService service 8 | 9 | // SubAccount is the resource representing your Paystack subaccount. 10 | // For more details see https://developers.paystack.co/v1.0/reference#create-subaccount 11 | type SubAccount struct { 12 | ID int `json:"id,omitempty"` 13 | CreatedAt string `json:"createdAt,omitempty"` 14 | UpdatedAt string `json:"updatedAt,omitempty"` 15 | Domain string `json:"domain,omitempty"` 16 | Integration int `json:"integration,omitempty"` 17 | BusinessName string `json:"business_name,omitempty"` 18 | SubAccountCode string `json:"subaccount_code,omitempty"` 19 | Description string `json:"description,omitempty"` 20 | PrimaryContactName string `json:"primary_contact_name,omitempty"` 21 | PrimaryContactEmail string `json:"primary_contact_email,omitempty"` 22 | PrimaryContactPhone string `json:"primary_contact_phone,omitempty"` 23 | Metadata Metadata `json:"metadata,omitempty"` 24 | PercentageCharge float32 `json:"percentage_charge,omitempty"` 25 | IsVerified bool `json:"is_verified,omitempty"` 26 | SettlementBank string `json:"settlement_bank,omitempty"` 27 | AccountNumber string `json:"account_number,omitempty"` 28 | SettlementSchedule string `json:"settlement_schedule,omitempty"` 29 | Active bool `json:"active,omitempty"` 30 | Migrate bool `json:"migrate,omitempty"` 31 | } 32 | 33 | // SubAccountList is a list object for subaccounts. 34 | type SubAccountList struct { 35 | Meta ListMeta 36 | Values []SubAccount `json:"data"` 37 | } 38 | 39 | // Create creates a new subaccount 40 | // For more details see https://paystack.com/docs/api/#subaccount-create 41 | func (s *SubAccountService) Create(subaccount *SubAccount) (*SubAccount, error) { 42 | u := fmt.Sprintf("/subaccount") 43 | acc := &SubAccount{} 44 | err := s.client.Call("POST", u, subaccount, acc) 45 | return acc, err 46 | } 47 | 48 | // Update updates a subaccount's properties. 49 | // For more details see https://developers.paystack.co/v1.0/reference#update-subaccount 50 | // TODO: use ID or slug 51 | func (s *SubAccountService) Update(subaccount *SubAccount) (*SubAccount, error) { 52 | u := fmt.Sprintf("subaccount/%d", subaccount.ID) 53 | acc := &SubAccount{} 54 | err := s.client.Call("PUT", u, subaccount, acc) 55 | 56 | return acc, err 57 | } 58 | 59 | // Get returns the details of a subaccount. 60 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-subaccount 61 | // TODO: use ID or slug 62 | func (s *SubAccountService) Get(id int) (*SubAccount, error) { 63 | u := fmt.Sprintf("/subaccount/%d", id) 64 | acc := &SubAccount{} 65 | err := s.client.Call("GET", u, nil, acc) 66 | 67 | return acc, err 68 | } 69 | 70 | // List returns a list of subaccounts. 71 | // For more details see https://developers.paystack.co/v1.0/reference#list-subaccounts 72 | func (s *SubAccountService) List() (*SubAccountList, error) { 73 | return s.ListN(10, 1) 74 | } 75 | 76 | // ListN returns a list of subaccounts 77 | // For more details see https://paystack.com/docs/api/#subaccount-list 78 | func (s *SubAccountService) ListN(count, offset int) (*SubAccountList, error) { 79 | u := paginateURL("/subaccount", count, offset) 80 | acc := &SubAccountList{} 81 | err := s.client.Call("GET", u, nil, acc) 82 | return acc, err 83 | } 84 | -------------------------------------------------------------------------------- /subaccount_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "testing" 4 | 5 | func TestSubAccountCRUD(t *testing.T) { 6 | subAccount1 := &SubAccount{ 7 | BusinessName: "Sunshine Studios", 8 | SettlementBank: "044", 9 | AccountNumber: "0193278965", 10 | PercentageCharge: 18.2, 11 | } 12 | 13 | // create the subAccount 14 | subAccount, err := c.SubAccount.Create(subAccount1) 15 | if err != nil { 16 | t.Errorf("CREATE SubAccount returned error: %v", err) 17 | } 18 | 19 | if subAccount.SubAccountCode == "" { 20 | t.Errorf("Expected SubAccount code to be set") 21 | } 22 | 23 | // retrieve the subAccount 24 | subAccount, err = c.SubAccount.Get(subAccount.ID) 25 | if err != nil { 26 | t.Errorf("GET SubAccount returned error: %v", err) 27 | } 28 | 29 | if subAccount.BusinessName != subAccount1.BusinessName { 30 | t.Errorf("Expected SubAccount BusinessName %v, got %v", subAccount.BusinessName, subAccount1.BusinessName) 31 | } 32 | 33 | // retrieve the subAccount list 34 | subAccounts, err := c.SubAccount.List() 35 | if err != nil || !(len(subAccounts.Values) > 0) || !(subAccounts.Meta.Total > 0) { 36 | t.Errorf("Expected SubAccount list, got %d, returned error %v", len(subAccounts.Values), err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /subscription.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | // SubscriptionService handles operations related to the subscription 9 | // For more details see https://developers.paystack.co/v1.0/reference#create-subscription 10 | type SubscriptionService service 11 | 12 | // Subscription represents a Paystack subscription 13 | // For more details see https://developers.paystack.co/v1.0/reference#create-subscription 14 | type Subscription struct { 15 | ID int `json:"id,omitempty"` 16 | CreatedAt string `json:"createdAt,omitempty"` 17 | UpdatedAt string `json:"updatedAt,omitempty"` 18 | Domain string `json:"domain,omitempty"` 19 | Integration int `json:"integration,omitempty"` 20 | // inconsistent API response. Create returns Customer code, Fetch returns an object 21 | Customer interface{} `json:"customer,omitempty"` 22 | Plan string `json:"plan,omitempty"` 23 | StartDate string `json:"start,omitempty"` 24 | // inconsistent API response. Fetch returns string, List returns an object 25 | Authorization interface{} `json:"authorization,omitempty"` 26 | Invoices []interface{} `json:"invoices,omitempty"` 27 | Status string `json:"status,omitempty"` 28 | Quantity int `json:"quantity,omitempty"` 29 | Amount int `json:"amount,omitempty"` 30 | SubscriptionCode string `json:"subscription_code,omitempty"` 31 | EmailToken string `json:"email_token,omitempty"` 32 | EasyCronID string `json:"easy_cron_id,omitempty"` 33 | CronExpression string `json:"cron_expression,omitempty"` 34 | NextPaymentDate string `json:"next_payment_date,omitempty"` 35 | OpenInvoice string `json:"open_invoice,omitempty"` 36 | } 37 | 38 | // SubscriptionRequest represents a Paystack subscription request 39 | type SubscriptionRequest struct { 40 | // customer code or email address 41 | Customer string `json:"customer,omitempty"` 42 | // plan code 43 | Plan string `json:"plan,omitempty"` 44 | Authorization string `json:"authorization,omitempty"` 45 | StartDate string `json:"start,omitempty"` 46 | } 47 | 48 | // SubscriptionList is a list object for subscriptions. 49 | type SubscriptionList struct { 50 | Meta ListMeta 51 | Values []Subscription `json:"data"` 52 | } 53 | 54 | // Create creates a new subscription 55 | // For more details see https://developers.paystack.co/v1.0/reference#create-subscription 56 | func (s *SubscriptionService) Create(subscription *SubscriptionRequest) (*Subscription, error) { 57 | u := fmt.Sprintf("/subscription") 58 | sub := &Subscription{} 59 | err := s.client.Call("POST", u, subscription, sub) 60 | return sub, err 61 | } 62 | 63 | // Update updates a subscription's properties. 64 | // For more details see https://developers.paystack.co/v1.0/reference#update-subscription 65 | func (s *SubscriptionService) Update(subscription *Subscription) (*Subscription, error) { 66 | u := fmt.Sprintf("subscription/%d", subscription.ID) 67 | sub := &Subscription{} 68 | err := s.client.Call("PUT", u, subscription, sub) 69 | return sub, err 70 | } 71 | 72 | // Get returns the details of a subscription. 73 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-subscription 74 | func (s *SubscriptionService) Get(id int) (*Subscription, error) { 75 | u := fmt.Sprintf("/subscription/%d", id) 76 | sub := &Subscription{} 77 | err := s.client.Call("GET", u, nil, sub) 78 | return sub, err 79 | } 80 | 81 | // List returns a list of subscriptions. 82 | // For more details see https://developers.paystack.co/v1.0/reference#list-subscriptions 83 | func (s *SubscriptionService) List() (*SubscriptionList, error) { 84 | return s.ListN(10, 0) 85 | } 86 | 87 | // ListN returns a list of subscriptions 88 | // For more details see https://developers.paystack.co/v1.0/reference#list-subscriptions 89 | func (s *SubscriptionService) ListN(count, offset int) (*SubscriptionList, error) { 90 | u := paginateURL("/subscription", count, offset) 91 | sub := &SubscriptionList{} 92 | err := s.client.Call("GET", u, nil, sub) 93 | return sub, err 94 | } 95 | 96 | // Enable enables a subscription 97 | // For more details see https://developers.paystack.co/v1.0/reference#enable-subscription 98 | func (s *SubscriptionService) Enable(subscriptionCode, emailToken string) (Response, error) { 99 | params := url.Values{} 100 | params.Add("code", subscriptionCode) 101 | params.Add("token", emailToken) 102 | resp := Response{} 103 | err := s.client.Call("POST", "/subscription/enable", params, &resp) 104 | return resp, err 105 | } 106 | 107 | // Disable disables a subscription 108 | // For more details see https://developers.paystack.co/v1.0/reference#disable-subscription 109 | func (s *SubscriptionService) Disable(subscriptionCode, emailToken string) (Response, error) { 110 | params := url.Values{} 111 | params.Add("code", subscriptionCode) 112 | params.Add("token", emailToken) 113 | resp := Response{} 114 | err := s.client.Call("POST", "/subscription/disable", params, &resp) 115 | return resp, err 116 | } 117 | -------------------------------------------------------------------------------- /subscription_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "testing" 4 | 5 | func TestSubscriptionCRUD(t *testing.T) { 6 | cust := &Customer{ 7 | FirstName: "User123", 8 | LastName: "AdminUser", 9 | Email: "user123-subscription@gmail.com", 10 | Phone: "+23400000000000000", 11 | } 12 | // create the customer 13 | customer, err := c.Customer.Create(cust) 14 | if err != nil { 15 | t.Errorf("CREATE Subscription Customer returned error: %v", err) 16 | } 17 | 18 | plan1 := &Plan{ 19 | Name: "Monthly subscription retainer", 20 | Interval: "monthly", 21 | Amount: 250000, 22 | } 23 | 24 | // create the plan 25 | plan, err := c.Plan.Create(plan1) 26 | if err != nil { 27 | t.Errorf("CREATE Plan returned error: %v", err) 28 | } 29 | 30 | subscription1 := &SubscriptionRequest{ 31 | Customer: customer.CustomerCode, 32 | Plan: plan.PlanCode, 33 | } 34 | 35 | // create the subscription 36 | _, err = c.Subscription.Create(subscription1) 37 | if err == nil { 38 | t.Errorf("Expected CREATE Subscription to fail with aunthorized customer, got %+v", err) 39 | } 40 | 41 | // retrieve the subscription list 42 | subscriptions, err := c.Subscription.List() 43 | if err != nil { 44 | t.Errorf("Expected Subscription list, got %d, returned error %v", len(subscriptions.Values), err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /transaction.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import "fmt" 4 | 5 | // TransactionService handles operations related to transactions 6 | // For more details see https://developers.paystack.co/v1.0/reference#create-transaction 7 | type TransactionService service 8 | 9 | // TransactionList is a list object for transactions. 10 | type TransactionList struct { 11 | Meta ListMeta 12 | Values []Transaction `json:"data"` 13 | } 14 | 15 | // TransactionRequest represents a request to start a transaction. 16 | type TransactionRequest struct { 17 | CallbackURL string `json:"callback_url,omitempty"` 18 | Reference string `json:"reference,omitempty"` 19 | AuthorizationCode string `json:"authorization_code,omitempty"` 20 | Currency string `json:"currency,omitempty"` 21 | Amount float32 `json:"amount,omitempty"` 22 | Email string `json:"email,omitempty"` 23 | Plan string `json:"plan,omitempty"` 24 | InvoiceLimit int `json:"invoice_limit,omitempty"` 25 | Metadata Metadata `json:"metadata,omitempty"` 26 | SubAccount string `json:"subaccount,omitempty"` 27 | TransactionCharge int `json:"transaction_charge,omitempty"` 28 | Bearer string `json:"bearer,omitempty"` 29 | Channels []string `json:"channels,omitempty"` 30 | } 31 | 32 | // AuthorizationRequest represents a request to enable/revoke an authorization 33 | type AuthorizationRequest struct { 34 | Reference string `json:"reference,omitempty"` 35 | AuthorizationCode string `json:"authorization_code,omitempty"` 36 | Amount int `json:"amount,omitempty"` 37 | Currency string `json:"currency,omitempty"` 38 | Email string `json:"email,omitempty"` 39 | Metadata Metadata `json:"metadata,omitempty"` 40 | } 41 | 42 | // Transaction is the resource representing your Paystack transaction. 43 | // For more details see https://developers.paystack.co/v1.0/reference#initialize-a-transaction 44 | type Transaction struct { 45 | ID int `json:"id,omitempty"` 46 | CreatedAt string `json:"createdAt,omitempty"` 47 | Domain string `json:"domain,omitempty"` 48 | Metadata string `json:"metadata,omitempty"` //TODO: why is transaction metadata a string? 49 | Status string `json:"status,omitempty"` 50 | Reference string `json:"reference,omitempty"` 51 | Amount float32 `json:"amount,omitempty"` 52 | Message string `json:"message,omitempty"` 53 | GatewayResponse string `json:"gateway_response,omitempty"` 54 | PaidAt string `json:"piad_at,omitempty"` 55 | Channel string `json:"channel,omitempty"` 56 | Currency string `json:"currency,omitempty"` 57 | IPAddress string `json:"ip_address,omitempty"` 58 | Log map[string]interface{} `json:"log,omitempty"` // TODO: same as timeline? 59 | Fees int `json:"int,omitempty"` 60 | FeesSplit string `json:"fees_split,omitempty"` // TODO: confirm data type 61 | Customer Customer `json:"customer,omitempty"` 62 | Authorization Authorization `json:"authorization,omitempty"` 63 | Plan Plan `json:"plan,omitempty"` 64 | SubAccount SubAccount `json:"sub_account,omitempty"` 65 | } 66 | 67 | // Authorization represents Paystack authorization object 68 | type Authorization struct { 69 | AuthorizationCode string `json:"authorization_code,omitempty"` 70 | Bin string `json:"bin,omitempty"` 71 | Last4 string `json:"last4,omitempty"` 72 | ExpMonth string `json:"exp_month,omitempty"` 73 | ExpYear string `json:"exp_year,omitempty"` 74 | Channel string `json:"channel,omitempty"` 75 | CardType string `json:"card_type,omitempty"` 76 | Bank string `json:"bank,omitempty"` 77 | CountryCode string `json:"country_code,omitempty"` 78 | Brand string `json:"brand,omitempty"` 79 | Resusable bool `json:"reusable,omitempty"` 80 | Signature string `json:"signature,omitempty"` 81 | } 82 | 83 | // TransactionTimeline represents a timeline of events in a transaction session 84 | type TransactionTimeline struct { 85 | TimeSpent int `json:"time_spent,omitempty"` 86 | Attempts int `json:"attempts,omitempty"` 87 | Authentication string `json:"authentication,omitempty"` // TODO: confirm type 88 | Errors int `json:"errors,omitempty"` 89 | Success bool `json:"success,omitempty"` 90 | Mobile bool `json:"mobile,omitempty"` 91 | Input []string `json:"input,omitempty"` // TODO: confirm type 92 | Channel string `json:"channel,omitempty"` 93 | History []map[string]interface{} `json:"history,omitempty"` 94 | } 95 | 96 | // Initialize initiates a transaction process 97 | // For more details see https://developers.paystack.co/v1.0/reference#initialize-a-transaction 98 | func (s *TransactionService) Initialize(txn *TransactionRequest) (Response, error) { 99 | u := fmt.Sprintf("/transaction/initialize") 100 | resp := Response{} 101 | err := s.client.Call("POST", u, txn, &resp) 102 | return resp, err 103 | } 104 | 105 | // Verify checks that transaction with the given reference exists 106 | // For more details see https://api.paystack.co/transaction/verify/reference 107 | func (s *TransactionService) Verify(reference string) (*Transaction, error) { 108 | u := fmt.Sprintf("/transaction/verify/%s", reference) 109 | txn := &Transaction{} 110 | err := s.client.Call("GET", u, nil, txn) 111 | return txn, err 112 | } 113 | 114 | // List returns a list of transactions. 115 | // For more details see https://paystack.com/docs/api/#transaction-list 116 | func (s *TransactionService) List() (*TransactionList, error) { 117 | return s.ListN(10, 1) 118 | } 119 | 120 | // ListN returns a list of transactions 121 | // For more details see https://developers.paystack.co/v1.0/reference#list-transactions 122 | func (s *TransactionService) ListN(count, offset int) (*TransactionList, error) { 123 | u := paginateURL("/transaction", count, offset) 124 | txns := &TransactionList{} 125 | err := s.client.Call("GET", u, nil, txns) 126 | return txns, err 127 | } 128 | 129 | // Get returns the details of a transaction. 130 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-transaction 131 | func (s *TransactionService) Get(id int) (*Transaction, error) { 132 | u := fmt.Sprintf("/transaction/%d", id) 133 | txn := &Transaction{} 134 | err := s.client.Call("GET", u, nil, txn) 135 | return txn, err 136 | } 137 | 138 | // ChargeAuthorization is for charging all authorizations marked as reusable whenever you need to recieve payments. 139 | // For more details see https://developers.paystack.co/v1.0/reference#charge-authorization 140 | func (s *TransactionService) ChargeAuthorization(req *TransactionRequest) (*Transaction, error) { 141 | txn := &Transaction{} 142 | err := s.client.Call("POST", "/transaction/charge_authorization", req, txn) 143 | return txn, err 144 | } 145 | 146 | // Timeline fetches the transaction timeline. Reference can be ID or transaction reference 147 | // For more details see https://developers.paystack.co/v1.0/reference#view-transaction-timeline 148 | func (s *TransactionService) Timeline(reference string) (*TransactionTimeline, error) { 149 | u := fmt.Sprintf("/transaction/timeline/%s", reference) 150 | timeline := &TransactionTimeline{} 151 | err := s.client.Call("GET", u, nil, timeline) 152 | return timeline, err 153 | } 154 | 155 | // Totals returns total amount received on your account 156 | // For more details see https://developers.paystack.co/v1.0/reference#transaction-totals 157 | func (s *TransactionService) Totals() (Response, error) { 158 | u := fmt.Sprintf("/transaction/totals") 159 | resp := Response{} 160 | err := s.client.Call("GET", u, nil, &resp) 161 | return resp, err 162 | } 163 | 164 | // Export exports transactions to a downloadable file and returns a link to the file 165 | // For more details see https://developers.paystack.co/v1.0/reference#export-transactions 166 | func (s *TransactionService) Export(params RequestValues) (Response, error) { 167 | u := fmt.Sprintf("/transaction/export") 168 | resp := Response{} 169 | err := s.client.Call("GET", u, nil, &resp) 170 | return resp, err 171 | } 172 | 173 | // ReAuthorize requests reauthorization 174 | // For more details see https://developers.paystack.co/v1.0/reference#request-reauthorization 175 | func (s *TransactionService) ReAuthorize(req AuthorizationRequest) (Response, error) { 176 | u := fmt.Sprintf("/transaction/request_reauthorization") 177 | resp := Response{} 178 | err := s.client.Call("POST", u, nil, &resp) 179 | return resp, err 180 | } 181 | 182 | // CheckAuthorization checks authorization 183 | // For more details see https://developers.paystack.co/v1.0/reference#check-authorization 184 | func (s *TransactionService) CheckAuthorization(req AuthorizationRequest) (Response, error) { 185 | u := fmt.Sprintf("/transaction/check_reauthorization") 186 | resp := Response{} 187 | err := s.client.Call("POST", u, nil, &resp) 188 | return resp, err 189 | } 190 | -------------------------------------------------------------------------------- /transaction_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func makeTimestamp() int64 { 10 | return time.Now().UnixNano() / int64(time.Millisecond) 11 | } 12 | 13 | func TestInitializeTransaction(t *testing.T) { 14 | txn := &TransactionRequest{ 15 | Email: "user123@gmail.com", 16 | Amount: 6000, 17 | Reference: "Txn-" + fmt.Sprintf("%d", makeTimestamp()), 18 | } 19 | resp, err := c.Transaction.Initialize(txn) 20 | if err != nil { 21 | t.Error(err) 22 | } 23 | 24 | if resp["authorization_code"] == "" { 25 | t.Error("Missing transaction authorization code") 26 | } 27 | 28 | if resp["access_code"] == "" { 29 | t.Error("Missing transaction access code") 30 | } 31 | 32 | if resp["reference"] == "" { 33 | t.Error("Missing transaction reference") 34 | } 35 | 36 | txn1, err := c.Transaction.Verify(resp["reference"].(string)) 37 | 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | 42 | if txn1.Amount != txn.Amount { 43 | t.Errorf("Expected transaction amount %f, got %+v", txn.Amount, txn1.Amount) 44 | } 45 | 46 | if txn1.Reference == "" { 47 | t.Errorf("Missing transaction reference") 48 | } 49 | 50 | _, err = c.Transaction.Get(txn1.ID) 51 | 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | } 56 | 57 | func TestTransactionList(t *testing.T) { 58 | // retrieve the transaction list 59 | transactions, err := c.Transaction.List() 60 | if err != nil { 61 | t.Errorf("Expected Transaction list, got %d, returned error %v", len(transactions.Values), err) 62 | } 63 | } 64 | 65 | func TestTransactionTotals(t *testing.T) { 66 | _, err := c.Transaction.Totals() 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | } 71 | 72 | func TestExportTransaction(t *testing.T) { 73 | resp, err := c.Transaction.Export(nil) 74 | if err != nil { 75 | t.Error(err) 76 | } 77 | 78 | if _, ok := resp["path"]; !ok { 79 | t.Error("Expected transactiion export path") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /transfer.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | // TransferService handles operations related to the transfer 9 | // For more details see https://developers.paystack.co/v1.0/reference#create-transfer 10 | type TransferService service 11 | 12 | // TransferRequest represents a request to create a transfer. 13 | type TransferRequest struct { 14 | Source string `json:"source,omitempty"` 15 | Amount float32 `json:"amount,omitempty"` 16 | Currency string `json:"currency,omitempty"` 17 | Reason string `json:"reason,omitempty"` 18 | Recipient string `json:"recipient,omitempty"` 19 | } 20 | 21 | // Transfer is the resource representing your Paystack transfer. 22 | // For more details see https://developers.paystack.co/v1.0/reference#initiate-transfer 23 | type Transfer struct { 24 | ID int `json:"id,omitempty"` 25 | CreatedAt string `json:"createdAt,omitempty"` 26 | UpdatedAt string `json:"updatedAt,omitempty"` 27 | Domain string `json:"domain,omitempty"` 28 | Integration int `json:"integration,omitempty"` 29 | Source string `json:"source,omitempty"` 30 | Amount float32 `json:"amount,omitempty"` 31 | Currency string `json:"currency,omitempty"` 32 | Reason string `json:"reason,omitempty"` 33 | TransferCode string `json:"transfer_code,omitempty"` 34 | // Initiate returns recipient ID as recipient value, Fetch returns recipient object 35 | Recipient interface{} `json:"recipient,omitempty"` 36 | Status string `json:"status,omitempty"` 37 | // confirm types for source_details and failures 38 | SourceDetails interface{} `json:"source_details,omitempty"` 39 | Failures interface{} `json:"failures,omitempty"` 40 | TransferredAt string `json:"transferred_at,omitempty"` 41 | TitanCode string `json:"titan_code,omitempty"` 42 | } 43 | 44 | // TransferRecipient represents a Paystack transfer recipient 45 | // For more details see https://developers.paystack.co/v1.0/reference#create-transfer-recipient 46 | type TransferRecipient struct { 47 | ID int `json:"id,omitempty"` 48 | CreatedAt string `json:"createdAt,omitempty"` 49 | UpdatedAt string `json:"updatedAt,omitempty"` 50 | Type string `json:",omitempty"` 51 | Name string `json:"name,omitempty"` 52 | Metadata Metadata `json:"metadata,omitempty"` 53 | AccountNumber string `json:"account_number,omitempty"` 54 | BankCode string `json:"bank_code,omitempty"` 55 | Currency string `json:"currency,omitempty"` 56 | Description string `json:"description,omitempty"` 57 | Active bool `json:"active,omitempty"` 58 | Details map[string]interface{} `json:"details,omitempty"` 59 | Domain string `json:"domain,omitempty"` 60 | RecipientCode string `json:"recipient_code,omitempty"` 61 | } 62 | 63 | // BulkTransfer represents a Paystack bulk transfer 64 | // You need to disable the Transfers OTP requirement to use this endpoint 65 | type BulkTransfer struct { 66 | Currency string `json:"currency,omitempty"` 67 | Source string `json:"source,omitempty"` 68 | Transfers []map[string]interface{} `json:"transfers,omitempty"` 69 | } 70 | 71 | // TransferList is a list object for transfers. 72 | type TransferList struct { 73 | Meta ListMeta 74 | Values []Transfer `json:"data,omitempty"` 75 | } 76 | 77 | // TransferRecipientList is a list object for transfer recipient. 78 | type TransferRecipientList struct { 79 | Meta ListMeta 80 | Values []TransferRecipient `json:"data,omitempty"` 81 | } 82 | 83 | // Initiate initiates a new transfer 84 | // For more details see https://developers.paystack.co/v1.0/reference#initiate-transfer 85 | func (s *TransferService) Initiate(req *TransferRequest) (*Transfer, error) { 86 | transfer := &Transfer{} 87 | err := s.client.Call("POST", "/transfer", req, transfer) 88 | return transfer, err 89 | } 90 | 91 | // Finalize completes a transfer request 92 | // For more details see https://developers.paystack.co/v1.0/reference#finalize-transfer 93 | func (s *TransferService) Finalize(code, otp string) (Response, error) { 94 | u := fmt.Sprintf("/transfer/finalize_transfer") 95 | req := url.Values{} 96 | req.Add("transfer_code", code) 97 | req.Add("otp", otp) 98 | resp := Response{} 99 | err := s.client.Call("POST", u, req, &resp) 100 | return resp, err 101 | } 102 | 103 | // MakeBulkTransfer initiates a new bulk transfer request 104 | // You need to disable the Transfers OTP requirement to use this endpoint 105 | // For more details see https://developers.paystack.co/v1.0/reference#initiate-bulk-transfer 106 | func (s *TransferService) MakeBulkTransfer(req *BulkTransfer) (Response, error) { 107 | u := fmt.Sprintf("/transfer") 108 | resp := Response{} 109 | err := s.client.Call("POST", u, req, &resp) 110 | return resp, err 111 | } 112 | 113 | // Get returns the details of a transfer. 114 | // For more details see https://developers.paystack.co/v1.0/reference#fetch-transfer 115 | func (s *TransferService) Get(idCode string) (*Transfer, error) { 116 | u := fmt.Sprintf("/transfer/%s", idCode) 117 | transfer := &Transfer{} 118 | err := s.client.Call("GET", u, nil, transfer) 119 | return transfer, err 120 | } 121 | 122 | // List returns a list of transfers. 123 | // For more details see https://developers.paystack.co/v1.0/reference#list-transfers 124 | func (s *TransferService) List() (*TransferList, error) { 125 | return s.ListN(10, 0) 126 | } 127 | 128 | // ListN returns a list of transfers 129 | // For more details see https://developers.paystack.co/v1.0/reference#list-transfers 130 | func (s *TransferService) ListN(count, offset int) (*TransferList, error) { 131 | u := paginateURL("/transfer", count, offset) 132 | transfers := &TransferList{} 133 | err := s.client.Call("GET", u, nil, transfers) 134 | return transfers, err 135 | } 136 | 137 | // ResendOTP generates a new OTP and sends to customer in the event they are having trouble receiving one. 138 | // For more details see https://developers.paystack.co/v1.0/reference#resend-otp-for-transfer 139 | func (s *TransferService) ResendOTP(transferCode, reason string) (Response, error) { 140 | data := url.Values{} 141 | data.Add("transfer_code", transferCode) 142 | data.Add("reason", reason) 143 | resp := Response{} 144 | err := s.client.Call("POST", "/transfer/resend_otp", data, &resp) 145 | return resp, err 146 | } 147 | 148 | // EnableOTP enables OTP requirement for Transfers 149 | // In the event that a customer wants to stop being able to complete 150 | // transfers programmatically, this endpoint helps turn OTP requirement back on. 151 | // No arguments required. 152 | func (s *TransferService) EnableOTP() (Response, error) { 153 | resp := Response{} 154 | err := s.client.Call("POST", "/transfer/enable_otp", nil, &resp) 155 | return resp, err 156 | } 157 | 158 | // DisableOTP disables OTP requirement for Transfers 159 | // In the event that you want to be able to complete transfers 160 | // programmatically without use of OTPs, this endpoint helps disable that…. 161 | // with an OTP. No arguments required. You will get an OTP. 162 | func (s *TransferService) DisableOTP() (Response, error) { 163 | resp := Response{} 164 | err := s.client.Call("POST", "/transfer/disable_otp", nil, &resp) 165 | return resp, err 166 | } 167 | 168 | // FinalizeOTPDisable finalizes disabling of OTP requirement for Transfers 169 | // For more details see https://developers.paystack.co/v1.0/reference#finalize-disabling-of-otp-requirement-for-transfers 170 | func (s *TransferService) FinalizeOTPDisable(otp string) (Response, error) { 171 | data := url.Values{} 172 | data.Add("otp", otp) 173 | resp := Response{} 174 | err := s.client.Call("POST", "/transfer/disable_otp_finalize", data, &resp) 175 | return resp, err 176 | } 177 | 178 | // CreateRecipient creates a new transfer recipient 179 | // For more details see https://developers.paystack.co/v1.0/reference#create-transferrecipient 180 | func (s *TransferService) CreateRecipient(recipient *TransferRecipient) (*TransferRecipient, error) { 181 | recipient1 := &TransferRecipient{} 182 | err := s.client.Call("POST", "/transferrecipient", recipient, recipient1) 183 | return recipient1, err 184 | } 185 | 186 | // ListRecipients returns a list of transfer recipients. 187 | // For more details see https://developers.paystack.co/v1.0/reference#list-transferrecipients 188 | func (s *TransferService) ListRecipients() (*TransferRecipientList, error) { 189 | return s.ListRecipientsN(10, 1) 190 | } 191 | 192 | // ListRecipientsN returns a list of transfer recipients 193 | // For more details see https://developers.paystack.co/v1.0/reference#list-transferrecipients 194 | func (s *TransferService) ListRecipientsN(count, offset int) (*TransferRecipientList, error) { 195 | u := paginateURL("/transferrecipient", count, offset) 196 | resp := &TransferRecipientList{} 197 | err := s.client.Call("GET", u, nil, &resp) 198 | return resp, err 199 | } 200 | -------------------------------------------------------------------------------- /transfer_test.go: -------------------------------------------------------------------------------- 1 | package paystack 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestInitiateTransfer(t *testing.T) { 8 | c.Transfer.EnableOTP() 9 | 10 | recipient := &TransferRecipient{ 11 | Type: "Nuban", 12 | Name: "Customer 1", 13 | Description: "Demo customer", 14 | AccountNumber: "0001234560", 15 | BankCode: "058", 16 | Currency: "NGN", 17 | Metadata: map[string]interface{}{"job": "Plumber"}, 18 | } 19 | 20 | recipient1, err := c.Transfer.CreateRecipient(recipient) 21 | 22 | req := &TransferRequest{ 23 | Source: "balance", 24 | Reason: "Delivery pickup", 25 | Amount: 300, 26 | Recipient: recipient1.RecipientCode, 27 | } 28 | 29 | transfer, err := c.Transfer.Initiate(req) 30 | 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | 35 | if transfer.TransferCode == "" { 36 | t.Errorf("Expected transfer code, got %+v", transfer.TransferCode) 37 | } 38 | 39 | // fetch transfer 40 | trf, err := c.Transfer.Get(transfer.TransferCode) 41 | if err != nil { 42 | t.Error(err) 43 | } 44 | 45 | if trf.TransferCode == "" { 46 | t.Errorf("Expected transfer code, got %+v", trf.TransferCode) 47 | } 48 | } 49 | 50 | /* FAILS: Error message: Invalid amount passed 51 | func TestBulkTransfer(t *testing.T) { 52 | // You need to disable the Transfers OTP requirement to use this endpoint 53 | c.Transfer.DisableOTP() 54 | 55 | // retrieve the transfer recipient list 56 | recipients, err := createDemoRecipients() 57 | 58 | if err != nil { 59 | t.Error(err) 60 | } 61 | 62 | transfer := &BulkTransfer{ 63 | Source: "balance", 64 | Currency: "NGN", 65 | Transfers: []map[string]interface{}{ 66 | { 67 | "amount": 50000, 68 | "recipient": recipients[0].RecipientCode, 69 | }, 70 | { 71 | "amount": 50000, 72 | "recipient": recipients[1].RecipientCode, 73 | }, 74 | }, 75 | } 76 | 77 | _, err = c.Transfer.MakeBulkTransfer(transfer) 78 | 79 | if err != nil { 80 | t.Error(err) 81 | } 82 | } 83 | */ 84 | 85 | func TestTransferList(t *testing.T) { 86 | // retrieve the transfer list 87 | transfers, err := c.Transfer.List() 88 | if err != nil { 89 | t.Errorf("Expected Transfer list, got %d, returned error %v", len(transfers.Values), err) 90 | } 91 | } 92 | 93 | func TestTransferRecipientList(t *testing.T) { 94 | //fmt.Println("createDemoRecipients <<<<<<<") 95 | //_, err := createDemoRecipients() 96 | 97 | //if err != nil { 98 | // t.Error(err) 99 | //} 100 | 101 | //fmt.Println("ListRecipients <<<<<<<") 102 | // retrieve the transfer recipient list 103 | recipients, err := c.Transfer.ListRecipients() 104 | 105 | if err != nil || !(len(recipients.Values) > 0) || !(recipients.Meta.Total > 0) { 106 | t.Errorf("Expected Recipients list, got %d, returned error %v", len(recipients.Values), err) 107 | } 108 | } 109 | 110 | func createDemoRecipients() ([]*TransferRecipient, error) { 111 | recipient1 := &TransferRecipient{ 112 | Type: "Nuban", 113 | Name: "Customer 1", 114 | Description: "Demo customer", 115 | AccountNumber: "0001234560", 116 | BankCode: "058", 117 | Currency: "NGN", 118 | Metadata: map[string]interface{}{"job": "Carpenter"}, 119 | } 120 | 121 | recipient2 := &TransferRecipient{ 122 | Type: "Nuban", 123 | Name: "Customer 2", 124 | Description: "Demo customer", 125 | AccountNumber: "0001234560", 126 | BankCode: "058", 127 | Currency: "NGN", 128 | Metadata: map[string]interface{}{"job": "Chef"}, 129 | } 130 | 131 | recipient3 := &TransferRecipient{ 132 | Type: "Nuban", 133 | Name: "Customer 2", 134 | Description: "Demo customer", 135 | AccountNumber: "0001234560", 136 | BankCode: "058", 137 | Currency: "NGN", 138 | Metadata: map[string]interface{}{"job": "Plumber"}, 139 | } 140 | 141 | _, err := c.Transfer.CreateRecipient(recipient1) 142 | _, err = c.Transfer.CreateRecipient(recipient2) 143 | _, err = c.Transfer.CreateRecipient(recipient3) 144 | 145 | return []*TransferRecipient{recipient1, recipient2, recipient3}, err 146 | } 147 | --------------------------------------------------------------------------------