Test Setup Failed
Push — main ( e730c5...c96f7d )
by Acho
01:32
created

campay.*Client.Collect   A

Complexity

Conditions 5

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
dl 0
loc 22
c 0
b 0
f 0
rs 9.2333
nop 2
1
package campay
2
3
import (
4
	"bytes"
5
	"context"
6
	"encoding/json"
7
	"fmt"
8
	"io"
9
	"io/ioutil"
10
	"net/http"
11
	"sync"
12
	"time"
13
)
14
15
type service struct {
16
	client *Client
17
}
18
19
// Client is the campay API client.
20
// Do not instantiate this client with Client{}. Use the New method instead.
21
type Client struct {
22
	httpClient          *http.Client
23
	common              service
24
	environment         Environment
25
	mutex               sync.Mutex
26
	apiUsername         string
27
	apiPassword         string
28
	token               string
29
	tokenExpirationTime int64
30
	Transaction         *TransactionService
31
}
32
33
// New creates and returns a new campay.Client from a slice of campay.ClientOption.
34
func New(options ...ClientOption) *Client {
35
	config := defaultClientConfig()
36
37
	for _, option := range options {
38
		option.apply(config)
39
	}
40
41
	client := &Client{
42
		httpClient:  config.httpClient,
43
		environment: config.environment,
44
		apiUsername: config.apiUsername,
45
		apiPassword: config.apiPassword,
46
		mutex:       sync.Mutex{},
47
	}
48
49
	client.common.client = client
50
	client.Transaction = (*TransactionService)(&client.common)
51
	return client
52
}
53
54
// Token Gets the access token
55
// POST /token/
56
// API Doc: https://documenter.getpostman.com/view/2391374/T1LV8PVA#8803168b-d451-4d65-b8cc-85e385bc3050
57
func (client *Client) Token(ctx context.Context) (*Token, *Response, error) {
58
	payload := map[string]string{
59
		"username": client.apiUsername,
60
		"password": client.apiPassword,
61
	}
62
63
	request, err := client.newRequest(ctx, http.MethodPost, "/token", payload)
64
	if err != nil {
65
		return nil, nil, err
66
	}
67
68
	resp, err := client.do(request)
69
	if err != nil {
70
		return nil, resp, err
71
	}
72
73
	var token Token
74
	if err = json.Unmarshal(*resp.Body, &token); err != nil {
75
		return nil, resp, err
76
	}
77
78
	return &token, resp, nil
79
}
80
81
// Collect Requests a Payment
82
// POST /collect/
83
// API Doc: https://documenter.getpostman.com/view/2391374/T1LV8PVA#31757962-2e07-486b-a6f4-a7cc7a06d032
84
func (client *Client) Collect(ctx context.Context, options *CollectOptions) (*CollectResponse, *Response, error) {
85
	err := client.refreshToken(ctx)
86
	if err != nil {
87
		return nil, nil, err
88
	}
89
90
	request, err := client.newRequest(ctx, http.MethodPost, "/token", options)
91
	if err != nil {
92
		return nil, nil, err
93
	}
94
95
	response, err := client.do(request)
96
	if err != nil {
97
		return nil, response, err
98
	}
99
100
	var collectResponse CollectResponse
101
	if err = json.Unmarshal(*response.Body, &collectResponse); err != nil {
102
		return nil, response, err
103
	}
104
105
	return &collectResponse, response, nil
106
}
107
108
// newRequest creates an API request. A relative URL can be provided in uri,
109
// in which case it is resolved relative to the BaseURL of the Client.
110
// URI's should always be specified without a preceding slash.
111
func (client *Client) newRequest(ctx context.Context, method, uri string, body interface{}) (*http.Request, error) {
112
	var buf io.ReadWriter
113
	if body != nil {
114
		buf = &bytes.Buffer{}
115
		enc := json.NewEncoder(buf)
116
		enc.SetEscapeHTML(false)
117
		err := enc.Encode(body)
118
		if err != nil {
119
			return nil, err
120
		}
121
	}
122
123
	req, err := http.NewRequestWithContext(ctx, method, client.environment.String()+uri, buf)
124
	if err != nil {
125
		return nil, err
126
	}
127
128
	req.Header.Set("Content-Type", "application/json")
129
130
	if len(client.token) > 0 {
131
		req.Header.Set("Authorization", "Token "+client.token)
132
	}
133
134
	return req, nil
135
}
136
137
// do carries out an HTTP request and returns a Response
138
func (client *Client) do(req *http.Request) (*Response, error) {
139
	if req == nil {
140
		return nil, fmt.Errorf("%T cannot be nil", req)
141
	}
142
143
	httpResponse, err := client.httpClient.Do(req)
144
	if err != nil {
145
		return nil, err
146
	}
147
148
	defer func() { _ = httpResponse.Body.Close() }()
149
150
	resp, err := client.newResponse(httpResponse)
151
	if err != nil {
152
		return resp, err
153
	}
154
155
	_, err = io.Copy(ioutil.Discard, httpResponse.Body)
156
	if err != nil {
157
		return resp, err
158
	}
159
160
	return resp, nil
161
}
162
163
// refreshToken refreshes the authentication Token
164
func (client *Client) refreshToken(ctx context.Context) error {
165
	if client.tokenExpirationTime > time.Now().UTC().Unix() {
166
		return nil
167
	}
168
169
	client.mutex.Lock()
170
	defer client.mutex.Unlock()
171
172
	token, _, err := client.Token(ctx)
173
	if err != nil {
174
		return err
175
	}
176
177
	client.tokenExpirationTime = time.Now().UTC().Unix() + token.ExpiresIn - 100 // Give extra 100 second buffer
178
179
	return nil
180
}
181
182
// newResponse converts an *http.Response to *Response
183
func (client *Client) newResponse(httpResponse *http.Response) (*Response, error) {
184
	if httpResponse == nil {
185
		return nil, fmt.Errorf("%T cannot be nil", httpResponse)
186
	}
187
188
	resp := new(Response)
189
	resp.HTTPResponse = httpResponse
190
191
	buf, err := ioutil.ReadAll(resp.HTTPResponse.Body)
192
	if err != nil {
193
		return nil, err
194
	}
195
	resp.Body = &buf
196
197
	return resp, resp.Error()
198
}
199