ravelin 0.1.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.
@@ -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: