Class: ISO::IBAN

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/iso/iban/no_autoload.rb,
lib/iso/iban/invalid.rb,
lib/iso/iban/version.rb,
lib/iso/iban/specification.rb

Overview

IBAN - ISO 13616-1

Usage

require 'iso/iban'
ISO::IBAN.valid?('CH35 1234 5987 6543 2109 A')       # => true
ISO::IBAN.validate('CH37 1234 5987 6543 2109 A')     # => [:invalid_checksum]
ISO::IBAN.generate('CH', '12345', '987')             # => #<ISO::IBAN CH76 1234 5000 0000 0098 7>
iban = ISO::IBAN.parse('CH35 1234 5987 6543 2109 A') # => #<ISO::IBAN CH35 1234 5987 6543 2109 A>
iban = ISO::IBAN.new('CH351234598765432109A')        # => #<ISO::IBAN CH35 1234 5987 6543 2109 A>
iban.formatted       # => "CH35 1234 5987 6543 2109 A"
iban.compact         # => "CH351234598765432109A"
iban.country         # => "CH"
iban.checksum_digits # => "35"
iban.bank_code       # => "12345"
iban.    # => "98765432109A"
iban.valid?          # => true
iban.validate        # => []

General IBAN Information

  • What is an IBAN?
    IBAN stands for International Bank Account Number. It is the ISO 13616 international standard for numbering bank accounts. In 2006, the International Organization for Standardization (ISO) designated SWIFT as the Registration Authority for ISO 13616.

  • Use
    The IBAN facilitates the communication and processing of cross-border transactions. It allows exchanging account identification details in a machine-readable form.

The ISO 13616 IBAN Standard

  • Structure
    The IBAN structure is defined in ISO 13616-1 and consists of a two-letter ISO 3166-1 country code, followed by two check digits and up to thirty alphanumeric characters for a BBAN (Basic Bank Account Number) which has a fixed length per country and, included within it, a bank identifier with a fixed position and a fixed length per country. The check digits are calculated based on the scheme defined in ISO/IEC 7064 (MOD97-10).

  • Terms and definitions

    • Bank identifier: The identifier that uniquely identifies the financial institution and, when appropriate, the branch of that financial institution servicing an account.
    • BBAN: basic bank account number: The identifier that uniquely identifies an individual account, at a specific financial institution, in a particular country. The BBAN includes a bank identifier of the financial institution servicing that account.
    • IBAN: international bank account number: The expanded version of the basic bank account number (BBAN), intended for use internationally. The IBAN uniquely identifies an individual account, at a specific financial institution, in a particular country.
  • Submitters
    Nationally-agreed, ISO13616-compliant IBAN formats are submitted to the registration authority exclusively by the National Standards Body or the National Central Bank of the country.

Defined Under Namespace

Classes: Invalid, Specification

Constant Summary collapse

Version =
Note:

require 'iso/iban' loads the version.

The version of the sorting gem.

Gem::Version.new("0.1.3")
CharacterCodes =

Character code translation used to convert an IBAN into its numeric (digits-only) form

Hash[('0'..'9').zip('0'..'9')+('a'..'z').zip(10..35)+('A'..'Z').zip(10..35)]
UpperAlpha =

All uppercase letters

[*'A'..'Z']
LowerAlpha =

All lowercase letters

[*'a'..'z']
Digits =

All digits

[*'0'..'9']
AlphaNumeric =

All uppercase letters, lowercase letters and digits

[*'A'..'Z', *'a'..'z', *'0'..'9']

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(iban) ⇒ IBAN

Returns a new instance of IBAN.

Raises:

  • (ArgumentError)

280
281
282
283
284
285
286
# File 'lib/iso/iban/no_autoload.rb', line 280

def initialize(iban)
  raise ArgumentError, "String expected for iban, but got #{iban.class}" unless iban.is_a?(String)

  @compact       = iban.b.upcase
  @country       = @compact[0,2]
  @specification = self.class.specification(@country, nil)
end

Instance Attribute Details

#compactString (readonly)


270
271
272
# File 'lib/iso/iban/no_autoload.rb', line 270

def compact
  @compact
end

#countryString (readonly)


273
274
275
# File 'lib/iso/iban/no_autoload.rb', line 273

def country
  @country
end

#specificationISO::IBAN::Specification (readonly)


276
277
278
# File 'lib/iso/iban/no_autoload.rb', line 276

def specification
  @specification
end

Class Method Details

.generate(country, *components) ⇒ Object

Note:

generate will pad all segments with zeros, which means it will generate invalid IBANs if you provide too short segments which are alphabetic only.
For example, ISO::IBAN.generate('BG', 'A', '2', 'C') generates an invalid IBAN.

Generate an IBAN from country code and components, automatically filling in the checksum.

Examples:

Generate an IBAN for UBS Switzerland with account number '12345'

ISO::IBAN.generate('CH', '216', '12345') # => #<ISO::IBAN CH92 0021 6000 0000 1234 5>

218
219
220
221
222
223
224
225
# File 'lib/iso/iban/no_autoload.rb', line 218

def self.generate(country, *components)
  spec      = specification(country)
  justified = spec.component_lengths.zip(components).map { |length, component| component.rjust(length, "0") }
  iban      = new(country+'??'+justified.join(''))
  iban.update_checksum!

  iban
end

.load_specifications(spec_file = nil) ⇒ self

Note:

Using require 'iso/iban' will automatically invoke this method. If you do not wish this behavior, require 'iso/iban/no_autoload' instead.

Load the IBAN specifications file, which determines how the IBAN for any given country looks.

It will use the following sources in this order (first one which exists wins)

  • Path passed as spec_file parameter
  • Path provided by the env variable IBAN_SPECIFICATIONS
  • The file ../data/iso-iban/specs.yaml relative to the lib dir
  • The Gem datadir path

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/iso/iban/no_autoload.rb', line 115

def self.load_specifications(spec_file=nil)
  if spec_file then
    # do nothing
  elsif ENV['IBAN_SPECIFICATIONS'] then
    spec_file = ENV['IBAN_SPECIFICATIONS']
  else
    spec_file = File.expand_path('../../../../data/iso-iban/specs.yaml', __FILE__)
    if !File.file?(spec_file) && defined?(Gem) && Gem.datadir('iso-iban')
      spec_file = Gem.datadir('iso-iban')+'/specs.yaml'
    end
  end

  if spec_file && File.file?(spec_file)
    @specifications = ISO::IBAN::Specification.load_yaml(spec_file)
  elsif spec_file
    raise "Could not load IBAN specifications, specs file #{spec_file.inspect} does not exist or can't be read."
  else
    raise "Could not load IBAN specifications, no specs file found."
  end

  self
end

.numerify(string) ⇒ String

Converts a String into its digits-only form, i.e. all characters a-z are replaced with their corresponding digit sequences, according to the IBAN specification.


261
262
263
264
265
266
267
# File 'lib/iso/iban/no_autoload.rb', line 261

def self.numerify(string)
  string.downcase.gsub(/\D/) { |char|
    CharacterCodes.fetch(char) {
      raise ArgumentError, "The string contains an invalid character #{char.inspect}"
    }
  }.to_i
end

.parse(iban_number) ⇒ ISO::IBAN


201
202
203
# File 'lib/iso/iban/no_autoload.rb', line 201

def self.parse(iban_number)
  new(strip(iban_number || ""))
end

.parse!(iban_number) ⇒ ISO::IBAN

Like ISO::IBAN.parse, but raises an ISO::IBAN::Invalid exception if the IBAN is invalid.

Raises:


189
190
191
192
193
194
# File 'lib/iso/iban/no_autoload.rb', line 189

def self.parse!(iban_number)
  iban   = parse(iban_number)
  raise Invalid.new(iban) unless iban.valid?

  iban
end

.random(*countries) ⇒ ISO::IBAN


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/iso/iban/no_autoload.rb', line 231

def self.random(*countries)
  countries = specifications.keys if countries.empty?
  country   = countries.sample
     = specification(country).iban_structure.scan(/([A-Z]+)|(\d+)(!?)([nac])/).map { |exact, length, fixed, code|
    if exact
      exact
    elsif code == 'a'
      Array.new(length.to_i) { UpperAlpha.sample }.join('')
    elsif code == 'c'
      Array.new(length.to_i) { AlphaNumeric.sample }.join('')
    elsif code == 'e'
      ' '*length.to_i
    elsif code == 'n'
      Array.new(length.to_i) { Digits.sample }.join('')
    end
  }.join('')
  [2,2] = '??'
  iban = new()
  iban.update_checksum!

  iban
end

.specification(a2_country_code, *default, &default_block) ⇒ ISO::IBAN::Specification


149
150
151
# File 'lib/iso/iban/no_autoload.rb', line 149

def self.specification(a2_country_code, *default, &default_block)
  specifications.fetch(a2_country_code, *default, &default_block)
end

.specificationsHash<String => ISO::IBAN::Specification>


140
141
142
# File 'lib/iso/iban/no_autoload.rb', line 140

def self.specifications
  @specifications || raise("No specifications have been loaded yet - Check the docs for ISO::IBAN::load_specifications.")
end

.strip(iban) ⇒ String


178
179
180
# File 'lib/iso/iban/no_autoload.rb', line 178

def self.strip(iban)
  iban.delete("\n\r\t -")
end

.valid?(iban) ⇒ true, false


159
160
161
# File 'lib/iso/iban/no_autoload.rb', line 159

def self.valid?(iban)
  parse(iban).valid?
end

.validate(iban) ⇒ Array<Symbol>


169
170
171
# File 'lib/iso/iban/no_autoload.rb', line 169

def self.validate(iban)
  parse(iban).validate
end

Instance Method Details

#<=>(other) ⇒ -1, ...

See Object#<=>


425
426
427
# File 'lib/iso/iban/no_autoload.rb', line 425

def <=>(other)
  other.respond_to?(:compact) ? @compact <=> other.compact : nil
end

#account_codeString


373
374
375
# File 'lib/iso/iban/no_autoload.rb', line 373

def 
  @compact[((@specification.branch_position_to || @specification.bank_position_to || 3)+1)..-1]
end

#bank_codeString?


355
356
357
358
359
360
361
# File 'lib/iso/iban/no_autoload.rb', line 355

def bank_code
  if @specification && @specification.bank_position_from && @specification.bank_position_to
    @compact[@specification.bank_position_from..@specification.bank_position_to]
  else
    nil
  end
end

#bbanString


350
351
352
# File 'lib/iso/iban/no_autoload.rb', line 350

def bban
  @compact[4..-1]
end

#branch_codeString?


364
365
366
367
368
369
370
# File 'lib/iso/iban/no_autoload.rb', line 364

def branch_code
  if @specification && @specification.branch_position_from && @specification.branch_position_to
    @compact[@specification.branch_position_from..@specification.branch_position_to]
  else
    nil
  end
end

#calculated_check_digitsString


443
444
445
# File 'lib/iso/iban/no_autoload.rb', line 443

def calculated_check_digits
  "%02d" % (98-(self.class.numerify(bban+@country)*100)%97)
end

#checksum_digitsString


345
346
347
# File 'lib/iso/iban/no_autoload.rb', line 345

def checksum_digits
  @compact[2,2]
end

#eql?(other) ⇒ true, false


450
451
452
# File 'lib/iso/iban/no_autoload.rb', line 450

def eql?(other)
  self.class.equal?(other.class) && self == other
end

#formattedString

Returns The IBAN in its formatted form, which is more human readable than the compact form.

Examples:

Formatted IBAN


ISO::IBAN.new('CH')

293
294
295
# File 'lib/iso/iban/no_autoload.rb', line 293

def formatted
  @_formatted ||= @compact.gsub(/.{4}(?=.)/, '\0 ')
end

#hashInteger


456
457
458
# File 'lib/iso/iban/no_autoload.rb', line 456

def hash
  [self.class, @compact].hash
end

#inspectObject

See Object#inspect


461
462
463
# File 'lib/iso/iban/no_autoload.rb', line 461

def inspect
  sprintf "#<%p %s>", self.class, formatted
end

#invalid_characters(input_encoding = nil) ⇒ Array

Returns An Array with all invalid characters.

Examples:

invalid = "hägar"
invalid.encoding # => #<Encoding:UTF-8>
ISO::IBAN.new(invalid).invalid_characters          # => ["\xC3", "\xA4"]
ISO::IBAN.new(invalid).invalid_characters('utf-8') # => ["ä"]

389
390
391
392
393
# File 'lib/iso/iban/no_autoload.rb', line 389

def invalid_characters(input_encoding=nil)
  iban = input_encoding ? @compact.dup.force_encoding(input_encoding) : @compact

  iban.gsub(/[A-Z0-9?]*/i, '').chars.to_a.uniq # to_a is for ruby <= 2.0 where String#chars returns an Enumerator
end

#numericInteger?


301
302
303
# File 'lib/iso/iban/no_autoload.rb', line 301

def numeric
  @compact.size < 5 ? nil : self.class.numerify(@compact[4..-1]+@compact[0,4])
end

#to_aArray

Note:

This method is experimental. It might change or be removed in future versions!

Returns The individual IBAN components as defined by the SWIFT specification. An empty array if this IBAN does not have a specification.


476
477
478
479
480
# File 'lib/iso/iban/no_autoload.rb', line 476

def to_a
  @_components ||= @specification ? @compact.match(@specification.iban_regex).captures : []

  @_components.dup
end

#to_sString


466
467
468
# File 'lib/iso/iban/no_autoload.rb', line 466

def to_s
  @compact.dup
end

#update_checksum!self

Requires that the checksum digits were left as '??', replaces them with the proper checksum.


433
434
435
436
437
438
439
440
# File 'lib/iso/iban/no_autoload.rb', line 433

def update_checksum!
  raise "Checksum digit placeholders missing" unless @compact[2,2] == '??'

  @compact[2,2] = calculated_check_digits
  @_formatted   = nil

  self
end

#valid?true, false


308
309
310
# File 'lib/iso/iban/no_autoload.rb', line 308

def valid?
  valid_characters? && valid_country? && valid_checksum? && valid_length? && valid_format?
end

#valid_characters?true, false


396
397
398
# File 'lib/iso/iban/no_autoload.rb', line 396

def valid_characters?
  @compact =~ /\A[A-Z]{2}(?:\d\d|\?\?)[A-Z0-9]*\z/in ? true : false
end

#valid_checksum?true, false


416
417
418
419
420
# File 'lib/iso/iban/no_autoload.rb', line 416

def valid_checksum?
  numerified = numeric()

  numerified && (numerified % 97 == 1)
end

#valid_country?true, false


401
402
403
# File 'lib/iso/iban/no_autoload.rb', line 401

def valid_country?
  @specification ? true : false
end

#valid_format?true, false


406
407
408
# File 'lib/iso/iban/no_autoload.rb', line 406

def valid_format?
  @specification && @specification.iban_regex =~ @compact ? true : false
end

#valid_length?true, false


411
412
413
# File 'lib/iso/iban/no_autoload.rb', line 411

def valid_length?
  @specification && @compact.size == @specification.iban_length ? true : false
end

#validateArray<Symbol>

Note:

The class method validate uses parse, which means it will strip whitespace and dashes from the IBAN.
#initialize on the other hand expects the IBAN in compact format and will not strip those characters.

Error codes:

  • :invalid_characters
  • :invalid_country
  • :invalid_checksum
  • :invalid_length
  • :invalid_format

Invalid characters means that the IBAN contains characters which are not in the set of A-Za-z0-9. See #invalid_characters.
Invalid country means the country is unknown (character 1 & 2 in the IBAN).
Invalid checksum means the two check digits (character 3 & 4 in the IBAN).
Invalid length means the IBAN does not comply with the length specified for the country of that IBAN.
Invalid format means the IBAN does not comply with the format specified for the country of that IBAN.


333
334
335
336
337
338
339
340
341
342
# File 'lib/iso/iban/no_autoload.rb', line 333

def validate
  errors   = []
  errors << :invalid_characters unless valid_characters?
  errors << :invalid_country    unless valid_country?
  errors << :invalid_checksum   unless valid_characters? && valid_checksum?
  errors << :invalid_length     unless valid_length?
  errors << :invalid_format     unless valid_format?

  errors
end