Skip to content

Instantly share code, notes, and snippets.

@zmalltalker
Created May 10, 2019 06:58
Show Gist options
  • Save zmalltalker/afcb4cb90ad080d980e37ad35b5e283f to your computer and use it in GitHub Desktop.
Save zmalltalker/afcb4cb90ad080d980e37ad35b5e283f to your computer and use it in GitHub Desktop.
# 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