ibandit 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.
@@ -0,0 +1,528 @@
1
+ module Ibandit
2
+ module IBANBuilder
3
+ SUPPORTED_COUNTRY_CODES = %w(AT BE CY DE EE ES FI FR GB IE IT LU LV MC NL PT
4
+ SI SK SM).freeze
5
+
6
+ def self.build(opts)
7
+ country_code = opts.delete(:country_code)
8
+
9
+ if country_code.nil?
10
+ raise ArgumentError, 'You must provide a country_code'
11
+ elsif !SUPPORTED_COUNTRY_CODES.include?(country_code)
12
+ msg = "Don't know how to build an IBAN for country code #{country_code}"
13
+ raise UnsupportedCountryError, msg
14
+ else
15
+ require_fields(country_code, opts)
16
+ bban = send(:"build_#{country_code.downcase}_bban", opts)
17
+ build_iban(country_code, bban)
18
+ end
19
+ end
20
+
21
+ ##################################
22
+ # Country-specific BBAN creation #
23
+ ##################################
24
+
25
+ def self.build_at_bban(opts)
26
+ # Local account details format:
27
+ # aaaaaaaaaaa bbbbb
28
+ # Account number may be 4-11 digits long
29
+ #
30
+ # Local account details name(s):
31
+ # Bank code: 'Bankleitzahl' or 'BLZ'
32
+ # Account number: 'Kontonummer' or 'Kto.-Nr'
33
+ #
34
+ # BBAN-specific check digits: none
35
+ #
36
+ # Other check digits:
37
+ # Austrian account numbers have built-in check digits. The checking
38
+ # rules are not public.
39
+ #
40
+ # Padding:
41
+ # Add leading zeros to account number if < 11 digits.
42
+ [
43
+ opts[:bank_code],
44
+ opts[:account_number].rjust(11, '0')
45
+ ].join
46
+ end
47
+
48
+ def self.build_be_bban(opts)
49
+ # Local account details format: bbb-aaaaaaa-cc
50
+ #
51
+ # Local account details name(s):
52
+ # Single name for all fields: "Rekeningnummer / Numéro de compte"
53
+ # All fields are entered in one block, separated by hyphens for clarity
54
+ #
55
+ # BBAN-specific check digits: none
56
+ #
57
+ # Other check digits:
58
+ # The last two digits of the account number are check digits, but these
59
+ # are considered integral to the account number itself. See
60
+ # CheckDigit#belgian for details of their calculation.
61
+ #
62
+ # Additional info:
63
+ # The first three digits of Belgian account numbers are the bank_code,
64
+ # but the account number is not considered complete without these three
65
+ # numbers and the IBAN structure file includes them in its definition of
66
+ # the account number. As a result, this method ignores all arguments
67
+ # other than the account number.
68
+ opts[:account_number].gsub('-', '')
69
+ end
70
+
71
+ def self.build_cy_bban(opts)
72
+ # Local account details format:
73
+ # bbb-sssss aaaaaaaaaaaaaaaa
74
+ # Account number may be 7-16 digits long
75
+ #
76
+ # Local account details name(s):
77
+ # Bank code: 'Kodikos Trapezas'
78
+ # Branch code: 'Kodikos Katastimatos'
79
+ # Account number: 'Arithmos Logariasmou'
80
+ #
81
+ # BBAN-specific check digits: none
82
+ #
83
+ # Other check digits:
84
+ # Some Cypriot banks may be using check digits in their account numbers,
85
+ # but there's no central source of them.
86
+ #
87
+ # Padding:
88
+ # Add leading zeros to account number if < 16 digits.
89
+ #
90
+ # Additional info:
91
+ # Cypriot bank and branch codes are often communicated as a single code,
92
+ # so this method handles being passed them together or separately.
93
+ combined_bank_code = opts[:bank_code]
94
+ combined_bank_code += opts[:branch_code] || ''
95
+
96
+ [
97
+ combined_bank_code,
98
+ opts[:account_number].rjust(16, '0')
99
+ ].join
100
+ end
101
+
102
+ def self.build_de_bban(opts)
103
+ # Local account details format:
104
+ # bbbbbbbb aaaaaaaaaa
105
+ # Account number may be 1-10 digits long
106
+ #
107
+ # Local account details name(s):
108
+ # Bank code: 'Bankleitzahl' or 'BLZ'
109
+ # Account number: 'Kontonummer' or 'Kto.-Nr'
110
+ #
111
+ # BBAN-specific check digits: none
112
+ #
113
+ # Other check digits:
114
+ # Local bank validation is carried out by matching the bank code with
115
+ # the right algorithm key, then applying that bank-specific algorithm.
116
+ #
117
+ # Padding:
118
+ # Add leading zeros to account number if < 10 digits.
119
+ #
120
+ # Additional info:
121
+ # There are many exceptions to the way German bank details translate
122
+ # into an IBAN, detailed into a 200 page document compiled by the
123
+ # Bundesbank.
124
+ [
125
+ opts[:bank_code],
126
+ opts[:account_number].rjust(10, '0')
127
+ ].join
128
+ end
129
+
130
+ def self.build_ee_bban(opts)
131
+ # Local account details format:
132
+ # bbaaaaaaaaaaax
133
+ # Account number may be up to 14 characters long
134
+ # Bank code can be found by extracted from the first two digits of the
135
+ # account number and converted using the rules at
136
+ # http://www.pangaliit.ee/en/settlements-and-standards/bank-codes-of-estonian-banks
137
+ #
138
+ # Local account details name(s):
139
+ # Account number: 'Kontonumber'
140
+ #
141
+ # BBAN-specific check digits: none
142
+ #
143
+ # Other check digits:
144
+ # The last digit of the account number is a check digit, but this is
145
+ # built-in. An implementation of the check digit algorithm is available
146
+ # in CheckDigit#estonian.
147
+ #
148
+ # Padding:
149
+ # Add leading zeros to account number if < 14 digits.
150
+ #
151
+ # Additional info:
152
+ # Estonian national bank details were replaced with IBANs in Feb 2014.
153
+ # All Estonian payers should therefore know their IBAN.
154
+ domestic_bank_code = opts[:account_number].gsub(/\A0+/, '').slice(0, 2)
155
+
156
+ case domestic_bank_code
157
+ when '11' then iban_bank_code = '22'
158
+ when '93' then iban_bank_code = '00'
159
+ else iban_bank_code = domestic_bank_code
160
+ end
161
+
162
+ iban_bank_code + opts[:account_number].rjust(14, '0')
163
+ end
164
+
165
+ def self.build_es_bban(opts)
166
+ # Local account details format:
167
+ # bbbb-ssss-xx-aaaaaaaaaa
168
+ # Usually not separated, except by spaces or dashes
169
+ #
170
+ # Local account details name(s):
171
+ # Full details (20 digits): Código Cuenta Cliente
172
+ # Bank code (first 4 digits): Código de entidad
173
+ # Branch code (next 4 digits): Código de oficina
174
+ # Check digits (next 2 ditigs): Dígitos de control
175
+ # Account number (final 10 digits): Número de cuenta
176
+ #
177
+ # BBAN-specific check digits: none
178
+ #
179
+ # Other check digits:
180
+ # The 2 check digits described above are part of the "account number" as
181
+ # defined by SWIFT. See CheckDigit#spanish for their generation.
182
+ #
183
+ # Padding: None
184
+ #
185
+ # Additional info:
186
+ # This method supports being passed the component IBAN parts, as defined
187
+ # by SWIFT, or a single 20 digit string.
188
+ if opts.include?(:bank_code) && opts.include?(:branch_code)
189
+ [
190
+ opts[:bank_code],
191
+ opts[:branch_code],
192
+ opts[:account_number]
193
+ ].join
194
+ else
195
+ opts[:account_number].gsub('-', '')
196
+ end
197
+ end
198
+
199
+ def self.build_fi_bban(opts)
200
+ # Local account details format:
201
+ # bbbbbb-aaaaaaax
202
+ # Usually two joined fields separated by a hyphen
203
+ #
204
+ # Local account details name(s):
205
+ # Full details: 'Tilinumeron rakenne'
206
+ #
207
+ # BBAN-specific check digits: none
208
+ #
209
+ # Other check digits:
210
+ # The last digit of the account number is a check digit. The check digit
211
+ # algorithm is available in CheckDigit#lund.
212
+ #
213
+ # Padding:
214
+ # Finnish account numbers need to be expanded into "electronic format"
215
+ # by adding zero-padding. The expansion method depends on the first
216
+ # character of the bank code.
217
+ if %w(4 5 6).include?(opts[:bank_code][0])
218
+ [
219
+ opts[:bank_code],
220
+ opts[:account_number][0],
221
+ opts[:account_number][1..-1].rjust(7, '0')
222
+ ].join
223
+ else
224
+ opts[:bank_code] + opts[:account_number].rjust(8, '0')
225
+ end
226
+ end
227
+
228
+ def self.build_fr_bban(opts)
229
+ # Local account details format:
230
+ # bbbbb-sssss-aaaaaaaaaaa-xx
231
+ # 4 separated fields
232
+ #
233
+ # Local account details name(s):
234
+ # Bank code (5 digits): 'Code banque'
235
+ # Branch code (5 digits): 'Code guichet'
236
+ # Account number (max 11 digits): 'Numéro de compte'
237
+ # Check digits (2 digits): 'Clé RIB'
238
+ #
239
+ # BBAN-specific check digits: none
240
+ #
241
+ # Other check digits:
242
+ # French BBANs include two "RIB key" check digits. In the SWIFT IBAN
243
+ # structure definition these check digits are part of the account
244
+ # number, although customers expect to see them as a separate field.
245
+ # You should concatenate the account number with the entered check
246
+ # digits when using this method.
247
+ #
248
+ # Padding: None
249
+ [
250
+ opts[:bank_code],
251
+ opts[:branch_code],
252
+ opts[:account_number]
253
+ ].join
254
+ end
255
+
256
+ def self.build_gb_bban(opts)
257
+ # Local account details format:
258
+ # ssssss aaaaaaaa
259
+ # 2 separated fields
260
+ #
261
+ # Local account details name(s):
262
+ # Branch code: Sort code
263
+ # Account number: Account number
264
+ #
265
+ # BBAN-specific check digits: none
266
+ #
267
+ # Other check digits:
268
+ # Local bank validation is carried out on a bank-by-bank basis, and out
269
+ # of scope for this gem.
270
+ #
271
+ # Padding:
272
+ # Add leading zeros to account number if < 8 digits.
273
+ #
274
+ # Additional info:
275
+ # UK BBANs include the first four characters of the BIC. This requires a
276
+ # BIC finder lambda to be defined, or the bank_code to be supplied.
277
+ branch_code = opts[:branch_code].gsub(/[-\s]/, '')
278
+
279
+ if opts[:bank_code]
280
+ bank_code = opts[:bank_code]
281
+ else
282
+ bic = Ibandit.find_bic('GB', branch_code)
283
+ raise BicNotFoundError, 'BIC finder failed to find a BIC.' if bic.nil?
284
+ bank_code = bic.slice(0, 4)
285
+ end
286
+
287
+ [
288
+ bank_code,
289
+ branch_code,
290
+ opts[:account_number].gsub(/[-\s]/, '').rjust(8, '0')
291
+ ].join
292
+ end
293
+
294
+ def self.build_lu_bban(opts)
295
+ # Additional info:
296
+ # Luxembourgian national bank details were replaced with IBANs in 2002.
297
+ # All Luxembourgian payers should therefore know their IBAN, and are
298
+ # unlikely to know how it breaks down. This method is included for
299
+ # consistency with the IBAN structure only.
300
+ [opts[:bank_code], opts[:account_number]].join
301
+ end
302
+
303
+ def self.build_lv_bban(opts)
304
+ # Additional info:
305
+ # Latvian national bank details were replaced with IBANs in 2004.
306
+ # All Latvian payers should therefore know their IBAN, and are
307
+ # unlikely to know how it breaks down. This method is included for
308
+ # consistency with the IBAN structure only.
309
+ [opts[:bank_code], opts[:account_number]].join
310
+ end
311
+
312
+ def self.build_ie_bban(opts)
313
+ # Ireland uses the same BBAN construction method as the United Kingdom
314
+ branch_code = opts[:branch_code].gsub(/[-\s]/, '')
315
+
316
+ if opts[:bank_code]
317
+ bank_code = opts[:bank_code]
318
+ else
319
+ bic = Ibandit.find_bic('IE', branch_code)
320
+ raise BicNotFoundError, 'BIC finder failed to find a BIC.' if bic.nil?
321
+ bank_code = bic.slice(0, 4)
322
+ end
323
+
324
+ [
325
+ bank_code,
326
+ branch_code,
327
+ opts[:account_number].gsub(/[-\s]/, '').rjust(8, '0')
328
+ ].join
329
+ end
330
+
331
+ def self.build_it_bban(opts)
332
+ # Local account details format:
333
+ # x/bbbbb/sssss/cccccccccccc
334
+ # 4 fields, separated by slashes
335
+ # The check digit is NOT included in the any of the other SWIFT
336
+ # elements, so should be passed explicitly or left blank for it to be
337
+ # calculated implicitly
338
+ #
339
+ # Local bank details name(s):
340
+ # Check digit: 'CIN'
341
+ # Bank code: 'Codice ABI'
342
+ # Branch code: 'CAB'
343
+ # Account number: 'Numero di conto'
344
+ #
345
+ # BBAN-specific check digits:
346
+ # Italian BBANs include a single BBAN-specific check digit, calculated
347
+ # using a bespoke algorithm. See CheckDigit#italian.
348
+ #
349
+ # Padding:
350
+ # Add leading zeros to account number if < 10 digits.
351
+ combined_code = [
352
+ opts[:bank_code],
353
+ opts[:branch_code],
354
+ opts[:account_number].rjust(12, '0')
355
+ ].join
356
+
357
+ check_digit = opts[:check_digit] || CheckDigit.italian(combined_code)
358
+
359
+ [check_digit, combined_code].join
360
+ end
361
+
362
+ def self.build_mc_bban(opts)
363
+ # Monaco uses the same BBAN construction method as France
364
+ build_fr_bban(opts)
365
+ end
366
+
367
+ def self.build_nl_bban(opts)
368
+ # Local account details format:
369
+ # aaaaaaaaaa
370
+ # 1 field for account number only
371
+ # In theory the bank code can be looked up from the account number, but
372
+ # we don't currently have a way of doing so.
373
+ #
374
+ # Local bank details name(s):
375
+ # Account number: 'Rekeningnummer'
376
+ #
377
+ # BBAN-specific check digits: none
378
+ #
379
+ # Other check digits:
380
+ # A modulus 11 check can be applied to Dutch IBANs. See CheckDigit#dutch
381
+ # for an implementation.
382
+ #
383
+ # Padding:
384
+ # Add leading zeros to account number if < 10 digits.
385
+ [
386
+ opts[:bank_code],
387
+ opts[:account_number].rjust(10, '0')
388
+ ].join
389
+ end
390
+
391
+ def self.build_pt_bban(opts)
392
+ # Local account details format:
393
+ # bbbb.ssss.ccccccccccc.xx
394
+ # Usually presented in one block
395
+ #
396
+ # Local account details name(s):
397
+ # Full details: Número de Identificaçao Bancária (NIB)
398
+ # Bank code: Código de Banco
399
+ # Branch code: Código de Balcao
400
+ # Account number: Número de conta
401
+ # Local check digits: Dígitos de controlo
402
+ #
403
+ # BBAN-specific check digits:
404
+ # Technically none, but see below
405
+ #
406
+ # Other check digits:
407
+ # The last two digits of Portuguese account numbers, as defined by
408
+ # SWIFT, are check digits, calculated using the same algorithm as the
409
+ # overall IBAN check digits (i.e., mod_97_10). However, customers expect
410
+ # to see these check digits as a separate field. You should concatenate
411
+ # the account number with the entered check digits when using this
412
+ # method.
413
+ #
414
+ # Additional info:
415
+ # A side-effect of Portugal using the same algorithm for its local check
416
+ # digits as the overall IBAN check digits is that the overall digits are
417
+ # always 50.
418
+ [
419
+ opts[:bank_code],
420
+ opts[:branch_code],
421
+ opts[:account_number]
422
+ ].join
423
+ end
424
+
425
+ def self.build_si_bban(opts)
426
+ # Local account details format:
427
+ # bbbbb-aaaaaaaaxx
428
+ # Two fields, separated by a dash
429
+ #
430
+ # Local account details name(s):
431
+ # Full details: Transakcijski račun
432
+ #
433
+ # BBAN-specific check digits: none
434
+ #
435
+ # Other check digits:
436
+ # The last two digits of Slovenian account numbers, as defined by
437
+ # SWIFT, are check digits, calculated using the same algorithm as the
438
+ # overall IBAN check digits (i.e., mod_97_10).
439
+ #
440
+ # Additional info:
441
+ # A side-effect of Slovenia using the same algorithm for its local check
442
+ # digits as the overall IBAN check digits is that the overall digits are
443
+ # always 56.
444
+ [
445
+ opts[:bank_code],
446
+ opts[:account_number].rjust(10, '0')
447
+ ].join
448
+ end
449
+
450
+ def self.build_sk_bban(opts)
451
+ # Local account details format:
452
+ # pppppp-aaaaaaaaaa/bbbb
453
+ # Three fields (or two, if prefix and account number are merged)
454
+ #
455
+ # Local account details name(s):
456
+ # Account number prefix: Předčíslí
457
+ # Account number: číslo účtu
458
+ # Bank code: 'Kód banky'
459
+ #
460
+ # BBAN-specific check digits: none
461
+ #
462
+ # Other check digits:
463
+ # The last digits of the account_number_prefix and the account_number
464
+ # are check digits. See CheckDigit#slovakian_prefix and
465
+ # CheckDigit#slovakian_basic
466
+ #
467
+ # Additional info:
468
+ # The SWIFT definition of a Slovakian IBAN includes both the account
469
+ # number prefix and the account number. This method therefore supports
470
+ # passing those fields concatenated.
471
+ if opts.include?(:account_number_prefix)
472
+ [
473
+ opts[:bank_code],
474
+ opts[:account_number_prefix].rjust(6, '0'),
475
+ opts[:account_number].rjust(10, '0')
476
+ ].join
477
+ else
478
+ [
479
+ opts[:bank_code],
480
+ opts[:account_number].gsub('-', '').rjust(16, '0')
481
+ ].join
482
+ end
483
+ end
484
+
485
+ def self.build_sm_bban(opts)
486
+ # San Marino uses the same BBAN construction method as Italy
487
+ build_it_bban(opts)
488
+ end
489
+
490
+ ##################
491
+ # Helper methods #
492
+ ##################
493
+
494
+ def self.require_fields(country_code, opts)
495
+ required_fields(country_code).each do |arg|
496
+ next if opts[arg]
497
+
498
+ msg = "#{arg} is a required field when building an #{country_code} IBAN"
499
+ raise ArgumentError, msg
500
+ end
501
+ end
502
+
503
+ def self.required_fields(country_code)
504
+ case country_code
505
+ when 'AT', 'CY', 'DE', 'FI', 'LU', 'LV', 'NL', 'SI', 'SK'
506
+ %i(bank_code account_number)
507
+ when 'BE', 'EE', 'ES'
508
+ %i(account_number)
509
+ when 'GB', 'IE'
510
+ if Ibandit.bic_finder.nil? then %i(bank_code branch_code account_number)
511
+ else %i(branch_code account_number)
512
+ end
513
+ else
514
+ %i(bank_code branch_code account_number)
515
+ end
516
+ end
517
+
518
+ def self.build_iban(country_code, bban)
519
+ iban = [
520
+ country_code,
521
+ CheckDigit.iban(country_code, bban),
522
+ bban
523
+ ].join
524
+
525
+ IBAN.new(iban)
526
+ end
527
+ end
528
+ end