ibandit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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