minfraud 1.1.0 → 1.5.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yml +12 -0
  3. data/.github/workflows/test.yml +33 -0
  4. data/.rubocop.yml +108 -0
  5. data/CHANGELOG.md +57 -0
  6. data/Gemfile +4 -2
  7. data/README.dev.md +1 -1
  8. data/README.md +170 -55
  9. data/Rakefile +9 -3
  10. data/bin/console +4 -3
  11. data/lib/maxmind/geoip2/model/city.rb +3 -3
  12. data/lib/maxmind/geoip2/model/country.rb +5 -5
  13. data/lib/maxmind/geoip2/record/traits.rb +13 -4
  14. data/lib/minfraud.rb +44 -6
  15. data/lib/minfraud/assessments.rb +113 -53
  16. data/lib/minfraud/components/account.rb +31 -9
  17. data/lib/minfraud/components/addressable.rb +73 -26
  18. data/lib/minfraud/components/base.rb +26 -14
  19. data/lib/minfraud/components/billing.rb +5 -0
  20. data/lib/minfraud/components/credit_card.rb +64 -20
  21. data/lib/minfraud/components/custom_inputs.rb +14 -3
  22. data/lib/minfraud/components/device.rb +45 -15
  23. data/lib/minfraud/components/email.rb +120 -9
  24. data/lib/minfraud/components/event.rb +60 -24
  25. data/lib/minfraud/components/order.rb +59 -22
  26. data/lib/minfraud/components/payment.rb +44 -9
  27. data/lib/minfraud/components/report/transaction.rb +50 -39
  28. data/lib/minfraud/components/shipping.rb +14 -5
  29. data/lib/minfraud/components/shopping_cart.rb +19 -12
  30. data/lib/minfraud/components/shopping_cart_item.rb +42 -13
  31. data/lib/minfraud/enum.rb +22 -8
  32. data/lib/minfraud/error_handler.rb +32 -25
  33. data/lib/minfraud/errors.rb +22 -2
  34. data/lib/minfraud/http_service.rb +23 -8
  35. data/lib/minfraud/http_service/request.rb +19 -18
  36. data/lib/minfraud/http_service/response.rb +19 -14
  37. data/lib/minfraud/model/address.rb +4 -4
  38. data/lib/minfraud/model/credit_card.rb +7 -7
  39. data/lib/minfraud/model/device.rb +2 -2
  40. data/lib/minfraud/model/email.rb +4 -4
  41. data/lib/minfraud/model/error.rb +1 -1
  42. data/lib/minfraud/model/insights.rb +5 -5
  43. data/lib/minfraud/model/ip_address.rb +20 -1
  44. data/lib/minfraud/model/ip_risk_reason.rb +48 -0
  45. data/lib/minfraud/model/issuer.rb +3 -3
  46. data/lib/minfraud/model/score.rb +6 -6
  47. data/lib/minfraud/model/shipping_address.rb +1 -1
  48. data/lib/minfraud/model/subscores.rb +38 -16
  49. data/lib/minfraud/model/warning.rb +2 -2
  50. data/lib/minfraud/report.rb +33 -13
  51. data/lib/minfraud/resolver.rb +25 -17
  52. data/lib/minfraud/validates.rb +187 -0
  53. data/lib/minfraud/version.rb +4 -1
  54. data/minfraud.gemspec +8 -2
  55. metadata +77 -10
  56. data/.travis.yml +0 -22
@@ -1,54 +1,81 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
2
4
  module Components
5
+ # This is a parent class for the Billing and Shipping components.
3
6
  class Addressable < Base
4
- # @attribute first_name
5
- # @return [String] The first name of the end user as provided in their billing / shipping information
7
+ include Minfraud::Validates
8
+
9
+ # The first name of the end user as provided in their billing / shipping
10
+ # information.
11
+ #
12
+ # @return [String, nil]
6
13
  attr_accessor :first_name
7
14
 
8
- # @attribute last_name
9
- # @return [String] The last name of the end user as provided in their billing / shipping information
15
+ # The last name of the end user as provided in their billing / shipping
16
+ # information.
17
+ #
18
+ # @return [String, nil]
10
19
  attr_accessor :last_name
11
20
 
12
- # @attribute company
13
- # @return [String] The company of the end user as provided in their billing / shipping information
21
+ # The company of the end user as provided in their billing / shipping
22
+ # information.
23
+ #
24
+ # @return [String, nil]
14
25
  attr_accessor :company
15
26
 
16
- # @attribute address
17
- # @return [String] The first line of the user’s billing / shipping address
27
+ # The first line of the user's billing / shipping address.
28
+ #
29
+ # @return [String, nil]
18
30
  attr_accessor :address
19
31
 
20
- # @attribute address_2
21
- # @return [String] The second line of the user’s billing / shipping address
32
+ # The second line of the user's billing / shipping address.
33
+ #
34
+ # @return [String, nil]
22
35
  attr_accessor :address_2
23
36
 
24
- # @attribute city
25
- # @return [String] The city of the user's billing / shipping address
37
+ # The city of the user's billing / shipping address.
38
+ #
39
+ # @return [String, nil]
26
40
  attr_accessor :city
27
41
 
28
- # @attribute region
29
- # @return [String] The ISO 3166-2 subdivision code for the user’s billing / shipping address
42
+ # The ISO 3166-2 subdivision code for the user's billing / shipping
43
+ # address.
44
+ #
45
+ # @see https://en.wikipedia.org/wiki/ISO_3166-2
46
+ #
47
+ # @return [String, nil]
30
48
  attr_accessor :region
31
49
 
32
- # @attribute country
33
- # @return [String] The two character ISO 3166-1 alpha-2 country code of the user’s billing / shipping address
50
+ # The two character ISO 3166-1 alpha-2 country code of the user's billing
51
+ # / shipping address.
52
+ #
53
+ # @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
54
+ #
55
+ # @return [String, nil]
34
56
  attr_accessor :country
35
57
 
36
- # @attribute postal
37
- # @return [String] The postal code of the user’s billing / shipping address
58
+ # The postal code of the user's billing / shipping address.
59
+ #
60
+ # @return [String, nil]
38
61
  attr_accessor :postal
39
62
 
40
- # @attribute phone_number
41
- # @return [String] The phone number without the country code for the user’s billing / shipping address
63
+ # The phone number without the country code for the user's billing /
64
+ # shipping address. Punctuation characters will be stripped. After
65
+ # stripping punctuation characters, the number must contain only digits.
66
+ #
67
+ # @return [String, nil]
42
68
  attr_accessor :phone_number
43
69
 
44
- # @attribute phone_country_code
45
- # @return [String] The country code for phone number associated with the user’s billing / shipping address
70
+ # The country code for the phone number associated with the user's
71
+ # billing / shipping address. If you provide this information then you
72
+ # must provide at least one digit.
73
+ #
74
+ # @return [String, nil]
46
75
  attr_accessor :phone_country_code
47
76
 
48
- # Creates Minfraud::Components::Addressable instance
49
- # @note This class is used as a parent class for Billing and Shipping components
50
- # @param [Hash] params hash of parameters
51
- # @return [Minfraud::Components::Addressable] an Addressable instance
77
+ # @param params [Hash] Hash of parameters. Each key/value should
78
+ # correspond to one of the available attributes.
52
79
  def initialize(params = {})
53
80
  @first_name = params[:first_name]
54
81
  @last_name = params[:last_name]
@@ -61,6 +88,26 @@ module Minfraud
61
88
  @postal = params[:postal]
62
89
  @phone_number = params[:phone_number]
63
90
  @phone_country_code = params[:phone_country_code]
91
+
92
+ validate
93
+ end
94
+
95
+ private
96
+
97
+ def validate
98
+ return if !Minfraud.enable_validation
99
+
100
+ validate_string('first_name', 255, @first_name)
101
+ validate_string('last_name', 255, @last_name)
102
+ validate_string('company', 255, @company)
103
+ validate_string('address', 255, @address)
104
+ validate_string('address_2', 255, @address_2)
105
+ validate_string('city', 255, @city)
106
+ validate_subdivision_code('region', @region)
107
+ validate_country_code('country', @country)
108
+ validate_string('postal', 255, @postal)
109
+ validate_string('phone_number', 255, @phone_number)
110
+ validate_telephone_country_code('phone_country_code', @phone_country_code)
64
111
  end
65
112
  end
66
113
  end
@@ -1,35 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
2
4
  module Components
3
- # @note This class is used as a parent class for all other components
4
- # @note It defines a method which is used for basic JSON representation of PORO objects
5
+ # This is a parent class for all components. It defines a method which is
6
+ # used for basic JSON representation of the component objects.
5
7
  class Base
6
- # @return [Hash] a JSON representation of component attributes
7
- def to_json
8
- instance_variables.inject({}) { |mem, e| populate!(mem, e) }
8
+ # A JSON representation of component attributes.
9
+ #
10
+ # @return [Hash]
11
+ def to_json(*_args)
12
+ instance_variables.reduce({}) { |mem, e| populate!(mem, e) }
9
13
  end
10
14
 
11
15
  private
12
16
 
13
- # @note This method may modify passed hash. Non-existing instance variables are ignored
14
- # @param [Hash] hash an accumulator
15
- # @param [Symbol] v_sym an instance variable symbol
16
- # @return [Hash] a hash containing a JSON representation of instance variable name and it's value
17
+ # Create a hash containing a JSON representation of instance variable
18
+ # name and its value.
19
+ #
20
+ # @param hash [Hash] An accumulator.
21
+ #
22
+ # @param v_sym [Symbol] An instance variable symbol.
23
+ #
24
+ # @return [Hash]
17
25
  def populate!(hash, v_sym)
18
- return hash unless value = instance_variable_get(v_sym)
26
+ return hash unless (value = instance_variable_get(v_sym))
19
27
 
20
28
  key = v_sym.to_s.gsub(/@/, '')
21
29
  hash.merge!(key => represent(key, value))
22
30
  end
23
31
 
24
- # param [Symbol] key instance variable symbol
25
- # param [Object] value instance variable value
26
- # @return [Object] value representation according to the request format
32
+ # Return the value according to the request format.
33
+ #
34
+ # @param key [Symbol] An instance variable symbol.
35
+ #
36
+ # @param value [Object] An instance variable value.
37
+ #
38
+ # @return [Object]
27
39
  def represent(key, value)
28
40
  BOOLS.include?(key) ? value : value.to_s
29
41
  end
30
42
 
31
43
  # Keys that have to remain boolean
32
- BOOLS = %w(was_authorized is_gift has_gift_message)
44
+ BOOLS = %w[was_authorized is_gift has_gift_message].freeze
33
45
  end
34
46
  end
35
47
  end
@@ -1,5 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
2
4
  module Components
5
+ # Billing corresponds to the billing object of a minFraud request.
6
+ #
7
+ # @see https://dev.maxmind.com/minfraud/#Billing_(/billing)
3
8
  class Billing < Addressable; end
4
9
  end
5
10
  end
@@ -1,42 +1,69 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
2
4
  module Components
5
+ # CreditCard corresponds to the credit_card object of a minFraud request.
6
+ #
7
+ # @see https://dev.maxmind.com/minfraud/#Credit_Card_(/creditcard)
3
8
  class CreditCard < Base
4
- # @attribute issuer_id_number
5
- # # @return [String] The issuer ID number for the credit card. This is the first 6 digits of the credit card number.
6
- # It identifies the issuing bank
9
+ include Minfraud::Validates
10
+
11
+ # The issuer ID number for the credit card. This is the first 6 digits of
12
+ # the credit card number. It identifies the issuing bank.
13
+ #
14
+ # @return [String, nil]
7
15
  attr_accessor :issuer_id_number
8
16
 
9
- # @attribute last_4_digits
10
- # @return [String] The last four digits of the credit card number
17
+ # The last four digits of the credit card number.
18
+ #
19
+ # @return [String, nil]
11
20
  attr_accessor :last_4_digits
12
21
 
13
- # @attribute bank_name
14
- # @return [String] The name of the issuing bank as provided by the end user
22
+ # The name of the issuing bank as provided by the end user.
23
+ #
24
+ # @return [String, nil]
15
25
  attr_accessor :bank_name
16
26
 
17
- # @attribute bank_phone_country_code
18
- # @return [String] The phone country code for the issuing bank as provided by the end user
27
+ # The phone country code for the issuing bank as provided by the end
28
+ # user. If you provide this information then you must provide at least
29
+ # one digit.
30
+ #
31
+ # @return [String, nil]
19
32
  attr_accessor :bank_phone_country_code
20
33
 
21
- # @attribute bank_phone_number
22
- # @return [String] The phone number, without the country code, for the issuing bank as provided by the end user
34
+ # The phone number, without the country code, for the issuing bank as
35
+ # provided by the end user. Punctuation characters will be stripped.
36
+ # After stripping punctuation characters, the number must contain only
37
+ # digits.
38
+ #
39
+ # @return [String, nil]
23
40
  attr_accessor :bank_phone_number
24
41
 
25
- #@attribute token
26
- #@return [String] A token uniquely identifying the card. The token should consist of non-space printable ASCII characters.
42
+ # A token uniquely identifying the card. The token should consist of
43
+ # non-space printable ASCII characters. If the token is all digits, it
44
+ # must be more than 19 characters long. The token must not be a primary
45
+ # account number (PAN) or a simple transformation of it. If you have a
46
+ # valid token that looks like a PAN but is not one, you may prefix that
47
+ # token with a fixed string, e.g., +token-+.
48
+ #
49
+ # @return [String, nil]
27
50
  attr_accessor :token
28
51
 
29
- # @attribute avs_result
30
- # @return [String] The address verification system (AVS) check result, as returned to you by the credit card processor
52
+ # The address verification system (AVS) check result, as returned to you
53
+ # by the credit card processor. The minFraud service supports the
54
+ # standard AVS codes.
55
+ #
56
+ # @return [String, nil]
31
57
  attr_accessor :avs_result
32
58
 
33
- # @attribute cvv_result
34
- # @return [String] The card verification value (CVV) code as provided by the payment processor
59
+ # The card verification value (CVV) code as provided by the payment
60
+ # processor.
61
+ #
62
+ # @return [String, nil]
35
63
  attr_accessor :cvv_result
36
64
 
37
- # Creates Minfraud::Components::CreditCard instance
38
- # @param [Hash] params hash of parameters
39
- # @return [Minfraud::Components::CreditCard] a CreditCard instance
65
+ # @param params [Hash] Hash of parameters. Each key/value should
66
+ # correspond to one of the available attributes.
40
67
  def initialize(params = {})
41
68
  @bank_phone_country_code = params[:bank_phone_country_code]
42
69
  @issuer_id_number = params[:issuer_id_number]
@@ -46,6 +73,23 @@ module Minfraud
46
73
  @avs_result = params[:avs_result]
47
74
  @cvv_result = params[:cvv_result]
48
75
  @token = params[:token]
76
+
77
+ validate
78
+ end
79
+
80
+ private
81
+
82
+ def validate
83
+ return if !Minfraud.enable_validation
84
+
85
+ validate_telephone_country_code('bank_phone_country_code', @bank_phone_country_code)
86
+ validate_regex('issuer_id_number', /\A[0-9]{6}\z/, @issuer_id_number)
87
+ validate_regex('last_4_digits', /\A[0-9]{4}\z/, @last_4_digits)
88
+ validate_string('bank_name', 255, @bank_name)
89
+ validate_string('bank_phone_number', 255, @bank_phone_number)
90
+ validate_string('avs_result', 1, @avs_result)
91
+ validate_string('cvv_result', 1, @cvv_result)
92
+ validate_credit_card_token('token', @token)
49
93
  end
50
94
  end
51
95
  end
@@ -1,12 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
2
4
  module Components
5
+ # CustomInputs corresponds to the custom_inputs object of a minFraud
6
+ # request.
7
+ #
8
+ # @see https://dev.maxmind.com/minfraud/#Custom_Inputs_(/custominputs)
3
9
  class CustomInputs < Base
4
- # Creates Minfraud::Components::CustomInputs instance
5
- # @param [Hash] params hash with keys that match your created custom input keys
6
- # @return [Minfraud::Components::CustomInputs] a CustomInputs instance
10
+ include Minfraud::Validates
11
+
12
+ # @param params [Hash] Each key/value should correspond to your defined
13
+ # custom inputs.
7
14
  def initialize(params = {})
8
15
  params.each do |name, value|
9
16
  instance_variable_set("@#{name}", value)
17
+
18
+ if Minfraud.enable_validation
19
+ validate_custom_input_value(name, value)
20
+ end
10
21
  end
11
22
  end
12
23
  end
@@ -1,37 +1,67 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minfraud
2
4
  module Components
5
+ # Device corresponds to the device object of a minFraud request.
6
+ #
7
+ # @see https://dev.maxmind.com/minfraud/#Device_(/device)
3
8
  class Device < Base
4
- # @!attribute ip_address
5
- # @return [String] The IP address associated with the device used by the customer in the transaction.
6
- # The IP address must be in IPv4 or IPv6 presentation format
9
+ include Minfraud::Validates
10
+
11
+ # The IP address associated with the device used by the customer in the
12
+ # transaction. The IP address must be in IPv4 or IPv6 presentation
13
+ # format, i.e., dotted-quad notation or the IPv6 hexadecimal-colon
14
+ # notation.
15
+ #
16
+ # @return [String, nil]
7
17
  attr_accessor :ip_address
8
18
 
9
- # @attribute user_agent
10
- # @return [String] The HTTP "User-Agent" header of the browser used in the transaction
19
+ # The HTTP "User-Agent" header of the browser used in the transaction.
20
+ #
21
+ # @return [String, nil]
11
22
  attr_accessor :user_agent
12
23
 
13
- # @attribute :accept_language
14
- # @return [String] The HTTP "Accept-Language" header of the browser used in the transaction
24
+ # The HTTP "Accept-Language" header of the browser used in the
25
+ # transaction.
26
+ #
27
+ # @return [String, nil]
15
28
  attr_accessor :accept_language
16
29
 
17
- # @attribute :session_age
18
- # @return [Decimal] The number of seconds between the creation of the user’s session and the time of the transaction.
19
- # Note that session_age is not the duration of the current visit, but the time since the start of the first visit.
30
+ # The number of seconds between the creation of the user's session and
31
+ # the time of the transaction. Note that session_age is not the duration
32
+ # of the current visit, but the time since the start of the first visit.
33
+ # The value must be at least 0 and at most 10^13-1.
34
+ #
35
+ # @return [Float, nil]
20
36
  attr_accessor :session_age
21
37
 
22
- # @attribute :session_id
23
- # @return [String] An ID that uniquely identifies a visitor’s session on the site.
38
+ # An ID that uniquely identifies a visitor's session on the site.
39
+ #
40
+ # @return [String, nil]
24
41
  attr_accessor :session_id
25
42
 
26
- # Creates Minfraud::Components::Device instance
27
- # @param [Hash] params hash of parameters
28
- # @return [Minfraud::Components::Device] a Device instance
43
+ # @param params [Hash] Hash of parameters. Each key/value should
44
+ # correspond to one of the available attributes.
29
45
  def initialize(params = {})
30
46
  @ip_address = params[:ip_address]
31
47
  @user_agent = params[:user_agent]
32
48
  @accept_language = params[:accept_language]
33
49
  @session_age = params[:session_age]
34
50
  @session_id = params[:session_id]
51
+
52
+ validate
53
+ end
54
+
55
+ private
56
+
57
+ def validate
58
+ return if !Minfraud.enable_validation
59
+
60
+ validate_ip('ip_address', @ip_address)
61
+ validate_string('user_agent', 512, @user_agent)
62
+ validate_string('accept_language', 255, @accept_language)
63
+ validate_nonnegative_number('session_age', @session_age)
64
+ validate_string('session_id', 255, @session_id)
35
65
  end
36
66
  end
37
67
  end
@@ -1,20 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+ require 'simpleidn'
5
+
1
6
  module Minfraud
2
7
  module Components
8
+ # Email corresponds to the email object of a minFraud request.
9
+ #
10
+ # @see https://dev.maxmind.com/minfraud/#Email_(/email)
3
11
  class Email < Base
4
- # @attribute address
5
- # @return [String] This field must be either a valid email address or an MD5 of the email used in the transaction
12
+ include Minfraud::Validates
13
+
14
+ # This field must be either be a valid email address or an MD5 of the
15
+ # lowercased email used in the transaction. Important: if using the MD5
16
+ # hash, please be sure to convert the email address to lowercase before
17
+ # calculating its MD5 hash. Instead of converting an address to an MD5
18
+ # hash yourself, please use the hash_address attribute in this class.
19
+ #
20
+ # @return [String, nil]
6
21
  attr_accessor :address
7
22
 
8
- # @attribute domain
9
- # @return [String] The domain of the email address used in the transaction
23
+ # The domain of the email address used in the transaction.
24
+ #
25
+ # @return [String, nil]
10
26
  attr_accessor :domain
11
27
 
12
- # Creates Minfraud::Components::Email instance
13
- # @param [Hash] params hash of parameters
14
- # @return [Minfraud::Components::Email] an Email instance
28
+ # By default, the address will be sent in plain text. If this is set
29
+ # true, the address will instead be sent as an MD5 hash.
30
+ #
31
+ # @return [Boolean, nil]
32
+ attr_accessor :hash_address
33
+
34
+ # @param params [Hash] Hash of parameters. Each key/value should
35
+ # correspond to one of the available attributes.
15
36
  def initialize(params = {})
16
- @address = params[:address]
17
- @domain = params[:domain]
37
+ @address = params[:address]
38
+ @domain = params[:domain]
39
+ @hash_address = params[:hash_address]
40
+
41
+ validate
42
+ end
43
+
44
+ # A JSON representation of Minfraud::Components::Email.
45
+ #
46
+ # @return [Hash]
47
+ def to_json(*_args)
48
+ json = super
49
+
50
+ if json['address'] && !json['domain']
51
+ _, domain = address.split('@', 2)
52
+ if domain
53
+ domain = clean_domain(domain)
54
+ json['domain'] = domain if domain
55
+ end
56
+ end
57
+
58
+ if json.delete('hash_address') && json['address']
59
+ hash = hash_email_address(json['address'])
60
+
61
+ # We could consider clearing the key if !hash.
62
+ json['address'] = hash if hash
63
+ end
64
+
65
+ json
66
+ end
67
+
68
+ private
69
+
70
+ def validate
71
+ return if !Minfraud.enable_validation
72
+
73
+ validate_email('email', @address)
74
+ validate_string('domain', 255, @domain)
75
+ end
76
+
77
+ def hash_email_address(address)
78
+ address = clean_email_address(address)
79
+ return nil if !address
80
+
81
+ Digest::MD5.hexdigest(address)
82
+ end
83
+
84
+ def clean_email_address(address)
85
+ address = address.strip
86
+ address.downcase!
87
+
88
+ local_part, domain = address.split('@', 2)
89
+ return nil if !local_part || !domain
90
+
91
+ domain = clean_domain(domain)
92
+
93
+ if domain == 'yahoo.com'
94
+ local_part.sub!(/\A([^-]+)-.*\z/, '\1')
95
+ else
96
+ local_part.sub!(/\A([^+]+)\+.*\z/, '\1')
97
+ end
98
+
99
+ "#{local_part}@#{domain}"
100
+ end
101
+
102
+ TYPO_DOMAINS = {
103
+ # gmail.com
104
+ '35gmai.com' => 'gmail.com',
105
+ '636gmail.com' => 'gmail.com',
106
+ 'gamil.com' => 'gmail.com',
107
+ 'gmail.comu' => 'gmail.com',
108
+ 'gmial.com' => 'gmail.com',
109
+ 'gmil.com' => 'gmail.com',
110
+ 'yahoogmail.com' => 'gmail.com',
111
+ # outlook.com
112
+ 'putlook.com' => 'outlook.com',
113
+ }.freeze
114
+ private_constant :TYPO_DOMAINS
115
+
116
+ def clean_domain(domain)
117
+ domain = domain.strip
118
+
119
+ # We could use delete_suffix!, but that is in Ruby 2.5+ only.
120
+ domain.sub!(/\.\z/, '')
121
+
122
+ domain = SimpleIDN.to_ascii(domain)
123
+
124
+ if TYPO_DOMAINS.key?(domain)
125
+ domain = TYPO_DOMAINS[domain]
126
+ end
127
+
128
+ domain
18
129
  end
19
130
  end
20
131
  end