sift 1.1.0 → 4.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 57a679adbe12a556895633060cde177cf89fcb06
4
+ data.tar.gz: d3eff77b83c2ba5696c4c391f82d592a8df7443a
5
+ SHA512:
6
+ metadata.gz: 5809b998556c975ff30faceacf5468a812e815dead3e836640343fe681c1d612192a9a26615f8a0c35c4c13a3cb9b232f3aff1bbf7649d6b3e6c8250cd7f2896
7
+ data.tar.gz: 8b941c79e41784d1452f8ae4fe8b1052f7f32ed3954f8afb16b16f3e7346a0bf6e5cfc71f6080c6475ff75a0fce78172a58e4f8ece8f73ab47385ffd57d3001a
@@ -0,0 +1,43 @@
1
+ version: 2
2
+
3
+ jobs:
4
+ build:
5
+ parallelism: 3 # run three instances of this job in parallel
6
+ docker:
7
+ - image: circleci/ruby:2.4.2-jessie-node
8
+ environment:
9
+ BUNDLE_JOBS: 3
10
+ BUNDLE_RETRY: 3
11
+ BUNDLE_PATH: vendor/bundle
12
+ steps:
13
+ - checkout
14
+
15
+ - run:
16
+ name: Which bundler?
17
+ command: bundle -v
18
+
19
+ - restore_cache:
20
+ keys:
21
+ - sift-bundle-{{ checksum "sift.gemspec" }}
22
+ - sift-bundle-
23
+
24
+ - run:
25
+ name: Bundle Install
26
+ command: bundle check || bundle install
27
+
28
+ - save_cache:
29
+ key: sift-bundle-{{ checksum "sift.gemspec" }}
30
+ paths:
31
+ - vendor/bundle
32
+
33
+ - run:
34
+ name: Run rspec in parallel
35
+ command: |
36
+ bundle exec rspec --profile 10 \
37
+ --format RspecJunitFormatter \
38
+ --out test_results/rspec.xml \
39
+ --format progress \
40
+ $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
41
+
42
+ - store_test_results:
43
+ path: test_results
data/HISTORY CHANGED
@@ -1,3 +1,91 @@
1
+ === 4.0.0 2019-05-15
2
+ - Breaking change: Propagate exception in Client.track() – previously we were silently swallowing exceptions and now we are bubbling them up. You will need to add exception handling code.
3
+ - Fix URL encoding
4
+ - Add Circle build
5
+
6
+ === 3.3.0 2018-07-31
7
+ - Add support for rescore_user and get_user_score APIs
8
+
9
+ === 3.2.0 2018-07-05
10
+ - Add new query parameter force_workflow_run
11
+
12
+ === 3.1.0 2018-06-04
13
+ - Adds support for get session decisions to [Decisions API](https://siftscience.com/developers/docs/curl/decisions-api)
14
+
15
+ === 3.0.1 2018-04-06
16
+ - Improved documentation on HISTORY and README.MD
17
+
18
+ === 3.0.0 2018-03-05
19
+ - Adds support for Sift Science API Version 205, including new [`$create_content`](https://siftscience.com/developers/docs/curl/events-api/reserved-events/create-content) and [`$update_content`](https://siftscience.com/developers/docs/curl/events-api/reserved-events/update-content) formats
20
+ - V205 APIs are now called -- **this is an incompatible change**
21
+ - (use `:version => 204` to call the previous API version)
22
+ - Adds support for content decisions to [Decisions API](https://siftscience.com/developers/docs/curl/decisions-api)
23
+
24
+ INCOMPATIBLE CHANGES INTRODUCED IN API V205:
25
+ - `$create_content` and `$update_content` have significantly changed, and the old format will be rejected
26
+ - `$send_message` and `$submit_review` events are no longer valid
27
+ - V205 improves server-side event data validation. In V204 and earlier, server-side validation accepted some events that did not conform to the published APIs in our [developer documentation](https://siftscience.com/developers/docs/curl/events-api). V205 does not modify existing event APIs other than those mentioned above, but may reject invalid event data that were previously accepted. **Please test your integration on V205 in sandbox before using in production.**
28
+
29
+ === 2.2.1.0 2018-02-12
30
+ * Add session level decisions in Apply Decisions APIs.
31
+
32
+ === 2.0.0.0 2016-07-19
33
+ * adds support for v204 of Sift Science's APIs
34
+ * adds Workflow Status API, User Decisions API, Order Decisions API
35
+ * v204 APIs are now called by default -- this is an incompatible change
36
+ (use :version => 203 to call the previous API version)
37
+ * uses Hash arg for optional params in Client methods -- incompatible change
38
+
39
+ === 1.1.7.2 2015-04-13
40
+ * Fixed backwards compatibility issue
41
+
42
+ === 1.1.7.0 2015-02-24
43
+ * Added Unlabel functionality
44
+
45
+ === 1.1.6.2 2014-09-19
46
+ * added API key parameter to track and label functions
47
+
48
+ === 1.1.6.0 2014-09-03
49
+ * added module scoped API key variable
50
+
51
+ === 1.1.4 2014-01-02
52
+ * removed support for ruby 1.9.2
53
+ * track/label return nil on failure
54
+
55
+ === 1.1.3 2013-12-10
56
+ * nil values are removed from JSON body sent to track/label now.
57
+ * relaxed multi_json requirement to 1.0 or newer
58
+ * relaxed httparty requirement to 0.11.0 or newer
59
+ * migrated from fakeweb to webmock 1.16.0 or newer
60
+
61
+ === 1.1.1 2013-11-14
62
+ * score request now requires API key
63
+
64
+ === 1.1.0 2013-11-08
65
+ * now uses v203 API endpoint by default
66
+
67
+ === 1.0.13 2014-10-22
68
+ * added path parameter to track function
69
+ * added label function for applying labels
70
+ * now requires httparty 0.12.0 or newer
71
+ * now requires multi_json 1.8.2 or newer
72
+ * now requires rspec 2.14.1 or newer for compilation only
73
+
74
+ === 1.0.12 2013-06-13
75
+ * added score function for getting user scores.
76
+ * now requires httparty 0.10.0 or newer
77
+ * added rspec 2.0 or newer and rake dependency
78
+
79
+ === 1.0.10 2013-01-09
80
+ * Add configurable path variable to Client
81
+
82
+ === 1.0.5 2012-07-20
83
+
84
+ === 1.0.3 2012-05-05
85
+
86
+ === 1.0.2 2012-05-03
87
+
88
+ === 1.0.1 2012-05-02
1
89
 
2
90
  === 1.0 2012-05-02
3
91
  * Initial release
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Sift Science
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # Sift Ruby bindings
2
+
3
+ [![CircleCI](https://circleci.com/gh/SiftScience/sift-ruby.svg?style=svg)](https://circleci.com/gh/SiftScience/sift-ruby)
4
+
5
+ ## Requirements
6
+
7
+ * Ruby 2.0.0 or above.
8
+
9
+
10
+ ## Installation
11
+
12
+ If you want to build the gem from source:
13
+
14
+ ```
15
+ $ gem build sift.gemspec
16
+ ```
17
+
18
+ Alternatively, you can install the gem from rubygems.org:
19
+
20
+ ```
21
+ gem install sift
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Creating a Client:
27
+
28
+ ```ruby
29
+ require "sift"
30
+
31
+ Sift.api_key = '<your_api_key_here>'
32
+ Sift.account_id = '<your_account_id_here>'
33
+ client = Sift::Client.new()
34
+
35
+ ##### OR
36
+
37
+ client = Sift::Client.new(api_key: '<your_api_key_here>', account_id: '<your_account_id_here>')
38
+
39
+ ```
40
+
41
+ ### Sending a transaction event
42
+
43
+ ```ruby
44
+ event = "$transaction"
45
+
46
+ user_id = "23056" # User ID's may only contain a-z, A-Z, 0-9, =, ., -, _, +, @, :, &, ^, %, !, $
47
+
48
+ properties = {
49
+ "$user_id" => user_id,
50
+ "$user_email" => "buyer@gmail.com",
51
+ "$seller_user_id" => "2371",
52
+ "seller_user_email" => "seller@gmail.com",
53
+ "$transaction_id" => "573050",
54
+ "$payment_method" => {
55
+ "$payment_type" => "$credit_card",
56
+ "$payment_gateway" => "$braintree",
57
+ "$card_bin" => "542486",
58
+ "$card_last4" => "4444"
59
+ },
60
+ "$currency_code" => "USD",
61
+ "$amount" => 15230000,
62
+ }
63
+
64
+ response = client.track(event, properties)
65
+
66
+ response.ok? # returns true or false
67
+ response.body # API response body
68
+ response.http_status_code # HTTP response code, 200 is ok.
69
+ response.api_status # status field in the return body, Link to Error Codes
70
+ response.api_error_message # Error message associated with status Error Code
71
+
72
+ # Request a score for the user with user_id 23056
73
+ response = client.score(user_id)
74
+ ```
75
+
76
+ ## Decisions
77
+
78
+ To learn more about the decisions endpoint visit our [developer docs](https://sift.com/developers/docs/ruby/decisions-api/get-decisions).
79
+
80
+ ### List of Configured Decisions
81
+
82
+ Get a list of your decisions.
83
+
84
+ **Optional Params**
85
+ - `entity_type`: `:user` or `:order` or `:session` or `:content`
86
+ - `abuse_types`: `["payment_abuse", "content_abuse", "content_abuse",
87
+ "account_abuse", "legacy", "account_takeover"]`
88
+
89
+ **Returns**
90
+
91
+ A `Response` object
92
+
93
+ **Example:**
94
+ ```ruby
95
+ # fetch a list of all your decisions
96
+ response = client.decisions({
97
+ entity_type: :user,
98
+ abuse_types: ["payment_abuse", "content_abuse"]
99
+ })
100
+
101
+ # Check that response is okay.
102
+ unless response.ok?
103
+ raise "Unable to fetch decisions #{response.api_error_message} " +
104
+ "#{response.api_error_description}"
105
+ end
106
+
107
+ # find a decisions with the id "block_bad_user"
108
+ user_decision = response.body["data"].find do |decision|
109
+ decision["id"] == "block_bad_user"
110
+ end
111
+
112
+ # Get the next page
113
+
114
+ if response.body["has_more"]
115
+ client.decisions(response.body)
116
+ end
117
+ ```
118
+
119
+
120
+ ### Apply a decision
121
+
122
+ Applies a decision to an entity. Visit our [developer docs](http://sift.com/developers/docs/ruby/decisions-api/apply-decision) for more information.
123
+
124
+ **Required Params:**
125
+ - `decision_id`, `source`, `user_id`
126
+
127
+ **Other Params**
128
+ - `order_id`: when applying a decision to an order, you must pass in the `order_id`
129
+ - `session_id`: when applying a decision to a session, you must pass in the `session_id`
130
+ - `analyst`: when `source` is set to `manual_review`, this field *is required*
131
+
132
+ **Returns**
133
+ `Response` object.
134
+
135
+ **Examples:**
136
+ ```ruby
137
+ # apply decision to a user
138
+ response = client.apply_decision({
139
+ decision_id: "block_bad_user",
140
+ source: "manual_review",
141
+ analyst: "bob@your_company.com",
142
+ user_id: "john@example.com"
143
+ })
144
+
145
+ # apply decision to "john@example.com"'s order
146
+ response = client.apply_decision({
147
+ decision_id: "block_bad_order",
148
+ source: "manual_review",
149
+ analyst: "bob@your_company.com",
150
+ user_id: "john@example.com",
151
+ order_id: "ORDER_1234"
152
+ })
153
+
154
+ # apply decision to "john@example.com"'s session
155
+ response = client.apply_decision({
156
+ decision_id: "block_bad_session",
157
+ source: "manual_review",
158
+ analyst: "bob@your_company.com",
159
+ user_id: "john@example.com",
160
+ session_id: "SESSION_ID_1234"
161
+ })
162
+
163
+ # apply decision to "john@example.com"'s content
164
+ response = client.apply_decision({
165
+ decision_id: "block_bad_session",
166
+ source: "manual_review",
167
+ analyst: "bob@your_company.com",
168
+ user_id: "john@example.com",
169
+ content_id: "CONTENT_ID_1234"
170
+ })
171
+
172
+
173
+ # Make sure you handle the response after applying a decision:
174
+
175
+ if response.ok?
176
+ # do stuff
177
+ else
178
+ # Error message
179
+ response.api_error_message
180
+
181
+ # Summary of the error
182
+ response.api_error_description
183
+
184
+ # hash of errors:
185
+ # key: field in question
186
+ # value: description of the issue
187
+ response.api_error_issues
188
+ end
189
+ ```
190
+
191
+ ## Sending a Label
192
+
193
+ ```ruby
194
+ # Label the user with user_id 23056 as Bad with all optional fields
195
+ response = client.label(user_id, {
196
+ "$is_bad" => true,
197
+ "$abuse_type" => "payment_abuse",
198
+ "$description" => "Chargeback issued",
199
+ "$source" => "Manual Review",
200
+ "$analyst" => "analyst.name@your_domain.com"
201
+ })
202
+
203
+ # Get the status of a workflow run
204
+ response = client.get_workflow_status('my_run_id')
205
+
206
+ # Get the latest decisions for a user
207
+ response = client.get_user_decisions('example_user_id')
208
+
209
+ # Get the latest decisions for an order
210
+ response = client.get_order_decisions('example_order_id')
211
+
212
+ # Get the latest decisions for a session
213
+ response = client.get_session_decisions('example_user_id', 'example_session_id')
214
+
215
+ # Get the latest decisions for an content
216
+ response = client.get_content_decisions('example_user_id', 'example_order_id')
217
+ ```
218
+
219
+ ## Response Object
220
+
221
+ All requests to our apis will return a `Response` instance.
222
+
223
+ ### Public Methods:
224
+ - `ok?` returns `true` when the response is a `200`-`299`, `false` if it isn't
225
+ - `body` returns a hash representation of the json body returned.
226
+ - `api_error_message` returns a string describing the api error code.
227
+ - `api_error_description` a summary of the error that occured.
228
+ - `api_error_issues` a hash describing the items the error occured. The `key` is the item and the `value` is the description of the error.
229
+
230
+
231
+ ## Building
232
+
233
+ Building and publishing the gem is captured by the following steps:
234
+
235
+ ```ruby
236
+ $ gem build sift.gemspec
237
+ $ gem push sift-<current version>.gem
238
+
239
+ $ bundle
240
+ $ rake -T
241
+ $ rake build
242
+ $ rake install
243
+ $ rake release
244
+ ```
245
+
246
+
247
+ ## Testing
248
+
249
+ To run the various tests use the rake command as follows:
250
+
251
+ ```ruby
252
+ $ rake spec
253
+ ```
@@ -0,0 +1,129 @@
1
+ require "multi_json"
2
+
3
+ require_relative "../../validate/decision"
4
+ require_relative "../../client"
5
+ require_relative "../../router"
6
+ require_relative "../../utils/hash_getter"
7
+
8
+ module Sift
9
+ class Client
10
+ class Decision
11
+ class ApplyTo
12
+ PROPERTIES = %w{
13
+ source
14
+ analyst
15
+ description
16
+ order_id
17
+ session_id
18
+ content_id
19
+ user_id
20
+ account_id
21
+ time
22
+ }
23
+
24
+ attr_reader :decision_id, :configs, :getter, :api_key
25
+
26
+ PROPERTIES.each do |attribute|
27
+ class_eval %{
28
+ def #{attribute}
29
+ getter.get(:#{attribute})
30
+ end
31
+ }
32
+ end
33
+
34
+ def initialize(api_key, decision_id, configs)
35
+ @api_key = api_key
36
+ @decision_id = decision_id
37
+ @configs = configs
38
+ @getter = Utils::HashGetter.new(configs)
39
+ end
40
+
41
+ def run
42
+ if errors.empty?
43
+ send_request
44
+ else
45
+ Response.new(
46
+ MultiJson.dump({
47
+ status: 55,
48
+ error_message: errors.join(", ")
49
+ }),
50
+ 400,
51
+ nil
52
+ )
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def send_request
59
+ Router.post(path, {
60
+ body: request_body,
61
+ headers: headers
62
+ })
63
+ end
64
+
65
+ def request_body
66
+ {
67
+ source: source,
68
+ description: description,
69
+ analyst: analyst,
70
+ decision_id: decision_id,
71
+ time: time
72
+ }
73
+ end
74
+
75
+ def errors
76
+ validator = Validate::Decision.new(configs)
77
+
78
+ if applying_to_order?
79
+ validator.valid_order?
80
+ elsif applying_to_session?
81
+ validator.valid_session?
82
+ else
83
+ validator.valid_user?
84
+ end
85
+
86
+ validator.error_messages
87
+ end
88
+
89
+ def applying_to_order?
90
+ configs.has_key?("order_id") || configs.has_key?(:order_id)
91
+ end
92
+
93
+ def applying_to_session?
94
+ configs.has_key?("session_id") || configs.has_key?(:session_id)
95
+ end
96
+
97
+ def applying_to_content?
98
+ configs.has_key?("content_id") || configs.has_key?(:content_id)
99
+ end
100
+
101
+ def path
102
+ if applying_to_order?
103
+ "#{user_path}/orders/#{CGI.escape(order_id)}/decisions"
104
+ elsif applying_to_session?
105
+ "#{user_path}/sessions/#{CGI.escape(session_id)}/decisions"
106
+ elsif applying_to_content?
107
+ "#{user_path}/content/#{CGI.escape(content_id)}/decisions"
108
+ else
109
+ "#{user_path}/decisions"
110
+ end
111
+ end
112
+
113
+ def user_path
114
+ "#{base_path}/users/#{CGI.escape(user_id)}"
115
+ end
116
+
117
+ def base_path
118
+ "#{Client::API3_ENDPOINT}/v3/accounts/#{account_id}"
119
+ end
120
+
121
+ def headers
122
+ {
123
+ "Content-type" => "application/json"
124
+ }.merge(Client.build_auth_header(api_key))
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,66 @@
1
+ require "cgi"
2
+
3
+ require_relative "../router"
4
+ require_relative "../validate/decision"
5
+ require_relative "../utils/hash_getter"
6
+ require_relative "./decision/apply_to"
7
+
8
+ module Sift
9
+ class Client
10
+ class Decision
11
+ FILTER_PARAMS = %w{ limit entity_type abuse_types from }
12
+
13
+ attr_reader :account_id, :api_key
14
+
15
+ def initialize(api_key, account_id)
16
+ @account_id = account_id
17
+ @api_key = api_key
18
+ end
19
+
20
+ def list(options = {})
21
+ getter = Utils::HashGetter.new(options)
22
+
23
+ if path = getter.get(:next_ref)
24
+ request_next_page(path)
25
+ else
26
+ Router.get(index_path, {
27
+ query: build_query(getter),
28
+ headers: auth_header
29
+ })
30
+ end
31
+ end
32
+
33
+ def build_query(getter)
34
+ FILTER_PARAMS.inject({}) do |result, filter|
35
+ if value = getter.get(filter)
36
+ result[filter] = value.is_a?(Array) ? value.join(",") : value
37
+ end
38
+
39
+ result
40
+ end
41
+ end
42
+
43
+ def apply_to(configs = {})
44
+ getter = Utils::HashGetter.new(configs)
45
+ configs[:account_id] = account_id
46
+
47
+ ApplyTo.new(api_key, getter.get(:decision_id), configs).run
48
+ end
49
+
50
+ def index_path
51
+ "#{Client::API3_ENDPOINT}/v3/accounts/#{account_id}/decisions"
52
+ end
53
+
54
+ private
55
+
56
+ def request_next_page(path)
57
+ Router.get(path, headers: auth_header)
58
+ end
59
+
60
+ def auth_header
61
+ Client.build_auth_header(api_key)
62
+ end
63
+ end
64
+ end
65
+ end
66
+