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.
- checksums.yaml +5 -13
- data/.circleci/config.yml +105 -0
- data/.github/pull_request_template.md +12 -0
- data/.github/workflows/publishing_sift_ruby.yml +38 -0
- data/.gitignore +1 -0
- data/.jenkins/Jenkinsfile +103 -0
- data/HISTORY +104 -0
- data/README.md +351 -0
- data/examples/psp_merchant_management_apis.rb +105 -0
- data/examples/validation_apis.rb +47 -0
- data/lib/sift/client/decision/apply_to.rb +129 -0
- data/lib/sift/client/decision.rb +66 -0
- data/lib/sift/client.rb +845 -112
- data/lib/sift/error.rb +13 -0
- data/lib/sift/router.rb +41 -0
- data/lib/sift/utils/hash_getter.rb +15 -0
- data/lib/sift/validate/decision.rb +65 -0
- data/lib/sift/validate/primitive.rb +43 -0
- data/lib/sift/version.rb +2 -2
- data/lib/sift.rb +85 -11
- data/sift.gemspec +5 -3
- data/spec/fixtures/fake_responses.rb +16 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/client/decision/apply_to_spec.rb +262 -0
- data/spec/unit/client/decision_spec.rb +83 -0
- data/spec/unit/client_203_spec.rb +193 -0
- data/spec/unit/client_205_spec.rb +117 -0
- data/spec/unit/client_label_spec.rb +68 -11
- data/spec/unit/client_psp_merchant_spec.rb +133 -0
- data/spec/unit/client_spec.rb +556 -79
- data/spec/unit/client_validationapi_spec.rb +91 -0
- data/spec/unit/router_spec.rb +37 -0
- data/spec/unit/validate/decision_spec.rb +85 -0
- data/spec/unit/validate/primitive_spec.rb +73 -0
- data/test_integration_app/decisions_api/test_decisions_api.rb +31 -0
- data/test_integration_app/events_api/test_events_api.rb +843 -0
- data/test_integration_app/globals.rb +2 -0
- data/test_integration_app/main.rb +67 -0
- data/test_integration_app/psp_merchants_api/test_psp_merchant_api.rb +44 -0
- data/test_integration_app/score_api/test_score_api.rb +11 -0
- data/test_integration_app/verification_api/test_verification_api.rb +32 -0
- metadata +85 -28
- data/.travis.yml +0 -13
- data/README.rdoc +0 -85
- data/spec/unit/sift_spec.rb +0 -6
data/lib/sift/client.rb
CHANGED
@@ -1,51 +1,88 @@
|
|
1
|
-
require
|
2
|
-
require
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
#
|
18
|
-
#
|
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
|
-
@
|
28
|
-
|
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
|
-
#
|
35
|
-
#
|
63
|
+
# ==== Returns:
|
64
|
+
#
|
65
|
+
# true on success; false otherwise
|
66
|
+
#
|
36
67
|
def ok?
|
37
|
-
|
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
|
-
#
|
42
|
-
# Getter method for
|
78
|
+
# DEPRECATED
|
79
|
+
# Getter method for deprecated 'json' member variable.
|
43
80
|
def json
|
44
81
|
@body
|
45
82
|
end
|
46
83
|
|
47
|
-
#
|
48
|
-
# Getter method for
|
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
|
-
|
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
|
-
|
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
|
-
|
83
|
-
|
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#{
|
150
|
+
"SiftScience/v#{@version} sift-ruby/#{VERSION}"
|
88
151
|
end
|
89
152
|
|
90
|
-
|
91
|
-
#
|
153
|
+
|
154
|
+
# Sends an event to the Sift Science Events API.
|
92
155
|
#
|
93
|
-
#
|
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
|
-
#
|
100
|
-
#
|
101
|
-
#
|
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
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
146
|
-
#
|
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
|
-
#
|
149
|
-
#
|
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
|
-
#
|
154
|
-
# A
|
155
|
-
#
|
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
|
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(
|
160
|
-
raise(
|
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
|
-
|
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(
|
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
|
-
|
440
|
+
|
441
|
+
# Labels a user.
|
442
|
+
#
|
443
|
+
# See https://siftscience.com/developers/docs/ruby/labels-api/label-user .
|
172
444
|
#
|
173
|
-
#
|
174
|
-
#
|
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
|
-
#
|
183
|
-
#
|
184
|
-
#
|
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
|
-
#
|
187
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
195
|
-
|
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
|