Class: BankTools::SE::OCR

Inherits:
Object
  • Object
show all
Defined in:
lib/banktools-se/ocr.rb

Defined Under Namespace

Classes: BadChecksum, BadLengthDigit, BadPadding, InvalidOCR, MustBeNumeric, OverlongOCR, TooShortOCR

Constant Summary collapse

MIN_LENGTH =
2
MAX_LENGTH =
25

Class Method Summary collapse

Class Method Details

.find_all_in_string(string, length_digit: false, pad: "", min_length: 4, max_length: 19) ⇒ Object

max_length is 19 because that’s the longest allowed integer by default in a Postgres integer column with Ruby on Rails. So attempting some queries with longer OCRs may cause exceptions.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/banktools-se/ocr.rb', line 68

def self.find_all_in_string(string, length_digit: false, pad: "", min_length: 4, max_length: 19)
  # First, treat the input as one long string of digits.
  # E.g. "1234 and 5678" becomes "12345678".
  digit_string = string.gsub(/\D/, "")
  digit_string_length = digit_string.length

  candidates = []

  # Then find all substrings of min_length, and of all other lengths, up to max_length.
  # So e.g. find all four-digit substrings ("1234", "2345", …), all five-digit substrings and so on.

  0.upto(digit_string.length - min_length) do |start_pos|
    min_end_pos = start_pos + min_length - 1
    max_end_pos = [ start_pos + max_length, digit_string_length ].min - 1

    min_end_pos.upto(max_end_pos) do |end_pos|
      candidates << digit_string.slice(start_pos..end_pos)
    end
  end

  # Finally, limit these substrings to ones that are actually valid OCRs.

  candidates.select { |candidate|
    begin
      to_number(candidate, length_digit: length_digit, pad: pad)
      true
    rescue InvalidOCR
      false
    end
  }.uniq
end

.from_number(number, length_digit: false, pad: "") ⇒ Object

Raises:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/banktools-se/ocr.rb', line 17

def self.from_number(number, length_digit: false, pad: "")
  number = number.to_s
  add_length_digit = length_digit
  pad = pad.to_s

  raise MustBeNumeric unless number.match(/\A\d+\z/)
  # Padding isn't something BGC specifies, but we needed it to support a legacy scheme.
  number += pad
  # Adding 2: 1 length digit, 1 check digit.
  number += ((number.length + 2) % 10).to_s if add_length_digit

  number_with_ocr = number + Utils.luhn_checksum(number).to_s

  length = number_with_ocr.length
  if length > MAX_LENGTH
    raise OverlongOCR, "OCR must be #{MIN_LENGTH} - #{MAX_LENGTH} characters (this one would be #{length} characters)"
  end

  number_with_ocr
end

.to_number(ocr, length_digit: false, pad: "") ⇒ Object

Raises:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/banktools-se/ocr.rb', line 38

def self.to_number(ocr, length_digit: false, pad: "")
  ocr = ocr.to_s
  should_have_length_digit = length_digit
  strip_padding = pad.to_s

  raise MustBeNumeric unless ocr.match(/\A\d+\z/)
  raise BadChecksum unless Utils.valid_luhn?(ocr)
  raise TooShortOCR if ocr.length < MIN_LENGTH

  if should_have_length_digit
    length_digit = ocr[-2]
    last_digit_of_actual_length = ocr.length.to_s[-1]
    raise BadLengthDigit if length_digit != last_digit_of_actual_length
  end

  digits_to_chop  = 1  # Checksum.
  digits_to_chop += 1 if should_have_length_digit

  if strip_padding.length > 0
    expected_padding_end = -digits_to_chop - 1
    expected_padding_start = expected_padding_end - strip_padding.length + 1
    raise BadPadding if ocr[expected_padding_start..expected_padding_end] != strip_padding
  end

  digits_to_chop += strip_padding.length

  ocr[0...-digits_to_chop]
end