iso-iban 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|