uk_account_validator_auctionet 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rubocop.yml +11 -0
  4. data/.travis.yml +14 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE.txt +23 -0
  7. data/README.md +51 -0
  8. data/Rakefile +11 -0
  9. data/data/scsubtab.txt +21 -0
  10. data/data/valacdos.txt +1074 -0
  11. data/features/allow_hyphens_in_sort_codes.feature +9 -0
  12. data/features/exceptions.feature +49 -0
  13. data/features/invalid_formats.feature +33 -0
  14. data/features/modulus10.feature +18 -0
  15. data/features/modulus11.feature +18 -0
  16. data/features/modulus_weight.feature +26 -0
  17. data/features/modulus_weights_table.feature +14 -0
  18. data/features/step_definitions/basics.rb +23 -0
  19. data/features/step_definitions/modulus_weight.rb +7 -0
  20. data/features/step_definitions/modulus_weights_table.rb +13 -0
  21. data/features/support/env.rb +2 -0
  22. data/features/two_modulus_check.feature +25 -0
  23. data/lib/uk_account_validator.rb +57 -0
  24. data/lib/uk_account_validator/backports/array_bsearch_index.rb +33 -0
  25. data/lib/uk_account_validator/exceptions/base_exception.rb +65 -0
  26. data/lib/uk_account_validator/exceptions/exception_1.rb +10 -0
  27. data/lib/uk_account_validator/exceptions/exception_10.rb +18 -0
  28. data/lib/uk_account_validator/exceptions/exception_12.rb +9 -0
  29. data/lib/uk_account_validator/exceptions/exception_14.rb +30 -0
  30. data/lib/uk_account_validator/exceptions/exception_2_9.rb +68 -0
  31. data/lib/uk_account_validator/exceptions/exception_3.rb +10 -0
  32. data/lib/uk_account_validator/exceptions/exception_4.rb +16 -0
  33. data/lib/uk_account_validator/exceptions/exception_5.rb +48 -0
  34. data/lib/uk_account_validator/exceptions/exception_6.rb +16 -0
  35. data/lib/uk_account_validator/exceptions/exception_7.rb +9 -0
  36. data/lib/uk_account_validator/exceptions/exception_8.rb +7 -0
  37. data/lib/uk_account_validator/modulus_weight.rb +35 -0
  38. data/lib/uk_account_validator/modulus_weights_table.rb +35 -0
  39. data/lib/uk_account_validator/number_indices.rb +18 -0
  40. data/lib/uk_account_validator/validator.rb +92 -0
  41. data/lib/uk_account_validator/validators/base_validator.rb +28 -0
  42. data/lib/uk_account_validator/validators/double_alternate.rb +35 -0
  43. data/lib/uk_account_validator/validators/modulus10.rb +9 -0
  44. data/lib/uk_account_validator/validators/modulus11.rb +9 -0
  45. data/lib/uk_account_validator/validators/standard_modulus.rb +30 -0
  46. data/lib/uk_account_validator/version.rb +3 -0
  47. data/uk_account_validator.gemspec +22 -0
  48. metadata +104 -0
@@ -0,0 +1,9 @@
1
+ # These are for Nationwide Flex accounts. Where there is a 12 in the exception
2
+ # column for the first check for a sorting code and a 13 in the exception column
3
+ # for the second check for the same sorting code, if either check is successful
4
+ # the account number is deemed valid.
5
+ class Exception12 < BaseException
6
+ def self.allow_any?
7
+ true
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ # Perform the modulus 11 check as normal:
2
+ # * If the check passes (that is, there is no remainder), then the account
3
+ # number should be considered valid. Do not perform the second check.
4
+ # * If the first check fails, then the second check must be performed as
5
+ # specified below.
6
+ #
7
+ # Second check:
8
+ # * If the 8th digit of the account number (reading from left to right) is not
9
+ # 0, 1 or 9 then the account number fails the second check and is not a valid
10
+ # Coutts account number
11
+ # * If the 8th digit is 0, 1 or 9, then remove the digit from the account
12
+ # number and insert a 0 as the 1st digit for check purposes only
13
+ # * Perform the modulus 11 check on the modified account number using the same
14
+ # weightings as specified in the table (that is, 0 0 0 0 0 0 8 7 6 5 4 3 2 1):
15
+ # - If there is no remainder, then the account number should be considered
16
+ # valid
17
+ # - If there is a remainder, then the account number fails the second check
18
+ # and is not a valid Coutts account number
19
+ class Exception14 < BaseException
20
+ def self.allow_any?
21
+ true
22
+ end
23
+
24
+ def apply_account_number_substitutions
25
+ return account_number unless %w(0 1 9).include?(account_number[7])
26
+
27
+ account_number.slice!(7)
28
+ return '0' + account_number
29
+ end
30
+ end
@@ -0,0 +1,68 @@
1
+ # Only occurs for some standard modulus 11 checks, when there is a 2 in the
2
+ # exception column for the first check for a sorting code and a 9 in the
3
+ # exception column for the second check for the same sorting code. This is used
4
+ # specifically for Lloyds euro accounts.
5
+ #
6
+ # Perform the standard check, except:
7
+ # * If a <> 0 and g <> 9, substitute the weight specified in the modulus weight
8
+ # table with:
9
+ # u v w x y z a b c d e f g h
10
+ # 0 0 1 2 5 3 6 4 8 7 10 9 3 1
11
+ #
12
+ # * If a <> 0 and g = 9, substitute the weight specified in the modulus weight
13
+ # table with:
14
+ # u v w x y z a b c d e f g h
15
+ # 0 0 0 0 0 0 0 0 8 7 10 9 3 1
16
+ #
17
+ # If the first row with exception 2 passes the standard modulus 11 check, you do
18
+ # not need to carry out the second check (ie it is deemed to be a valid sterling
19
+ # account).
20
+ #
21
+ # All Lloyds euro accounts are held at sorting code 30-96-34, however customers
22
+ # may perceive that their euro account is held at the branch where sterling
23
+ # accounts are held and thus quote a sorting code other than 30-96-34. The
24
+ # combination of the "sterling" sorting code and "euro" account number will
25
+ # cause the first standard modulus 11 check to fail. In such cases, carry out
26
+ # the second modulus 11 check, substituting the sorting code with 309634 and the
27
+ # appropriate weighting. If this check passes it is deemed to be a valid euro
28
+ # account.
29
+
30
+ class Exception29 < BaseException
31
+ def self.allow_any?
32
+ true
33
+ end
34
+
35
+ def apply_sort_code_substitutions
36
+ return '309634' if check_number == 2
37
+
38
+ return sort_code
39
+ end
40
+
41
+ def replace_weight(test_digits)
42
+ return modulus_weight if test_digits[NUMBER_INDEX[:a]] == 0
43
+ return substitute_modulus_weight if check_number == 2
44
+
45
+ if test_digits[NUMBER_INDEX[:g]] != 9
46
+ return UkAccountValidator::ModulusWeight.new(
47
+ modulus_weight.sort_code_start,
48
+ modulus_weight.sort_code_end,
49
+ modulus_weight.modulus,
50
+ 0, 0, 1, 2, 5, 3, 6, 4, 8, 7, 10, 9, 3, 1,
51
+ modulus_weight.exception
52
+ )
53
+ else
54
+ return UkAccountValidator::ModulusWeight.new(
55
+ modulus_weight.sort_code_start,
56
+ modulus_weight.sort_code_end,
57
+ modulus_weight.modulus,
58
+ 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 10, 9, 3, 1,
59
+ modulus_weight.exception
60
+ )
61
+ end
62
+ end
63
+
64
+ # Returns the modulus weight for 309634
65
+ def substitute_modulus_weight
66
+ UkAccountValidator.modulus_weights_table.find('309634').first
67
+ end
68
+ end
@@ -0,0 +1,10 @@
1
+ # If c=6 or c=9 the double alternate check does not need to be carried out
2
+ class Exception3 < BaseException
3
+ def replace_weight(test_digits)
4
+ c = test_digits[NUMBER_INDEX[:c]]
5
+
6
+ return zero_all if c == 6 || c == 9
7
+
8
+ return modulus_weight
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # Perform the standard modulus 11 check.
2
+ # After you have finished the check, ensure that the remainder is the same as
3
+ # the two-digit checkdigit;
4
+ # the checkdigit for exception 4 is gh from the original account number.
5
+ class Exception4 < BaseException
6
+ def override_test?
7
+ true
8
+ end
9
+
10
+ def test(modulus, total, test_digits, _test)
11
+ check_sum = [test_digits[NUMBER_INDEX[:g]], test_digits[NUMBER_INDEX[:h]]].join
12
+ check_sum = check_sum.to_i
13
+
14
+ total % modulus == check_sum
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ # Perform the first check (standard modulus check) except:
2
+ # * If the sorting code appears in SCSUBTAB.txt, substitute it for the
3
+ # "substitute with" column (for check purposes only).
4
+ # If the sorting code is not found, use the original sorting code.
5
+ #
6
+ # For the standard check with exception 5 the checkdigit is g from the original
7
+ # account number.
8
+ # * After dividing the result by 11:
9
+ # - if the remainder = 0 and g = 0 the account number is valid
10
+ # - if the remainder = 1 the account number is invalid
11
+ # - for all other remainders, take the remainder away from 11. If the number
12
+ # you get is the same as g then the account number is valid.
13
+ #
14
+ # Perform the second double alternate check, and for the double alternate check
15
+ # with exception 5 the checkdigit is h from the original account number, except:
16
+ # * After dividing the result by 10:
17
+ # - if the remainder = 0 and h = 0 the account number is valid
18
+ # - for all other remainders, take the remainder away from 10. If the number
19
+ # you get is the same as h then the account number is valid.
20
+ class Exception5 < BaseException
21
+ def apply_sort_code_substitutions
22
+ substitutions = UkAccountValidator.read_sort_code_substitution
23
+
24
+ return substitutions[sort_code] if substitutions.keys.include?(sort_code)
25
+
26
+ return sort_code
27
+ end
28
+
29
+ def override_test?
30
+ true
31
+ end
32
+
33
+ def test(modulus, total, test_digits, test)
34
+ if test == :double_alternate
35
+ check_digit = :h
36
+ else
37
+ check_digit = :g
38
+ end
39
+
40
+ check_sum = total % modulus
41
+ expected_sum = test_digits[NUMBER_INDEX[check_digit]].to_i
42
+
43
+ return false if check_sum == 1 && check_digit == :g
44
+ return true if check_sum == 0 && expected_sum == 0
45
+
46
+ return (modulus - check_sum) == expected_sum
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ # Indicates that these sorting codes may contain foreign currency accounts which
2
+ # cannot be checked.
3
+ # Perform the first and second checks, except:
4
+ # If a = 4, 5, 6, 7 or 8, and g and h are the same, the accounts are for a
5
+ # foreign currency and the checks cannot be used.
6
+ class Exception6 < BaseException
7
+ def replace_weight(test_digits)
8
+ a = test_digits[NUMBER_INDEX[:a]]
9
+ g = test_digits[NUMBER_INDEX[:g]]
10
+ h = test_digits[NUMBER_INDEX[:h]]
11
+
12
+ return zero_all if (a == 4 || a == 5 || a == 6 || a == 7 || a == 8) && g == h
13
+
14
+ return modulus_weight
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ # Perform the check as specified, except if g = 9 zeroise weighting positions
2
+ # u-b.
3
+ class Exception7 < BaseException
4
+ def replace_weight(test_digits)
5
+ return modulus_weight unless test_digits[NUMBER_INDEX[:g]] == 9
6
+
7
+ return zero_u_b
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # Perform the check as specified, except substitute the sorting code with
2
+ # 090126, for check purposes only
3
+ class Exception8 < BaseException
4
+ def apply_sort_code_substitutions
5
+ return '090126'
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ module UkAccountValidator
2
+ class ModulusWeight
3
+ attr_reader :sort_code_start, :sort_code_end, :modulus, :u, :v, :w, :x,
4
+ :y, :z, :a, :b, :c, :d, :e, :f, :g, :h, :exception
5
+
6
+ # the size of each column
7
+ COLUMN_SIZES = [6, 7, 9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3]
8
+
9
+ # @param definition_line The line from valacdos.txt that defines this weight.
10
+ def self.from_line(definition_line)
11
+ # See https://www.ruby-forum.com/topic/184294#805359
12
+ data = definition_line.unpack("A#{COLUMN_SIZES.join('A')}")
13
+
14
+ data.map!(&:strip)
15
+
16
+ @sort_code_start, @sort_code_end, @modulus, @u, @v, @w, @x,
17
+ @y, @z, @a, @b, @c, @d, @e, @f, @g, @h, @exception = data
18
+
19
+ ModulusWeight.new(*data)
20
+ end
21
+
22
+ def initialize(sort_code_start, sort_code_end, modulus, u, v, w, x, y, z,
23
+ a, b, c, d, e, f, g, h, exception)
24
+
25
+ @sort_code_start = sort_code_start
26
+ @sort_code_end = sort_code_end
27
+ @modulus = modulus
28
+ @exception = exception
29
+
30
+ @u, @v, @w, @x, @y, @z, @a, @b, @c, @d, @e, @f, @g, @h =
31
+ [u.to_i, v.to_i, w.to_i, x.to_i, y.to_i, z.to_i, a.to_i, b.to_i, c.to_i,
32
+ d.to_i, e.to_i, f.to_i, g.to_i, h.to_i]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module UkAccountValidator
2
+ class ModulusWeightsTable
3
+
4
+ def initialize(path)
5
+ @weights = []
6
+
7
+ File.readlines(path).each do |line|
8
+ @weights << ModulusWeight.from_line(line)
9
+ end
10
+
11
+ @weights.sort_by! { |weight| -weight.sort_code_start.to_i }
12
+ end
13
+
14
+ def find(sort_code)
15
+ sort_code = sort_code.to_i
16
+
17
+ min_found_weight_index = @weights.bsearch_index do |w|
18
+ w.sort_code_start.to_i <= sort_code
19
+ end
20
+
21
+ return [] if min_found_weight_index.nil?
22
+
23
+ found_weights = []
24
+ index = min_found_weight_index
25
+ while index < @weights.size &&
26
+ @weights[index].sort_code_start.to_i <= sort_code &&
27
+ sort_code <= @weights[index].sort_code_end.to_i
28
+ found_weights << @weights[index]
29
+ index += 1
30
+ end
31
+
32
+ found_weights
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ module NumberIndices
2
+ NUMBER_INDEX = {
3
+ u: 0,
4
+ v: 1,
5
+ w: 2,
6
+ x: 3,
7
+ y: 4,
8
+ z: 5,
9
+ a: 6,
10
+ b: 7,
11
+ c: 8,
12
+ d: 9,
13
+ e: 10,
14
+ f: 11,
15
+ g: 12,
16
+ h: 13
17
+ }
18
+ end
@@ -0,0 +1,92 @@
1
+ module UkAccountValidator
2
+ class Validator
3
+
4
+ attr_writer :sort_code
5
+ attr_accessor :account_number
6
+
7
+ def initialize(account_number = nil, sort_code = nil)
8
+ @account_number = account_number
9
+ @sort_code = sort_code
10
+ end
11
+
12
+ def sort_code
13
+ @sort_code.gsub('-', '')
14
+ end
15
+
16
+ def modulus_weights
17
+ @modulus_weights ||= UkAccountValidator.modulus_weights_table.find(sort_code)
18
+ end
19
+
20
+ def modulus_validator(modulus)
21
+ case modulus
22
+ when 'MOD10'
23
+ Validators::Modulus10
24
+ when 'MOD11'
25
+ Validators::Modulus11
26
+ when 'DBLAL'
27
+ Validators::DoubleAlternate
28
+ else
29
+ fail NotImplementedError
30
+ end
31
+ end
32
+
33
+ def valid?
34
+ return false unless valid_format?
35
+
36
+ exceptions = modulus_weights.map(&:exception)
37
+ exception_class = self.exception_class(exceptions)
38
+
39
+ results = modulus_weights.each_with_index.map do |modulus_weight, i|
40
+ exception = exception_class.new(modulus_weight, account_number, sort_code, i + 1)
41
+
42
+ @account_number = exception.apply_account_number_substitutions
43
+
44
+ modulus_validator(modulus_weight.modulus).new(
45
+ account_number, sort_code, modulus_weight, exception
46
+ ).valid?
47
+ end
48
+
49
+ return results.any? if exception_class.allow_any?
50
+
51
+ results.all?
52
+ end
53
+
54
+ def valid_format?
55
+ return false if account_number =~ /\D/
56
+ return false if account_number.length < 6
57
+ return false if account_number.length > 10
58
+ return false if sort_code.length != 6
59
+
60
+ return true
61
+ end
62
+
63
+ def exception_class(exception_strings)
64
+ case
65
+ when exception_strings.include?('1')
66
+ Exception1
67
+ when exception_strings.include?('2') && exception_strings.include?('9')
68
+ Exception29
69
+ when exception_strings.include?('3')
70
+ Exception3
71
+ when exception_strings.include?('4')
72
+ Exception4
73
+ when exception_strings.include?('5')
74
+ Exception5
75
+ when exception_strings.include?('6')
76
+ Exception6
77
+ when exception_strings.include?('7')
78
+ Exception7
79
+ when exception_strings.include?('8')
80
+ Exception8
81
+ when exception_strings.include?('10') && exception_strings.include?('11')
82
+ Exception10
83
+ when exception_strings.include?('12') && exception_strings.include?('13')
84
+ Exception12
85
+ when exception_strings.include?('14')
86
+ Exception14
87
+ else
88
+ BaseException
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,28 @@
1
+ module UkAccountValidator
2
+ module Validators
3
+ class BaseValidator
4
+ include NumberIndices
5
+
6
+ attr_reader :account_number, :sort_code, :modulus_weight, :exception
7
+
8
+ def initialize(account_number, sort_code, modulus_weight, exception)
9
+ @account_number = account_number
10
+ @sort_code = sort_code
11
+ @modulus_weight = modulus_weight
12
+ @exception = exception
13
+
14
+ @sort_code = exception.apply_sort_code_substitutions
15
+ end
16
+
17
+ def applying_exceptions(test_digits)
18
+ @modulus_weight = exception.replace_weight(test_digits)
19
+
20
+ total = yield
21
+
22
+ total = exception.after_calculate_total(total, test_digits)
23
+
24
+ total
25
+ end
26
+ end
27
+ end
28
+ end