This repository has been archived by the owner on Mar 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathclient.go
156 lines (123 loc) · 3.84 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package sagepay
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
"golang.org/x/net/context/ctxhttp"
)
// Client represents a SagePay API client instance
type Client struct {
// HTTP Client - This can be overridden to change the
// HTTP transport behaviour.
HTTP *http.Client
// A writer which will be used to write out raw requests
// for diganostics.
DebugWriter io.Writer
provider CredentialsProvider
testMode bool
sessionKey string
sessionKeyExpiresAt time.Time
}
const (
// TestHost is the address of the test API
TestHost = "https://pi-test.sagepay.com/api/v1"
// ProductionHost is the address of the production API
ProductionHost = "https://pi-live.sagepay.com/api/v1"
)
// New creates a new Sagepay API Client with the given CredentialsProvider
func New(ctx context.Context, credentials CredentialsProvider) *Client {
hc := &http.Client{
Transport: http.DefaultTransport,
}
return &Client{
HTTP: hc,
provider: credentials,
testMode: false,
sessionKey: "",
sessionKeyExpiresAt: time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC),
}
}
// Do performs the given HTTP Request
func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", "Sagepay-go +https://github.com/mrzen/go-sagepay")
credentials, err := c.provider.GetCredentials(ctx)
if err != nil {
return nil, err
}
req.SetBasicAuth(credentials.Username, credentials.Password)
// If a DebugWriter is given and the API is in test mode
// Write out the raw HTTP Request to the given writer.
//
// * Note: This feature is available only in test mode
// to prevent accidental leakage of sensitive data
// within these logs.
if c.testMode && c.DebugWriter != nil {
if req.Body != nil {
fmt.Fprintln(c.DebugWriter, "--------- REQUEST --------")
cb := new(bytes.Buffer)
tr := io.TeeReader(req.Body, cb)
req.Body = ioutil.NopCloser(tr)
req.Write(os.Stdout)
req.Body = ioutil.NopCloser(bytes.NewReader(cb.Bytes()))
}
}
return ctxhttp.Do(ctx, c.HTTP, req)
}
func (c *Client) getEndpoint() string {
if c.testMode {
return TestHost
}
return ProductionHost
}
// JSON performs an HTTP request with a given request body value encoded as JSON
// and decodes the response as JSON into the given response pointer.
func (c *Client) JSON(ctx context.Context, method, path string, body, into interface{}) error {
buffer := new(bytes.Buffer)
if err := json.NewEncoder(buffer).Encode(body); err != nil {
return err
}
req, err := http.NewRequest(method, c.getEndpoint()+path, bytes.NewReader(buffer.Bytes()))
// Content-Type and Accept headers must be set for Sage to recognize the input.
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if err != nil {
return err
}
res, err := c.Do(ctx, req)
if err != nil {
return err
}
defer res.Body.Close()
// If we get an error response
// attempt to decode the response body as an error document
// and return that as an error.
if res.StatusCode >= 400 {
errorBody := ErrorResponse{}
cb := new(bytes.Buffer)
tr := io.TeeReader(res.Body, cb)
// If we can’t decode the body, or the body doesn't appear to
// contain a list of errors, then return the response body itself
// as an error.
if err := json.NewDecoder(tr).Decode(&errorBody); err != nil {
return err
} else if len(errorBody.Errors) == 0 {
return errors.New(cb.String())
}
return errorBody
}
// Decode the response into the given `into` object,
// returning any error encountered.
return json.NewDecoder(res.Body).Decode(&into)
}
// SetTestMode determines if the API client will communite with a test
// or production endpoint
func (c *Client) SetTestMode(testMode bool) {
c.testMode = testMode
}