Created
May 10, 2019 06:58
-
-
Save zmalltalker/afcb4cb90ad080d980e37ad35b5e283f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: utf-8 | |
class NorwegianBankAccount | |
# Pass me any kind of string representing an account number | |
def initialize(str) | |
@numbers = str.gsub(/[^\d]/,'') | |
end | |
# Prettified output | |
def to_s | |
begin | |
parts = @numbers.split(//) | |
formatted = [parts[0,4].join(""), parts[4,2].join(""), parts[6,5].join("")].join(".") | |
formatted | |
rescue => e | |
"##{self.class.name}<#{@numbers}> (#{e})" | |
end | |
end | |
# Validation combo. Using a tuple as return value, in true Golang fashion | |
def valid? | |
if @numbers.length != 11 | |
return false, "Needs to be 11 digits" | |
end | |
if !mod_11_control(@numbers, [5,4,3,2,7,6,5,4,3,2]) | |
return false, "Invalid checksum" | |
end | |
return true, nil | |
end | |
# Validate an account number using mod 11 with multiplication factors. | |
# Includes check digit for simplicity (ie 11 digits as input) | |
def mod_11_control(account_number, factors) | |
account_parts = account_number.split(//) | |
account_digits = account_parts[0,10].map(&:to_i) | |
sum = 0 | |
account_digits.each_with_index do |n, i| | |
s = n * factors[i] | |
sum += (n * factors[i]) | |
end | |
check_digit = account_parts[10].to_i | |
remainder = sum % 11 | |
expected_check_digit = case remainder | |
when 0 then remainder | |
when 1 then nil | |
else 11- remainder | |
end | |
return check_digit == expected_check_digit | |
end | |
end | |
class PaymentCardRecognizer | |
def self.recognize_account_number(blocks) | |
candidates = [] | |
blocks.each do |b| | |
without_spaces = b.gsub(/[\s_]/,"") | |
number_re = /KONTONR\.(?<account>\d{11})/ | |
matches = number_re.match(without_spaces) | |
if matches | |
candidates << matches[:account] | |
end | |
end | |
result = candidates.first | |
account = NorwegianBankAccount.new(result) | |
if account.valid? | |
return account.to_s | |
end | |
end | |
end | |
# Unittest built in, skip for production | |
require 'minitest/autorun' | |
class PaymentCardRecognizerTest < Minitest::Test | |
def test_extract_from_actual_card | |
blocks = [ | |
"M A R I U S M Å R N E S M A T H I E S E N K O N T O N R . 1 2 0 6 4 4 4 8 2 6 7 S I S T K O N T R O L L N R . V F W N 8 3 0 F Ø D S E L S N R . 0 9 1 1 7 0 _ 4 6 3 9 4", | |
"B A N K K O R T 0 0 0 0 2", | |
"I f f o u n d , p l e a s e f o r w a r d t o : D N B , R e t u r , P b . 1 6 0 0 s e n t r u m , N O - 0 0 2 1 O s l o . R e w a r d p a y a b l e ." | |
] | |
assert_equal "1206.44.48267", PaymentCardRecognizer.recognize_account_number(blocks) | |
end | |
end | |
class NorwegianBankAccountTest < Minitest::Test | |
def test_invalid_length | |
@v = NorwegianBankAccount.new("1234.56.789") | |
valid, message = @v.valid? | |
refute valid, "#{@v} has an incorrect length" | |
end | |
def test_invalid_checksum | |
@v = NorwegianBankAccount.new("1234.56.78901") | |
valid, message = @v.valid? | |
refute valid, "#{@v} has an incorrect checksum" | |
end | |
def test_valid_account_number | |
@v = NorwegianBankAccount.new("1299.14.91285") | |
valid, message = @v.valid? | |
assert valid, "#{@v} should be a valid account number" | |
end | |
def test_crazy_input | |
@v = NorwegianBankAccount.new("Morten Bankebiff") | |
valid, message = @v.valid? | |
refute valid, "#{@v} is wrong in so many ways" | |
end | |
def test_ivans_account_number | |
acct = NorwegianBankAccount.new("60210746490") | |
valid, message = acct.valid? | |
assert valid, "Account number ending with 0 should be valid" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment