iso-iban 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.markdown +33 -7
- data/iso-iban.gemspec +13 -1
- data/lib/iso/iban.rb +3 -370
- data/lib/iso/iban/autoload.rb +7 -3
- data/lib/iso/iban/backports.rb +17 -0
- data/lib/iso/iban/invalid.rb +25 -0
- data/lib/iso/iban/no_autoload.rb +481 -0
- data/lib/iso/iban/specification.rb +5 -5
- data/lib/iso/iban/version.rb +1 -1
- data/test/unit/lib/iso/iban.rb +40 -24
- metadata +24 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72a53aeb4de7eb495706e51a08f34c861e706c03
|
4
|
+
data.tar.gz: e3af74706241ab8bff72986d8ed79aa34d1b2231
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb22f4ea028de0f22100dfefba44860e0dcaec1cb3619b64659b0b22562a8aba3c3570577944363e22d4331123609d93b6c6a38f747874303adb0a15ecc4675f
|
7
|
+
data.tar.gz: 495fa573e6734141cc389101ed4bb83048e5619064bfe62b33548b230f01f649bb9898833c07a5e5a3a428e84ff77a5db1a442b7d67dee7964ccfd1380f87b22
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2012, Stefan Rusterholz <stefan.rusterholz@gmail.com>
|
1
|
+
Copyright (c) 2012-2014, Stefan Rusterholz <stefan.rusterholz@gmail.com>
|
2
2
|
All rights reserved.
|
3
3
|
|
4
4
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
data/README.markdown
CHANGED
@@ -4,22 +4,36 @@ README
|
|
4
4
|
|
5
5
|
Summary
|
6
6
|
-------
|
7
|
-
|
7
|
+
|
8
|
+
ISO::IBAN implements the IBAN (International Bank Account Number) specification as per ISO 13616-1.
|
8
9
|
It provides methods to generate valid IBAN numbers from components, or to validate a given IBAN.
|
9
10
|
|
10
11
|
|
11
12
|
Installation
|
12
13
|
------------
|
13
|
-
|
14
|
+
|
15
|
+
### Via rubygems
|
16
|
+
|
17
|
+
gem install iso-iban
|
18
|
+
|
19
|
+
### From github
|
20
|
+
|
21
|
+
git clone https://github.com/apeiros/iso-iban.git
|
22
|
+
cd iso-iban
|
23
|
+
rm -r *.gem
|
24
|
+
gem build *.gemspec
|
25
|
+
gem install *.gem
|
14
26
|
|
15
27
|
|
16
28
|
Usage
|
17
29
|
-----
|
18
30
|
|
19
|
-
|
20
|
-
ISO::IBAN.
|
21
|
-
ISO::IBAN.
|
22
|
-
|
31
|
+
require 'iso/iban'
|
32
|
+
ISO::IBAN.valid?('CH35 1234 5987 6543 2109 A') # => true
|
33
|
+
ISO::IBAN.validate('CH37 1234 5987 6543 2109 A') # => [:invalid_checksum]
|
34
|
+
ISO::IBAN.generate('CH', '12345', '987') # => #<ISO::IBAN CH76 1234 5000 0000 0098 7>
|
35
|
+
iban = ISO::IBAN.parse('CH35 1234 5987 6543 2109 A') # => #<ISO::IBAN CH35 1234 5987 6543 2109 A>
|
36
|
+
iban = ISO::IBAN.new('CH351234598765432109A') # => #<ISO::IBAN CH35 1234 5987 6543 2109 A>
|
23
37
|
iban.formatted # => "CH35 1234 5987 6543 2109 A"
|
24
38
|
iban.compact # => "CH351234598765432109A"
|
25
39
|
iban.country # => "CH"
|
@@ -29,6 +43,18 @@ Usage
|
|
29
43
|
iban.valid? # => true
|
30
44
|
iban.validate # => []
|
31
45
|
|
46
|
+
**Note:** iso/iban automatically loads the IBAN specifications delivered with the gem. If you do not wish
|
47
|
+
those to be loaded, `require 'iso/iban/no_autoload'` instead.
|
48
|
+
|
49
|
+
|
50
|
+
ENV
|
51
|
+
---
|
52
|
+
|
53
|
+
ISO::IBAN.load_specifications (which is automatically called when you require 'iso/iban') uses the
|
54
|
+
ENV variable `IBAN_SPECIFICATIONS` to determine where to look for IBAN specifications. If that
|
55
|
+
variable is not set, it will default to the datafile delivered with the gem.
|
56
|
+
|
57
|
+
|
32
58
|
Links
|
33
59
|
-----
|
34
60
|
|
@@ -42,4 +68,4 @@ License
|
|
42
68
|
-------
|
43
69
|
|
44
70
|
You can use this code under the {file:LICENSE.txt BSD-2-Clause License}, free of charge.
|
45
|
-
If you need a different license, please ask the author.
|
71
|
+
If you need a different license, please ask the author.
|
data/iso-iban.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "iso-iban"
|
5
|
-
s.version = "0.0
|
5
|
+
s.version = "0.1.0"
|
6
6
|
s.authors = "Stefan Rusterholz"
|
7
7
|
s.email = "stefan.rusterholz@gmail.com"
|
8
8
|
s.homepage = "https://github.com/apeiros/iso-iban"
|
@@ -16,6 +16,18 @@ Gem::Specification.new do |s|
|
|
16
16
|
Utilities for International Bank Account Numbers (IBAN) as per ISO 13616-1.
|
17
17
|
SUMMARY
|
18
18
|
|
19
|
+
s.post_install_message = <<-POST_INSTALL_MESSAGE.gsub(/^ /, '').chomp
|
20
|
+
IMPORTANT!
|
21
|
+
|
22
|
+
As of 0.1.0, there are 2 backward incompatible changes:
|
23
|
+
|
24
|
+
* `require 'iso/iban/autoload'` is deprecated. Please use plain `require 'iso/iban'`.
|
25
|
+
To load ISO::IBAN without loading the specifications, please use
|
26
|
+
`require 'iso/iban/no_autoload'`.
|
27
|
+
* ISO::IBAN.new no longer accepts formatted input.
|
28
|
+
Use ISO::IBAN.parse if your input is potentially not in the compact format.
|
29
|
+
POST_INSTALL_MESSAGE
|
30
|
+
|
19
31
|
s.files =
|
20
32
|
Dir['bin/**/*'] +
|
21
33
|
Dir['data/**/*'] +
|
data/lib/iso/iban.rb
CHANGED
@@ -1,373 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
require 'iso/iban/
|
5
|
-
require 'yaml'
|
3
|
+
# Actual implementation is in iso/iban/no_autoload.rb
|
4
|
+
require 'iso/iban/no_autoload'
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
# IBAN - ISO 13616-1
|
10
|
-
#
|
11
|
-
# General IBAN Information
|
12
|
-
# ========================
|
13
|
-
#
|
14
|
-
# * What is an IBAN?
|
15
|
-
# IBAN stands for International Bank Account Number. It is the ISO 13616
|
16
|
-
# international standard for numbering bank accounts. In 2006, the
|
17
|
-
# International Organization for Standardization (ISO) designated SWIFT as
|
18
|
-
# the Registration Authority for ISO 13616.
|
19
|
-
#
|
20
|
-
# * Use
|
21
|
-
# The IBAN facilitates the communication and processing of cross-border
|
22
|
-
# transactions. It allows exchanging account identification details in a
|
23
|
-
# machine-readable form.
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# The ISO 13616 IBAN Standard
|
27
|
-
# ===========================
|
28
|
-
#
|
29
|
-
# * Structure
|
30
|
-
# The IBAN structure is defined in ISO 13616-1 and consists of a two-letter
|
31
|
-
# ISO 3166-1 country code, followed by two check digits and up to thirty
|
32
|
-
# alphanumeric characters for a BBAN (Basic Bank Account Number) which has a
|
33
|
-
# fixed length per country and, included within it, a bank identifier with a
|
34
|
-
# fixed position and a fixed length per country. The check digits are
|
35
|
-
# calculated based on the scheme defined in ISO/IEC 7064 (MOD97-10).
|
36
|
-
#
|
37
|
-
# * Terms and definitions
|
38
|
-
# Bank identifier: The identifier that uniquely identifies the financial
|
39
|
-
# institution and, when appropriate, the branch of that financial institution
|
40
|
-
# servicing an account
|
41
|
-
#
|
42
|
-
# `In this registry, the branch identifier format is shown specifically, when
|
43
|
-
# present.`
|
44
|
-
#
|
45
|
-
# *BBAN*: basic bank account number: The identifier that uniquely identifies
|
46
|
-
# an individual account, at a specific financial institution, in a particular
|
47
|
-
# country. The BBAN includes a bank identifier of the financial institution
|
48
|
-
# servicing that account.
|
49
|
-
# *IBAN*: international bank account number: The expanded version of the
|
50
|
-
# basic bank account number (BBAN), intended for use internationally. The
|
51
|
-
# IBAN uniquely identifies an individual account, at a specific financial
|
52
|
-
# institution, in a particular country.
|
53
|
-
#
|
54
|
-
# * Submitters
|
55
|
-
# Nationally-agreed, ISO13616-compliant IBAN formats are submitted to the
|
56
|
-
# registration authority exclusively by the National Standards Body or the
|
57
|
-
# National Central Bank of the country.
|
58
|
-
class IBAN
|
59
|
-
include Comparable
|
60
|
-
|
61
|
-
# Character code translation used to convert an IBAN into its numeric
|
62
|
-
# (digits-only) form
|
63
|
-
CharacterCodes = Hash[('0'..'9').zip('0'..'9')+('a'..'z').zip(10..35)+('A'..'Z').zip(10..35)]
|
64
|
-
|
65
|
-
# All uppercase letters
|
66
|
-
UpperAlpha = [*'A'..'Z']
|
67
|
-
|
68
|
-
# All lowercase letters
|
69
|
-
LowerAlpha = [*'a'..'z']
|
70
|
-
|
71
|
-
# All digits
|
72
|
-
Digits = [*'0'..'9']
|
73
|
-
|
74
|
-
# All uppercase letters, lowercase letters and digits
|
75
|
-
AlphaNumeric = [*'A'..'Z', *'a'..'z', *'0'..'9']
|
76
|
-
|
77
|
-
# All specifications, see ISO::IBAN::Specification
|
78
|
-
@specifications = nil
|
79
|
-
|
80
|
-
# Load the IBAN specifications file, which determines how the IBAN
|
81
|
-
# for any given country looks like.
|
82
|
-
#
|
83
|
-
# It will use the following sources in this order (first one which exists wins)
|
84
|
-
#
|
85
|
-
# * Path passed as spec_file parameter
|
86
|
-
# * Path provided by the env variable IBAN_SPECIFICATIONS
|
87
|
-
# * The file ../data/iso-iban/specs.yaml relative to the lib dir
|
88
|
-
# * The Gem datadir path
|
89
|
-
#
|
90
|
-
# @param [String] spec_file
|
91
|
-
# Override the default specifications file path.
|
92
|
-
#
|
93
|
-
# @return [self]
|
94
|
-
def self.load_specifications(spec_file=nil)
|
95
|
-
if spec_file then
|
96
|
-
# do nothing
|
97
|
-
elsif ENV['IBAN_SPECIFICATIONS'] then
|
98
|
-
spec_file = ENV['IBAN_SPECIFICATIONS']
|
99
|
-
else
|
100
|
-
spec_file = File.expand_path('../../../data/iso-iban/specs.yaml', __FILE__)
|
101
|
-
if !File.file?(spec_file) && defined?(Gem) && Gem.datadir('iso-iban')
|
102
|
-
spec_file = Gem.datadir('iso-iban')+'/specs.yaml'
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
if spec_file && File.file?(spec_file)
|
107
|
-
@specifications = ISO::IBAN::Specification.load_yaml(spec_file)
|
108
|
-
else
|
109
|
-
raise "Could not load IBAN specifications, no specs file found."
|
110
|
-
end
|
111
|
-
|
112
|
-
self
|
113
|
-
end
|
114
|
-
|
115
|
-
# @return [Hash<String => ISO::IBAN::Specification>]
|
116
|
-
# A hash with the country (ISO3166 2-letter) as key and the specification for that country as value
|
117
|
-
def self.specifications
|
118
|
-
@specifications || raise("No specifications have been loaded yet.")
|
119
|
-
end
|
120
|
-
|
121
|
-
# @param [String] a2_country_code
|
122
|
-
# The country (ISO3166 2-letter), e.g. 'CH' or 'DE'.
|
123
|
-
#
|
124
|
-
# @return [ISO::IBAN::Specification]
|
125
|
-
# The specification for the given country
|
126
|
-
def self.specification(a2_country_code, *default, &default_block)
|
127
|
-
specifications.fetch(a2_country_code, *default, &default_block)
|
128
|
-
end
|
129
|
-
|
130
|
-
# @param [String] iban
|
131
|
-
# An IBAN number, either in compact or human format.
|
132
|
-
#
|
133
|
-
# @return [true, false]
|
134
|
-
# Whether the IBAN is valid.
|
135
|
-
# See {#validate} for details.
|
136
|
-
def self.valid?(iban)
|
137
|
-
new(iban).valid?
|
138
|
-
end
|
139
|
-
|
140
|
-
# @param [String] iban
|
141
|
-
# An IBAN number, either in compact or human format.
|
142
|
-
#
|
143
|
-
# @return [Array<Symbol>]
|
144
|
-
# An array with a code of all validation errors, empty if valid.
|
145
|
-
# See {#validate} for details.
|
146
|
-
def self.validate(iban)
|
147
|
-
new(iban).validate
|
148
|
-
end
|
149
|
-
|
150
|
-
# @param [String] iban
|
151
|
-
# The IBAN in either compact or human readable form.
|
152
|
-
#
|
153
|
-
# @return [String]
|
154
|
-
# The IBAN in compact form.
|
155
|
-
def self.strip(iban)
|
156
|
-
iban.tr(' -', '')
|
157
|
-
end
|
158
|
-
|
159
|
-
# Generate an IBAN from country code and components, automatically filling in the checksum.
|
160
|
-
#
|
161
|
-
# @example Generate an IBAN for UBS Switzerland with account number '12345'
|
162
|
-
# ISO::IBAN.generate('CH', '216', '12345') # => #<ISO::IBAN CH92 0021 6000 0000 1234 5>
|
163
|
-
#
|
164
|
-
# @param [String] country
|
165
|
-
# The ISO-3166 2-letter country code.
|
166
|
-
#
|
167
|
-
def self.generate(country, *components)
|
168
|
-
spec = specification(country)
|
169
|
-
justified = spec.component_lengths.zip(components).map { |length, component| component.rjust(length, "0") }
|
170
|
-
iban = new(country+'??'+justified.join(''))
|
171
|
-
iban.update_checksum!
|
172
|
-
|
173
|
-
iban
|
174
|
-
end
|
175
|
-
|
176
|
-
# @param [String] countries
|
177
|
-
# A list of 2 letter country codes. If empty, all countries in
|
178
|
-
# ISO::IBAN::specifications are used.
|
179
|
-
#
|
180
|
-
# @return [ISO::IBAN] A random, valid IBAN
|
181
|
-
def self.random(*countries)
|
182
|
-
countries = specifications.keys if countries.empty?
|
183
|
-
country = countries.sample
|
184
|
-
account = specification(country).iban_structure.scan(/([A-Z]+)|(\d+)(!?)([nac])/).map { |exact, length, fixed, code|
|
185
|
-
if exact
|
186
|
-
exact
|
187
|
-
elsif code == 'a'
|
188
|
-
Array.new(length.to_i) { UpperAlpha.sample }.join('')
|
189
|
-
elsif code == 'c'
|
190
|
-
Array.new(length.to_i) { AlphaNumeric.sample }.join('')
|
191
|
-
elsif code == 'e'
|
192
|
-
' '*length.to_i
|
193
|
-
elsif code == 'n'
|
194
|
-
Array.new(length.to_i) { Digits.sample }.join('')
|
195
|
-
end
|
196
|
-
}.join('')
|
197
|
-
account[2,2] = '??'
|
198
|
-
iban = new(account)
|
199
|
-
iban.update_checksum!
|
200
|
-
|
201
|
-
iban
|
202
|
-
end
|
203
|
-
|
204
|
-
# Converts a String into its digits-only form, i.e. all characters a-z are replaced with their corresponding
|
205
|
-
# digit sequences, according to the IBAN specification.
|
206
|
-
#
|
207
|
-
# @param [String] string
|
208
|
-
# The string to convert into its numeric form.
|
209
|
-
#
|
210
|
-
# @return [String] The string in its numeric, digits-only form.
|
211
|
-
def self.numerify(string)
|
212
|
-
string.downcase.gsub(/\D/) { |char|
|
213
|
-
CharacterCodes.fetch(char) {
|
214
|
-
raise ArgumentError, "The string contains an invalid character #{char.inspect}"
|
215
|
-
}
|
216
|
-
}.to_i
|
217
|
-
end
|
218
|
-
|
219
|
-
# @return [String] The standard form of the IBAN for machine communication, without spaces.
|
220
|
-
attr_reader :compact
|
221
|
-
|
222
|
-
# @return [String] The ISO-3166 2-letter country code.
|
223
|
-
attr_reader :country
|
224
|
-
|
225
|
-
# @return [ISO::IBAN::Specification] The specification for this IBAN (determined by its country).
|
226
|
-
attr_reader :specification
|
227
|
-
|
228
|
-
# @param [String] iban
|
229
|
-
# The IBAN number, either in formatted, human readable or in compact form.
|
230
|
-
def initialize(iban)
|
231
|
-
raise ArgumentError, "String expected for iban, but got #{iban.class}" unless iban.is_a?(String)
|
232
|
-
|
233
|
-
@compact = self.class.strip(iban)
|
234
|
-
@country = iban[0,2]
|
235
|
-
@specification = self.class.specification(@country, nil)
|
236
|
-
end
|
237
|
-
|
238
|
-
# @example Formatted IBAN
|
239
|
-
#
|
240
|
-
# ISO::IBAN.new('CH')
|
241
|
-
#
|
242
|
-
# @return [String] The IBAN in its formatted form, which is more human readable than the compact form.
|
243
|
-
def formatted
|
244
|
-
@_formatted ||= @compact.gsub(/.{4}(?=.)/, '\0 ')
|
245
|
-
end
|
246
|
-
|
247
|
-
# @return [String]
|
248
|
-
# IBAN in its numeric form, i.e. all characters a-z are replaced with their corresponding
|
249
|
-
# digit sequences.
|
250
|
-
def numeric
|
251
|
-
@compact.size < 5 ? nil : self.class.numerify(@compact[4..-1]+@compact[0,4])
|
252
|
-
end
|
253
|
-
|
254
|
-
# @return [true, false]
|
255
|
-
# Whether the IBAN is valid.
|
256
|
-
# See {#validate} for details.
|
257
|
-
def valid?
|
258
|
-
validate.empty?
|
259
|
-
end
|
260
|
-
|
261
|
-
# Validation error codes:
|
262
|
-
#
|
263
|
-
# * :invalid_country
|
264
|
-
# * :invalid_checksum
|
265
|
-
# * :invalid_length
|
266
|
-
# * :invalid_format
|
267
|
-
#
|
268
|
-
# Invalid country means the country is unknown (char 1 & 2 in the IBAN).
|
269
|
-
# Invalid checksum means the two check digits (char 3 & 4 in the IBAN).
|
270
|
-
# Invalid length means the IBAN does not comply with the length specified for the country of that IBAN.
|
271
|
-
# Invalid format means the IBAN does not comply with the format specified for the country of that IBAN.
|
272
|
-
#
|
273
|
-
# @return [Array<Symbol>] An array with a code of all validation errors, empty if valid.
|
274
|
-
def validate
|
275
|
-
errors = []
|
276
|
-
errors << :invalid_country unless valid_country?
|
277
|
-
errors << :invalid_checksum unless valid_checksum?
|
278
|
-
errors << :invalid_length unless valid_length?
|
279
|
-
errors << :invalid_format unless valid_format?
|
280
|
-
|
281
|
-
errors
|
282
|
-
end
|
283
|
-
|
284
|
-
# @return [String] The checksum digits in the IBAN.
|
285
|
-
def checksum_digits
|
286
|
-
@compact[2,2]
|
287
|
-
end
|
288
|
-
|
289
|
-
# @return [String] The BBAN of the IBAN.
|
290
|
-
def bban
|
291
|
-
@compact[4..-1]
|
292
|
-
end
|
293
|
-
|
294
|
-
# @return [String, nil] The bank code part of the IBAN, nil if not applicable.
|
295
|
-
def bank_code
|
296
|
-
if @specification && @specification.bank_position_from && @specification.bank_position_to
|
297
|
-
@compact[@specification.bank_position_from..@specification.bank_position_to]
|
298
|
-
else
|
299
|
-
nil
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
# @return [String, nil] The branch code part of the IBAN, nil if not applicable.
|
304
|
-
def branch_code
|
305
|
-
if @specification && @specification.branch_position_from && @specification.branch_position_to
|
306
|
-
@compact[@specification.branch_position_from..@specification.branch_position_to]
|
307
|
-
else
|
308
|
-
nil
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
# @return [String] The account code part of the IBAN.
|
313
|
-
def account_code
|
314
|
-
@compact[((@specification.branch_position_to || @specification.bank_position_to || 3)+1)..-1]
|
315
|
-
end
|
316
|
-
|
317
|
-
# @return [true, false] Whether the country of the IBAN is valid.
|
318
|
-
def valid_country?
|
319
|
-
@specification ? true : false
|
320
|
-
end
|
321
|
-
|
322
|
-
# @return [true, false] Whether the format of the IBAN is valid.
|
323
|
-
def valid_format?
|
324
|
-
specification && specification.iban_regex =~ @compact ? true : false
|
325
|
-
end
|
326
|
-
|
327
|
-
# @return [true, false] Whether the length of the IBAN is valid.
|
328
|
-
def valid_length?
|
329
|
-
specification && @compact.size == specification.iban_length ? true : false
|
330
|
-
end
|
331
|
-
|
332
|
-
# @return [true, false] Whether the checksum of the IBAN is valid.
|
333
|
-
def valid_checksum?
|
334
|
-
numerified = numeric()
|
335
|
-
|
336
|
-
numerified && (numerified % 97 == 1)
|
337
|
-
end
|
338
|
-
|
339
|
-
# See Object#<=>
|
340
|
-
#
|
341
|
-
# @return [-1, 0, 1, nil]
|
342
|
-
def <=>(other)
|
343
|
-
other.respond_to?(:compact) ? @compact <=> other.compact : nil
|
344
|
-
end
|
345
|
-
|
346
|
-
# Requires that the checksum digits were left as '??', replaces them with
|
347
|
-
# the proper checksum.
|
348
|
-
#
|
349
|
-
# @return [self]
|
350
|
-
def update_checksum!
|
351
|
-
raise "Checksum digit placeholders missing" unless @compact[2,2] == '??'
|
352
|
-
|
353
|
-
@compact[2,2] = calculated_check_digits
|
354
|
-
|
355
|
-
self
|
356
|
-
end
|
357
|
-
|
358
|
-
# @return [String] The check-digits as calculated from the IBAN.
|
359
|
-
def calculated_check_digits
|
360
|
-
"%02d" % (98-(self.class.numerify(bban+@country)*100)%97)
|
361
|
-
end
|
362
|
-
|
363
|
-
# See Object#inspect
|
364
|
-
def inspect
|
365
|
-
sprintf "#<%p %s>", self.class, formatted
|
366
|
-
end
|
367
|
-
|
368
|
-
# @return [String] The compact form of the IBAN as a String.
|
369
|
-
def to_s
|
370
|
-
@compact.dup
|
371
|
-
end
|
372
|
-
end
|
373
|
-
end
|
6
|
+
ISO::IBAN.load_specifications
|
data/lib/iso/iban/autoload.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
# This file used automatically load all specifications.
|
4
|
+
# Now this is the default behavior of require 'iso/iban', and this file only
|
5
|
+
# exists to inform about the move.
|
6
|
+
# The file will be removed (at the earlier of) either when reaching 1.0 or in
|
7
|
+
# february 2015 (1 year from now).
|
4
8
|
|
5
|
-
|
9
|
+
raise "Deprecated - use just `require 'iso/iban'` instead.\nIf you wish to suppress autoloading, use `require 'iso/iban/no_autoload'`."
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Port some newer ruby methods back
|
4
|
+
unless String.method_defined?(:b)
|
5
|
+
|
6
|
+
# Backports from ruby 2.0 used in ISO::IBAN
|
7
|
+
class String
|
8
|
+
|
9
|
+
# @example
|
10
|
+
# str.b -> str
|
11
|
+
#
|
12
|
+
# Returns a copied string whose encoding is ASCII-8BIT.
|
13
|
+
def b
|
14
|
+
dup.force_encoding(Encoding::BINARY)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ISO
|
4
|
+
class IBAN
|
5
|
+
|
6
|
+
# Raised by ISO::IBAN::parse!
|
7
|
+
class Invalid < ArgumentError
|
8
|
+
|
9
|
+
# @return [Array<Symbol>] The errors in the IBAN.
|
10
|
+
# @see ISO::IBAN#validate
|
11
|
+
attr_reader :errors
|
12
|
+
|
13
|
+
# @return [ISO::IBAN] The faulty IBAN.
|
14
|
+
attr_reader :iban
|
15
|
+
|
16
|
+
# @param [ISO::IBAN] iban
|
17
|
+
# The faulty IBAN.
|
18
|
+
def initialize(iban)
|
19
|
+
@iban = iban
|
20
|
+
@errors = iban.validate
|
21
|
+
super("The IBAN #{@iban.formatted} is invalid (#{@errors.join(', ')})")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,481 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'iso/iban/backports'
|
4
|
+
require 'iso/iban/invalid'
|
5
|
+
require 'iso/iban/specification'
|
6
|
+
require 'iso/iban/version'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
# Container for classes which implement ISO standards.
|
10
|
+
module ISO
|
11
|
+
|
12
|
+
# IBAN - ISO 13616-1
|
13
|
+
#
|
14
|
+
# Usage
|
15
|
+
# =====
|
16
|
+
#
|
17
|
+
# require 'iso/iban'
|
18
|
+
# ISO::IBAN.valid?('CH35 1234 5987 6543 2109 A') # => true
|
19
|
+
# ISO::IBAN.validate('CH37 1234 5987 6543 2109 A') # => [:invalid_checksum]
|
20
|
+
# ISO::IBAN.generate('CH', '12345', '987') # => #<ISO::IBAN CH76 1234 5000 0000 0098 7>
|
21
|
+
# iban = ISO::IBAN.parse('CH35 1234 5987 6543 2109 A') # => #<ISO::IBAN CH35 1234 5987 6543 2109 A>
|
22
|
+
# iban = ISO::IBAN.new('CH351234598765432109A') # => #<ISO::IBAN CH35 1234 5987 6543 2109 A>
|
23
|
+
# iban.formatted # => "CH35 1234 5987 6543 2109 A"
|
24
|
+
# iban.compact # => "CH351234598765432109A"
|
25
|
+
# iban.country # => "CH"
|
26
|
+
# iban.checksum_digits # => "35"
|
27
|
+
# iban.bank_code # => "12345"
|
28
|
+
# iban.account_code # => "98765432109A"
|
29
|
+
# iban.valid? # => true
|
30
|
+
# iban.validate # => []
|
31
|
+
#
|
32
|
+
# General IBAN Information
|
33
|
+
# ========================
|
34
|
+
#
|
35
|
+
# * **What is an IBAN?**
|
36
|
+
# IBAN stands for International Bank Account Number. It is the ISO 13616
|
37
|
+
# international standard for numbering bank accounts. In 2006, the
|
38
|
+
# International Organization for Standardization (ISO) designated SWIFT as
|
39
|
+
# the Registration Authority for ISO 13616.
|
40
|
+
#
|
41
|
+
# * **Use**
|
42
|
+
# The IBAN facilitates the communication and processing of cross-border
|
43
|
+
# transactions. It allows exchanging account identification details in a
|
44
|
+
# machine-readable form.
|
45
|
+
#
|
46
|
+
#
|
47
|
+
# The ISO 13616 IBAN Standard
|
48
|
+
# ===========================
|
49
|
+
#
|
50
|
+
# * **Structure**
|
51
|
+
# The IBAN structure is defined in ISO 13616-1 and consists of a two-letter
|
52
|
+
# ISO 3166-1 country code, followed by two check digits and up to thirty
|
53
|
+
# alphanumeric characters for a BBAN (Basic Bank Account Number) which has a
|
54
|
+
# fixed length per country and, included within it, a bank identifier with a
|
55
|
+
# fixed position and a fixed length per country. The check digits are
|
56
|
+
# calculated based on the scheme defined in ISO/IEC 7064 (MOD97-10).
|
57
|
+
#
|
58
|
+
# * **Terms and definitions**
|
59
|
+
# * *Bank identifier:* The identifier that uniquely identifies the financial
|
60
|
+
# institution and, when appropriate, the branch of that financial institution
|
61
|
+
# servicing an account.
|
62
|
+
# * *BBAN:* basic bank account number: The identifier that uniquely identifies
|
63
|
+
# an individual account, at a specific financial institution, in a particular
|
64
|
+
# country. The BBAN includes a bank identifier of the financial institution
|
65
|
+
# servicing that account.
|
66
|
+
# * *IBAN:* international bank account number: The expanded version of the
|
67
|
+
# basic bank account number (BBAN), intended for use internationally. The
|
68
|
+
# IBAN uniquely identifies an individual account, at a specific financial
|
69
|
+
# institution, in a particular country.
|
70
|
+
#
|
71
|
+
# * **Submitters**
|
72
|
+
# Nationally-agreed, ISO13616-compliant IBAN formats are submitted to the
|
73
|
+
# registration authority exclusively by the National Standards Body or the
|
74
|
+
# National Central Bank of the country.
|
75
|
+
class IBAN
|
76
|
+
include Comparable
|
77
|
+
|
78
|
+
# Character code translation used to convert an IBAN into its numeric
|
79
|
+
# (digits-only) form
|
80
|
+
CharacterCodes = Hash[('0'..'9').zip('0'..'9')+('a'..'z').zip(10..35)+('A'..'Z').zip(10..35)]
|
81
|
+
|
82
|
+
# All uppercase letters
|
83
|
+
UpperAlpha = [*'A'..'Z']
|
84
|
+
|
85
|
+
# All lowercase letters
|
86
|
+
LowerAlpha = [*'a'..'z']
|
87
|
+
|
88
|
+
# All digits
|
89
|
+
Digits = [*'0'..'9']
|
90
|
+
|
91
|
+
# All uppercase letters, lowercase letters and digits
|
92
|
+
AlphaNumeric = [*'A'..'Z', *'a'..'z', *'0'..'9']
|
93
|
+
|
94
|
+
# All specifications, see ISO::IBAN::Specification
|
95
|
+
@specifications = nil
|
96
|
+
|
97
|
+
# @note
|
98
|
+
# Using `require 'iso/iban'` will automatically invoke this method.
|
99
|
+
# If you do not wish this behavior, `require 'iso/iban/no_autoload'` instead.
|
100
|
+
#
|
101
|
+
# Load the IBAN specifications file, which determines how the IBAN
|
102
|
+
# for any given country looks.
|
103
|
+
#
|
104
|
+
# It will use the following sources in this order (first one which exists wins)
|
105
|
+
#
|
106
|
+
# * Path passed as `spec_file` parameter
|
107
|
+
# * Path provided by the env variable `IBAN_SPECIFICATIONS`
|
108
|
+
# * The file `../data/iso-iban/specs.yaml` relative to the lib dir
|
109
|
+
# * The Gem datadir path
|
110
|
+
#
|
111
|
+
# @param [String] spec_file
|
112
|
+
# Override the default specifications file path.
|
113
|
+
#
|
114
|
+
# @return [self]
|
115
|
+
def self.load_specifications(spec_file=nil)
|
116
|
+
if spec_file then
|
117
|
+
# do nothing
|
118
|
+
elsif ENV['IBAN_SPECIFICATIONS'] then
|
119
|
+
spec_file = ENV['IBAN_SPECIFICATIONS']
|
120
|
+
else
|
121
|
+
spec_file = File.expand_path('../../../../data/iso-iban/specs.yaml', __FILE__)
|
122
|
+
if !File.file?(spec_file) && defined?(Gem) && Gem.datadir('iso-iban')
|
123
|
+
spec_file = Gem.datadir('iso-iban')+'/specs.yaml'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if spec_file && File.file?(spec_file)
|
128
|
+
@specifications = ISO::IBAN::Specification.load_yaml(spec_file)
|
129
|
+
elsif spec_file
|
130
|
+
raise "Could not load IBAN specifications, specs file #{spec_file.inspect} does not exist or can't be read."
|
131
|
+
else
|
132
|
+
raise "Could not load IBAN specifications, no specs file found."
|
133
|
+
end
|
134
|
+
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [Hash<String => ISO::IBAN::Specification>]
|
139
|
+
# A hash with the country (ISO3166 2-letter) as key and the specification for that country as value.
|
140
|
+
def self.specifications
|
141
|
+
@specifications || raise("No specifications have been loaded yet - Check the docs for ISO::IBAN::load_specifications.")
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param [String] a2_country_code
|
145
|
+
# The country (ISO3166 2-letter), e.g. 'CH' or 'DE'.
|
146
|
+
#
|
147
|
+
# @return [ISO::IBAN::Specification]
|
148
|
+
# The specification for the given country.
|
149
|
+
def self.specification(a2_country_code, *default, &default_block)
|
150
|
+
specifications.fetch(a2_country_code, *default, &default_block)
|
151
|
+
end
|
152
|
+
|
153
|
+
# @param [String] iban
|
154
|
+
# An IBAN number, either in compact or human format.
|
155
|
+
#
|
156
|
+
# @return [true, false]
|
157
|
+
# Whether the IBAN is valid.
|
158
|
+
# See {#validate} for details.
|
159
|
+
def self.valid?(iban)
|
160
|
+
parse(iban).valid?
|
161
|
+
end
|
162
|
+
|
163
|
+
# @param [String] iban
|
164
|
+
# An IBAN number, either in compact or human format.
|
165
|
+
#
|
166
|
+
# @return [Array<Symbol>]
|
167
|
+
# An array with a code of all validation errors, empty if valid.
|
168
|
+
# See {#validate} for details.
|
169
|
+
def self.validate(iban)
|
170
|
+
parse(iban).validate
|
171
|
+
end
|
172
|
+
|
173
|
+
# @param [String] iban
|
174
|
+
# The IBAN in either compact or human readable form.
|
175
|
+
#
|
176
|
+
# @return [String]
|
177
|
+
# The IBAN in compact form, all whitespace and dashes stripped.
|
178
|
+
def self.strip(iban)
|
179
|
+
iban.delete("\n\r\t -")
|
180
|
+
end
|
181
|
+
|
182
|
+
# Like ISO::IBAN.parse, but raises an ISO::IBAN::Invalid exception if the IBAN is invalid.
|
183
|
+
#
|
184
|
+
# @param [String] iban_number
|
185
|
+
# The IBAN in either compact or human readable form.
|
186
|
+
#
|
187
|
+
# @return [ISO::IBAN]
|
188
|
+
# An IBAN instance representing the passed IBAN number.
|
189
|
+
def self.parse!(iban_number)
|
190
|
+
iban = parse(iban_number)
|
191
|
+
raise Invalid.new(iban) unless iban.valid?
|
192
|
+
|
193
|
+
iban
|
194
|
+
end
|
195
|
+
|
196
|
+
# @param [String] iban_number
|
197
|
+
# The IBAN in either compact or human readable form.
|
198
|
+
#
|
199
|
+
# @return [ISO::IBAN]
|
200
|
+
# An IBAN instance representing the passed IBAN number.
|
201
|
+
def self.parse(iban_number)
|
202
|
+
new(strip(iban_number))
|
203
|
+
end
|
204
|
+
|
205
|
+
# Generate an IBAN from country code and components, automatically filling in the checksum.
|
206
|
+
#
|
207
|
+
# @note
|
208
|
+
# `generate` will pad all segments with zeros, which means it will generate invalid
|
209
|
+
# IBANs if you provide too short segments which are alphabetic only.
|
210
|
+
# For example, `ISO::IBAN.generate('BG', 'A', '2', 'C')` generates an invalid IBAN.
|
211
|
+
#
|
212
|
+
# @example Generate an IBAN for UBS Switzerland with account number '12345'
|
213
|
+
# ISO::IBAN.generate('CH', '216', '12345') # => #<ISO::IBAN CH92 0021 6000 0000 1234 5>
|
214
|
+
#
|
215
|
+
# @param [String] country
|
216
|
+
# The ISO-3166 2-letter country code.
|
217
|
+
#
|
218
|
+
def self.generate(country, *components)
|
219
|
+
spec = specification(country)
|
220
|
+
justified = spec.component_lengths.zip(components).map { |length, component| component.rjust(length, "0") }
|
221
|
+
iban = new(country+'??'+justified.join(''))
|
222
|
+
iban.update_checksum!
|
223
|
+
|
224
|
+
iban
|
225
|
+
end
|
226
|
+
|
227
|
+
# @param [String] countries
|
228
|
+
# A list of 2 letter country codes. If empty, all countries in ISO::IBAN::specifications are used.
|
229
|
+
#
|
230
|
+
# @return [ISO::IBAN] A random, valid IBAN.
|
231
|
+
def self.random(*countries)
|
232
|
+
countries = specifications.keys if countries.empty?
|
233
|
+
country = countries.sample
|
234
|
+
account = specification(country).iban_structure.scan(/([A-Z]+)|(\d+)(!?)([nac])/).map { |exact, length, fixed, code|
|
235
|
+
if exact
|
236
|
+
exact
|
237
|
+
elsif code == 'a'
|
238
|
+
Array.new(length.to_i) { UpperAlpha.sample }.join('')
|
239
|
+
elsif code == 'c'
|
240
|
+
Array.new(length.to_i) { AlphaNumeric.sample }.join('')
|
241
|
+
elsif code == 'e'
|
242
|
+
' '*length.to_i
|
243
|
+
elsif code == 'n'
|
244
|
+
Array.new(length.to_i) { Digits.sample }.join('')
|
245
|
+
end
|
246
|
+
}.join('')
|
247
|
+
account[2,2] = '??'
|
248
|
+
iban = new(account)
|
249
|
+
iban.update_checksum!
|
250
|
+
|
251
|
+
iban
|
252
|
+
end
|
253
|
+
|
254
|
+
# Converts a String into its digits-only form, i.e. all characters a-z are replaced with their corresponding
|
255
|
+
# digit sequences, according to the IBAN specification.
|
256
|
+
#
|
257
|
+
# @param [String] string
|
258
|
+
# The string to convert into its numeric form.
|
259
|
+
#
|
260
|
+
# @return [String] The string in its numeric, digits-only form.
|
261
|
+
def self.numerify(string)
|
262
|
+
string.downcase.gsub(/\D/) { |char|
|
263
|
+
CharacterCodes.fetch(char) {
|
264
|
+
raise ArgumentError, "The string contains an invalid character #{char.inspect}"
|
265
|
+
}
|
266
|
+
}.to_i
|
267
|
+
end
|
268
|
+
|
269
|
+
# @return [String] The standard form of the IBAN for machine communication, without spaces, encoded in Encoding::BINARY.
|
270
|
+
attr_reader :compact
|
271
|
+
|
272
|
+
# @return [String] The ISO-3166 2-letter country code (first and second character).
|
273
|
+
attr_reader :country
|
274
|
+
|
275
|
+
# @return [ISO::IBAN::Specification] The specification for this IBAN (determined by its country).
|
276
|
+
attr_reader :specification
|
277
|
+
|
278
|
+
# @param [String] iban
|
279
|
+
# The IBAN number, must be in compact form. Use ISO::IBAN::parse for formatted IBANs.
|
280
|
+
def initialize(iban)
|
281
|
+
raise ArgumentError, "String expected for iban, but got #{iban.class}" unless iban.is_a?(String)
|
282
|
+
|
283
|
+
@compact = iban.b
|
284
|
+
@country = iban[0,2]
|
285
|
+
@specification = self.class.specification(@country, nil)
|
286
|
+
end
|
287
|
+
|
288
|
+
# @example Formatted IBAN
|
289
|
+
#
|
290
|
+
# ISO::IBAN.new('CH')
|
291
|
+
#
|
292
|
+
# @return [String] The IBAN in its formatted form, which is more human readable than the compact form.
|
293
|
+
def formatted
|
294
|
+
@_formatted ||= @compact.gsub(/.{4}(?=.)/, '\0 ')
|
295
|
+
end
|
296
|
+
|
297
|
+
# @return [Integer, nil]
|
298
|
+
# IBAN in its numeric form, i.e. all characters a-z are replaced with their corresponding
|
299
|
+
# digit sequences.
|
300
|
+
# Will return nil if the IBAN is shorter than 5 characters (which makes it invalid).
|
301
|
+
def numeric
|
302
|
+
@compact.size < 5 ? nil : self.class.numerify(@compact[4..-1]+@compact[0,4])
|
303
|
+
end
|
304
|
+
|
305
|
+
# @return [true, false]
|
306
|
+
# Whether the IBAN is valid.
|
307
|
+
# See {#validate} for details.
|
308
|
+
def valid?
|
309
|
+
valid_country? && valid_checksum? && valid_length? && valid_format?
|
310
|
+
end
|
311
|
+
|
312
|
+
# @note
|
313
|
+
# The class method {ISO::IBAN.validate} uses {ISO::IBAN.parse}, which means it will strip whitespace and
|
314
|
+
# dashes from the IBAN.
|
315
|
+
# {ISO::IBAN#initialize} on the other hand expects the IBAN in compact format and will not strip
|
316
|
+
# those characters.
|
317
|
+
#
|
318
|
+
# Error codes:
|
319
|
+
#
|
320
|
+
# * :invalid_characters
|
321
|
+
# * :invalid_country
|
322
|
+
# * :invalid_checksum
|
323
|
+
# * :invalid_length
|
324
|
+
# * :invalid_format
|
325
|
+
#
|
326
|
+
# Invalid characters means that the IBAN contains characters which are not in the set of A-Za-z0-9. See {#invalid_characters}.
|
327
|
+
# Invalid country means the country is unknown (character 1 & 2 in the IBAN).
|
328
|
+
# Invalid checksum means the two check digits (character 3 & 4 in the IBAN).
|
329
|
+
# Invalid length means the IBAN does not comply with the length specified for the country of that IBAN.
|
330
|
+
# Invalid format means the IBAN does not comply with the format specified for the country of that IBAN.
|
331
|
+
#
|
332
|
+
# @return [Array<Symbol>] An array with a code of all validation errors, empty if valid.
|
333
|
+
def validate
|
334
|
+
errors = []
|
335
|
+
errors << :invalid_characters unless valid_characters?
|
336
|
+
errors << :invalid_country unless valid_country?
|
337
|
+
errors << :invalid_checksum unless valid_characters? && valid_checksum?
|
338
|
+
errors << :invalid_length unless valid_length?
|
339
|
+
errors << :invalid_format unless valid_format?
|
340
|
+
|
341
|
+
errors
|
342
|
+
end
|
343
|
+
|
344
|
+
# @return [String] The checksum digits in the IBAN (character 3 & 4).
|
345
|
+
def checksum_digits
|
346
|
+
@compact[2,2]
|
347
|
+
end
|
348
|
+
|
349
|
+
# @return [String] The BBAN of the IBAN (everything except the country code and check digits).
|
350
|
+
def bban
|
351
|
+
@compact[4..-1]
|
352
|
+
end
|
353
|
+
|
354
|
+
# @return [String, nil] The bank code part of the IBAN, nil if not applicable.
|
355
|
+
def bank_code
|
356
|
+
if @specification && @specification.bank_position_from && @specification.bank_position_to
|
357
|
+
@compact[@specification.bank_position_from..@specification.bank_position_to]
|
358
|
+
else
|
359
|
+
nil
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# @return [String, nil] The branch code part of the IBAN, nil if not applicable.
|
364
|
+
def branch_code
|
365
|
+
if @specification && @specification.branch_position_from && @specification.branch_position_to
|
366
|
+
@compact[@specification.branch_position_from..@specification.branch_position_to]
|
367
|
+
else
|
368
|
+
nil
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# @return [String] The account code part of the IBAN.
|
373
|
+
def account_code
|
374
|
+
@compact[((@specification.branch_position_to || @specification.bank_position_to || 3)+1)..-1]
|
375
|
+
end
|
376
|
+
|
377
|
+
# @example
|
378
|
+
# invalid = "hägar"
|
379
|
+
# invalid.encoding # => #<Encoding:UTF-8>
|
380
|
+
# ISO::IBAN.new(invalid).invalid_characters # => ["\xC3", "\xA4"]
|
381
|
+
# ISO::IBAN.new(invalid).invalid_characters('utf-8') # => ["ä"]
|
382
|
+
#
|
383
|
+
# @param [String, Encoding, nil] input_encoding
|
384
|
+
# ISO::IBAN::new interprets the passed IBAN as binary.
|
385
|
+
# If you got the IBAN from a source which is not binary, you should provide that encoding.
|
386
|
+
# Otherwise an invalid character may be split into multiple bytes.
|
387
|
+
#
|
388
|
+
# @return [Array] An Array with all invalid characters.
|
389
|
+
def invalid_characters(input_encoding=nil)
|
390
|
+
iban = input_encoding ? @compact.dup.force_encoding(input_encoding) : @compact
|
391
|
+
|
392
|
+
iban.gsub(/[A-Z0-9?]*/i, '').chars.to_a.uniq # to_a is for ruby <= 2.0 where String#chars returns an Enumerator
|
393
|
+
end
|
394
|
+
|
395
|
+
# @return [true, false] Whether IBAN consists only of valid characters.
|
396
|
+
def valid_characters?
|
397
|
+
@compact =~ /\A[A-Z]{2}(?:\d\d|\?\?)[A-Z0-9]*\z/in ? true : false
|
398
|
+
end
|
399
|
+
|
400
|
+
# @return [true, false] Whether the country of the IBAN is valid.
|
401
|
+
def valid_country?
|
402
|
+
@specification ? true : false
|
403
|
+
end
|
404
|
+
|
405
|
+
# @return [true, false] Whether the format of the IBAN is valid.
|
406
|
+
def valid_format?
|
407
|
+
@specification && @specification.iban_regex =~ @compact ? true : false
|
408
|
+
end
|
409
|
+
|
410
|
+
# @return [true, false] Whether the length of the IBAN is valid.
|
411
|
+
def valid_length?
|
412
|
+
@specification && @compact.size == @specification.iban_length ? true : false
|
413
|
+
end
|
414
|
+
|
415
|
+
# @return [true, false] Whether the checksum of the IBAN is valid.
|
416
|
+
def valid_checksum?
|
417
|
+
numerified = numeric()
|
418
|
+
|
419
|
+
numerified && (numerified % 97 == 1)
|
420
|
+
end
|
421
|
+
|
422
|
+
# See Object#<=>
|
423
|
+
#
|
424
|
+
# @return [-1, 0, 1, nil]
|
425
|
+
def <=>(other)
|
426
|
+
other.respond_to?(:compact) ? @compact <=> other.compact : nil
|
427
|
+
end
|
428
|
+
|
429
|
+
# Requires that the checksum digits were left as '??', replaces them with
|
430
|
+
# the proper checksum.
|
431
|
+
#
|
432
|
+
# @return [self]
|
433
|
+
def update_checksum!
|
434
|
+
raise "Checksum digit placeholders missing" unless @compact[2,2] == '??'
|
435
|
+
|
436
|
+
@compact[2,2] = calculated_check_digits
|
437
|
+
|
438
|
+
self
|
439
|
+
end
|
440
|
+
|
441
|
+
# @return [String] The check-digits as calculated from the IBAN.
|
442
|
+
def calculated_check_digits
|
443
|
+
"%02d" % (98-(self.class.numerify(bban+@country)*100)%97)
|
444
|
+
end
|
445
|
+
|
446
|
+
# @return [true, false]
|
447
|
+
# Whether two ISO::IBANs are equal.
|
448
|
+
# Comparison is based on class and IBAN number
|
449
|
+
def eql?(other)
|
450
|
+
self.class.equal?(other.class) && self == other
|
451
|
+
end
|
452
|
+
|
453
|
+
# @return [Integer]
|
454
|
+
# A hash value, see Object#hash
|
455
|
+
def hash
|
456
|
+
[self.class, @compact].hash
|
457
|
+
end
|
458
|
+
|
459
|
+
# See Object#inspect
|
460
|
+
def inspect
|
461
|
+
sprintf "#<%p %s>", self.class, formatted
|
462
|
+
end
|
463
|
+
|
464
|
+
# @return [String] The compact form of the IBAN as a String.
|
465
|
+
def to_s
|
466
|
+
@compact.dup
|
467
|
+
end
|
468
|
+
|
469
|
+
# @note
|
470
|
+
# This method is experimental. It might change or be removed in future versions!
|
471
|
+
#
|
472
|
+
# @return [Array]
|
473
|
+
# The individual IBAN components as defined by the SWIFT specification.
|
474
|
+
# An empty array if this IBAN does not have a specification.
|
475
|
+
def to_a
|
476
|
+
@_components ||= @specification ? @compact.match(@specification.iban_regex).captures : []
|
477
|
+
|
478
|
+
@_components.dup
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
@@ -68,9 +68,9 @@ module ISO
|
|
68
68
|
else
|
69
69
|
StructureCodes[code]+(fixed ? "{#{length}}" : "{,#{length}}")
|
70
70
|
end
|
71
|
-
}.join('')
|
71
|
+
}.join(')(')
|
72
72
|
|
73
|
-
anchored ? /\A#{source}\z/ :
|
73
|
+
anchored ? /\A(#{source})\z/ : /(#{source})/n
|
74
74
|
end
|
75
75
|
|
76
76
|
attr_reader :country_name,
|
@@ -99,7 +99,7 @@ module ISO
|
|
99
99
|
|
100
100
|
# @return [Regexp] A regex to verify the structure of the IBAN.
|
101
101
|
def iban_regex
|
102
|
-
@
|
102
|
+
@_iban_regex ||= self.class.structure_regex(@iban_structure)
|
103
103
|
end
|
104
104
|
|
105
105
|
# @return [Regexp] A regex to identify the structure of the IBAN, without anchors.
|
@@ -109,7 +109,7 @@ module ISO
|
|
109
109
|
|
110
110
|
# @return [Array<Integer>] An array with the lengths of all components.
|
111
111
|
def component_lengths
|
112
|
-
[bank_code_length, branch_code_length,
|
112
|
+
[bank_code_length, branch_code_length, account_code_length].tap { |lengths| lengths.delete(0) }
|
113
113
|
end
|
114
114
|
|
115
115
|
# @return [Fixnum]
|
@@ -126,7 +126,7 @@ module ISO
|
|
126
126
|
|
127
127
|
# @return [Fixnum]
|
128
128
|
# The length of the account code in the IBAN.
|
129
|
-
def
|
129
|
+
def account_code_length
|
130
130
|
bban_length-bank_code_length-branch_code_length
|
131
131
|
end
|
132
132
|
|
data/lib/iso/iban/version.rb
CHANGED
data/test/unit/lib/iso/iban.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'iso/iban'
|
3
|
+
require 'iso/iban/no_autoload'
|
4
4
|
|
5
5
|
suite "ISO::IBAN" do
|
6
6
|
setup do
|
@@ -28,7 +28,7 @@ suite "ISO::IBAN" do
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
test 'ISO::IBAN::generate problem, TODO' do
|
31
|
+
test 'ISO::IBAN::generate problem, TODO (this test is expected to fail at the moment)' do
|
32
32
|
ISO::IBAN.instance_variable_set(:@specifications, {'BG' => ISO::IBAN::Specification.new("Bulgaria", "BG", "BG2!n4!a4!n2!n8!c", 22, "4!a4!n2!n8!c", 18, 4, 7, 8, 11)})
|
33
33
|
assert ISO::IBAN.generate('BG', 'AAAA', '2', 'C').valid? # this works now
|
34
34
|
assert ISO::IBAN.generate('BG', 'A', '2', 'C').valid? # this still fails, because ISO::IBAN::generate can't pad 'a' format fields
|
@@ -73,73 +73,89 @@ suite "ISO::IBAN" do
|
|
73
73
|
assert_equal [:invalid_length, :invalid_format], ISO::IBAN.validate('CH83 X234 5987 6543 2109 AB')
|
74
74
|
assert_equal [:invalid_checksum, :invalid_length, :invalid_format], ISO::IBAN.validate('CH99 X234 5987 6543 2109 AB')
|
75
75
|
assert_equal [:invalid_country, :invalid_checksum, :invalid_length, :invalid_format], ISO::IBAN.validate('XX35 1234 5987 6543 2109 A')
|
76
|
+
assert_equal [:invalid_characters, :invalid_checksum, :invalid_length, :invalid_format], ISO::IBAN.validate('CH35 1234 5987 6543 2109 Ä')
|
77
|
+
assert_equal [:invalid_characters, :invalid_country, :invalid_checksum, :invalid_length, :invalid_format], ISO::IBAN.validate('XX35 1234 5987 6543 2109 Ä')
|
78
|
+
end
|
79
|
+
|
80
|
+
test "ISO::IBAN::parse" do
|
81
|
+
assert_kind_of ISO::IBAN, ISO::IBAN.parse('CH35 1234 5987 6543 2109 A')
|
82
|
+
assert_equal 'CH351234598765432109A', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').compact
|
76
83
|
end
|
77
84
|
|
78
85
|
test "ISO::IBAN::new" do
|
79
|
-
assert_kind_of ISO::IBAN, ISO::IBAN.new('CH35 1234 5987 6543 2109 A')
|
80
86
|
assert_kind_of ISO::IBAN, ISO::IBAN.new('CH351234598765432109A')
|
81
87
|
end
|
82
88
|
|
83
89
|
test "ISO::IBAN#formatted" do
|
84
|
-
assert_equal 'CH35 1234 5987 6543 2109 A', ISO::IBAN.
|
85
|
-
assert_equal 'CH35 1234 5987 6543 2109 A', ISO::IBAN.
|
90
|
+
assert_equal 'CH35 1234 5987 6543 2109 A', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').formatted
|
91
|
+
assert_equal 'CH35 1234 5987 6543 2109 A', ISO::IBAN.parse('CH351234598765432109A').formatted
|
86
92
|
end
|
87
93
|
|
88
94
|
test "ISO::IBAN#compact" do
|
89
|
-
assert_equal 'CH351234598765432109A', ISO::IBAN.
|
90
|
-
assert_equal 'CH351234598765432109A', ISO::IBAN.
|
95
|
+
assert_equal 'CH351234598765432109A', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').compact
|
96
|
+
assert_equal 'CH351234598765432109A', ISO::IBAN.parse('CH351234598765432109A').compact
|
91
97
|
end
|
92
98
|
|
93
99
|
test "ISO::IBAN#to_s" do
|
94
|
-
assert_equal 'CH351234598765432109A', ISO::IBAN.
|
95
|
-
assert_equal 'CH351234598765432109A', ISO::IBAN.
|
100
|
+
assert_equal 'CH351234598765432109A', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').to_s
|
101
|
+
assert_equal 'CH351234598765432109A', ISO::IBAN.parse('CH351234598765432109A').to_s
|
96
102
|
end
|
97
103
|
|
98
104
|
test "ISO::IBAN#country" do
|
99
|
-
assert_equal 'CH', ISO::IBAN.
|
105
|
+
assert_equal 'CH', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').country
|
100
106
|
end
|
101
107
|
|
102
108
|
test "ISO::IBAN#checksum_digits" do
|
103
|
-
assert_equal '35', ISO::IBAN.
|
109
|
+
assert_equal '35', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').checksum_digits
|
104
110
|
end
|
105
111
|
|
106
112
|
test "ISO::IBAN#bban" do
|
107
|
-
assert_equal '1234598765432109A', ISO::IBAN.
|
113
|
+
assert_equal '1234598765432109A', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').bban
|
108
114
|
end
|
109
115
|
|
110
116
|
test "ISO::IBAN#bank_code" do
|
111
|
-
assert_equal '12345', ISO::IBAN.
|
117
|
+
assert_equal '12345', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').bank_code
|
112
118
|
end
|
113
119
|
|
114
120
|
test "ISO::IBAN#branch_code" do
|
115
121
|
ISO::IBAN.instance_variable_set(:@specifications, {'BG' => ISO::IBAN::Specification.new("Bulgaria", "BG", "BG2!n4!a4!n2!n8!c", 22, "4!a4!n2!n8!c", 18, 4, 7, 8, 11)})
|
116
|
-
assert_equal "0002", ISO::IBAN.
|
122
|
+
assert_equal "0002", ISO::IBAN.parse('BG69 0001 0002 0300 0000 04').branch_code
|
117
123
|
end
|
118
124
|
|
119
125
|
test "ISO::IBAN#account_code" do
|
120
|
-
assert_equal '98765432109A', ISO::IBAN.
|
126
|
+
assert_equal '98765432109A', ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').account_code
|
121
127
|
end
|
122
128
|
|
123
129
|
test "ISO::IBAN#valid?" do
|
124
|
-
assert_equal true, ISO::IBAN.
|
125
|
-
assert_equal false, ISO::IBAN.
|
130
|
+
assert_equal true, ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').valid?
|
131
|
+
assert_equal false, ISO::IBAN.parse('CH99 1234 5987 6543 2109 A').valid?
|
126
132
|
end
|
127
133
|
|
128
134
|
test "ISO::IBAN#validate" do
|
129
|
-
assert_equal [], ISO::IBAN.
|
135
|
+
assert_equal [], ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').validate
|
136
|
+
end
|
137
|
+
|
138
|
+
test 'ISO::IBAN#invalid_characters' do
|
139
|
+
assert_equal ["\xC3".b, "\x84".b], ISO::IBAN.parse('Ä').invalid_characters
|
140
|
+
assert_equal ['Ä'], ISO::IBAN.parse('Ä').invalid_characters('utf-8')
|
130
141
|
end
|
131
142
|
|
132
143
|
test "ISO::IBAN#<=>" do
|
133
144
|
iban0 = ISO::IBAN.generate('CH', '0', '0')
|
134
145
|
iban1 = ISO::IBAN.generate('CH', '0', '97') # 97 to have the same checksum
|
135
|
-
assert_equal
|
136
|
-
assert_equal
|
137
|
-
assert_equal
|
138
|
-
assert_equal
|
139
|
-
assert_equal
|
146
|
+
assert_equal( -1, iban0 <=> iban1 )
|
147
|
+
assert_equal( 0, iban0 <=> iban0 )
|
148
|
+
assert_equal( 1, iban1 <=> iban0 )
|
149
|
+
assert_equal(nil, iban0 <=> "incomparable")
|
150
|
+
assert_equal(nil, "incomparable" <=> iban0)
|
151
|
+
end
|
152
|
+
|
153
|
+
test "ISO::IBAN#to_a" do
|
154
|
+
assert_equal %w[CH 35 12345 98765432109A], ISO::IBAN.parse("CH351234598765432109A").to_a
|
155
|
+
assert_equal [], ISO::IBAN.parse("XX351234598765432109A").to_a # no specification
|
140
156
|
end
|
141
157
|
|
142
158
|
test "ISO::IBAN#inspect" do
|
143
|
-
assert_equal "#<ISO::IBAN CH35 1234 5987 6543 2109 A>", ISO::IBAN.
|
159
|
+
assert_equal "#<ISO::IBAN CH35 1234 5987 6543 2109 A>", ISO::IBAN.parse('CH35 1234 5987 6543 2109 A').inspect
|
144
160
|
end
|
145
161
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iso-iban
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Rusterholz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |-
|
14
14
|
ISO::IBAN implements IBAN (International Bank Account Number) specification as per ISO 13616-1.
|
@@ -18,42 +18,54 @@ executables: []
|
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
|
+
- ".yardopts"
|
22
|
+
- LICENSE.txt
|
23
|
+
- README.markdown
|
24
|
+
- Rakefile
|
21
25
|
- data/iso-iban/specs.yaml
|
26
|
+
- iso-iban.gemspec
|
27
|
+
- lib/iso/iban.rb
|
22
28
|
- lib/iso/iban/autoload.rb
|
29
|
+
- lib/iso/iban/backports.rb
|
30
|
+
- lib/iso/iban/invalid.rb
|
31
|
+
- lib/iso/iban/no_autoload.rb
|
23
32
|
- lib/iso/iban/specification.rb
|
24
33
|
- lib/iso/iban/version.rb
|
25
|
-
- lib/iso/iban.rb
|
26
34
|
- test/data/test_spec.yaml
|
27
35
|
- test/lib/helper.rb
|
28
36
|
- test/runner.rb
|
29
37
|
- test/tmp/test_spec.yaml
|
30
38
|
- test/unit/lib/iso/iban.rb
|
31
|
-
- iso-iban.gemspec
|
32
|
-
- .yardopts
|
33
|
-
- LICENSE.txt
|
34
|
-
- Rakefile
|
35
|
-
- README.markdown
|
36
39
|
homepage: https://github.com/apeiros/iso-iban
|
37
40
|
licenses:
|
38
41
|
- BSD 2-Clause
|
39
42
|
metadata: {}
|
40
|
-
post_install_message:
|
43
|
+
post_install_message: |-
|
44
|
+
IMPORTANT!
|
45
|
+
|
46
|
+
As of 0.1.0, there are 2 backward incompatible changes:
|
47
|
+
|
48
|
+
* `require 'iso/iban/autoload'` is deprecated. Please use plain `require 'iso/iban'`.
|
49
|
+
To load ISO::IBAN without loading the specifications, please use
|
50
|
+
`require 'iso/iban/no_autoload'`.
|
51
|
+
* ISO::IBAN.new no longer accepts formatted input.
|
52
|
+
Use ISO::IBAN.parse if your input is potentially not in the compact format.
|
41
53
|
rdoc_options: []
|
42
54
|
require_paths:
|
43
55
|
- lib
|
44
56
|
required_ruby_version: !ruby/object:Gem::Requirement
|
45
57
|
requirements:
|
46
|
-
- -
|
58
|
+
- - ">="
|
47
59
|
- !ruby/object:Gem::Version
|
48
60
|
version: 1.9.2
|
49
61
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
62
|
requirements:
|
51
|
-
- -
|
63
|
+
- - ">"
|
52
64
|
- !ruby/object:Gem::Version
|
53
65
|
version: 1.3.1
|
54
66
|
requirements: []
|
55
67
|
rubyforge_project:
|
56
|
-
rubygems_version: 2.1
|
68
|
+
rubygems_version: 2.2.1
|
57
69
|
signing_key:
|
58
70
|
specification_version: 3
|
59
71
|
summary: Utilities for International Bank Account Numbers (IBAN) as per ISO 13616-1.
|