ravelin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ace9b2ca5a0ad8ebc0db1a5d1b1c4d13138a1b80
4
+ data.tar.gz: d5040e417b5abd918aea069c6ae8453d7537ecc1
5
+ SHA512:
6
+ metadata.gz: 024cc6dd06228437355c2ca1132b5e105c4335f0201cb3667132c5c77fbeceef3e6dbbef1627ca6f4b79d7b941178837ad7de81edac9abaabae4682e0c0f9403
7
+ data.tar.gz: c8c5938e1205e7c2fa04b78bf37505290c71eefffb1e2e1b1e2cd63950e4aab562f08348360bf0c87021e95637ab29f8e293322baf8c2306f3c2bda43694d0d2
@@ -0,0 +1,56 @@
1
+ require 'date'
2
+ require 'json'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ require 'ravelin/version'
7
+
8
+ require 'ravelin/errors/api_error'
9
+ require 'ravelin/errors/invalid_request_error'
10
+ require 'ravelin/errors/authentication_error'
11
+ require 'ravelin/errors/rate_limit_error'
12
+
13
+ require 'ravelin/ravelin_object'
14
+ require 'ravelin/chargeback'
15
+ require 'ravelin/customer'
16
+ require 'ravelin/device'
17
+ require 'ravelin/item'
18
+ require 'ravelin/location'
19
+ require 'ravelin/order'
20
+ require 'ravelin/payment_method'
21
+ require 'ravelin/pre_transaction'
22
+ require 'ravelin/transaction'
23
+
24
+ require 'ravelin/event'
25
+ require 'ravelin/response'
26
+ require 'ravelin/client'
27
+
28
+ module Ravelin
29
+ @faraday_adapter = Faraday.default_adapter
30
+ @faraday_timeout = 1
31
+
32
+ class << self
33
+ attr_accessor :faraday_adapter, :faraday_timeout
34
+
35
+ def camelize(str)
36
+ str.to_s.gsub(/_(.)/) { |e| $1.upcase }
37
+ end
38
+
39
+ def datetime_to_epoch(val)
40
+ case val
41
+ when Date
42
+ val.to_datetime.to_time.to_i
43
+ when DateTime
44
+ val.to_time.to_i
45
+ when Time
46
+ val.to_i
47
+ else
48
+ val.to_i
49
+ end
50
+ end
51
+
52
+ def convert_ids_to_strings(key, value)
53
+ key.to_s.match(/_id\Z/) && value.is_a?(Integer) ? value.to_s : value
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ module Ravelin
2
+ class Chargeback < RavelinObject
3
+ attr_accessor :chargeback_id,
4
+ :gateway,
5
+ :gateway_reference,
6
+ :reason,
7
+ :status,
8
+ :amount,
9
+ :currency,
10
+ :dispute_time,
11
+ :custom
12
+
13
+ attr_required :chargeback_id, :gateway, :gateway_reference
14
+ end
15
+ end
@@ -0,0 +1,69 @@
1
+ module Ravelin
2
+ class Client
3
+ API_BASE = 'https://api.ravelin.com'
4
+
5
+ def initialize(api_key:)
6
+ @api_key = api_key
7
+
8
+ @connection = Faraday.new(API_BASE, faraday_options) do |conn|
9
+ conn.response :json, context_type: /\bjson$/
10
+ conn.adapter Ravelin.faraday_adapter
11
+ end
12
+ end
13
+
14
+ def send_event(**args)
15
+ score = args.delete(:score)
16
+ event = Event.new(**args)
17
+
18
+ score_param = score ? "?score=true" : nil
19
+
20
+ post("/v2/#{event.name}#{score_param}", event.serializable_hash)
21
+ end
22
+
23
+ def send_backfill_event(**args)
24
+ unless args.has_key?(:timestamp)
25
+ raise ArgumentError.new('missing parameters: timestamp')
26
+ end
27
+
28
+ event = Event.new(**args)
29
+
30
+ post("/v2/backfill/#{event.name}", event.serializable_hash)
31
+ end
32
+
33
+ private
34
+
35
+ def post(url, payload)
36
+ response = @connection.post(url, payload.to_json)
37
+
38
+ if response.success?
39
+ return Response.new(response)
40
+ else
41
+ handle_error_response(response)
42
+ end
43
+ end
44
+
45
+ def handle_error_response(response)
46
+ case response.status
47
+ when 400, 403, 404, 405, 406
48
+ raise InvalidRequestError.new(response)
49
+ when 401
50
+ raise AuthenticationError.new(response)
51
+ when 429
52
+ raise RateLimitError.new(response)
53
+ else
54
+ raise ApiError.new(response)
55
+ end
56
+ end
57
+
58
+ def faraday_options
59
+ {
60
+ request: { timeout: Ravelin.faraday_timeout },
61
+ headers: {
62
+ 'Authorization' => "token #{@api_key}",
63
+ 'Content-Type' => 'application/json; charset=utf-8'.freeze,
64
+ 'User-Agent' => "Ravelin RubyGem/#{Ravelin::VERSION}".freeze
65
+ }
66
+ }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,26 @@
1
+ module Ravelin
2
+ class Customer < RavelinObject
3
+ attr_accessor :customer_id,
4
+ :registration_time,
5
+ :name,
6
+ :given_name,
7
+ :family_name,
8
+ :date_of_birth,
9
+ :gender,
10
+ :email,
11
+ :email_verified_time,
12
+ :user_name,
13
+ :telephone,
14
+ :telephone_verified_time,
15
+ :telephone_country,
16
+ :location,
17
+ :country,
18
+ :custom
19
+
20
+ attr_required :customer_id
21
+
22
+ def location=(obj)
23
+ @location = Ravelin::Location.new(obj)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ module Ravelin
2
+ class Device < RavelinObject
3
+ attr_accessor :device_id,
4
+ :type,
5
+ :manufacturer,
6
+ :model,
7
+ :os,
8
+ :ip_address,
9
+ :browser,
10
+ :javascript_enabled,
11
+ :cookies_enabled,
12
+ :screen_resolution
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module Ravelin
2
+ class ApiError < StandardError
3
+ attr_reader :response, :status, :error, :error_message, :validation_errors
4
+
5
+ def initialize(response)
6
+ @response = response
7
+ @status = response.status
8
+ @error = response.body.fetch('Error', nil)
9
+ @error_message = response.body.fetch('message', nil)
10
+ @validation_errors = response.body.fetch('validationErrors', [])
11
+ end
12
+
13
+ def to_s
14
+ parts = [self.status, self.error, self.error_message]
15
+ parts << self.validation_errors.join('; ') if self.validation_errors.any?
16
+ parts.compact.join(' - ')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ module Ravelin
2
+ class AuthenticationError < ApiError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Ravelin
2
+ class InvalidRequestError < ApiError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Ravelin
2
+ class RateLimitError < ApiError
3
+ end
4
+ end
@@ -0,0 +1,114 @@
1
+ module Ravelin
2
+ class Event
3
+ attr_accessor :name, :timestamp, :payload
4
+
5
+ def initialize(name:, payload:, timestamp: nil)
6
+ @name = convert_event_name(name)
7
+ @payload = convert_to_ravelin_objects(payload)
8
+ @timestamp = timestamp.nil? ? Time.now.to_i : convert_to_epoch(timestamp)
9
+
10
+ validate_top_level_payload_params
11
+ end
12
+
13
+ def serializable_hash
14
+ payload_hash = hash_map(self.payload) do |k, v|
15
+ k = Ravelin.camelize(k)
16
+
17
+ if v.is_a?(Ravelin::RavelinObject)
18
+ [k, v.serializable_hash]
19
+ else
20
+ [k, v]
21
+ end
22
+ end
23
+
24
+ payload_hash.merge('timestamp' => timestamp)
25
+ end
26
+
27
+ def object_classes
28
+ {
29
+ chargeback: Chargeback,
30
+ customer: Customer,
31
+ device: Device,
32
+ location: Location,
33
+ order: Order,
34
+ payment_method: PaymentMethod,
35
+ transaction: self.name == :pretransaction ? PreTransaction : Transaction
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ def validate_top_level_payload_params
42
+ validate_customer_id_presence_on :order, :paymentmethod,
43
+ :pretransaction, :transaction
44
+
45
+ case self.name
46
+ when :customer
47
+ validate_payload_inclusion_of :customer
48
+ when :pretransaction, :transaction
49
+ validate_payload_inclusion_of :order_id, :payment_method_id
50
+ when :login
51
+ validate_payload_inclusion_of :customer_id, :temp_customer_id
52
+ when :checkout
53
+ validate_payload_inclusion_of :customer, :order,
54
+ :payment_method, :transaction
55
+ end
56
+ end
57
+
58
+ def validate_customer_id_presence_on(*events)
59
+ if events.include?(self.name) && !payload_customer_reference_present?
60
+ raise ArgumentError.
61
+ new(%q{payload missing customer_id or temp_customer_id parameter})
62
+ end
63
+ end
64
+
65
+ def validate_payload_inclusion_of(*required_keys)
66
+ missing = required_keys - self.payload.keys
67
+
68
+ if missing.any?
69
+ raise ArgumentError.
70
+ new(%Q{payload missing parameters: #{missing.join(', ')}})
71
+ end
72
+ end
73
+
74
+ def payload_customer_reference_present?
75
+ self.payload.keys.any? {|k| %i(customer_id temp_customer_id).include? k }
76
+ end
77
+
78
+ def convert_to_epoch(val)
79
+ if val.is_a?(Time) || val.is_a?(Date) || val.is_a?(DateTime)
80
+ Ravelin.datetime_to_epoch(val)
81
+ elsif val.is_a?(Integer)
82
+ val
83
+ else
84
+ raise TypeError.new(%Q{timestamp requires a Time or epoch Integer})
85
+ end
86
+ end
87
+
88
+ def convert_to_ravelin_objects(payload)
89
+ hash_map(payload) do |k, v|
90
+ k = k.to_sym
91
+ v = Ravelin.convert_ids_to_strings(k, v)
92
+
93
+ if v.is_a?(Hash) && klass = object_classes[k]
94
+ [k, klass.new(v)]
95
+ else
96
+ [k, v]
97
+ end
98
+ end
99
+ end
100
+
101
+ def convert_event_name(str)
102
+ underscore_mapping = {
103
+ payment_method: :paymentmethod,
104
+ pre_transaction: :pretransaction
105
+ }
106
+
107
+ underscore_mapping.fetch(str.to_sym, str.to_sym)
108
+ end
109
+
110
+ def hash_map(hash, &block)
111
+ Hash[hash.map { |k, v| block.call(k, v) }]
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,15 @@
1
+ module Ravelin
2
+ class Item < RavelinObject
3
+ attr_accessor :sku,
4
+ :name,
5
+ :price,
6
+ :currency,
7
+ :brand,
8
+ :upc,
9
+ :category,
10
+ :quantity,
11
+ :custom
12
+
13
+ attr_required :sku, :quantity
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Ravelin
2
+ class Location < RavelinObject
3
+ attr_accessor :location_id,
4
+ :street1,
5
+ :street2,
6
+ :neighbourhood,
7
+ :zone,
8
+ :city,
9
+ :region,
10
+ :country,
11
+ :po_box_number,
12
+ :postal_code,
13
+ :latitude,
14
+ :longitude,
15
+ :geohash,
16
+ :custom
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ module Ravelin
2
+ class Order < RavelinObject
3
+ attr_accessor :order_id,
4
+ :email,
5
+ :price,
6
+ :currency,
7
+ :seller_id,
8
+ :items,
9
+ :from,
10
+ :to,
11
+ :country,
12
+ :market,
13
+ :custom,
14
+ :creation_time,
15
+ :execution_time,
16
+ :status
17
+
18
+ attr_required :order_id
19
+
20
+ def items=(arr)
21
+ raise ArgumentError.new('items= requires an Array') unless arr.is_a?(Array)
22
+
23
+ @items = arr.map { |item| Ravelin::Item.new(item) }
24
+ end
25
+
26
+ def from=(obj)
27
+ @from = Ravelin::Location.new(obj)
28
+ end
29
+
30
+ def to=(obj)
31
+ @to = Ravelin::Location.new(obj)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ module Ravelin
2
+ class PaymentMethod < RavelinObject
3
+ attr_accessor :payment_method_id,
4
+ :nick_name,
5
+ :method_type,
6
+ :banned,
7
+ :active,
8
+ :registration_time,
9
+ # Only when type = card
10
+ :instrument_id,
11
+ :name_on_card,
12
+ :card_bin,
13
+ :card_last_four,
14
+ :card_type,
15
+ :expiry_month,
16
+ :expiry_year,
17
+ :successful_registration,
18
+ :prepaid_card,
19
+ :issuer,
20
+ :country_issued,
21
+ # Only when type = paypal
22
+ :email,
23
+ :custom
24
+
25
+ attr_required :payment_method_id, :method_type
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module Ravelin
2
+ class PreTransaction < RavelinObject
3
+ attr_accessor :transaction_id,
4
+ :email,
5
+ :currency,
6
+ :debit,
7
+ :credit,
8
+ :gateway,
9
+ :custom
10
+
11
+ attr_required :transaction_id, :currency, :debit, :credit, :gateway
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ module Ravelin
2
+ class RavelinObject
3
+ class << self
4
+ attr_reader :attr_accessor_names
5
+
6
+ def required_attributes
7
+ @required_attributes ||= []
8
+ end
9
+
10
+ def attr_required(*names)
11
+ @required_attributes = *names
12
+ end
13
+
14
+ def attr_accessor(*names)
15
+ @attr_accessor_names ||= []
16
+ @attr_accessor_names.concat(names)
17
+ super
18
+ end
19
+ end
20
+
21
+ def initialize(args)
22
+ args.each do |key, value|
23
+ self.send("#{key}=", Ravelin.convert_ids_to_strings(key, value))
24
+ end
25
+
26
+ validate
27
+ end
28
+
29
+ def validate
30
+ missing = self.class.required_attributes.select do |name|
31
+ self.send(name).nil? || self.send(name) == ''
32
+ end
33
+
34
+ if missing.any?
35
+ raise ArgumentError.new(%Q{missing parameters: #{missing.join(', ')}})
36
+ end
37
+ end
38
+
39
+ def serializable_hash
40
+ self.class.attr_accessor_names.each_with_object({}) do |key, hash|
41
+ value = self.send(key)
42
+ key = Ravelin.camelize(key)
43
+
44
+ if value.is_a?(RavelinObject)
45
+ hash[key] = value.serializable_hash
46
+ elsif value.is_a?(Array)
47
+ hash[key] = value.map(&:serializable_hash)
48
+ elsif value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
49
+ hash[key] = Ravelin.datetime_to_epoch(value)
50
+ elsif !value.nil?
51
+ hash[key] = value
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ module Ravelin
2
+ class Response
3
+ attr_reader :customer_id,
4
+ :action,
5
+ :score,
6
+ :score_id,
7
+ :source,
8
+ :comment,
9
+ :http_status,
10
+ :http_body
11
+
12
+ def initialize(faraday_response)
13
+ data = faraday_response.body.fetch('data', {})
14
+
15
+ @customer_id = data['customerId']
16
+ @action = data['action']
17
+ @score = data['score']
18
+ @score_id = data['scoreId']
19
+ @source = data['source']
20
+ @comment = data['comment']
21
+ @customer_id = data['customerId']
22
+ @http_status = faraday_response.status
23
+ @http_body = faraday_response.body
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module Ravelin
2
+ class Transaction < RavelinObject
3
+ attr_accessor :transaction_id,
4
+ :email,
5
+ :currency,
6
+ :debit,
7
+ :credit,
8
+ :gateway,
9
+ :custom,
10
+ :success,
11
+ :auth_code,
12
+ :decline_code,
13
+ :gateway_reference,
14
+ :avs_result_code,
15
+ :cvv_result_code
16
+
17
+ attr_required :transaction_id,
18
+ :currency,
19
+ :debit,
20
+ :credit,
21
+ :gateway,
22
+ :gateway_reference,
23
+ :success
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Ravelin
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ravelin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sam Levy
8
+ - Tommy Palmer
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2016-06-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.9'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.9'
28
+ - !ruby/object:Gem::Dependency
29
+ name: faraday_middleware
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.10'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.10'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.11'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.11'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '10.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '10.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: webmock
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.22'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.22'
98
+ description: Ravelin is a fraud detection tool. See https://www.ravelin.com for details.
99
+ email:
100
+ - sam.levy@deliveroo.co.uk
101
+ - tommy.palmer@deliveroo.co.uk
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - lib/ravelin.rb
107
+ - lib/ravelin/chargeback.rb
108
+ - lib/ravelin/client.rb
109
+ - lib/ravelin/customer.rb
110
+ - lib/ravelin/device.rb
111
+ - lib/ravelin/errors/api_error.rb
112
+ - lib/ravelin/errors/authentication_error.rb
113
+ - lib/ravelin/errors/invalid_request_error.rb
114
+ - lib/ravelin/errors/rate_limit_error.rb
115
+ - lib/ravelin/event.rb
116
+ - lib/ravelin/item.rb
117
+ - lib/ravelin/location.rb
118
+ - lib/ravelin/order.rb
119
+ - lib/ravelin/payment_method.rb
120
+ - lib/ravelin/pre_transaction.rb
121
+ - lib/ravelin/ravelin_object.rb
122
+ - lib/ravelin/response.rb
123
+ - lib/ravelin/transaction.rb
124
+ - lib/ravelin/version.rb
125
+ homepage: https://developer.ravelin.com
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.4.5.1
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Ruby bindings for the Ravelin API
149
+ test_files: []
150
+ has_rdoc: