sift 2.0.0.0 → 2.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5116181929ffbab6a17941eb341a852a7c4c75c6
4
- data.tar.gz: 4699c62cdadc3fd2f9123b929925abcf45cfb19b
3
+ metadata.gz: 48d2e914b4876ebfd0d5ec6d14c53149342e807c
4
+ data.tar.gz: d608b5808f89566020a2d115cc71f34232986f0f
5
5
  SHA512:
6
- metadata.gz: 5d228bd45520217fe3e06a0e2673e4a176b497ff83fc5f73940a6e656006b7725521597fb263d64cc133020162400562380b30aee1f29c9a3bde91ce080e03a3
7
- data.tar.gz: bce89304cbf0eb1c963e05c9ec76248ba5e06d9c0829df983735dfaf5828288faf8e73fd3c81a53876ee39081cf3695364a929775ffdb5665caa99fc4dc6f957
6
+ metadata.gz: 6933162c8d8e2f9e61ddb84128f7cc69dd32c00c1dec5f1413c256778718cfda82f59d47ba5d9ddd8783629f7444bc36b10c19966652358bd7499a889ea38a32
7
+ data.tar.gz: 8ff5d3999392b1a5308344d198f02af3bd70df507021175fff59de52941ec6ae50a03dc5bce8a17cbe8f95ee6d65db57f60e4d443b6dcbc83590586b6d8b3f6d
@@ -0,0 +1,225 @@
1
+ # Sift Science Ruby bindings [![Build Status](https://travis-ci.org/SiftScience/sift-ruby.png?branch=master)](https://travis-ci.org/SiftScience/sift-ruby)
2
+
3
+ ## Requirements
4
+
5
+ * Ruby 1.9.3 or above.
6
+
7
+
8
+ ## Installation
9
+
10
+ If you want to build the gem from source:
11
+
12
+ ```
13
+ $ gem build sift.gemspec
14
+ ```
15
+
16
+ Alternatively, you can install the gem from rubygems.org:
17
+
18
+ ```
19
+ gem install sift
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Creating a Client:
25
+
26
+ ```ruby
27
+ require "sift"
28
+
29
+ Sift.api_key = '<your_api_key_here>'
30
+ Sift.account_id = '<your_account_id_here>'
31
+ client = Sift::Client.new()
32
+
33
+ ##### OR
34
+
35
+ client = Sift::Cient.new(api_key: '<your_api_key_here>', account_id: '<your_account_id_here>'
36
+
37
+ ```
38
+
39
+ ### Sending a transaction event
40
+
41
+ ```ruby
42
+ event = "$transaction"
43
+
44
+ user_id = "23056" # User ID's may only contain a-z, A-Z, 0-9, =, ., -, _, +, @, :, &, ^, %, !, $
45
+
46
+ properties = {
47
+ "$user_id" => user_id,
48
+ "$user_email" => "buyer@gmail.com",
49
+ "$seller_user_id" => "2371",
50
+ "seller_user_email" => "seller@gmail.com",
51
+ "$transaction_id" => "573050",
52
+ "$payment_method" => {
53
+ "$payment_type" => "$credit_card",
54
+ "$payment_gateway" => "$braintree",
55
+ "$card_bin" => "542486",
56
+ "$card_last4" => "4444"
57
+ },
58
+ "$currency_code" => "USD",
59
+ "$amount" => 15230000,
60
+ }
61
+
62
+ response = client.track(event, properties)
63
+
64
+ response.ok? # returns true or false
65
+ response.body # API response body
66
+ response.http_status_code # HTTP response code, 200 is ok.
67
+ response.api_status # status field in the return body, Link to Error Codes
68
+ response.api_error_message # Error message associated with status Error Code
69
+
70
+ # Request a score for the user with user_id 23056
71
+ response = client.score(user_id)
72
+ ```
73
+
74
+ ## Decisions
75
+
76
+ To learn more about the decisions endpoint visit our [developer docs](https://siftscience.com/developers/docs/ruby/decisions-api/get-decisions).
77
+
78
+ ### List of Configured Decisions
79
+
80
+ Get a list of your decisions.
81
+
82
+ **Optional Params**
83
+ - `entity_type`: `:user` or `:order`
84
+ - `abuse_types`: `["payment_abuse", "content_abuse", "content_abuse",
85
+ "account_abuse", "legacy"]`
86
+
87
+ **Returns**
88
+
89
+ A `Response` object
90
+
91
+ **Example:**
92
+ ```ruby
93
+ # fetch a list of all your decisions
94
+ response = client.decisions({
95
+ entity_type: :user,
96
+ abuse_types: ["payment_abuse", "content_abuse"]
97
+ })
98
+
99
+ # Check that response is okay.
100
+ unless response.ok?
101
+ raise "Unable to fetch decisions #{response.api_error_message} " +
102
+ "#{response.api_error_description}"
103
+ end
104
+
105
+ # find a decisions with the id "block_bad_user"
106
+ user_decision = response.body["data"].find do |decision|
107
+ decision["id"] == "block_bad_user"
108
+ end
109
+
110
+ # Get the next page
111
+
112
+ if response.body["has_more"]
113
+ client.decisions(response.body)
114
+ end
115
+ ```
116
+
117
+
118
+ ### Apply a decision
119
+
120
+ Applies a decision to an entity. Visit our [developer docs](http://siftscience.com/developers/docs/ruby/decisions-api/apply-decision) for more information.
121
+
122
+ **Required Params:**
123
+ - `decision_id`, `source`, `user_id`
124
+
125
+ **Other Params**
126
+ - `order_id`: when applying a decision to an order, you must pass in the `order_id`
127
+ - `analyst`: when `source` is set to `manual_review`, this field *is required*
128
+
129
+ **Returns**
130
+ `Response` object.
131
+
132
+ **Examples:**
133
+ ```ruby
134
+ # apply decision to a user
135
+ response = client.apply_decision_to({
136
+ decision_id: "block_bad_user",
137
+ source: "manual_review",
138
+ analyst: "bob@your_company.com",
139
+ user_id: "john@example.com"
140
+ })
141
+
142
+ # apply decision to "bob@example.com"'s order
143
+ response = client.apply_decision_to({
144
+ decision_id: "block_bad_order",
145
+ source: "manual_review",
146
+ analyst: "bob@your_company.com",
147
+ user_id: "john@example.com",
148
+ order_id: "ORDER_1234"
149
+ })
150
+
151
+ # Make sure you handle the response after applying a decision:
152
+
153
+ if response.ok?
154
+ # do stuff
155
+ else
156
+ # Error message
157
+ response.api_error_message
158
+
159
+ # Summary of the error
160
+ response.api_error_description
161
+
162
+ # hash of errors:
163
+ # key: field in question
164
+ # value: description of the issue
165
+ response.api_error_issues
166
+ end
167
+ ```
168
+
169
+ ## Sending a Label
170
+
171
+ ```ruby
172
+ # Label the user with user_id 23056 as Bad with all optional fields
173
+ response = client.label(user_id, {
174
+ "$is_bad" => true,
175
+ "$abuse_type" => "payment_abuse",
176
+ "$description" => "Chargeback issued",
177
+ "$source" => "Manual Review",
178
+ "$analyst" => "analyst.name@your_domain.com"
179
+ })
180
+
181
+ # Get the status of a workflow run
182
+ response = client.get_workflow_status('my_run_id')
183
+
184
+ # Get the latest decisions for a user
185
+ response = client.get_user_decisions('example_user_id')
186
+
187
+ # Get the latest decisions for an order
188
+ response = client.get_order_decisions('example_order_id')
189
+ ```
190
+
191
+ ## Response Object
192
+
193
+ All requests to our apis will return a `Response` instance.
194
+
195
+ ### Public Methods:
196
+ - `ok?` returns `true` when the response is a `200`-`299`, `false` if it isn't
197
+ - `body` returns a hash representation of the json body returned.
198
+ - `api_error_message` returns a string describing the api error code.
199
+ - `api_error_description` a summary of the error that occured.
200
+ - `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.
201
+
202
+
203
+ ## Building
204
+
205
+ Building and publishing the gem is captured by the following steps:
206
+
207
+ ```ruby
208
+ $ gem build sift.gemspec
209
+ $ gem push sift-<current version>.gem
210
+
211
+ $ bundle
212
+ $ rake -T
213
+ $ rake build
214
+ $ rake install
215
+ $ rake release
216
+ ```
217
+
218
+
219
+ ## Testing
220
+
221
+ To run the various tests use the rake command as follows:
222
+
223
+ ```ruby
224
+ $ rake spec
225
+ ```
@@ -1,5 +1,5 @@
1
- require "sift/version"
2
- require "sift/client"
1
+ require_relative "./sift/version"
2
+ require_relative "./sift/client"
3
3
 
4
4
  module Sift
5
5
 
@@ -1,17 +1,22 @@
1
1
  require 'httparty'
2
2
  require 'multi_json'
3
3
 
4
+ require_relative "./client/decision"
5
+ require_relative "./error"
6
+
4
7
  module Sift
5
8
 
6
9
  # Represents the payload returned from a call through the track API
7
10
  #
8
11
  class Response
9
- attr_reader :body
10
- attr_reader :http_class
11
- attr_reader :http_status_code
12
- attr_reader :api_status
13
- attr_reader :api_error_message
14
- attr_reader :request
12
+ attr_reader :body,
13
+ :http_class,
14
+ :http_status_code,
15
+ :api_status,
16
+ :api_error_message,
17
+ :request,
18
+ :api_error_description,
19
+ :api_error_issues
15
20
 
16
21
  # Constructor
17
22
  #
@@ -31,7 +36,13 @@ module Sift
31
36
  @body = MultiJson.load(http_response) unless http_response.nil?
32
37
  @request = MultiJson.load(@body["request"].to_s) if @body["request"]
33
38
  @api_status = @body["status"].to_i if @body["status"]
34
- @api_error_message = @body["error_message"].to_s if @body["error_message"]
39
+ @api_error_message = @body["error_message"]
40
+
41
+ if @body["error"]
42
+ @api_error_message = @body["error"]
43
+ @api_error_description = @body["description"]
44
+ @api_error_issues = @body["issues"] || {}
45
+ end
35
46
  end
36
47
  end
37
48
 
@@ -69,12 +80,21 @@ module Sift
69
80
  # This class wraps accesses through the API
70
81
  #
71
82
  class Client
72
- API_ENDPOINT = 'https://api.siftscience.com'
73
- API3_ENDPOINT = 'https://api3.siftscience.com'
83
+ API_ENDPOINT = ENV["SIFT_RUBY_API_URL"] || 'https://api.siftscience.com'
84
+ API3_ENDPOINT = ENV["SIFT_RUBY_API3_URL"] || 'https://api3.siftscience.com'
74
85
 
75
86
  include HTTParty
76
87
  base_uri API_ENDPOINT
77
88
 
89
+ attr_reader :api_key, :account_id
90
+
91
+ def self.build_auth_header(api_key)
92
+ { "Authorization" => "Basic #{Base64.encode64(api_key)}" }
93
+ end
94
+
95
+ def self.user_agent
96
+ "sift-ruby/#{VERSION}"
97
+ end
78
98
 
79
99
  # Constructor
80
100
  #
@@ -115,10 +135,6 @@ module Sift
115
135
  raise("path must be a non-empty string") if !@path.is_a?(String) || @path.empty?
116
136
  end
117
137
 
118
- def api_key
119
- @api_key
120
- end
121
-
122
138
  def user_agent
123
139
  "SiftScience/v#{@version} sift-ruby/#{VERSION}"
124
140
  end
@@ -487,9 +503,36 @@ module Sift
487
503
  Response.new(response.body, response.code, response.response)
488
504
  end
489
505
 
506
+ def decisions(opts = {})
507
+ decision_instance.list(opts)
508
+ end
509
+
510
+ def decisions!(opts = {})
511
+ handle_response(decisions(opts))
512
+ end
513
+
514
+ def apply_decision(configs = {})
515
+ decision_instance.apply_to(configs)
516
+ end
517
+
518
+ def apply_decision!(configs = {})
519
+ handle_response(apply_decision(configs))
520
+ end
490
521
 
491
522
  private
492
523
 
524
+ def handle_response(response)
525
+ if response.ok?
526
+ response.body
527
+ else
528
+ raise ApiError.new(response.api_error_message, response)
529
+ end
530
+ end
531
+
532
+ def decision_instance
533
+ @decision_instance ||= Decision.new(api_key, account_id)
534
+ end
535
+
493
536
  def delete_nils(properties)
494
537
  properties.delete_if do |k, v|
495
538
  case v
@@ -0,0 +1,67 @@
1
+ require "cgi"
2
+ require "Base64"
3
+
4
+ require_relative "../router"
5
+ require_relative "../validate/decision"
6
+ require_relative "../utils/hash_getter"
7
+ require_relative "./decision/apply_to"
8
+
9
+ module Sift
10
+ class Client
11
+ class Decision
12
+ FILTER_PARAMS = %w{ limit entity_type abuse_types from }
13
+
14
+ attr_reader :account_id, :api_key
15
+
16
+ def initialize(api_key, account_id)
17
+ @account_id = account_id
18
+ @api_key = api_key
19
+ end
20
+
21
+ def list(options = {})
22
+ getter = Utils::HashGetter.new(options)
23
+
24
+ if path = getter.get(:next_ref)
25
+ request_next_page(path)
26
+ else
27
+ Router.get(index_path, {
28
+ query: build_query(getter),
29
+ headers: auth_header
30
+ })
31
+ end
32
+ end
33
+
34
+ def build_query(getter)
35
+ FILTER_PARAMS.inject({}) do |result, filter|
36
+ if value = getter.get(filter)
37
+ result[filter] = value.is_a?(Array) ? value.join(",") : value
38
+ end
39
+
40
+ result
41
+ end
42
+ end
43
+
44
+ def apply_to(configs = {})
45
+ getter = Utils::HashGetter.new(configs)
46
+ configs[:account_id] = account_id
47
+
48
+ ApplyTo.new(api_key, getter.get(:decision_id), configs).run
49
+ end
50
+
51
+ def index_path
52
+ "#{Client::API3_ENDPOINT}/v3/accounts/#{account_id}/decisions"
53
+ end
54
+
55
+ private
56
+
57
+ def request_next_page(path)
58
+ Router.get(path, headers: auth_header)
59
+ end
60
+
61
+ def auth_header
62
+ Client.build_auth_header(api_key)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,104 @@
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{ source analyst description order_id user_id account_id }
13
+
14
+ attr_reader :decision_id, :configs, :getter, :api_key
15
+
16
+ PROPERTIES.each do |attribute|
17
+ class_eval %{
18
+ def #{attribute}
19
+ getter.get(:#{attribute})
20
+ end
21
+ }
22
+ end
23
+
24
+ def initialize(api_key, decision_id, configs)
25
+ @api_key = api_key
26
+ @decision_id = decision_id
27
+ @configs = configs
28
+ @getter = Utils::HashGetter.new(configs)
29
+ end
30
+
31
+ def run
32
+ if errors.empty?
33
+ send_request
34
+ else
35
+ Response.new(
36
+ MultiJson.dump({
37
+ status: 55,
38
+ error_message: errors.join(", ")
39
+ }),
40
+ 400,
41
+ nil
42
+ )
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def send_request
49
+ Router.post(path, {
50
+ body: request_body,
51
+ headers: headers
52
+ })
53
+ end
54
+
55
+ def request_body
56
+ {
57
+ source: source,
58
+ description: description,
59
+ analyst: analyst,
60
+ decision_id: decision_id
61
+ }
62
+ end
63
+
64
+ def errors
65
+ validator = Validate::Decision.new(configs)
66
+
67
+ if applying_to_order?
68
+ validator.valid_order?
69
+ else
70
+ validator.valid_user?
71
+ end
72
+
73
+ validator.error_messages
74
+ end
75
+
76
+ def applying_to_order?
77
+ configs.has_key?("order_id") || configs.has_key?(:order_id)
78
+ end
79
+
80
+ def path
81
+ if applying_to_order?
82
+ "#{user_path}/orders/#{CGI.escape(order_id)}/decisions"
83
+ else
84
+ "#{user_path}/decisions"
85
+ end
86
+ end
87
+
88
+ def user_path
89
+ "#{base_path}/users/#{CGI.escape(user_id)}"
90
+ end
91
+
92
+ def base_path
93
+ "#{Client::API3_ENDPOINT}/v3/accounts/#{account_id}"
94
+ end
95
+
96
+ def headers
97
+ {
98
+ "Content-type" => "application/json"
99
+ }.merge(Client.build_auth_header(api_key))
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end