Class: SecID::IBAN
- 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] positionsSources:
{ # 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
Constants included from Normalizable
Instance Attribute Summary collapse
-
#account_number ⇒ String?
readonly
The account number (extracted from BBAN if country rules define it).
-
#bank_code ⇒ String?
readonly
The bank code (extracted from BBAN if country rules define it).
-
#bban ⇒ String?
readonly
The Basic Bank Account Number (country-specific format).
-
#branch_code ⇒ String?
readonly
The branch code (extracted from BBAN if country rules define it).
-
#country_code ⇒ String?
readonly
The ISO 3166-1 alpha-2 country code.
-
#national_check ⇒ String?
readonly
The national check digit (extracted from BBAN if country rules define it).
Attributes inherited from Base
Class Method Summary collapse
-
.supported_countries ⇒ Array<String>
Returns sorted array of all supported country codes.
Instance Method Summary collapse
-
#calculate_check_digit ⇒ Integer
The calculated 2-digit check value (1-98).
-
#country_rule ⇒ Hash?
The validation rule or nil if country is unknown.
-
#initialize(iban) ⇒ IBAN
constructor
A new instance of IBAN.
- #known_country? ⇒ Boolean
- #restore ⇒ String
- #to_pretty_s ⇒ String?
- #to_s ⇒ String
- #valid_bban_format? ⇒ Boolean
Methods included from Checkable
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.
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_number ⇒ String? (readonly)
Returns 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 @account_number end |
#bank_code ⇒ String? (readonly)
Returns 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 |
#bban ⇒ String? (readonly)
Returns the Basic Bank Account Number (country-specific format).
54 55 56 |
# File 'lib/sec_id/iban.rb', line 54 def bban @bban end |
#branch_code ⇒ String? (readonly)
Returns 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_code ⇒ String? (readonly)
Returns 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_check ⇒ String? (readonly)
Returns 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_countries ⇒ Array<String>
Returns sorted array of all supported country codes.
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_digit ⇒ Integer
Returns the calculated 2-digit check value (1-98).
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_rule ⇒ Hash?
Returns 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
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 |
#restore ⇒ String
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_s ⇒ String?
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_s ⇒ 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
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 |