signifyd 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +172 -0
- data/Rakefile +11 -0
- data/lib/data/ca-certificates.crt +3918 -0
- data/lib/signifyd.rb +387 -0
- data/lib/signifyd/api/create.rb +16 -0
- data/lib/signifyd/api/list.rb +20 -0
- data/lib/signifyd/api/update.rb +16 -0
- data/lib/signifyd/case.rb +7 -0
- data/lib/signifyd/errors/api_connection_error.rb +4 -0
- data/lib/signifyd/errors/api_error.rb +4 -0
- data/lib/signifyd/errors/authentication_error.rb +4 -0
- data/lib/signifyd/errors/invalid_request_error.rb +4 -0
- data/lib/signifyd/errors/not_implemented_error.rb +4 -0
- data/lib/signifyd/errors/signifyd_error.rb +20 -0
- data/lib/signifyd/resource.rb +19 -0
- data/lib/signifyd/signifyd_object.rb +151 -0
- data/lib/signifyd/util.rb +94 -0
- data/lib/signifyd/version.rb +3 -0
- data/spec/fixtures/signifyd_requests.rb +123 -0
- data/spec/lib/signifyd/case_spec.rb +126 -0
- data/spec/lib/signifyd_base_spec.rb +503 -0
- data/spec/spec_helper.rb +19 -0
- metadata +292 -0
data/lib/signifyd.rb
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# External Dependencies
|
|
2
|
+
require 'i18n'
|
|
3
|
+
require 'set'
|
|
4
|
+
require 'active_support'
|
|
5
|
+
require 'rest_client'
|
|
6
|
+
require 'uri'
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'base64'
|
|
9
|
+
require 'openssl'
|
|
10
|
+
|
|
11
|
+
# Gem Version
|
|
12
|
+
require 'signifyd/version'
|
|
13
|
+
|
|
14
|
+
# API operations
|
|
15
|
+
require 'signifyd/util'
|
|
16
|
+
require 'signifyd/signifyd_object'
|
|
17
|
+
require 'signifyd/resource'
|
|
18
|
+
require 'signifyd/api/create'
|
|
19
|
+
require 'signifyd/api/list'
|
|
20
|
+
require 'signifyd/api/update'
|
|
21
|
+
require 'signifyd/case'
|
|
22
|
+
|
|
23
|
+
# Internal Dependencies
|
|
24
|
+
require 'signifyd/errors/signifyd_error'
|
|
25
|
+
require 'signifyd/errors/api_error'
|
|
26
|
+
require 'signifyd/errors/api_connection_error'
|
|
27
|
+
require 'signifyd/errors/authentication_error'
|
|
28
|
+
require 'signifyd/errors/invalid_request_error'
|
|
29
|
+
require 'signifyd/errors/not_implemented_error'
|
|
30
|
+
|
|
31
|
+
module Signifyd
|
|
32
|
+
# ssl_bundle_path
|
|
33
|
+
#
|
|
34
|
+
# Path to hold Signifyd.com's certificate
|
|
35
|
+
# @return: String[path to certificate file]
|
|
36
|
+
@@ssl_bundle_path = File.join(File.dirname(__FILE__), 'data/ca-certificates.crt')
|
|
37
|
+
|
|
38
|
+
# api_key
|
|
39
|
+
#
|
|
40
|
+
# Default point for where the application will hold the API_KEY
|
|
41
|
+
# for the current instance. If not set, must be passed in all calls
|
|
42
|
+
# as last parameter.
|
|
43
|
+
# @return String[unique identifier]
|
|
44
|
+
@@api_key = nil
|
|
45
|
+
|
|
46
|
+
# api_base
|
|
47
|
+
#
|
|
48
|
+
# Root url where the Signifyd API endpoints will live. This can be
|
|
49
|
+
# changed by setting Signifyd.test_mode = true and will default to
|
|
50
|
+
# staging env.
|
|
51
|
+
# @return: String[url to Signifyd's API]
|
|
52
|
+
@@api_base = 'https://api.signifyd.com'
|
|
53
|
+
|
|
54
|
+
# api_version
|
|
55
|
+
#
|
|
56
|
+
# Version right now will be the url structure, might change later.
|
|
57
|
+
# For now this is ok.
|
|
58
|
+
# @return: String[url path of our current api version]
|
|
59
|
+
@@api_version = '/v2'
|
|
60
|
+
|
|
61
|
+
# verify_ssl_certs
|
|
62
|
+
#
|
|
63
|
+
# When this is set to false, any request made will not be a verfied
|
|
64
|
+
# and supported request by Signifyd. This should be set to true and
|
|
65
|
+
# the library will use the Signifyd keys..not for now :/
|
|
66
|
+
# @return: Boolean
|
|
67
|
+
@@verify_ssl_certs = true
|
|
68
|
+
|
|
69
|
+
# test_mode
|
|
70
|
+
#
|
|
71
|
+
# When set to true, will default to Signifyd's staging environment.
|
|
72
|
+
# This as well should always be set to false.
|
|
73
|
+
# @return: Boolean
|
|
74
|
+
@@test_mode = false
|
|
75
|
+
|
|
76
|
+
# local_mode
|
|
77
|
+
#
|
|
78
|
+
# When set to true, will default to a local environment of localhost:9000.
|
|
79
|
+
# This as well should always be set to false.
|
|
80
|
+
# @return: Boolean
|
|
81
|
+
@@local_mode = false
|
|
82
|
+
|
|
83
|
+
# ssl_bundle_path
|
|
84
|
+
#
|
|
85
|
+
# Returns the path to the certificate store location
|
|
86
|
+
def self.ssl_bundle_path
|
|
87
|
+
@@ssl_bundle_path
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# api_url
|
|
91
|
+
#
|
|
92
|
+
# This method is used to set the full url that the request will be made
|
|
93
|
+
# to. Ideally, pass in the version of the API and then the method that
|
|
94
|
+
# will be requested.
|
|
95
|
+
# An example retrun would be: 'https://signifyd.com/v2/cases
|
|
96
|
+
# @return: String[url for request to be made]
|
|
97
|
+
def self.api_url(url='')
|
|
98
|
+
@@api_base + url
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# api_key=
|
|
102
|
+
#
|
|
103
|
+
# Setter method to set the API key. Set into class variable and used
|
|
104
|
+
# globally on all calls made.
|
|
105
|
+
# @return: String[api key]
|
|
106
|
+
def self.api_key=(api_key)
|
|
107
|
+
@@api_key = api_key
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# api_key
|
|
111
|
+
#
|
|
112
|
+
# Getter method for the API key that has been set by the application.
|
|
113
|
+
# @return: String[api key]
|
|
114
|
+
def self.api_key
|
|
115
|
+
@@api_key
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# api_version=
|
|
119
|
+
#
|
|
120
|
+
# Setter method to set the API version. Set into class variable and used
|
|
121
|
+
# globally on all calls made.
|
|
122
|
+
# @return: String[api url version]
|
|
123
|
+
def self.api_version=(api_version)
|
|
124
|
+
@@api_version = api_version
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# api_version
|
|
128
|
+
#
|
|
129
|
+
# Getter method for the API version that has been set by the application.
|
|
130
|
+
# @return: String[api url version]
|
|
131
|
+
def self.api_version
|
|
132
|
+
@@api_version
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# api_base=
|
|
136
|
+
#
|
|
137
|
+
# Setter method to set the API url base. Set into class variable and used
|
|
138
|
+
# globally on all calls made.
|
|
139
|
+
# @return: String[api base]
|
|
140
|
+
def self.api_base=(api_base)
|
|
141
|
+
@@api_base = api_base
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# api_base
|
|
145
|
+
#
|
|
146
|
+
# Getter method for the API base that has been set by the application.
|
|
147
|
+
# @return: String[api base]
|
|
148
|
+
def self.api_base
|
|
149
|
+
@@api_base
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# verify_ssl_certs=
|
|
153
|
+
#
|
|
154
|
+
# Setter method to set the API verify_ssl_certs. Set into class variable and
|
|
155
|
+
# used globally on all calls made.
|
|
156
|
+
# @return: Boolean
|
|
157
|
+
def self.verify_ssl_certs=(verify)
|
|
158
|
+
@@verify_ssl_certs = verify
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# verify_ssl_certs
|
|
162
|
+
#
|
|
163
|
+
# Getter method for the API verify_ssl_certs that has been set by the application.
|
|
164
|
+
# @return: Boolean
|
|
165
|
+
def self.verify_ssl_certs
|
|
166
|
+
@@verify_ssl_certs
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# test_mode=
|
|
170
|
+
#
|
|
171
|
+
# Setter method to set the API test_mode. Set into class variable and used
|
|
172
|
+
# globally on all calls made.
|
|
173
|
+
# @return: Boolean
|
|
174
|
+
def self.test_mode=(test_mode)
|
|
175
|
+
Signifyd.api_base = 'https://staging.signifyd.com' if test_mode && !self.local_mode
|
|
176
|
+
@@test_mode = test_mode
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# test_mode
|
|
180
|
+
#
|
|
181
|
+
# Getter method for the API test_mode that has been set by the application.
|
|
182
|
+
# @return: Boolean
|
|
183
|
+
def self.test_mode
|
|
184
|
+
@@test_mode
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# local_mode=
|
|
188
|
+
#
|
|
189
|
+
# Setter method to set the API local_mode. Set into class variable and used
|
|
190
|
+
# globally on all calls made.
|
|
191
|
+
# @return: Boolean
|
|
192
|
+
def self.local_mode=(local_mode)
|
|
193
|
+
Signifyd.api_base = 'http://localhost:9000' if local_mode && !self.test_mode
|
|
194
|
+
@@local_mode = local_mode
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# local_mode
|
|
198
|
+
#
|
|
199
|
+
# Getter method for the API test_mode that has been set by the application.
|
|
200
|
+
# @return: Boolean
|
|
201
|
+
def self.local_mode
|
|
202
|
+
@@local_mode
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# request
|
|
206
|
+
#
|
|
207
|
+
# Global method that will use RestClient to make all requests. Everything else
|
|
208
|
+
# is set between Signifyd.{setter} methods. This method is called from other
|
|
209
|
+
# methods so direct calls won't be necessary.
|
|
210
|
+
#
|
|
211
|
+
# @param[Req]: String[method] - :get, :post, :put, :delete
|
|
212
|
+
# @param[Req]: String[url] - '/cases'
|
|
213
|
+
# @param[Req]: Hash[params] - {transaction...}
|
|
214
|
+
# @param[Opt]: String[api_key] - 'YOUR-API-KEY'
|
|
215
|
+
# @param[Opt]: Hash[options] - optional parameters
|
|
216
|
+
# @return: Hash - containing response code, body, and other data
|
|
217
|
+
def self.request(method, url, params={}, api_key=nil, options={})
|
|
218
|
+
api_key = api_key.nil? ? @@api_key : api_key
|
|
219
|
+
raise AuthenticationError.new('No API key provided. Fix: Signifyd.api_key = \'Your API KEY\'') unless api_key
|
|
220
|
+
|
|
221
|
+
uname = (@@uname ||= RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil)
|
|
222
|
+
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
|
223
|
+
ua = {
|
|
224
|
+
:bindings_version => Signifyd::VERSION,
|
|
225
|
+
:lang => 'ruby',
|
|
226
|
+
:lang_version => lang_version,
|
|
227
|
+
:platform => RUBY_PLATFORM,
|
|
228
|
+
:publisher => 'signifyd',
|
|
229
|
+
:uname => uname
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if @@verify_ssl_certs
|
|
233
|
+
ssl_opts = {
|
|
234
|
+
:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
|
|
235
|
+
:ssl_ca_file => @@ssl_bundle_path
|
|
236
|
+
}
|
|
237
|
+
else
|
|
238
|
+
ssl_opts = {
|
|
239
|
+
:verify_ssl => OpenSSL::SSL::VERIFY_NONE
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Determine how to send the data and encode it based on what method we are sending. Some
|
|
244
|
+
# are necessary, some are not.
|
|
245
|
+
case method.to_s
|
|
246
|
+
when 'get'
|
|
247
|
+
if options.has_key?(:order_id)
|
|
248
|
+
url = url.gsub('cases', "orders/#{options[:order_id]}/case")
|
|
249
|
+
end
|
|
250
|
+
when 'post'
|
|
251
|
+
|
|
252
|
+
when 'put'
|
|
253
|
+
# we need to eject out the case_id from the params hash and append it to the url
|
|
254
|
+
if params.has_key?(:case_id) || params.has_key?('case_id')
|
|
255
|
+
case_id = params.delete(:case_id)
|
|
256
|
+
params.reject! { |k| k == :case_id || k == 'case_id' }
|
|
257
|
+
url << "/#{case_id}"
|
|
258
|
+
end
|
|
259
|
+
when 'delete'
|
|
260
|
+
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Create the full url here
|
|
264
|
+
url = self.api_url(url)
|
|
265
|
+
|
|
266
|
+
# Parse into valid json
|
|
267
|
+
payload = JSON.dump(params)
|
|
268
|
+
|
|
269
|
+
# Convert the key
|
|
270
|
+
authkey = api_key == {} || api_key == nil ? '' : Base64.encode64(api_key)
|
|
271
|
+
|
|
272
|
+
# Headers must contain these keys
|
|
273
|
+
headers = {
|
|
274
|
+
"Content-Length" => payload.size,
|
|
275
|
+
"Content-Type" => "application/json",
|
|
276
|
+
"Authorization" => "Basic #{authkey}",
|
|
277
|
+
'User-Agent' => "Signifyd Ruby #{@@api_version.gsub('/', '')}"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# All necessary options must be set
|
|
281
|
+
opts = {
|
|
282
|
+
:method => method,
|
|
283
|
+
:url => url,
|
|
284
|
+
:headers => headers,
|
|
285
|
+
:open_timeout => 30,
|
|
286
|
+
:payload => payload,
|
|
287
|
+
:timeout => 80
|
|
288
|
+
}.merge(ssl_opts)
|
|
289
|
+
|
|
290
|
+
# Make the request
|
|
291
|
+
begin
|
|
292
|
+
response = execute_request(opts)
|
|
293
|
+
rescue SocketError => e
|
|
294
|
+
self.handle_restclient_error(e)
|
|
295
|
+
rescue NoMethodError => e
|
|
296
|
+
if e.message =~ /\WRequestFailed\W/
|
|
297
|
+
e = APIConnectionError.new('Unexpected HTTP response code')
|
|
298
|
+
self.handle_restclient_error(e)
|
|
299
|
+
else
|
|
300
|
+
raise
|
|
301
|
+
end
|
|
302
|
+
rescue RestClient::ExceptionWithResponse => e
|
|
303
|
+
if rcode = e.http_code and rbody = e.http_body
|
|
304
|
+
self.handle_api_error(rcode, rbody)
|
|
305
|
+
else
|
|
306
|
+
self.handle_restclient_error(e)
|
|
307
|
+
end
|
|
308
|
+
rescue RestClient::Exception, Errno::ECONNREFUSED => e
|
|
309
|
+
self.handle_restclient_error(e)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
rbody = response.body
|
|
313
|
+
rcode = response.code
|
|
314
|
+
return {code: rcode, body: JSON.parse(rbody)}
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# execute_request
|
|
318
|
+
#
|
|
319
|
+
# Handles the request, pass in opts hash, RestClient makes the call.
|
|
320
|
+
# @param: Hash[opts] - Configured options from Signifyd.request method.
|
|
321
|
+
# @return: RestClient::Request - the result of the request.
|
|
322
|
+
def self.execute_request(opts)
|
|
323
|
+
RestClient::Request.execute(opts)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# handle_restclient_error
|
|
327
|
+
#
|
|
328
|
+
# @param: RestClient[error] - could be many different types of errors.
|
|
329
|
+
# @return: String[error message]
|
|
330
|
+
def self.handle_restclient_error(e)
|
|
331
|
+
case e
|
|
332
|
+
when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
|
|
333
|
+
message = "Could not connect to Signifyd (#{@@api_base}). Please check your internet connection and try again."
|
|
334
|
+
when RestClient::SSLCertificateNotVerified
|
|
335
|
+
message = "Could not verify Signifyd's SSL certificate. Please make sure that your network is not intercepting certificates. (Try going to https://api.signifyd.com/v2 in your browser.) If this problem persists, let us know at support@signifyd.com."
|
|
336
|
+
when SocketError
|
|
337
|
+
message = "Unexpected error communicating when trying to connect to Signifyd. HINT: You may be seeing this message because your DNS is not working. To check, try running 'host signifyd.com' from the command line."
|
|
338
|
+
else
|
|
339
|
+
message = "Unexpected error communicating with Signifyd. If this problem persists, let us know at support@signifyd.com."
|
|
340
|
+
end
|
|
341
|
+
message += "\n\n(Network error: #{e.message})"
|
|
342
|
+
raise APIConnectionError.new(message)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# handle_api_error
|
|
346
|
+
#
|
|
347
|
+
# @param: String
|
|
348
|
+
# @param: String
|
|
349
|
+
def self.handle_api_error(rcode, rbody)
|
|
350
|
+
error = {}
|
|
351
|
+
case rcode
|
|
352
|
+
when 400, 404
|
|
353
|
+
error[:message] = "Invalid request"
|
|
354
|
+
error[:param] = ""
|
|
355
|
+
raise invalid_request_error error, rcode, rbody
|
|
356
|
+
when 401
|
|
357
|
+
error[:message] = "Authentication error"
|
|
358
|
+
error[:param] = ""
|
|
359
|
+
raise authentication_error error, rcode, rbody
|
|
360
|
+
else
|
|
361
|
+
error[:message] = "API error"
|
|
362
|
+
error[:param] = ""
|
|
363
|
+
raise general_api_error rcode, rbody
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def self.invalid_request_error(error, rcode, rbody)
|
|
368
|
+
raise InvalidRequestError.new(error[:message], error[:param], rcode, rbody)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def self.authentication_error(error, rcode, rbody)
|
|
372
|
+
raise AuthenticationError.new(error[:message], error[:param], rcode, rbody)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def self.general_api_error(rcode, rbody)
|
|
376
|
+
raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})", rcode, rbody)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
private
|
|
380
|
+
# configured?
|
|
381
|
+
#
|
|
382
|
+
# Check to see if the API key has been set
|
|
383
|
+
# @return: Boolean
|
|
384
|
+
def self.configured?
|
|
385
|
+
!!@@api_key
|
|
386
|
+
end
|
|
387
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Signifyd
|
|
2
|
+
module API
|
|
3
|
+
module Create
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def create(params={}, api_key=nil)
|
|
6
|
+
raise InvalidRequestError.new('You have passed invalid parameters to Case.create') if params == {}
|
|
7
|
+
Signifyd.request(:post, self.url, params, api_key)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.included(base)
|
|
12
|
+
base.extend(ClassMethods)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Signifyd
|
|
2
|
+
module API
|
|
3
|
+
module List
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def all(filters={}, api_key=nil)
|
|
6
|
+
Signifyd.request(:get, url, {}, api_key, filters)
|
|
7
|
+
end
|
|
8
|
+
# To retrieve case by orderId pass in {order_id: value}
|
|
9
|
+
# into options
|
|
10
|
+
def find(options={}, filters={},api_key=nil)
|
|
11
|
+
Signifyd.request(:get, url, filters, api_key, options)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.included(base)
|
|
16
|
+
base.extend(ClassMethods)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Signifyd
|
|
2
|
+
module API
|
|
3
|
+
module Update
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def update(case_id, params={}, api_key=nil)
|
|
6
|
+
raise InvalidRequestError.new('You have passed invalid parameters to Case.create') if params == {} || case_id.nil?
|
|
7
|
+
Signifyd.request(:put, self.url, params.merge(case_id: case_id), api_key)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.included(base)
|
|
12
|
+
base.extend(ClassMethods)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|