uk_account_validator_auctionet 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +14 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +23 -0
- data/README.md +51 -0
- data/Rakefile +11 -0
- data/data/scsubtab.txt +21 -0
- data/data/valacdos.txt +1074 -0
- data/features/allow_hyphens_in_sort_codes.feature +9 -0
- data/features/exceptions.feature +49 -0
- data/features/invalid_formats.feature +33 -0
- data/features/modulus10.feature +18 -0
- data/features/modulus11.feature +18 -0
- data/features/modulus_weight.feature +26 -0
- data/features/modulus_weights_table.feature +14 -0
- data/features/step_definitions/basics.rb +23 -0
- data/features/step_definitions/modulus_weight.rb +7 -0
- data/features/step_definitions/modulus_weights_table.rb +13 -0
- data/features/support/env.rb +2 -0
- data/features/two_modulus_check.feature +25 -0
- data/lib/uk_account_validator.rb +57 -0
- data/lib/uk_account_validator/backports/array_bsearch_index.rb +33 -0
- data/lib/uk_account_validator/exceptions/base_exception.rb +65 -0
- data/lib/uk_account_validator/exceptions/exception_1.rb +10 -0
- data/lib/uk_account_validator/exceptions/exception_10.rb +18 -0
- data/lib/uk_account_validator/exceptions/exception_12.rb +9 -0
- data/lib/uk_account_validator/exceptions/exception_14.rb +30 -0
- data/lib/uk_account_validator/exceptions/exception_2_9.rb +68 -0
- data/lib/uk_account_validator/exceptions/exception_3.rb +10 -0
- data/lib/uk_account_validator/exceptions/exception_4.rb +16 -0
- data/lib/uk_account_validator/exceptions/exception_5.rb +48 -0
- data/lib/uk_account_validator/exceptions/exception_6.rb +16 -0
- data/lib/uk_account_validator/exceptions/exception_7.rb +9 -0
- data/lib/uk_account_validator/exceptions/exception_8.rb +7 -0
- data/lib/uk_account_validator/modulus_weight.rb +35 -0
- data/lib/uk_account_validator/modulus_weights_table.rb +35 -0
- data/lib/uk_account_validator/number_indices.rb +18 -0
- data/lib/uk_account_validator/validator.rb +92 -0
- data/lib/uk_account_validator/validators/base_validator.rb +28 -0
- data/lib/uk_account_validator/validators/double_alternate.rb +35 -0
- data/lib/uk_account_validator/validators/modulus10.rb +9 -0
- data/lib/uk_account_validator/validators/modulus11.rb +9 -0
- data/lib/uk_account_validator/validators/standard_modulus.rb +30 -0
- data/lib/uk_account_validator/version.rb +3 -0
- data/uk_account_validator.gemspec +22 -0
- 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,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,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,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
|