-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgateway.go
236 lines (197 loc) · 7.01 KB
/
gateway.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// Package tprogateway provide ability to make requests to Transact Pro Gateway API v3.
package tprogateway
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/TransactPRO/gw3-go-client/operations"
"github.com/TransactPRO/gw3-go-client/structures"
)
// Default API settings
const (
dAPIBaseURI = "https://api.sandbox.transactpro.io"
dAPIVersion = "3.0"
)
type (
// confAPI, endpoint config to Transact Pro system
confAPI struct {
// BaseURI typical host name with scheme. Example: http://some.host
BaseURI string
// Version is prefix in route path for url. Example: 42.1
Version string
}
// AuthData merchant authorization structure fields used in operation request
authData struct {
ObjectGUID string `json:"-"`
SecretKey string `json:"-"`
SessionID string `json:"session-id,omitempty"`
}
// GatewayClient represents REST API client
GatewayClient struct {
API *confAPI
Auth *authData
HTTPClient http.Client
}
// GenericRequest describes general request data structure
GenericRequest struct {
Auth *authData `json:"auth-data,omitempty"`
Data interface{} `json:"data,omitempty"`
FilterData interface{} `json:"filter-data,omitempty"`
}
)
// NewGatewayClient creates new instance of prepared gateway client structure
func NewGatewayClient(ObjectGUID, SecretKey string) (*GatewayClient, error) {
if ObjectGUID == "" {
return nil, errors.New("GUID can't be empty. It's required for merchant authorization")
}
if SecretKey == "" {
return nil, errors.New("secret key can't be empty. It's required for merchant authorization")
}
return &GatewayClient{
API: &confAPI{BaseURI: dAPIBaseURI, Version: dAPIVersion},
Auth: &authData{ObjectGUID: ObjectGUID, SecretKey: SecretKey},
}, nil
}
// NewGatewayClientForSession creates new instance of prepared gateway client structure
// Should be used when active session is available
func NewGatewayClientForSession(ObjectGUID, SecretKey, SessionID string) (response *GatewayClient, err error) {
if SessionID == "" {
return nil, errors.New("SessionID can't be empty. Session authorization means non-empty session")
}
if response, err = NewGatewayClient(ObjectGUID, SecretKey); err == nil {
response.Auth.SessionID = SessionID
}
return
}
// OperationBuilder method, returns builder for needed operation, like SMS, Reversal, even exploring transactions such as Refund ExploringHistory
func (gc *GatewayClient) OperationBuilder() *operations.Builder {
return &operations.Builder{}
}
// NewRequest method, send HTTP request to Transact Pro API
// GatewayResponse may be non-nil in case of error if a response payload was read
// but some validation after failed (like digest verification)
func (gc *GatewayClient) NewRequest(opData structures.OperationRequestInterface) (*structures.GatewayResponse, error) {
// Build whole payload structure with nested data bundles
rawReqData := &GenericRequest{}
rawReqData.Auth = gc.Auth
if opData.GetOperationType() == structures.Report {
rawReqData.FilterData = opData
} else {
rawReqData.Data = opData
}
// Get prepared structure of json byte array
var bufPayload *bytes.Buffer
if opData.GetHTTPMethod() != http.MethodGet {
var bufErr error
if bufPayload, bufErr = prepareJSONPayload(rawReqData); bufErr != nil {
return nil, bufErr
}
} else {
bufPayload = bytes.NewBuffer(nil)
}
// Get combined URL path for request to API
requestURL, errURLPath := determineURL(gc, opData.GetOperationType())
if errURLPath != nil {
return nil, errURLPath
}
// Build correct HTTP request
newReq, reqDigest, reqErr := buildHTTPRequest(rawReqData.Auth, opData.GetHTTPMethod(), requestURL, bufPayload)
if reqErr != nil {
return nil, reqErr
}
// Send HTTP request object
resp, respErr := gc.HTTPClient.Do(newReq)
if respErr != nil {
return nil, respErr
}
defer func() { _ = resp.Body.Close() }()
content, payloadErr := ioutil.ReadAll(resp.Body)
if payloadErr != nil {
return nil, payloadErr
}
gwResponse := structures.NewGatewayResponse(resp, content)
if gwResponse.Successful() {
var digestErr error
gwResponse.Digest, digestErr = structures.NewResponseDigest(resp.Header.Get("Authorization"))
if digestErr != nil {
return gwResponse, digestErr
}
gwResponse.Digest.OriginalURI = reqDigest.URI
gwResponse.Digest.OriginalCnonce = reqDigest.Cnonce
gwResponse.Digest.Body = gwResponse.Payload
digestErr = gwResponse.Digest.Verify(rawReqData.Auth.ObjectGUID, rawReqData.Auth.SecretKey)
if digestErr != nil {
return gwResponse, digestErr
}
}
return gwResponse, nil
}
// prepareJSONPayload, validates\combines AuthData and Data struct to one big structure and converts to json(Marshal) to buffer
func prepareJSONPayload(rawReq *GenericRequest) (*bytes.Buffer, error) {
// When payload ready, convert it to Json format
bReqData, err := json.Marshal(&rawReq)
if err != nil {
return nil, err
}
// Write json object to buffer
buffer := bytes.NewBuffer(bReqData)
return buffer, nil
}
// determineURL the full URL address to send request to Transact Pro API
func determineURL(gc *GatewayClient, opType structures.OperationType) (string, error) {
// Complete URL for request
var completeURL string
// Validate API config, base URL and version of API
if gc.API.BaseURI == "" {
return "", errors.New("gateway client's URL is empty in, API settings")
}
if gc.API.Version == "" {
return "", errors.New("gateway client's Version is empty in, API settings")
}
// Try to get operation type from request data
if opType == "" {
return "", errors.New("operation type is empty. Problem in operation builder")
}
// AS example must be like: http://url.pay.com/v55.0/sms
if strings.HasPrefix(string(opType), "http") {
completeURL = string(opType)
} else if opType == structures.Report {
completeURL = fmt.Sprintf("%s/%s", gc.API.BaseURI, opType)
} else {
completeURL = fmt.Sprintf("%s/v%s/%s", gc.API.BaseURI, gc.API.Version, opType)
}
return completeURL, nil
}
// buildHTTPRequest, accepts prepared body for HTTP
// Builds NewRequest from http package
func buildHTTPRequest(auth *authData, method, requestURL string, payload *bytes.Buffer) (*http.Request, *structures.RequestDigest, error) {
var err error
var parsedURL *url.URL
if parsedURL, err = url.Parse(requestURL); err != nil {
return nil, nil, fmt.Errorf("incorrect URL: %s", err)
}
var requestDigest *structures.RequestDigest
if requestDigest, err = structures.NewRequestDigest(auth.ObjectGUID, auth.SecretKey, parsedURL.Path, payload.Bytes()); err != nil {
return nil, nil, err
}
var digest string
if digest, err = requestDigest.CreateHeader(); err != nil {
return nil, nil, err
}
// Build whole HTTP request with payload data
var newReq *http.Request
if newReq, err = http.NewRequest(method, requestURL, payload); err != nil {
return nil, nil, err
}
// Set default headers for new request
newReq.Header.Set("Authorization", digest)
if method != http.MethodGet {
newReq.Header.Set("Content-type", "application/json")
}
return newReq, requestDigest, nil
}