sift 1.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+