minfraud 1.0.4 → 2.4.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.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +11 -0
  3. data/.github/workflows/release.yml +28 -0
  4. data/.github/workflows/rubocop.yml +18 -0
  5. data/.github/workflows/test.yml +36 -0
  6. data/.gitignore +2 -0
  7. data/.rubocop.yml +90 -0
  8. data/CHANGELOG.md +197 -4
  9. data/CODE_OF_CONDUCT.md +4 -4
  10. data/Gemfile +2 -2
  11. data/LICENSE.txt +2 -1
  12. data/README.dev.md +4 -0
  13. data/README.md +255 -58
  14. data/Rakefile +9 -3
  15. data/bin/console +4 -3
  16. data/dev-bin/release.sh +59 -0
  17. data/lib/minfraud/assessments.rb +127 -53
  18. data/lib/minfraud/components/account.rb +31 -9
  19. data/lib/minfraud/components/addressable.rb +73 -26
  20. data/lib/minfraud/components/base.rb +26 -14
  21. data/lib/minfraud/components/billing.rb +5 -0
  22. data/lib/minfraud/components/credit_card.rb +117 -29
  23. data/lib/minfraud/components/custom_inputs.rb +25 -0
  24. data/lib/minfraud/components/device.rb +51 -10
  25. data/lib/minfraud/components/email.rb +120 -9
  26. data/lib/minfraud/components/event.rb +60 -13
  27. data/lib/minfraud/components/order.rb +59 -22
  28. data/lib/minfraud/components/payment.rb +192 -22
  29. data/lib/minfraud/components/report/transaction.rb +80 -0
  30. data/lib/minfraud/components/shipping.rb +15 -6
  31. data/lib/minfraud/components/shopping_cart.rb +19 -12
  32. data/lib/minfraud/components/shopping_cart_item.rb +42 -13
  33. data/lib/minfraud/enum.rb +22 -8
  34. data/lib/minfraud/error_handler.rb +45 -18
  35. data/lib/minfraud/errors.rb +22 -2
  36. data/lib/minfraud/http_service/response.rb +61 -17
  37. data/lib/minfraud/model/abstract.rb +20 -0
  38. data/lib/minfraud/model/address.rb +52 -0
  39. data/lib/minfraud/model/billing_address.rb +11 -0
  40. data/lib/minfraud/model/credit_card.rb +75 -0
  41. data/lib/minfraud/model/device.rb +51 -0
  42. data/lib/minfraud/model/disposition.rb +42 -0
  43. data/lib/minfraud/model/email.rb +54 -0
  44. data/lib/minfraud/model/email_domain.rb +24 -0
  45. data/lib/minfraud/model/error.rb +28 -0
  46. data/lib/minfraud/model/factors.rb +24 -0
  47. data/lib/minfraud/model/geoip2_location.rb +25 -0
  48. data/lib/minfraud/model/insights.rb +68 -0
  49. data/lib/minfraud/model/ip_address.rb +58 -0
  50. data/lib/minfraud/model/ip_risk_reason.rb +48 -0
  51. data/lib/minfraud/model/issuer.rb +49 -0
  52. data/lib/minfraud/model/score.rb +76 -0
  53. data/lib/minfraud/model/score_ip_address.rb +23 -0
  54. data/lib/minfraud/model/shipping_address.rb +30 -0
  55. data/lib/minfraud/model/subscores.rb +156 -0
  56. data/lib/minfraud/model/warning.rb +63 -0
  57. data/lib/minfraud/report.rb +66 -0
  58. data/lib/minfraud/resolver.rb +25 -16
  59. data/lib/minfraud/validates.rb +187 -0
  60. data/lib/minfraud/version.rb +4 -1
  61. data/lib/minfraud.rb +55 -16
  62. data/minfraud.gemspec +27 -18
  63. metadata +107 -36
  64. data/.travis.yml +0 -5
  65. data/lib/minfraud/http_service/request.rb +0 -37
  66. data/lib/minfraud/http_service.rb +0 -31
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minfraud/model/abstract'
4
+
5
+ module Minfraud
6
+ module Model
7
+ # Warning about the minFraud request.
8
+ #
9
+ # Although more codes may be added in the future, the current warning codes
10
+ # are:
11
+ #
12
+ # * BILLING_CITY_NOT_FOUND - the billing city could not be found in our
13
+ # database.
14
+ # * BILLING_COUNTRY_MISSING - billing address information was provided
15
+ # without providing a billing country.
16
+ # * BILLING_COUNTRY_NOT_FOUND - the billing country could not be found in
17
+ # our database.
18
+ # * BILLING_POSTAL_NOT_FOUND - the billing postal could not be found in our
19
+ # database.
20
+ # * INPUT_INVALID - the value associated with the key does not meet the
21
+ # required constraints, e.g., "United States" in a field that requires a
22
+ # two-letter country code.
23
+ # * INPUT_UNKNOWN - an unknown key was encountered in the request body.
24
+ # * IP_ADDRESS_NOT_FOUND - the IP address could not be geolocated.
25
+ # * SHIPPING_COUNTRY_MISSING - shipping address information was provided
26
+ # without providing a shipping country.
27
+ # * SHIPPING_CITY_NOT_FOUND - the shipping city could not be found in our
28
+ # database.
29
+ # * SHIPPING_COUNTRY_NOT_FOUND - the shipping country could not be found in
30
+ # our database.
31
+ # * SHIPPING_POSTAL_NOT_FOUND - the shipping postal could not be found in
32
+ # our database.
33
+ class Warning < Abstract
34
+ # This value is a machine-readable code identifying the warning.
35
+ #
36
+ # @return [String]
37
+ attr_reader :code
38
+
39
+ # This property provides a human-readable explanation of the warning. The
40
+ # description may change at any time and should not be matched against.
41
+ #
42
+ # @return [String]
43
+ attr_reader :warning
44
+
45
+ # A JSON Pointer to the input field that the warning is associated with.
46
+ # For instance, if the warning was about the billing city, this would be
47
+ # '/billing/city'. If it was for the price in the second shopping cart
48
+ # item, it would be '/shopping_cart/1/price'.
49
+ #
50
+ # @return [String, nil]
51
+ attr_reader :input_pointer
52
+
53
+ # @!visibility private
54
+ def initialize(record)
55
+ super(record)
56
+
57
+ @code = get('code')
58
+ @warning = get('warning')
59
+ @input_pointer = get('input_pointer')
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minfraud
4
+ # Report is used to perform minFraud Report Transaction API requests.
5
+ #
6
+ # @see https://dev.maxmind.com/minfraud/report-a-transaction?lang=en
7
+ class Report
8
+ # The Report::Transaction component.
9
+ #
10
+ # @return [Minfraud::Components::Report::Transaction, nil]
11
+ attr_accessor :transaction
12
+
13
+ # @param params [Hash] Hash of parameters. The only supported key is
14
+ # +:transaction+, which should have a
15
+ # +Minfraud::Components::Report::Transaction+ as its value.
16
+ def initialize(params = {})
17
+ @transaction = params[:transaction]
18
+ end
19
+
20
+ # Perform a request to the minFraud Report Transaction API.
21
+ #
22
+ # @return [nil]
23
+ #
24
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
25
+ #
26
+ # @raise [Minfraud::AuthorizationError] If there was an authentication
27
+ # problem.
28
+ #
29
+ # @raise [Minfraud::ClientError] If there was a critical problem with one
30
+ # of your inputs.
31
+ #
32
+ # @raise [Minfraud::ServerError] If the server reported an error of some
33
+ # kind.
34
+ def report_transaction
35
+ response = nil
36
+ body = nil
37
+ Minfraud.connection_pool.with do |client|
38
+ response = client.post(
39
+ '/minfraud/v2.0/transactions/report',
40
+ json: @transaction.to_json,
41
+ )
42
+
43
+ body = response.to_s
44
+ end
45
+
46
+ endpoint = nil
47
+ locales = nil
48
+ response = ::Minfraud::HTTPService::Response.new(
49
+ endpoint,
50
+ locales,
51
+ response,
52
+ body,
53
+ )
54
+
55
+ ::Minfraud::ErrorHandler.examine(response)
56
+
57
+ nil
58
+ end
59
+
60
+ private
61
+
62
+ def request
63
+ @request ||= Request.new(::Minfraud::HTTPService.configuration)
64
+ end
65
+ end
66
+ end
@@ -1,10 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
4
+ # Resolver provides functionality for setting component attributes.
2
5
  module Resolver
3
6
  class << self
4
- # @param [Object] context an object for variable assignment
5
- # @param [Hash] params a hash of parameters
6
- # @return [Array] a list of supplied params
7
- # @note Raises RequestFormatError once unpermitted key is met
7
+ # Set keys on the context based on the provided parameters.
8
+ #
9
+ # @param context [Object] An object for variable assignment.
10
+ #
11
+ # @param params [Hash] A hash of parameters.
12
+ #
13
+ # @return [Array]
14
+ #
15
+ # @raise [Minfraud::RequestFormatError] If an unexpected key is found.
8
16
  def assign(context, params)
9
17
  Array(params).each do |key, value|
10
18
  raise RequestFormatError, "#{key} does not belong to request document format" unless MAPPING[key]
@@ -15,18 +23,19 @@ module Minfraud
15
23
  end
16
24
  end
17
25
 
18
- # Mapping between components & minFraud request keys
26
+ # @!visibility private
19
27
  MAPPING = {
20
- account: ::Minfraud::Components::Account,
21
- billing: ::Minfraud::Components::Billing,
22
- credit_card: ::Minfraud::Components::CreditCard,
23
- device: ::Minfraud::Components::Device,
24
- email: ::Minfraud::Components::Email,
25
- event: ::Minfraud::Components::Event,
26
- order: ::Minfraud::Components::Order,
27
- payment: ::Minfraud::Components::Payment,
28
- shipping: ::Minfraud::Components::Shipping,
29
- shopping_cart: ::Minfraud::Components::ShoppingCart
30
- }
28
+ account: ::Minfraud::Components::Account,
29
+ billing: ::Minfraud::Components::Billing,
30
+ credit_card: ::Minfraud::Components::CreditCard,
31
+ custom_inputs: ::Minfraud::Components::CustomInputs,
32
+ device: ::Minfraud::Components::Device,
33
+ email: ::Minfraud::Components::Email,
34
+ event: ::Minfraud::Components::Event,
35
+ order: ::Minfraud::Components::Order,
36
+ payment: ::Minfraud::Components::Payment,
37
+ shipping: ::Minfraud::Components::Shipping,
38
+ shopping_cart: ::Minfraud::Components::ShoppingCart,
39
+ }.freeze
31
40
  end
32
41
  end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'ipaddr'
5
+ require 'uri'
6
+
7
+ # rubocop:disable Metrics/ModuleLength
8
+ module Minfraud
9
+ # @!visibility private
10
+ module Validates
11
+ def validate_string(field, length, value)
12
+ return if !value
13
+
14
+ if value.to_s.length > length
15
+ raise InvalidInputError, "The #{field} value is not valid. The maximum length is #{length}."
16
+ end
17
+ end
18
+
19
+ def validate_md5(field, value)
20
+ return if !value
21
+
22
+ if !value.to_s.match(/\A[a-fA-F0-9]{32}\z/)
23
+ raise InvalidInputError, "The #{field} value is not valid. It must be an MD5 hash as a string."
24
+ end
25
+ end
26
+
27
+ def validate_subdivision_code(field, value)
28
+ return if !value
29
+
30
+ if !value.to_s.match(/\A[0-9A-Z]{1,4}\z/)
31
+ raise InvalidInputError, "The #{field} value is not valid. It must be an ISO 3166-2 subdivision code."
32
+ end
33
+ end
34
+
35
+ def validate_country_code(field, value)
36
+ return if !value
37
+
38
+ if !value.to_s.match(/\A[A-Z]{2}\z/)
39
+ raise InvalidInputError, "The #{field} value is not valid. It must be an ISO 3166-1 alpha-2 country code."
40
+ end
41
+ end
42
+
43
+ def validate_telephone_country_code(field, value)
44
+ return if !value
45
+
46
+ if !value.to_s.match(/\A[0-9]{1,4}\z/)
47
+ raise InvalidInputError, "The #{field} value is not valid. It must be at most 4 digits."
48
+ end
49
+ end
50
+
51
+ def validate_regex(field, regex, value)
52
+ return if !value
53
+
54
+ if !regex.match(value.to_s)
55
+ raise InvalidInputError, "The #{field} value is not valid. It must match the pattern #{regex}."
56
+ end
57
+ end
58
+
59
+ def validate_credit_card_token(field, value)
60
+ return if !value
61
+
62
+ s = value.to_s
63
+
64
+ if !/\A[\x21-\x7E]{1,255}\z/.match(s)
65
+ raise InvalidInputError, "The #{field} value is not valid. It must contain only non-space printable ASCII characters."
66
+ end
67
+
68
+ if /\A[0-9]{1,19}\z/.match(s)
69
+ raise InvalidInputError, "The #{field} value is not valid. If it is all digits, it must be longer than 19 characters."
70
+ end
71
+ end
72
+
73
+ def validate_custom_input_value(field, value)
74
+ return if !value
75
+
76
+ if [true, false].include?(value)
77
+ return
78
+ end
79
+
80
+ if value.is_a? Numeric
81
+ if value < -1e13 + 1 || value > 1e13 - 1
82
+ raise InvalidInputError, "The #{field} value is not valid. Numeric values must be between -1e13 and 1e13."
83
+ end
84
+
85
+ return
86
+ end
87
+
88
+ validate_string(field, 255, value)
89
+ end
90
+
91
+ def validate_ip(field, value)
92
+ return if !value
93
+
94
+ s = value.to_s
95
+ if s.include? '/'
96
+ raise InvalidInputError, "The #{field} value is not valid. It must be an individual IP address."
97
+ end
98
+
99
+ # rubocop:disable Style/RescueStandardError
100
+ begin
101
+ IPAddr.new(s)
102
+ rescue
103
+ raise InvalidInputError, "The #{field} value is not valid. It must be an IPv4 or IPv6 address."
104
+ end
105
+ # rubocop:enable Style/RescueStandardError
106
+
107
+ nil
108
+ end
109
+
110
+ def validate_nonnegative_number(field, value)
111
+ return if !value
112
+
113
+ if !value.is_a? Numeric
114
+ raise InvalidInputError, "The #{field} value is not valid. It must be numeric."
115
+ end
116
+
117
+ if value.negative? || value > 1e13 - 1
118
+ raise InvalidInputError, "The #{field} value is not valid. It must be at least 0 and at most 1e13 - 1."
119
+ end
120
+ end
121
+
122
+ def validate_nonnegative_integer(field, value)
123
+ return if !value
124
+
125
+ if !value.is_a? Integer
126
+ raise InvalidInputError, "The #{field} is not valid. It must be an integer."
127
+ end
128
+
129
+ if value.negative? || value > 1e13 - 1
130
+ raise InvalidInputError, "The #{field} is not valid. It must be at least 0 and at most 1e13 - 1."
131
+ end
132
+ end
133
+
134
+ def validate_email(field, value)
135
+ return if !value
136
+
137
+ if /.@./.match(value)
138
+ validate_string(field, 255, value)
139
+ return
140
+ end
141
+
142
+ validate_md5(field, value)
143
+ end
144
+
145
+ def validate_rfc3339(field, value)
146
+ return if !value
147
+
148
+ # rubocop:disable Style/RescueStandardError
149
+ begin
150
+ DateTime.rfc3339(value)
151
+ rescue
152
+ raise InvalidInputError, "The #{field} value is not valid. It must be in the RFC 3339 date-time format."
153
+ end
154
+ # rubocop:enable Style/RescueStandardError
155
+
156
+ nil
157
+ end
158
+
159
+ def validate_boolean(field, value)
160
+ return if !value
161
+
162
+ if ![false, true].include? value
163
+ raise InvalidInputError, "The #{field} value is not valid. It must be boolean."
164
+ end
165
+ end
166
+
167
+ def validate_uri(field, value)
168
+ return if !value
169
+
170
+ if value.to_s.length > 1_024
171
+ raise InvalidInputError, "The #{field} value is not valid. It must not exceed 1024 characters."
172
+ end
173
+
174
+ # rubocop:disable Style/RescueStandardError
175
+ begin
176
+ u = URI(value)
177
+ if !u.scheme
178
+ raise InvalidInputError
179
+ end
180
+ rescue
181
+ raise InvalidInputError, "The #{field} value is not valid. It must be an absolute URI."
182
+ end
183
+ # rubocop:enable Style/RescueStandardError
184
+ end
185
+ end
186
+ end
187
+ # rubocop:enable Metrics/ModuleLength
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
2
- VERSION = '1.0.4'
4
+ # The Gem version.
5
+ VERSION = '2.4.0'
3
6
  end
data/lib/minfraud.rb CHANGED
@@ -1,49 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'connection_pool'
4
+ require 'http'
1
5
  require 'minfraud'
2
6
  require 'minfraud/enum'
7
+ require 'minfraud/validates'
3
8
  require 'minfraud/components/base'
4
9
  require 'minfraud/components/account'
5
10
  require 'minfraud/components/addressable'
6
11
  require 'minfraud/components/billing'
7
12
  require 'minfraud/components/credit_card'
13
+ require 'minfraud/components/custom_inputs'
8
14
  require 'minfraud/components/device'
9
15
  require 'minfraud/components/email'
10
16
  require 'minfraud/components/event'
11
17
  require 'minfraud/components/order'
12
18
  require 'minfraud/components/payment'
13
- require 'minfraud/components/shopping_cart_item'
19
+ require 'minfraud/components/report/transaction'
14
20
  require 'minfraud/components/shipping'
15
21
  require 'minfraud/components/shopping_cart'
16
- require 'minfraud/components/device'
22
+ require 'minfraud/components/shopping_cart_item'
17
23
  require 'minfraud/resolver'
18
24
  require 'minfraud/version'
19
25
  require 'minfraud/errors'
20
- require 'minfraud/http_service'
21
- require 'minfraud/http_service/request'
22
26
  require 'minfraud/http_service/response'
23
27
  require 'minfraud/error_handler'
24
28
  require 'minfraud/assessments'
29
+ require 'minfraud/report'
25
30
 
31
+ # This class holds global configuration parameters and provides a namespace
32
+ # for the gem's classes.
26
33
  module Minfraud
27
34
  class << self
28
- # @!attribute user_id
29
- # @return [String] MaxMind username that is used for authorization
30
- attr_accessor :user_id
35
+ # The MaxMind account ID that is used for authorization.
36
+ #
37
+ # @return [Integer, nil]
38
+ attr_accessor :account_id
39
+
40
+ # Enable client side validation. This is disabled by default.
41
+ #
42
+ # @return [Boolean, nil]
43
+ attr_accessor :enable_validation
31
44
 
32
- # @!attribute license_key
33
- # @return [String] MaxMind license key that is used for authorization
45
+ # The host to use when connecting to the web service.
46
+ # By default, the client connects to the production host. However,
47
+ # during testing and development, you can set this option to
48
+ # 'sandbox.maxmind.com' to use the Sandbox environment's host. The
49
+ # sandbox allows you to experiment with the API without affecting your
50
+ # production data.
51
+ #
52
+ # @return [String, nil]
53
+ attr_accessor :host
54
+
55
+ # The MaxMind license key that is used for authorization.
56
+ #
57
+ # @return [String, nil]
34
58
  attr_accessor :license_key
35
59
 
36
- # @yield [self] to accept configuration settings
60
+ # @!visibility private
61
+ attr_reader :connection_pool
62
+
63
+ # Yield self to accept configuration settings.
64
+ #
65
+ # @yield [self]
37
66
  def configure
38
67
  yield self
68
+
69
+ pool_size = 5
70
+ host = @host.nil? ? 'minfraud.maxmind.com' : @host
71
+ @connection_pool = ConnectionPool.new(size: pool_size) do
72
+ make_http_client.persistent("https://#{host}")
73
+ end
39
74
  end
40
75
 
41
- # @return [Hash] current Minfraud configuration
42
- def configuration
43
- {
44
- user_id: @user_id,
45
- license_key: @license_key
46
- }
76
+ private
77
+
78
+ def make_http_client
79
+ HTTP.basic_auth(
80
+ user: @account_id,
81
+ pass: @license_key,
82
+ ).headers(
83
+ accept: 'application/json',
84
+ user_agent: "minfraud-api-ruby/#{Minfraud::VERSION} ruby/#{RUBY_VERSION} http/#{HTTP::VERSION}",
85
+ )
47
86
  end
48
87
  end
49
88
  end
data/minfraud.gemspec CHANGED
@@ -1,29 +1,38 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
4
6
  require 'minfraud/version'
5
7
 
6
8
  Gem::Specification.new do |spec|
7
- spec.name = "minfraud"
9
+ spec.name = 'minfraud'
8
10
  spec.version = Minfraud::VERSION
9
- spec.authors = ["kushnir.yb"]
10
- spec.email = ["kushnir.yb@gmail.com"]
11
+ spec.authors = ['kushnir.yb', 'William Storey']
12
+ spec.email = ['support@maxmind.com']
11
13
 
12
- spec.summary = %q{Ruby interface to the MaxMind minFraud v2.0 API services}
13
- spec.homepage = "https://github.com/kushniryb/minfraud-api-v2"
14
- spec.license = "MIT"
14
+ spec.summary = 'Ruby API for the minFraud Score, Insights, Factors, and Report Transactions services'
15
+ spec.homepage = 'https://github.com/maxmind/minfraud-api-ruby'
16
+ spec.license = 'MIT'
15
17
 
16
- spec.required_ruby_version = '>= 1.9'
18
+ spec.required_ruby_version = '>= 2.7.0'
17
19
 
18
20
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
- spec.bindir = "exe"
21
+ spec.bindir = 'exe'
20
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
- spec.require_paths = ["lib"]
22
-
23
- spec.add_runtime_dependency 'faraday', '~> 0.9', '>= 0.9.1'
24
- spec.add_runtime_dependency 'faraday_middleware', '~> 0.9', '>= 0.9.1'
25
- spec.add_runtime_dependency 'hashie', '~> 3.0'
26
- spec.add_development_dependency "bundler", "~> 1.12"
27
- spec.add_development_dependency "rake", "~> 10.0"
28
- spec.add_development_dependency "rspec", "~> 3.0"
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_runtime_dependency 'connection_pool', '~> 2.2'
26
+ spec.add_runtime_dependency 'http', '>= 4.3', '< 6.0'
27
+ spec.add_runtime_dependency 'maxmind-geoip2', '~> 1.2'
28
+ spec.add_runtime_dependency 'simpleidn', '~> 0.1', '>= 0.1.1'
29
+
30
+ spec.add_development_dependency 'bundler', '~> 2.2'
31
+ spec.add_development_dependency 'rake', '~> 13.0'
32
+ spec.add_development_dependency 'rspec', '~> 3.0'
33
+ spec.add_development_dependency 'rubocop', '~> 1.23'
34
+ spec.add_development_dependency 'webmock', '~> 3.14'
35
+ spec.metadata = {
36
+ 'rubygems_mfa_required' => 'true'
37
+ }
29
38
  end