sift 1.1.6.2 → 4.5.0

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.
Files changed (45) hide show
  1. checksums.yaml +5 -13
  2. data/.circleci/config.yml +105 -0
  3. data/.github/pull_request_template.md +12 -0
  4. data/.github/workflows/publishing_sift_ruby.yml +38 -0
  5. data/.gitignore +1 -0
  6. data/.jenkins/Jenkinsfile +103 -0
  7. data/HISTORY +104 -0
  8. data/README.md +351 -0
  9. data/examples/psp_merchant_management_apis.rb +105 -0
  10. data/examples/validation_apis.rb +47 -0
  11. data/lib/sift/client/decision/apply_to.rb +129 -0
  12. data/lib/sift/client/decision.rb +66 -0
  13. data/lib/sift/client.rb +845 -112
  14. data/lib/sift/error.rb +13 -0
  15. data/lib/sift/router.rb +41 -0
  16. data/lib/sift/utils/hash_getter.rb +15 -0
  17. data/lib/sift/validate/decision.rb +65 -0
  18. data/lib/sift/validate/primitive.rb +43 -0
  19. data/lib/sift/version.rb +2 -2
  20. data/lib/sift.rb +85 -11
  21. data/sift.gemspec +5 -3
  22. data/spec/fixtures/fake_responses.rb +16 -0
  23. data/spec/spec_helper.rb +1 -1
  24. data/spec/unit/client/decision/apply_to_spec.rb +262 -0
  25. data/spec/unit/client/decision_spec.rb +83 -0
  26. data/spec/unit/client_203_spec.rb +193 -0
  27. data/spec/unit/client_205_spec.rb +117 -0
  28. data/spec/unit/client_label_spec.rb +68 -11
  29. data/spec/unit/client_psp_merchant_spec.rb +133 -0
  30. data/spec/unit/client_spec.rb +556 -79
  31. data/spec/unit/client_validationapi_spec.rb +91 -0
  32. data/spec/unit/router_spec.rb +37 -0
  33. data/spec/unit/validate/decision_spec.rb +85 -0
  34. data/spec/unit/validate/primitive_spec.rb +73 -0
  35. data/test_integration_app/decisions_api/test_decisions_api.rb +31 -0
  36. data/test_integration_app/events_api/test_events_api.rb +843 -0
  37. data/test_integration_app/globals.rb +2 -0
  38. data/test_integration_app/main.rb +67 -0
  39. data/test_integration_app/psp_merchants_api/test_psp_merchant_api.rb +44 -0
  40. data/test_integration_app/score_api/test_score_api.rb +11 -0
  41. data/test_integration_app/verification_api/test_verification_api.rb +32 -0
  42. metadata +85 -28
  43. data/.travis.yml +0 -13
  44. data/README.rdoc +0 -85
  45. data/spec/unit/sift_spec.rb +0 -6
data/lib/sift/client.rb CHANGED
@@ -1,51 +1,88 @@
1
- require 'httparty'
2
- require 'multi_json'
1
+ require "httparty"
2
+ require "multi_json"
3
+ require "base64"
4
+
5
+ require_relative "./client/decision"
6
+ require_relative "./error"
3
7
 
4
8
  module Sift
5
9
 
6
10
  # Represents the payload returned from a call through the track API
7
11
  #
8
12
  class Response
9
- attr_reader :body
10
- attr_reader :http_status_code
11
- attr_reader :api_status
12
- attr_reader :api_error_message
13
- attr_reader :request
13
+ attr_reader :body,
14
+ :http_class,
15
+ :http_status_code,
16
+ :api_status,
17
+ :api_error_message,
18
+ :request,
19
+ :api_error_description,
20
+ :api_error_issues
14
21
 
15
22
  # Constructor
16
23
  #
17
- # == Parameters:
18
- # http_response
24
+ # ==== Parameters:
25
+ #
26
+ # http_response::
19
27
  # The HTTP body text returned from the API call. The body is expected to be
20
28
  # a JSON object that can be decoded into status, message and request
21
29
  # sections.
22
30
  #
23
- def initialize(http_response, http_response_code)
24
- @body = MultiJson.load(http_response)
25
- @request = MultiJson.load(@body["request"].to_s) if @body["request"]
31
+ def initialize(http_response, http_response_code, http_raw_response)
26
32
  @http_status_code = http_response_code
27
- @api_status = @body["status"].to_i
28
- @api_error_message = @body["error_message"].to_s
33
+ @http_raw_response = http_raw_response
34
+
35
+ # only set these variables if a message-body is expected.
36
+ if not @http_raw_response.kind_of? Net::HTTPNoContent
37
+
38
+ begin
39
+ @body = MultiJson.load(http_response) unless http_response.nil?
40
+ rescue
41
+ if @http_status_code == 200
42
+ raise TypeError.new
43
+ end
44
+ end
45
+
46
+ if not @body.nil?
47
+ @request = MultiJson.load(@body["request"].to_s) if @body["request"]
48
+ @api_status = @body["status"].to_i if @body["status"]
49
+ @api_error_message = @body["error_message"]
50
+
51
+ if @body["error"]
52
+ @api_error_message = @body["error"]
53
+ @api_error_description = @body["description"]
54
+ @api_error_issues = @body["issues"] || {}
55
+ end
56
+ end
57
+ end
29
58
  end
30
59
 
31
60
  # Helper method returns true if and only if the response from the API call was
32
61
  # successful
33
62
  #
34
- # == Returns:
35
- # true on success; false otherwise
63
+ # ==== Returns:
64
+ #
65
+ # true on success; false otherwise
66
+ #
36
67
  def ok?
37
- 0 == @api_status.to_i
68
+ if @http_raw_response.kind_of? Net::HTTPNoContent
69
+ #if there is no content expected, use HTTP code
70
+ 204 == @http_status_code
71
+ else
72
+ # otherwise use API status
73
+ @http_raw_response.kind_of? Net::HTTPOK and 0 == @api_status.to_i
74
+ end
38
75
  end
39
76
 
40
77
 
41
- # DEPRECIATED
42
- # Getter method for depreciated 'json' member variable.
78
+ # DEPRECATED
79
+ # Getter method for deprecated 'json' member variable.
43
80
  def json
44
81
  @body
45
82
  end
46
83
 
47
- # DEPRECIATED
48
- # Getter method for depreciated 'original_request' member variable.
84
+ # DEPRECATED
85
+ # Getter method for deprecated 'original_request' member variable.
49
86
  def original_request
50
87
  @request
51
88
  end
@@ -54,148 +91,844 @@ module Sift
54
91
  # This class wraps accesses through the API
55
92
  #
56
93
  class Client
57
- API_ENDPOINT = "https://api.siftscience.com"
58
- API_TIMEOUT = 2
94
+ API_ENDPOINT = ENV["SIFT_RUBY_API_URL"] || 'https://api.siftscience.com'
95
+ API3_ENDPOINT = ENV["SIFT_RUBY_API3_URL"] || 'https://api3.siftscience.com'
59
96
 
60
97
  include HTTParty
61
98
  base_uri API_ENDPOINT
62
99
 
63
- # Constructor
64
- #
65
- # == Parameters:
66
- # api_key
67
- # The Sift Science API key associated with your customer account. This parameter
68
- # cannot be nil or blank.
69
- # path
70
- # The path to the event API, e.g., "/v201/events"
71
- #
72
- def initialize(api_key = Sift.api_key, path = Sift.current_rest_api_path, timeout = API_TIMEOUT)
73
- raise(RuntimeError, "api_key must be a non-empty string") if (!api_key.is_a? String) || api_key.empty?
74
- raise(RuntimeError, "path must be a non-empty string") if (!path.is_a? String) || path.empty?
75
- @api_key = api_key
76
- @path = path
77
- @timeout = timeout
100
+ attr_reader :api_key, :account_id
78
101
 
102
+ def self.build_auth_header(api_key)
103
+ { "Authorization" => "Basic #{Base64.encode64(api_key)}" }
104
+ end
79
105
 
106
+ def self.user_agent
107
+ "sift-ruby/#{VERSION}"
80
108
  end
81
109
 
82
- def api_key
83
- @api_key
110
+ # Constructor
111
+ #
112
+ # ==== Parameters:
113
+ #
114
+ # opts (optional)::
115
+ # A Hash of optional parameters for this Client --
116
+ #
117
+ # :api_key::
118
+ # The Sift Science API key associated with your account.
119
+ # Sift.api_key is used if this parameter is not set.
120
+ #
121
+ # :account_id::
122
+ # The ID of your Sift Science account. Sift.account_id is
123
+ # used if this parameter is not set.
124
+ #
125
+ # :timeout::
126
+ # The number of seconds to wait before failing a request. By
127
+ # default this is configured to 2 seconds.
128
+ #
129
+ # :version::
130
+ # The version of the Events API, Score API, and Labels API to call.
131
+ # By default, version 205.
132
+ #
133
+ # :path::
134
+ # The URL path to use for Events API path. By default, the
135
+ # official path of the specified-version of the Events API.
136
+ #
137
+ #
138
+ def initialize(opts = {})
139
+ @api_key = opts[:api_key] || Sift.api_key
140
+ @account_id = opts[:account_id] || Sift.account_id
141
+ @version = opts[:version] || API_VERSION
142
+ @timeout = opts[:timeout] || 2 # 2-second timeout by default
143
+ @path = opts[:path] || Sift.rest_api_path(@version)
144
+
145
+ raise("api_key") if !@api_key.is_a?(String) || @api_key.empty?
146
+ raise("path must be a non-empty string") if !@path.is_a?(String) || @path.empty?
84
147
  end
85
148
 
86
149
  def user_agent
87
- "SiftScience/v#{API_VERSION} sift-ruby/#{VERSION}"
150
+ "SiftScience/v#{@version} sift-ruby/#{VERSION}"
88
151
  end
89
152
 
90
- # Tracks an event and associated properties through the Sift Science API. This call
91
- # is blocking.
153
+
154
+ # Sends an event to the Sift Science Events API.
92
155
  #
93
- # == Parameters:
94
- # event
95
- # The name of the event to send. This can be either a reserved event name, like
96
- # $transaction or $label or a custom event name (that does not start with a $).
97
- # This parameter must be specified.
156
+ # See https://siftscience.com/developers/docs/ruby/events-api .
98
157
  #
99
- # properties
100
- # A hash of name-value pairs that specify the event-specific attributes to track.
101
- # This parameter must be specified.
158
+ # ==== Parameters:
159
+ #
160
+ # event::
161
+ # The name of the event to send. This can be either a reserved
162
+ # event name, like $transaction or $label or a custom event name
163
+ # (that does not start with a $). This parameter must be
164
+ # specified.
165
+ #
166
+ # properties::
167
+ # A hash of name-value pairs that specify the event-specific
168
+ # attributes to track. This parameter must be specified.
169
+ #
170
+ # opts (optional)::
171
+ # A Hash of optional parameters for the request --
172
+ #
173
+ # :return_score::
174
+ # If true, requests that the response include a score for this
175
+ # user, computed using the submitted event. See
176
+ # https://siftscience.com/developers/docs/ruby/score-api/synchronous-scores
177
+ #
178
+ # :abuse_types::
179
+ # List of abuse types, specifying for which abuse types a
180
+ # score should be returned (if scoring was requested). By
181
+ # default, a score is returned for every abuse type to which
182
+ # you are subscribed.
183
+ #
184
+ # :return_action::
185
+ # If true, requests that the response include any actions
186
+ # triggered as a result of the tracked event.
102
187
  #
103
- # timeout (optional)
104
- # The number of seconds to wait before failing the request. By default this is
105
- # configured to 2 seconds (see above). This parameter is optional.
106
- #
107
- # path (optional)
108
- # Overrides the default API path with a different URL.
109
- #
110
- # return_score (optional)
111
- # Whether the API response should include a score for this user (the score will
112
- # be calculated using the submitted event
113
- #
114
- # == Returns:
115
- # In the case of an HTTP error (timeout, broken connection, etc.), this
116
- # method returns nil; otherwise, a Response object is returned and captures
117
- # the status message and status code. In general, you can ignore the returned
118
- # result, though.
119
- #
120
- def track(event, properties = {}, timeout = nil, path = nil, return_score = false, api_key = @api_key)
121
- raise(RuntimeError, "event must be a non-empty string") if (!event.is_a? String) || event.empty?
122
- raise(RuntimeError, "properties cannot be empty") if properties.empty?
123
- raise(RuntimeError, "Bad api_key parameter") if api_key.empty?
124
- path ||= @path
125
- timeout ||= @timeout
126
- if return_score
127
- path = path + "?return_score=true"
188
+ # :return_workflow_status::
189
+ # If true, requests that the response include the status of
190
+ # any workflow run as a result of the tracked event. See
191
+ # https://siftscience.com/developers/docs/ruby/workflows-api/workflow-decisions
192
+ #
193
+ # :timeout::
194
+ # Overrides the timeout (in seconds) for this call.
195
+ #
196
+ # :api_key::
197
+ # Overrides the API key for this call.
198
+ #
199
+ # :version::
200
+ # Overrides the version of the Events API to call.
201
+ #
202
+ # :path::
203
+ # Overrides the URI path for this API call.
204
+ #
205
+ # :include_score_percentiles::
206
+ # include_score_percentiles(optional) : Whether to add new parameter in the query parameter.
207
+ #
208
+ # :warnings::
209
+ # warnings(optional) : Whether to add list of warnings (if any) to response.
210
+ #
211
+ # ==== Returns:
212
+ #
213
+ # In the case of a network error (timeout, broken connection, etc.),
214
+ # this method propagates the exception, otherwise, a Response object is
215
+ # returned that captures the status message and status code.
216
+ #
217
+ def track(event, properties = {}, opts = {})
218
+ api_key = opts[:api_key] || @api_key
219
+ version = opts[:version] || @version
220
+ path = opts[:path] || (version && Sift.rest_api_path(version)) || @path
221
+ timeout = opts[:timeout] || @timeout
222
+ return_score = opts[:return_score]
223
+ return_action = opts[:return_action]
224
+ return_workflow_status = opts[:return_workflow_status]
225
+ return_route_info = opts[:return_route_info]
226
+ force_workflow_run = opts[:force_workflow_run]
227
+ abuse_types = opts[:abuse_types]
228
+ include_score_percentiles = opts[:include_score_percentiles]
229
+ warnings = opts[:warnings]
230
+
231
+ raise("event must be a non-empty string") if (!event.is_a? String) || event.empty?
232
+ raise("properties cannot be empty") if properties.empty?
233
+ raise("api_key cannot be empty") if api_key.empty?
234
+
235
+ query = {}
236
+ query["return_score"] = "true" if return_score
237
+ query["return_action"] = "true" if return_action
238
+ query["return_workflow_status"] = "true" if return_workflow_status
239
+ query["return_route_info"] = "true" if return_route_info
240
+ query["force_workflow_run"] = "true" if force_workflow_run
241
+ query["abuse_types"] = abuse_types.join(",") if abuse_types
242
+
243
+ if include_score_percentiles == "true" || warnings == "true"
244
+ fields = []
245
+ fields << "SCORE_PERCENTILES" if include_score_percentiles == "true"
246
+ fields << "WARNINGS" if warnings == "true"
247
+ query["fields"] = fields.join(",")
128
248
  end
249
+
129
250
  options = {
130
251
  :body => MultiJson.dump(delete_nils(properties).merge({"$type" => event,
131
252
  "$api_key" => api_key})),
132
- :headers => {"User-Agent" => user_agent}
253
+ :headers => {"User-Agent" => user_agent},
254
+ :query => query
133
255
  }
134
256
  options.merge!(:timeout => timeout) unless timeout.nil?
135
- begin
136
- response = self.class.post(path, options)
137
- Response.new(response.body, response.code)
138
- rescue StandardError => e
139
- Sift.warn("Failed to track event: " + e.to_s)
140
- Sift.warn(e.backtrace)
141
- nil
257
+
258
+ response = self.class.post(path, options)
259
+ Response.new(response.body, response.code, response.response)
260
+ end
261
+
262
+
263
+ # Retrieves a user's fraud score from the Sift Science API.
264
+ #
265
+ # See https://siftscience.com/developers/docs/ruby/score-api/score-api .
266
+ #
267
+ # ==== Parameters:
268
+ #
269
+ # user_id::
270
+ # A user's id. This id should be the same as the user_id used in
271
+ # event calls.
272
+ #
273
+ # opts (optional)::
274
+ # A Hash of optional parameters for the request --
275
+ #
276
+ # :abuse_types::
277
+ # List of abuse types, specifying for which abuse types a
278
+ # score should be returned. By default, a score is returned
279
+ # for every abuse type to which you are subscribed.
280
+ #
281
+ # :api_key::
282
+ # Overrides the API key for this call.
283
+ #
284
+ # :timeout::
285
+ # Overrides the timeout (in seconds) for this call.
286
+ #
287
+ # :version::
288
+ # Overrides the version of the Events API to call.
289
+ #
290
+ # :include_score_percentiles::
291
+ # include_score_percentiles(optional) : Whether to add new parameter in the query parameter.
292
+ #
293
+ # ==== Returns:
294
+ #
295
+ # A Response object containing a status code, status message, and,
296
+ # if successful, the user's score(s).
297
+ #
298
+ def score(user_id, opts = {})
299
+ abuse_types = opts[:abuse_types]
300
+ api_key = opts[:api_key] || @api_key
301
+ timeout = opts[:timeout] || @timeout
302
+ version = opts[:version] || @version
303
+ include_score_percentiles = opts[:include_score_percentiles]
304
+
305
+ raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
306
+ raise("Bad api_key parameter") if api_key.empty?
307
+
308
+ query = {}
309
+ query["api_key"] = api_key
310
+ query["abuse_types"] = abuse_types.join(",") if abuse_types
311
+ if include_score_percentiles == "true"
312
+ query["fields"] = "SCORE_PERCENTILES"
142
313
  end
314
+
315
+ options = {
316
+ :headers => {"User-Agent" => user_agent},
317
+ :query => query
318
+ }
319
+ options.merge!(:timeout => timeout) unless timeout.nil?
320
+
321
+ response = self.class.get(Sift.score_api_path(user_id, version), options)
322
+ Response.new(response.body, response.code, response.response)
143
323
  end
144
324
 
145
- # Retrieves a user's fraud score from the Sift Science API. This call
146
- # is blocking.
325
+
326
+ # Fetches the latest score(s) computed for the specified user and abuse types.
327
+ #
328
+ # As opposed to client.score() and client.rescore_user(), this *does not* compute
329
+ # a new score for the user; it simply fetches the latest score(s) which have computed.
330
+ # These scores may be arbitrarily old.
331
+ #
332
+ # See https://siftscience.com/developers/docs/ruby/score-api/get-score for more details.
147
333
  #
148
- # == Parameters:
149
- # user_id
334
+ # ==== Parameters:
335
+ #
336
+ # user_id::
150
337
  # A user's id. This id should be the same as the user_id used in
151
338
  # event calls.
152
339
  #
153
- # == Returns:
154
- # A Response object is returned and captures the status message and
155
- # status code. In general, you can ignore the returned result, though.
340
+ # opts (optional)::
341
+ # A Hash of optional parameters for the request --
342
+ #
343
+ # :abuse_types::
344
+ # List of abuse types, specifying for which abuse types a
345
+ # score should be returned. By default, a score is returned
346
+ # for every abuse type to which you are subscribed.
347
+ #
348
+ # :api_key::
349
+ # Overrides the API key for this call.
350
+ #
351
+ # :timeout::
352
+ # Overrides the timeout (in seconds) for this call.
353
+ #
354
+ # :include_score_percentiles::
355
+ # include_score_percentiles(optional) : Whether to add new parameter in the query parameter.
356
+ #
357
+ # ==== Returns:
358
+ #
359
+ # A Response object containing a status code, status message, and,
360
+ # if successful, the user's score(s).
156
361
  #
157
- def score(user_id, timeout = nil, api_key = @api_key)
362
+ def get_user_score(user_id, opts = {})
363
+ abuse_types = opts[:abuse_types]
364
+ api_key = opts[:api_key] || @api_key
365
+ timeout = opts[:timeout] || @timeout
366
+ include_score_percentiles = opts[:include_score_percentiles]
158
367
 
159
- raise(RuntimeError, "user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
160
- raise(RuntimeError, "Bad api_key parameter") if api_key.empty?
161
- timetout ||= @timeout
368
+ raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
369
+ raise("Bad api_key parameter") if api_key.empty?
162
370
 
163
- options = { :headers => {"User-Agent" => user_agent} }
371
+ query = {}
372
+ query["api_key"] = api_key
373
+ query["abuse_types"] = abuse_types.join(",") if abuse_types
374
+ if include_score_percentiles == "true"
375
+ query["fields"] = "SCORE_PERCENTILES"
376
+ end
377
+
378
+ options = {
379
+ :headers => {"User-Agent" => user_agent},
380
+ :query => query
381
+ }
164
382
  options.merge!(:timeout => timeout) unless timeout.nil?
165
383
 
166
- response = self.class.get("/v#{API_VERSION}/score/#{user_id}/?api_key=#{api_key}", options)
167
- Response.new(response.body, response.code)
384
+ response = self.class.get(Sift.user_score_api_path(user_id, @version), options)
385
+ Response.new(response.body, response.code, response.response)
386
+ end
387
+
388
+
389
+ # Rescores the specified user for the specified abuse types and returns the resulting score(s).
390
+ #
391
+ # See https://siftscience.com/developers/docs/ruby/score-api/rescore for more details.
392
+ #
393
+ # ==== Parameters:
394
+ #
395
+ # user_id::
396
+ # A user's id. This id should be the same as the user_id used in
397
+ # event calls.
398
+ #
399
+ # opts (optional)::
400
+ # A Hash of optional parameters for the request --
401
+ #
402
+ # :abuse_types::
403
+ # List of abuse types, specifying for which abuse types a
404
+ # score should be returned. By default, a score is returned
405
+ # for every abuse type to which you are subscribed.
406
+ #
407
+ # :api_key::
408
+ # Overrides the API key for this call.
409
+ #
410
+ # :timeout::
411
+ # Overrides the timeout (in seconds) for this call.
412
+ #
413
+ # ==== Returns:
414
+ #
415
+ # A Response object containing a status code, status message, and,
416
+ # if successful, the user's score(s).
417
+ #
418
+ def rescore_user(user_id, opts = {})
419
+ abuse_types = opts[:abuse_types]
420
+ api_key = opts[:api_key] || @api_key
421
+ timeout = opts[:timeout] || @timeout
422
+
423
+ raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
424
+ raise("Bad api_key parameter") if api_key.empty?
425
+
426
+ query = {}
427
+ query["api_key"] = api_key
428
+ query["abuse_types"] = abuse_types.join(",") if abuse_types
168
429
 
430
+ options = {
431
+ :headers => {"User-Agent" => user_agent},
432
+ :query => query
433
+ }
434
+ options.merge!(:timeout => timeout) unless timeout.nil?
435
+
436
+ response = self.class.post(Sift.user_score_api_path(user_id, @version), options)
437
+ Response.new(response.body, response.code, response.response)
169
438
  end
170
439
 
171
- # Labels a user as either good or bad. This call is blocking.
440
+
441
+ # Labels a user.
442
+ #
443
+ # See https://siftscience.com/developers/docs/ruby/labels-api/label-user .
172
444
  #
173
- # == Parameters:
174
- # user_id
445
+ # ==== Parameters:
446
+ #
447
+ # user_id::
175
448
  # A user's id. This id should be the same as the user_id used in
176
449
  # event calls.
177
450
  #
178
- # properties
451
+ # properties::
179
452
  # A hash of name-value pairs that specify the label attributes.
180
453
  # This parameter must be specified.
181
454
  #
182
- # timeout (optional)
183
- # The number of seconds to wait before failing the request. By default this is
184
- # configured to 2 seconds (see above). This parameter is optional.
455
+ # opts (optional)::
456
+ # A Hash of optional parameters for the request --
457
+ #
458
+ # :api_key::
459
+ # Overrides the API key for this call.
460
+ #
461
+ # :timeout::
462
+ # Overrides the timeout (in seconds) for this call.
463
+ #
464
+ # :version::
465
+ # Overrides the version of the Events API to call.
466
+ #
467
+ # ==== Returns:
468
+ #
469
+ # In the case of a connection error (timeout, broken connection,
470
+ # etc.), this method returns nil; otherwise, a Response object is
471
+ # returned that captures the status message and status code.
472
+ #
473
+ def label(user_id, properties = {}, opts = {})
474
+ api_key = opts[:api_key] || @api_key
475
+ timeout = opts[:timeout] || @timeout
476
+ version = opts[:version] || @version
477
+ path = Sift.users_label_api_path(user_id, version)
478
+
479
+ raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
480
+
481
+ track("$label", delete_nils(properties),
482
+ :path => path, :api_key => api_key, :timeout => timeout)
483
+ end
484
+
485
+
486
+ # Unlabels a user.
487
+ #
488
+ # See https://siftscience.com/developers/docs/ruby/labels-api/unlabel-user .
489
+ #
490
+ # ==== Parameters:
491
+ #
492
+ # user_id::
493
+ # A user's id. This id should be the same as the user_id used in
494
+ # event calls.
495
+ #
496
+ # opts (optional)::
497
+ # A Hash of optional parameters for this request --
498
+ #
499
+ # :abuse_type::
500
+ # The abuse type for which the user should be unlabeled. If
501
+ # omitted, the user is unlabeled for all abuse types.
502
+ #
503
+ # :api_key::
504
+ # Overrides the API key for this call.
505
+ #
506
+ # :timeout::
507
+ # Overrides the timeout (in seconds) for this call.
508
+ #
509
+ # :version::
510
+ # Overrides the version of the Events API to call.
511
+ #
512
+ # ==== Returns:
513
+ #
514
+ # A Response object is returned with only an http code of 204.
515
+ #
516
+ def unlabel(user_id, opts = {})
517
+ abuse_type = opts[:abuse_type]
518
+ api_key = opts[:api_key] || @api_key
519
+ timeout = opts[:timeout] || @timeout
520
+ version = opts[:version] || @version
521
+
522
+ raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
523
+
524
+ query = {}
525
+ query[:api_key] = api_key
526
+ query[:abuse_type] = abuse_type if abuse_type
527
+
528
+ options = {
529
+ :headers => {},
530
+ :query => query
531
+ }
532
+ options.merge!(:timeout => timeout) unless timeout.nil?
533
+
534
+ response = self.class.delete(Sift.users_label_api_path(user_id, version), options)
535
+ Response.new(response.body, response.code, response.response)
536
+ end
537
+
538
+
539
+ # Gets the status of a workflow run.
540
+ #
541
+ # See https://siftscience.com/developers/docs/ruby/workflows-api/workflow-status .
542
+ #
543
+ # ==== Parameters
544
+ #
545
+ # run_id::
546
+ # The ID of a workflow run.
547
+ #
548
+ # opts (optional)::
549
+ # A Hash of optional parameters for this request --
550
+ #
551
+ # :account_id::
552
+ # Overrides the API key for this call.
553
+ #
554
+ # :api_key::
555
+ # Overrides the API key for this call.
556
+ #
557
+ # :timeout::
558
+ # Overrides the timeout (in seconds) for this call.
559
+ #
560
+ def get_workflow_status(run_id, opts = {})
561
+ account_id = opts[:account_id] || @account_id
562
+ api_key = opts[:api_key] || @api_key
563
+ timeout = opts[:timeout] || @timeout
564
+
565
+ options = {
566
+ :headers => { "User-Agent" => user_agent },
567
+ :basic_auth => { :username => api_key, :password => "" }
568
+ }
569
+ options.merge!(:timeout => timeout) unless timeout.nil?
570
+
571
+ uri = API3_ENDPOINT + Sift.workflow_status_path(account_id, run_id)
572
+ response = self.class.get(uri, options)
573
+ Response.new(response.body, response.code, response.response)
574
+ end
575
+
576
+
577
+ # Gets the decision status of a user.
578
+ #
579
+ # See https://siftscience.com/developers/docs/ruby/decisions-api/decision-status .
580
+ #
581
+ # ==== Parameters
582
+ #
583
+ # user_id::
584
+ # The ID of user.
585
+ #
586
+ # opts (optional)::
587
+ # A Hash of optional parameters for this request --
588
+ #
589
+ # :account_id::
590
+ # Overrides the API key for this call.
591
+ #
592
+ # :api_key::
593
+ # Overrides the API key for this call.
594
+ #
595
+ # :timeout::
596
+ # Overrides the timeout (in seconds) for this call.
597
+ #
598
+ def get_user_decisions(user_id, opts = {})
599
+ account_id = opts[:account_id] || @account_id
600
+ api_key = opts[:api_key] || @api_key
601
+ timeout = opts[:timeout] || @timeout
602
+
603
+ options = {
604
+ :headers => { "User-Agent" => user_agent },
605
+ :basic_auth => { :username => api_key, :password => "" }
606
+ }
607
+ options.merge!(:timeout => timeout) unless timeout.nil?
608
+
609
+ uri = API3_ENDPOINT + Sift.user_decisions_api_path(account_id, user_id)
610
+ response = self.class.get(uri, options)
611
+ Response.new(response.body, response.code, response.response)
612
+ end
613
+
614
+
615
+ # Gets the decision status of an order.
616
+ #
617
+ # See https://siftscience.com/developers/docs/ruby/decisions-api/decision-status .
618
+ #
619
+ # ==== Parameters
620
+ #
621
+ # order_id::
622
+ # The ID of an order.
623
+ #
624
+ # opts (optional)::
625
+ # A Hash of optional parameters for this request --
185
626
  #
186
- # == Returns:
187
- # A Response object is returned and captures the status message and
188
- # status code. In general, you can ignore the returned result, though.
627
+ # :account_id::
628
+ # Overrides the API key for this call.
189
629
  #
190
- def label(user_id, properties = {}, timeout = nil, api_key = @api_key)
630
+ # :api_key::
631
+ # Overrides the API key for this call.
632
+ #
633
+ # :timeout::
634
+ # Overrides the timeout (in seconds) for this call.
635
+ #
636
+ def get_order_decisions(order_id, opts = {})
637
+ account_id = opts[:account_id] || @account_id
638
+ api_key = opts[:api_key] || @api_key
639
+ timeout = opts[:timeout] || @timeout
640
+
641
+ options = {
642
+ :headers => { "User-Agent" => user_agent },
643
+ :basic_auth => { :username => api_key, :password => "" }
644
+ }
645
+ options.merge!(:timeout => timeout) unless timeout.nil?
646
+
647
+ uri = API3_ENDPOINT + Sift.order_decisions_api_path(account_id, order_id)
648
+ response = self.class.get(uri, options)
649
+ Response.new(response.body, response.code, response.response)
650
+ end
651
+
652
+ # Gets the decision status of a session.
653
+ #
654
+ # See https://siftscience.com/developers/docs/ruby/decisions-api/decision-status .
655
+ #
656
+ # ==== Parameters
657
+ #
658
+ # user_id::
659
+ # The ID of the user in the session.
660
+ #
661
+ # session_id::
662
+ # The ID of a session.
663
+ #
664
+ # opts (optional)::
665
+ # A Hash of optional parameters for this request --
666
+ #
667
+ # :account_id::
668
+ # Overrides the account id for this call.
669
+ #
670
+ # :api_key::
671
+ # Overrides the API key for this call.
672
+ #
673
+ # :timeout::
674
+ # Overrides the timeout (in seconds) for this call.
675
+ #
676
+ def get_session_decisions(user_id, session_id, opts = {})
677
+ account_id = opts[:account_id] || @account_id
678
+ api_key = opts[:api_key] || @api_key
679
+ timeout = opts[:timeout] || @timeout
191
680
 
192
- raise(RuntimeError, "user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
681
+ options = {
682
+ :headers => { "User-Agent" => user_agent },
683
+ :basic_auth => { :username => api_key, :password => "" }
684
+ }
685
+ options.merge!(:timeout => timeout) unless timeout.nil?
193
686
 
194
- path = Sift.current_users_label_api_path(user_id)
195
- track("$label", delete_nils(properties), timeout, path, false, api_key)
687
+ uri = API3_ENDPOINT + Sift.session_decisions_api_path(account_id, user_id, session_id)
688
+ response = self.class.get(uri, options)
689
+ Response.new(response.body, response.code, response.response)
196
690
  end
197
691
 
692
+ # Gets the decision status of a piece of content.
693
+ #
694
+ # See https://siftscience.com/developers/docs/ruby/decisions-api/decision-status .
695
+ #
696
+ # ==== Parameters
697
+ #
698
+ # user_id::
699
+ # The ID of the owner of the content.
700
+ #
701
+ # content_id::
702
+ # The ID of a piece of content.
703
+ #
704
+ # opts (optional)::
705
+ # A Hash of optional parameters for this request --
706
+ #
707
+ # :account_id::
708
+ # Overrides the API key for this call.
709
+ #
710
+ # :api_key::
711
+ # Overrides the API key for this call.
712
+ #
713
+ # :timeout::
714
+ # Overrides the timeout (in seconds) for this call.
715
+ #
716
+ def get_content_decisions(user_id, content_id, opts = {})
717
+ account_id = opts[:account_id] || @account_id
718
+ api_key = opts[:api_key] || @api_key
719
+ timeout = opts[:timeout] || @timeout
720
+
721
+ options = {
722
+ :headers => { "User-Agent" => user_agent },
723
+ :basic_auth => { :username => api_key, :password => "" }
724
+ }
725
+ options.merge!(:timeout => timeout) unless timeout.nil?
726
+
727
+ uri = API3_ENDPOINT + Sift.content_decisions_api_path(account_id, user_id, content_id)
728
+ response = self.class.get(uri, options)
729
+ Response.new(response.body, response.code, response.response)
730
+ end
731
+
732
+ def decisions(opts = {})
733
+ decision_instance.list(opts)
734
+ end
735
+
736
+ def decisions!(opts = {})
737
+ handle_response(decisions(opts))
738
+ end
739
+
740
+ def apply_decision(configs = {})
741
+ decision_instance.apply_to(configs)
742
+ end
743
+
744
+ def apply_decision!(configs = {})
745
+ handle_response(apply_decision(configs))
746
+ end
747
+
748
+ def build_default_headers_post(api_key)
749
+ {
750
+ "Authorization" => "Basic #{Base64.encode64(api_key+":")}",
751
+ "User-Agent" => "SiftScience/v#{@version} sift-ruby/#{VERSION}",
752
+ "Content-Type" => "application/json"
753
+ }
754
+ end
755
+
756
+ def verification_send(properties = {}, opts = {})
757
+ api_key = opts[:api_key] || @api_key
758
+ version = opts[:version] || @version
759
+ timeout = opts[:timeout] || @timeout
760
+
761
+ raise("properties cannot be empty") if properties.empty?
762
+ raise("api_key cannot be empty") if api_key.empty?
763
+
764
+
765
+ options = {
766
+ :body => MultiJson.dump(delete_nils(properties)),
767
+ :headers => build_default_headers_post(api_key)
768
+ }
769
+ options.merge!(:timeout => timeout) unless timeout.nil?
770
+
771
+ response = self.class.post(Sift.verification_api_send_path(@version), options)
772
+ Response.new(response.body, response.code, response.response)
773
+ end
774
+
775
+ def verification_resend(properties = {}, opts = {})
776
+ api_key = opts[:api_key] || @api_key
777
+ version = opts[:version] || @version
778
+ timeout = opts[:timeout] || @timeout
779
+
780
+ raise("properties cannot be empty") if properties.empty?
781
+ raise("api_key cannot be empty") if api_key.empty?
782
+
783
+
784
+ options = {
785
+ :body => MultiJson.dump(delete_nils(properties)),
786
+ :headers => build_default_headers_post(api_key)
787
+ }
788
+ options.merge!(:timeout => timeout) unless timeout.nil?
789
+
790
+ response = self.class.post(Sift.verification_api_resend_path(@version), options)
791
+ Response.new(response.body, response.code, response.response)
792
+ end
793
+
794
+ def verification_check(properties = {}, opts = {})
795
+ api_key = opts[:api_key] || @api_key
796
+ version = opts[:version] || @version
797
+ timeout = opts[:timeout] || @timeout
798
+
799
+ raise("properties cannot be empty") if properties.empty?
800
+ raise("api_key cannot be empty") if api_key.empty?
801
+
802
+
803
+ options = {
804
+ :body => MultiJson.dump(delete_nils(properties)),
805
+ :headers => build_default_headers_post(api_key)
806
+ }
807
+ options.merge!(:timeout => timeout) unless timeout.nil?
808
+
809
+ response = self.class.post(Sift.verification_api_check_path(@version), options)
810
+ Response.new(response.body, response.code, response.response)
811
+ end
812
+
813
+ def create_psp_merchant_profile(properties = {}, opts = {})
814
+ # Create a new PSP Merchant profile
815
+ # Args:
816
+ # properties: A dict of merchant profile data.
817
+ # Returns
818
+ # A sift.client.Response object if the call succeeded, else raises an ApiException
819
+
820
+ account_id = opts[:account_id] || @account_id
821
+ api_key = opts[:api_key] || @api_key
822
+ timeout = opts[:timeout] || @timeout
823
+
824
+ raise("api_key cannot be empty") if api_key.empty?
825
+ raise("account_id cannot be empty") if account_id.empty?
826
+ raise("properties cannot be empty") if properties.empty?
827
+
828
+ options = {
829
+ :body => MultiJson.dump(delete_nils(properties)),
830
+ :headers => { "User-Agent" => user_agent, "Content-Type" => "application/json" },
831
+ :basic_auth => { :username => api_key, :password => "" }
832
+ }
833
+ options.merge!(:timeout => timeout) unless timeout.nil?
834
+ response = self.class.post(API_ENDPOINT + Sift.psp_merchant_api_path(account_id), options)
835
+ Response.new(response.body, response.code, response.response)
836
+ end
837
+
838
+ def update_psp_merchant_profile(merchant_id, properties = {}, opts = {})
839
+ # Update an existing PSP Merchant profile
840
+ # Args:
841
+ # merchant_id: id of merchant
842
+ # properties: A dict of merchant profile data.
843
+ # Returns
844
+ # A sift.client.Response object if the call succeeded, else raises an ApiException
845
+
846
+ account_id = opts[:account_id] || @account_id
847
+ api_key = opts[:api_key] || @api_key
848
+ timeout = opts[:timeout] || @timeout
849
+
850
+ raise("api_key cannot be empty") if api_key.empty?
851
+ raise("account_id cannot be empty") if account_id.empty?
852
+ raise("merchant_id cannot be empty") if merchant_id.empty?
853
+ raise("properties cannot be empty") if properties.empty?
854
+
855
+ options = {
856
+ :body => MultiJson.dump(delete_nils(properties)),
857
+ :headers => { "User-Agent" => user_agent, "Content-Type" => "application/json" },
858
+ :basic_auth => { :username => api_key, :password => "" }
859
+ }
860
+ options.merge!(:timeout => timeout) unless timeout.nil?
861
+ response = self.class.put(API_ENDPOINT + Sift.psp_merchant_id_api_path(account_id, merchant_id), options)
862
+ Response.new(response.body, response.code, response.response)
863
+ end
864
+
865
+ def get_a_psp_merchant_profile(merchant_id, opts = {})
866
+ # Gets a PSP merchant profile using merchant id.
867
+ # Args:
868
+ # merchant_id: id of merchant
869
+ # Returns
870
+ # A sift.client.Response object if the call succeeded, else raises an ApiException
871
+
872
+ account_id = opts[:account_id] || @account_id
873
+ api_key = opts[:api_key] || @api_key
874
+ timeout = opts[:timeout] || @timeout
875
+
876
+ raise("api_key cannot be empty") if api_key.empty?
877
+ raise("account_id cannot be empty") if account_id.empty?
878
+ raise("merchant_id cannot be empty") if merchant_id.empty?
879
+
880
+ options = {
881
+ :headers => { "User-Agent" => user_agent, "Content-Type" => "application/json" },
882
+ :basic_auth => { :username => api_key, :password => "" }
883
+ }
884
+ options.merge!(:timeout => timeout) unless timeout.nil?
885
+ response = self.class.get(API_ENDPOINT + Sift.psp_merchant_id_api_path(account_id, merchant_id), options)
886
+ Response.new(response.body, response.code, response.response)
887
+ end
888
+
889
+ def get_psp_merchant_profiles(batch_size = nil, batch_token = nil, opts = {})
890
+ # Get all PSP merchant profiles.
891
+ # Args:
892
+ # batch_size : Batch or page size of the paginated sequence.
893
+ # batch_token : Batch or page position of the paginated sequence.
894
+ # Returns
895
+ # A sift.client.Response object if the call succeeded, else raises an ApiException
896
+
897
+ account_id = opts[:account_id] || @account_id
898
+ api_key = opts[:api_key] || @api_key
899
+ timeout = opts[:timeout] || @timeout
900
+
901
+ raise("api_key cannot be empty") if api_key.empty?
902
+ raise("account_id cannot be empty") if account_id.empty?
903
+
904
+ query = {}
905
+ query["batch_size"] = batch_size if batch_size
906
+ query["batch_token"] = batch_token if batch_token
907
+
908
+ options = {
909
+ :headers => { "User-Agent" => user_agent, "Content-Type" => "application/json" },
910
+ :basic_auth => { :username => api_key, :password => "" },
911
+ :query => query
912
+ }
913
+ options.merge!(:timeout => timeout) unless timeout.nil?
914
+ response = self.class.get(API_ENDPOINT + Sift.psp_merchant_api_path(account_id), options)
915
+ Response.new(response.body, response.code, response.response)
916
+ end
917
+
198
918
  private
919
+
920
+ def handle_response(response)
921
+ if response.ok?
922
+ response.body
923
+ else
924
+ raise ApiError.new(response.api_error_message, response)
925
+ end
926
+ end
927
+
928
+ def decision_instance
929
+ @decision_instance ||= Decision.new(api_key, account_id)
930
+ end
931
+
199
932
  def delete_nils(properties)
200
933
  properties.delete_if do |k, v|
201
934
  case v