Class: SecID::IBAN

Inherits:
Base
  • Object
show all
Includes:
Checkable
Defined in:
lib/sec_id/iban.rb,
lib/sec_id/iban/country_rules.rb

Overview

rubocop:disable Metrics/ClassLength

Constant Summary collapse

FULL_NAME =

Human-readable name of the standard.

'International Bank Account Number'
ID_LENGTH =

Valid length(s) of a normalized identifier.

(15..34)
EXAMPLE =

A representative valid identifier.

'GB29NWBK60161331926819'
VALID_CHARS_REGEX =

Pattern matching the identifier's permitted character set.

/\A[A-Z0-9]+\z/
ID_REGEX =

Regular expression for parsing IBAN components. Note: Check digit positioning is handled in initialize, not in the regex.

/\A
  (?<country_code>[A-Z]{2})
  (?<rest>[A-Z0-9]{13,32})
\z/x
NUMERIC_COUNTRY_RULES =

Country rules whose BBAN format accepts an all-digit value, used for generation. Restricts generation to numeric BBANs so output validates without interpreting the format regex.

COUNTRY_RULES.select { |_cc, rule| rule[:format].match?('0' * rule[:length]) }.to_a.freeze
COUNTRY_RULES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Country-specific BBAN rules for EU/EEA countries Each entry defines:

- :length => total BBAN length
- :format => regex pattern for BBAN structure validation
- :components => hash mapping component names to [start, length] positions

Sources:

{
  # Austria - 16 chars: 5-digit bank code + 11-digit account
  'AT' => {
    length: 16,
    format: /\A\d{16}\z/,
    components: { bank_code: [0, 5], account_number: [5, 11] }
  },
  # Belgium - 12 chars: 3-digit bank code + 7-digit account + 2-digit national check
  'BE' => {
    length: 12,
    format: /\A\d{12}\z/,
    components: { bank_code: [0, 3], account_number: [3, 7], national_check: [10, 2] }
  },
  # Bulgaria - 18 chars: 4-letter bank code + 4-digit branch + 2-digit account type + 8-digit account
  'BG' => {
    length: 18,
    format: /\A[A-Z]{4}\d{14}\z/,
    components: { bank_code: [0, 4], branch_code: [4, 4], account_number: [10, 8] }
  },
  # Croatia - 17 chars: 7-digit bank code + 10-digit account
  'HR' => {
    length: 17,
    format: /\A\d{17}\z/,
    components: { bank_code: [0, 7], account_number: [7, 10] }
  },
  # Cyprus - 24 chars: 3-digit bank code + 5-digit branch + 16-char account
  'CY' => {
    length: 24,
    format: /\A\d{8}[A-Z0-9]{16}\z/,
    components: { bank_code: [0, 3], branch_code: [3, 5], account_number: [8, 16] }
  },
  # Czech Republic - 20 chars: 4-digit bank code + 16-digit account
  'CZ' => {
    length: 20,
    format: /\A\d{20}\z/,
    components: { bank_code: [0, 4], account_number: [4, 16] }
  },
  # Denmark - 14 chars: 4-digit bank code + 10-digit account
  'DK' => {
    length: 14,
    format: /\A\d{14}\z/,
    components: { bank_code: [0, 4], account_number: [4, 10] }
  },
  # Estonia - 16 chars: 2-digit bank code + 14-digit account
  'EE' => {
    length: 16,
    format: /\A\d{16}\z/,
    components: { bank_code: [0, 2], account_number: [2, 14] }
  },
  # Finland - 14 chars: 3-digit bank code + 11-digit account
  'FI' => {
    length: 14,
    format: /\A\d{14}\z/,
    components: { bank_code: [0, 3], account_number: [3, 11] }
  },
  # France - 23 chars: 5-digit bank + 5-digit branch + 11-char account + 2-digit national check
  'FR' => {
    length: 23,
    format: /\A\d{10}[A-Z0-9]{11}\d{2}\z/,
    components: { bank_code: [0, 5], branch_code: [5, 5], account_number: [10, 11], national_check: [21, 2] }
  },
  # Germany - 18 chars: 8-digit bank code (Bankleitzahl) + 10-digit account
  'DE' => {
    length: 18,
    format: /\A\d{18}\z/,
    components: { bank_code: [0, 8], account_number: [8, 10] }
  },
  # Greece - 23 chars: 3-digit bank code + 4-digit branch + 16-digit account
  'GR' => {
    length: 23,
    format: /\A\d{23}\z/,
    components: { bank_code: [0, 3], branch_code: [3, 4], account_number: [7, 16] }
  },
  # Hungary - 24 chars: 3-digit bank code + 4-digit branch + 16-digit account + 1-digit national check
  'HU' => {
    length: 24,
    format: /\A\d{24}\z/,
    components: { bank_code: [0, 3], branch_code: [3, 4], account_number: [7, 16], national_check: [23, 1] }
  },
  # Iceland - 22 chars: 4-digit bank code + 2-digit branch + 6-digit account + 10-digit holder ID
  'IS' => {
    length: 22,
    format: /\A\d{22}\z/,
    components: { bank_code: [0, 4], branch_code: [4, 2], account_number: [6, 6] }
  },
  # Ireland - 18 chars: 4-letter bank code + 6-digit branch + 8-digit account
  'IE' => {
    length: 18,
    format: /\A[A-Z]{4}\d{14}\z/,
    components: { bank_code: [0, 4], branch_code: [4, 6], account_number: [10, 8] }
  },
  # Italy - 23 chars: 1-letter check + 5-digit bank code + 5-digit branch + 12-char account
  'IT' => {
    length: 23,
    format: /\A[A-Z]\d{10}[A-Z0-9]{12}\z/,
    components: { national_check: [0, 1], bank_code: [1, 5], branch_code: [6, 5], account_number: [11, 12] }
  },
  # Latvia - 17 chars: 4-letter bank code + 13-digit account
  'LV' => {
    length: 17,
    format: /\A[A-Z]{4}[A-Z0-9]{13}\z/,
    components: { bank_code: [0, 4], account_number: [4, 13] }
  },
  # Liechtenstein - 17 chars: 5-digit bank code + 12-char account
  'LI' => {
    length: 17,
    format: /\A\d{5}[A-Z0-9]{12}\z/,
    components: { bank_code: [0, 5], account_number: [5, 12] }
  },
  # Lithuania - 16 chars: 5-digit bank code + 11-digit account
  'LT' => {
    length: 16,
    format: /\A\d{16}\z/,
    components: { bank_code: [0, 5], account_number: [5, 11] }
  },
  # Luxembourg - 16 chars: 3-digit bank code + 13-char account
  'LU' => {
    length: 16,
    format: /\A\d{3}[A-Z0-9]{13}\z/,
    components: { bank_code: [0, 3], account_number: [3, 13] }
  },
  # Malta - 27 chars: 4-letter bank code + 5-digit branch + 18-char account
  'MT' => {
    length: 27,
    format: /\A[A-Z]{4}\d{5}[A-Z0-9]{18}\z/,
    components: { bank_code: [0, 4], branch_code: [4, 5], account_number: [9, 18] }
  },
  # Monaco - 23 chars: same format as France
  'MC' => {
    length: 23,
    format: /\A\d{10}[A-Z0-9]{11}\d{2}\z/,
    components: { bank_code: [0, 5], branch_code: [5, 5], account_number: [10, 11], national_check: [21, 2] }
  },
  # Netherlands - 14 chars: 4-letter bank code + 10-digit account
  'NL' => {
    length: 14,
    format: /\A[A-Z]{4}\d{10}\z/,
    components: { bank_code: [0, 4], account_number: [4, 10] }
  },
  # Norway - 11 chars: 4-digit bank code + 6-digit account + 1-digit national check
  'NO' => {
    length: 11,
    format: /\A\d{11}\z/,
    components: { bank_code: [0, 4], account_number: [4, 6], national_check: [10, 1] }
  },
  # Poland - 24 chars: 3-digit bank code + 4-digit branch + 1-digit check + 16-digit account
  'PL' => {
    length: 24,
    format: /\A\d{24}\z/,
    components: { bank_code: [0, 3], branch_code: [3, 4], national_check: [7, 1], account_number: [8, 16] }
  },
  # Portugal - 21 chars: 4-digit bank code + 4-digit branch + 11-digit account + 2-digit national check
  'PT' => {
    length: 21,
    format: /\A\d{21}\z/,
    components: { bank_code: [0, 4], branch_code: [4, 4], account_number: [8, 11], national_check: [19, 2] }
  },
  # Romania - 20 chars: 4-letter bank code + 16-char account
  'RO' => {
    length: 20,
    format: /\A[A-Z]{4}[A-Z0-9]{16}\z/,
    components: { bank_code: [0, 4], account_number: [4, 16] }
  },
  # San Marino - 23 chars: same format as Italy
  'SM' => {
    length: 23,
    format: /\A[A-Z]\d{10}[A-Z0-9]{12}\z/,
    components: { national_check: [0, 1], bank_code: [1, 5], branch_code: [6, 5], account_number: [11, 12] }
  },
  # Slovakia - 20 chars: 4-digit bank code + 16-digit account
  'SK' => {
    length: 20,
    format: /\A\d{20}\z/,
    components: { bank_code: [0, 4], account_number: [4, 16] }
  },
  # Slovenia - 15 chars: 5-digit bank code + 8-digit account + 2-digit national check
  'SI' => {
    length: 15,
    format: /\A\d{15}\z/,
    components: { bank_code: [0, 5], account_number: [5, 8], national_check: [13, 2] }
  },
  # Spain - 20 chars: 4-digit bank code + 4-digit branch + 2-digit national check + 10-digit account
  'ES' => {
    length: 20,
    format: /\A\d{20}\z/,
    components: { bank_code: [0, 4], branch_code: [4, 4], national_check: [8, 2], account_number: [10, 10] }
  },
  # Sweden - 20 chars: 3-digit bank code + 17-digit account
  'SE' => {
    length: 20,
    format: /\A\d{20}\z/,
    components: { bank_code: [0, 3], account_number: [3, 17] }
  },
  # Switzerland - 17 chars: 5-digit bank code + 12-char account
  'CH' => {
    length: 17,
    format: /\A\d{5}[A-Z0-9]{12}\z/,
    components: { bank_code: [0, 5], account_number: [5, 12] }
  },
  # United Kingdom - 18 chars: 4-letter bank code + 6-digit branch (sort code) + 8-digit account
  'GB' => {
    length: 18,
    format: /\A[A-Z]{4}\d{14}\z/,
    components: { bank_code: [0, 4], branch_code: [4, 6], account_number: [10, 8] }
  }
}.freeze
LENGTH_ONLY_COUNTRIES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Countries where only length validation is performed (non-EU/EEA countries) Format: country_code => expected BBAN length

{
  'AD' => 20, # Andorra
  'AE' => 19, # UAE
  'AL' => 24, # Albania
  'AZ' => 24, # Azerbaijan
  'BA' => 16, # Bosnia and Herzegovina
  'BY' => 24, # Belarus
  'DO' => 24, # Dominican Republic
  'EG' => 25, # Egypt
  'GE' => 18, # Georgia
  'GI' => 19, # Gibraltar
  'GT' => 24, # Guatemala
  'IL' => 19, # Israel
  'IQ' => 19, # Iraq
  'JO' => 26, # Jordan
  'KW' => 26, # Kuwait
  'KZ' => 16, # Kazakhstan
  'LB' => 24, # Lebanon
  'LC' => 28, # Saint Lucia
  'MD' => 20, # Moldova
  'ME' => 18, # Montenegro
  'MK' => 15, # North Macedonia
  'MR' => 23, # Mauritania
  'MU' => 26, # Mauritius
  'PS' => 25, # Palestine
  'QA' => 25, # Qatar
  'RS' => 18, # Serbia
  'SA' => 20, # Saudi Arabia
  'SC' => 27, # Seychelles
  'ST' => 21, # Sao Tome and Principe
  'SV' => 24, # El Salvador
  'TL' => 19, # Timor-Leste
  'TN' => 20, # Tunisia
  'TR' => 22, # Turkey
  'UA' => 25, # Ukraine
  'VA' => 18, # Vatican City
  'VG' => 20, # British Virgin Islands
  'XK' => 16  # Kosovo
}.freeze

Constants included from Checkable

Checkable::CHAR_TO_DIGIT, Checkable::CHAR_TO_DIGITS

Constants included from Generatable

Generatable::ALPHA, Generatable::ALPHANUMERIC, Generatable::DIGITS

Constants included from Validatable

Validatable::ERROR_MAP

Constants included from Normalizable

Normalizable::SEPARATORS

Instance Attribute Summary collapse

Attributes inherited from Base

#full_id, #identifier

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Checkable

#restore!, #valid?

Methods inherited from Base

#==, #as_json, example, full_name, has_check_digit?, #hash, id_length, length_specificity, length_values, short_name, #to_h

Methods included from Validatable

#errors, #valid?, #validate, #validate!

Methods included from Normalizable

#normalize!, #normalized, #to_str

Constructor Details

#initialize(iban) ⇒ IBAN

Returns a new instance of IBAN.

Parameters:

  • iban (String)

    the IBAN string to parse



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/sec_id/iban.rb', line 69

def initialize(iban)
  iban_parts = parse(iban)
  @country_code = iban_parts[:country_code]
  rest = iban_parts[:rest]

  if @country_code && rest
    extract_check_digit_and_bban(rest)
    @identifier = "#{@country_code}#{@bban}" if @bban
  end

  extract_bban_components if valid_format?
end

Instance Attribute Details

#account_numberString? (readonly)

Returns the account number (extracted from BBAN if country rules define it).

Returns:

  • (String, nil)

    the account number (extracted from BBAN if country rules define it)



63
64
65
# File 'lib/sec_id/iban.rb', line 63

def 
  @account_number
end

#bank_codeString? (readonly)

Returns the bank code (extracted from BBAN if country rules define it).

Returns:

  • (String, nil)

    the bank code (extracted from BBAN if country rules define it)



57
58
59
# File 'lib/sec_id/iban.rb', line 57

def bank_code
  @bank_code
end

#bbanString? (readonly)

Returns the Basic Bank Account Number (country-specific format).

Returns:

  • (String, nil)

    the Basic Bank Account Number (country-specific format)



54
55
56
# File 'lib/sec_id/iban.rb', line 54

def bban
  @bban
end

#branch_codeString? (readonly)

Returns the branch code (extracted from BBAN if country rules define it).

Returns:

  • (String, nil)

    the branch code (extracted from BBAN if country rules define it)



60
61
62
# File 'lib/sec_id/iban.rb', line 60

def branch_code
  @branch_code
end

#country_codeString? (readonly)

Returns the ISO 3166-1 alpha-2 country code.

Returns:

  • (String, nil)

    the ISO 3166-1 alpha-2 country code



51
52
53
# File 'lib/sec_id/iban.rb', line 51

def country_code
  @country_code
end

#national_checkString? (readonly)

Returns the national check digit (extracted from BBAN if country rules define it).

Returns:

  • (String, nil)

    the national check digit (extracted from BBAN if country rules define it)



66
67
68
# File 'lib/sec_id/iban.rb', line 66

def national_check
  @national_check
end

Class Method Details

.supported_countriesArray<String>

Returns sorted array of all supported country codes.

Returns:

  • (Array<String>)


46
47
48
# File 'lib/sec_id/iban.rb', line 46

def self.supported_countries
  @supported_countries ||= (COUNTRY_RULES.keys + LENGTH_ONLY_COUNTRIES.keys).sort.freeze
end

Instance Method Details

#calculate_check_digitInteger

Returns the calculated 2-digit check value (1-98).

Returns:

  • (Integer)

    the calculated 2-digit check value (1-98)

Raises:



91
92
93
94
# File 'lib/sec_id/iban.rb', line 91

def calculate_check_digit
  validate_format_for_calculation!
  mod97(numeric_string_for_check)
end

#country_ruleHash?

Returns the validation rule or nil if country is unknown.

Returns:

  • (Hash, nil)

    the validation rule or nil if country is unknown



118
119
120
# File 'lib/sec_id/iban.rb', line 118

def country_rule
  COUNTRY_RULES[country_code]
end

#known_country?Boolean

Returns:

  • (Boolean)


123
124
125
# File 'lib/sec_id/iban.rb', line 123

def known_country?
  COUNTRY_RULES.key?(country_code) || LENGTH_ONLY_COUNTRIES.key?(country_code)
end

#restoreString

Returns:

  • (String)

Raises:



84
85
86
87
# File 'lib/sec_id/iban.rb', line 84

def restore
  cd = calculate_check_digit
  "#{country_code}#{cd.to_s.rjust(2, '0')}#{bban}"
end

#to_pretty_sString?

Returns:

  • (String, nil)


135
136
137
# File 'lib/sec_id/iban.rb', line 135

def to_pretty_s
  to_s.scan(/.{1,4}/).join(' ') if valid?
end

#to_sString

Returns:

  • (String)


128
129
130
131
132
# File 'lib/sec_id/iban.rb', line 128

def to_s
  return full_id unless check_digit

  "#{country_code}#{check_digit.to_s.rjust(2, '0')}#{bban}"
end

#valid_bban_format?Boolean

Returns:

  • (Boolean)


108
109
110
111
112
113
114
115
# File 'lib/sec_id/iban.rb', line 108

def valid_bban_format?
  return false unless bban

  rule = country_rule
  return valid_bban_length_only? unless rule

  bban.length == rule[:length] && bban.match?(rule[:format])
end