-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
238 lines (206 loc) · 6.57 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
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
237
238
package rize
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"runtime"
"strings"
"time"
"github.com/rizefinance/rize-go-sdk/internal"
"golang.org/x/exp/slices"
)
// Service type to store the client reference
type service struct {
client *Client
}
// ListResponse is the default 'List' endpoint response.
// It is intended to be included in a response type specific to a service, which
// includes a Data array specific to that service type
type ListResponse struct {
TotalCount int `json:"total_count"`
Count int `json:"count"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
// Config stores Rize configuration values
type Config struct {
// Program within the target environment
ProgramUID string
// HMAC key for the target environment
HMACKey string
// Rize infrastructure target environment. Defaults to `sandbox``
Environment string
// Provide your own HTTPClient configuration (optional)
HTTPClient *http.Client
// Change the API base URL for local/unit testing
BaseURL string
// Enable debug logging
Debug bool
}
// Client is the top-level client containing all APIs
type Client struct {
// All configuration values
cfg *Config
// Allows additional configuration options like proxy, timeouts, etc
httpClient *http.Client
// Set custom `user-agent` header string
userAgent string
// Cached Auth token data
*TokenCache
// All available Rize API services
Adjustments *adjustmentService
Auth *authService
CardArtworks *cardArtworkService
ComplianceWorkflows *complianceWorkflowService
CustodialAccounts *custodialAccountService
CustodialPartners *custodialPartnerService
CustomerProducts *customerProductService
Customers *customerService
DebitCards *debitCardService
Documents *documentService
Evaluations *evaluationService
KYCDocuments *kycDocumentService
PinwheelJobs *pinwheelJobService
Pools *poolService
Products *productService
Sandbox *sandboxService
SyntheticAccounts *syntheticAccountService
Transactions *transactionService
Transfers *transferService
}
// TokenCache stores Auth token data
type TokenCache struct {
Token string
Timestamp int64
}
// Error is the default API error format
type Error struct {
Errors []*ErrorDetails `json:"errors"`
Status int `json:"status"`
}
// ErrorDetails from the error response
type ErrorDetails struct {
Code int `json:"code,omitempty"`
Title string `json:"title,omitempty"`
Detail string `json:"detail,omitempty"`
OccurredAt time.Time `json:"occurred_at,omitempty"`
}
// Format error output
func (e *Error) Error() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintln("Rize API Error", e.Status))
for _, v := range e.Errors {
sb.WriteString(fmt.Sprintf("%+v", v))
}
return sb.String()
}
// NewClient initializes the Client and all services
func NewClient(cfg *Config) (*Client, error) {
// Enable debug logging
internal.EnableLogging(cfg.Debug)
log.Println("Creating client...")
// Validate client config
if err := cfg.validateConfig(); err != nil {
return nil, err
}
rc := &Client{}
rc.cfg = cfg
rc.httpClient = cfg.HTTPClient
rc.userAgent = fmt.Sprintf("%s/%s (Go: %s)", "rize-go-sdk", internal.SDKVersion, runtime.Version())
rc.TokenCache = &TokenCache{}
// Initialize API Services
rc.Adjustments = &adjustmentService{client: rc}
rc.Auth = &authService{client: rc}
rc.CardArtworks = &cardArtworkService{client: rc}
rc.ComplianceWorkflows = &complianceWorkflowService{client: rc}
rc.CustodialAccounts = &custodialAccountService{client: rc}
rc.CustodialPartners = &custodialPartnerService{client: rc}
rc.CustomerProducts = &customerProductService{client: rc}
rc.Customers = &customerService{client: rc}
rc.DebitCards = &debitCardService{client: rc}
rc.Documents = &documentService{client: rc}
rc.Evaluations = &evaluationService{client: rc}
rc.KYCDocuments = &kycDocumentService{client: rc}
rc.PinwheelJobs = &pinwheelJobService{client: rc}
rc.Pools = &poolService{client: rc}
rc.Products = &productService{client: rc}
rc.Sandbox = &sandboxService{client: rc}
rc.SyntheticAccounts = &syntheticAccountService{client: rc}
rc.Transactions = &transactionService{client: rc}
rc.Transfers = &transferService{client: rc}
// Generate Auth Token
_, err := rc.Auth.GetToken(context.Background())
if err != nil {
return nil, err
}
return rc, nil
}
// Make the API request and return an http.Response. Checks for valid auth token.
func (rc *Client) doRequest(ctx context.Context, method string, path string, query url.Values, data io.Reader) (*http.Response, error) {
// Check for valid auth token and refresh if necessary
if path != "auth" {
if _, err := rc.Auth.GetToken(ctx); err != nil {
return nil, err
}
}
url := fmt.Sprintf("%s/%s/%s", rc.cfg.BaseURL, internal.BasePath, path)
log.Printf("Sending %s request to %s\n", method, url)
req, err := http.NewRequestWithContext(ctx, method, url, data)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", rc.userAgent)
req.Header.Add("Authorization", rc.TokenCache.Token)
req.URL.RawQuery = query.Encode()
res, err := rc.httpClient.Do(req)
if err != nil {
return nil, err
}
// Check for error response
if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
var errorOut = &Error{}
if err = json.Unmarshal(body, &errorOut); err != nil {
return nil, err
}
// Use RizeError type to handle specific error codes from the API server
return nil, errorOut
}
return res, nil
}
// Make sure that we have the proper configuration variables
func (cfg *Config) validateConfig() error {
if cfg.ProgramUID == "" {
return fmt.Errorf("Config error: ProgramUID is required")
}
if cfg.HMACKey == "" {
return fmt.Errorf("Config error: HMACKey is required")
}
if ok := slices.Contains(internal.Environments, strings.ToLower(cfg.Environment)); !ok {
log.Printf("Environment %s not recognized. Defaulting to sandbox...\n", cfg.Environment)
cfg.Environment = "sandbox"
}
if cfg.HTTPClient == nil {
cfg.HTTPClient = &http.Client{
Timeout: internal.APITimeout,
}
}
if cfg.BaseURL == "" {
cfg.BaseURL = fmt.Sprintf("https://%s.newline53.com", cfg.Environment)
}
return nil
}
// Version outputs the current SDK version
func Version() string {
return internal.SDKVersion
}