ibandit 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ibandit.rb CHANGED
@@ -4,7 +4,9 @@ require 'ibandit/errors'
4
4
  require 'ibandit/constants'
5
5
  require 'ibandit/iban'
6
6
  require 'ibandit/german_details_converter'
7
- require 'ibandit/swedish_details_converter'
7
+ require 'ibandit/sweden/local_details_converter'
8
+ require 'ibandit/sweden/validator'
9
+ require 'ibandit/sweden/bank_lookup'
8
10
  require 'ibandit/iban_splitter'
9
11
  require 'ibandit/iban_assembler'
10
12
  require 'ibandit/pseudo_iban_assembler'
data/lib/ibandit/iban.rb CHANGED
@@ -276,19 +276,47 @@ module Ibandit
276
276
  def valid_swedish_details?
277
277
  return true unless country_code == 'SE'
278
278
 
279
- bank_details = {
280
- bank_code: swift_bank_code,
281
- account_number: swift_account_number
282
- }
279
+ if branch_code
280
+ valid_swedish_local_details?
281
+ else
282
+ valid_swedish_swift_details?
283
+ end
284
+ end
283
285
 
284
- unless SwedishDetailsConverter.valid_bank_code?(bank_details)
286
+ def valid_swedish_swift_details?
287
+ unless Sweden::Validator.bank_code_exists?(swift_bank_code)
285
288
  bank_code_field = bank_code.nil? ? :account_number : :bank_code
286
289
  @errors[bank_code_field] = Ibandit.translate(:is_invalid)
287
290
  @errors.delete(:bank_code) if bank_code.nil?
288
291
  return false
289
292
  end
290
293
 
291
- unless SwedishDetailsConverter.valid_length?(bank_details)
294
+ length_valid =
295
+ Sweden::Validator.account_number_length_valid_for_bank_code?(
296
+ bank_code: swift_bank_code,
297
+ account_number: swift_account_number
298
+ )
299
+
300
+ unless length_valid
301
+ @errors[:account_number] = Ibandit.translate(:is_invalid)
302
+ return false
303
+ end
304
+
305
+ true
306
+ end
307
+
308
+ def valid_swedish_local_details?
309
+ unless Sweden::Validator.valid_clearing_code_length?(branch_code)
310
+ @errors[:branch_code] = Ibandit.translate(:is_invalid)
311
+ return false
312
+ end
313
+
314
+ valid_serial_number = Sweden::Validator.valid_serial_number_length?(
315
+ clearing_code: branch_code,
316
+ serial_number: account_number
317
+ )
318
+
319
+ unless valid_serial_number
292
320
  @errors[:account_number] = Ibandit.translate(:is_invalid)
293
321
  return false
294
322
  end
@@ -441,20 +441,19 @@ module Ibandit
441
441
  end
442
442
 
443
443
  def self.clean_se_details(local_details)
444
- converted_details = SwedishDetailsConverter.new(
445
- branch_code: local_details[:branch_code],
446
- account_number: local_details[:account_number]
447
- ).convert
448
-
449
- bank_code = local_details[:bank_code] ||
450
- converted_details[:swift_bank_code]
451
-
452
- {
453
- account_number: converted_details[:account_number],
454
- branch_code: converted_details[:branch_code],
455
- swift_bank_code: bank_code,
456
- swift_account_number: converted_details[:swift_account_number]
457
- }
444
+ if local_details[:bank_code]
445
+ # If a bank_code was provided without a branch code we're (probably)
446
+ # dealing with SWIFT details and should just return them.
447
+ return {
448
+ swift_account_number: local_details[:account_number],
449
+ swift_bank_code: local_details[:bank_code]
450
+ }
451
+ else
452
+ Sweden::LocalDetailsConverter.new(
453
+ branch_code: local_details[:branch_code],
454
+ account_number: local_details[:account_number]
455
+ ).convert
456
+ end
458
457
  end
459
458
 
460
459
  def self.clean_si_details(local_details)
@@ -0,0 +1,24 @@
1
+ module Ibandit
2
+ module Sweden
3
+ class BankLookup
4
+ def self.for_clearing_code(clearing_code)
5
+ code = clearing_code.to_s.slice(0, 4).to_i
6
+ bank_info_table.find { |bank| bank[:range].include?(code) }
7
+ end
8
+
9
+ def self.for_bank_code(bank_code)
10
+ bank_info_table.select { |bank| bank[:bank_code] == bank_code.to_i }
11
+ end
12
+
13
+ def self.bank_info_table
14
+ @swedish_bank_lookup ||=
15
+ begin
16
+ relative_path = '../../../../data/raw/swedish_bank_lookup.yml'
17
+ raw_info = YAML.load_file(File.expand_path(relative_path, __FILE__))
18
+
19
+ raw_info.map { |bank| bank.merge(range: Range.new(*bank[:range])) }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,92 @@
1
+ module Ibandit
2
+ module Sweden
3
+ class LocalDetailsConverter
4
+ # Converts local Swedish details into SWIFT details.
5
+ #
6
+ # Local details can be provided as either:
7
+ # - branch_code: clearing number, account_number: serial number
8
+ # - branch_code: nil, account_number: #{clearing number}#{serial number}
9
+ #
10
+ # The reverse conversion (extracting local details from SWIFT details) is
11
+ # not possible, since the clearing number cannot be derived. You should
12
+ # NOT pass this class a SWIFT account number, as it will not convert it to
13
+ # local details successfully.
14
+ def initialize(branch_code: nil, account_number: nil)
15
+ @branch_code = branch_code
16
+ @account_number = account_number
17
+ end
18
+
19
+ def convert
20
+ if bank_info.nil?
21
+ return { swift_bank_code: nil,
22
+ swift_account_number: cleaned_account_number.rjust(17, '0') }
23
+ end
24
+
25
+ {
26
+ account_number: serial_number,
27
+ branch_code: clearing_code,
28
+ swift_bank_code: bank_info.fetch(:bank_code).to_s,
29
+ swift_account_number: swift_account_number
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def cleaned_account_number
36
+ # Don't trim leading zeroes if the account number we are given is a
37
+ # serial number (i.e. if the clearing code is separate).
38
+ @cleaned_account_number ||= remove_bad_chars(@account_number)
39
+ end
40
+
41
+ def cleaned_branch_code
42
+ @cleaned_branch_code ||= remove_bad_chars(@branch_code)
43
+ end
44
+
45
+ def remove_bad_chars(number)
46
+ return if number.nil?
47
+ number.gsub(/[-.\s]/, '')
48
+ end
49
+
50
+ def bank_info
51
+ @bank_info ||= Sweden::BankLookup.for_clearing_code(bank_info_key)
52
+ end
53
+
54
+ def bank_info_key
55
+ (cleaned_branch_code || cleaned_account_number).slice(0, 4)
56
+ end
57
+
58
+ def clearing_code_length
59
+ bank_info.fetch(:clearing_code_length)
60
+ end
61
+
62
+ def serial_number_length
63
+ bank_info.fetch(:serial_number_length)
64
+ end
65
+
66
+ def clearing_code
67
+ cleaned_branch_code ||
68
+ cleaned_account_number.slice(0, clearing_code_length)
69
+ end
70
+
71
+ def serial_number
72
+ serial_number = if @branch_code.nil?
73
+ cleaned_account_number[clearing_code_length..-1]
74
+ else
75
+ cleaned_account_number
76
+ end
77
+
78
+ return serial_number unless bank_info.fetch(:zerofill_serial_number)
79
+
80
+ serial_number.rjust(serial_number_length, '0')
81
+ end
82
+
83
+ def swift_account_number
84
+ if bank_info.fetch(:include_clearing_code)
85
+ (clearing_code + serial_number).rjust(17, '0')
86
+ else
87
+ serial_number.rjust(17, '0')
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,75 @@
1
+ module Ibandit
2
+ module Sweden
3
+ class Validator
4
+ ###########################
5
+ # Local detail validators #
6
+ ###########################
7
+
8
+ def self.bank_code_exists_for_clearing_code?(clearing_code)
9
+ !Sweden::BankLookup.for_clearing_code(clearing_code).nil?
10
+ end
11
+
12
+ def self.valid_clearing_code_length?(clearing_code)
13
+ return unless bank_code_exists_for_clearing_code?(clearing_code)
14
+
15
+ bank_info = Sweden::BankLookup.for_clearing_code(clearing_code)
16
+ bank_info.fetch(:clearing_code_length) == clearing_code.to_s.length
17
+ end
18
+
19
+ def self.valid_serial_number_length?(clearing_code: nil,
20
+ serial_number: nil)
21
+ return unless bank_code_exists_for_clearing_code?(clearing_code)
22
+
23
+ bank_info = Sweden::BankLookup.for_clearing_code(clearing_code)
24
+ serial_number_length = bank_info.fetch(:serial_number_length)
25
+
26
+ if bank_info.fetch(:zerofill_serial_number)
27
+ serial_number = serial_number.rjust(serial_number_length, '0')
28
+ end
29
+
30
+ serial_number_length == serial_number.to_s.length
31
+ end
32
+
33
+ ###########################
34
+ # SWIFT detail validators #
35
+ ###########################
36
+
37
+ def self.bank_code_exists?(bank_code)
38
+ Sweden::BankLookup.for_bank_code(bank_code).any?
39
+ end
40
+
41
+ def self.bank_code_possible_for_account_number?(bank_code: nil,
42
+ account_number: nil)
43
+ return unless bank_code_exists?(bank_code)
44
+
45
+ clearing_code = account_number.gsub(/\A0+/, '').slice(0, 4).to_i
46
+ Sweden::BankLookup.for_bank_code(bank_code).any? do |bank|
47
+ !bank[:include_clearing_code] || bank[:range].include?(clearing_code)
48
+ end
49
+ end
50
+
51
+ def self.account_number_length_valid_for_bank_code?(bank_code: nil,
52
+ account_number: nil)
53
+ bank_code_possible = bank_code_possible_for_account_number?(
54
+ bank_code: bank_code,
55
+ account_number: account_number
56
+ )
57
+ return unless bank_code_possible
58
+
59
+ Sweden::BankLookup.for_bank_code(bank_code).any? do |bank|
60
+ length = bank.fetch(:serial_number_length)
61
+ length += bank[:clearing_code_length] if bank[:include_clearing_code]
62
+
63
+ cleaned_account_number = account_number.gsub(/\A0+/, '')
64
+ if bank[:zerofill_serial_number] && !bank[:include_clearing_code]
65
+ cleaned_account_number =
66
+ cleaned_account_number.
67
+ rjust(bank.fetch(:serial_number_length), '0')
68
+ end
69
+
70
+ cleaned_account_number.length == length
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,3 @@
1
1
  module Ibandit
2
- VERSION = '0.8.1'.freeze
2
+ VERSION = '0.8.2'.freeze
3
3
  end
@@ -1353,60 +1353,113 @@ describe Ibandit::IBAN do
1353
1353
  describe 'valid_swedish_details?' do
1354
1354
  subject { iban.valid_swedish_details? }
1355
1355
 
1356
- context 'with an account number that is too long' do
1357
- let(:arg) do
1358
- {
1359
- country_code: 'SE',
1360
- bank_code: '500',
1361
- account_number: '00000543910240391'
1362
- }
1363
- end
1356
+ context 'with SWIFT details' do
1357
+ context 'with an account number that is too long' do
1358
+ let(:arg) do
1359
+ {
1360
+ country_code: 'SE',
1361
+ bank_code: '500',
1362
+ account_number: '00000543910240391'
1363
+ }
1364
+ end
1364
1365
 
1365
- it { is_expected.to eq(false) }
1366
+ it { is_expected.to eq(false) }
1366
1367
 
1367
- context 'locale en', locale: :en do
1368
- specify do
1369
- iban.valid_swedish_details?
1370
- expect(iban.errors).to eq(account_number: 'is invalid')
1368
+ context 'locale en', locale: :en do
1369
+ specify do
1370
+ iban.valid_swedish_details?
1371
+ expect(iban.errors).to eq(account_number: 'is invalid')
1372
+ end
1371
1373
  end
1372
1374
  end
1373
- end
1374
1375
 
1375
- context "with an account number that doesn't have a bank code" do
1376
- let(:arg) do
1377
- {
1378
- country_code: 'SE',
1379
- bank_code: nil,
1380
- account_number: '00000000000010011'
1381
- }
1376
+ context "with an account number that doesn't have a bank code" do
1377
+ let(:arg) do
1378
+ {
1379
+ country_code: 'SE',
1380
+ bank_code: nil,
1381
+ account_number: '00000000000010011'
1382
+ }
1383
+ end
1384
+
1385
+ it { is_expected.to eq(false) }
1386
+
1387
+ context 'locale en', locale: :en do
1388
+ specify do
1389
+ iban.valid?
1390
+ expect(iban.errors).to include(account_number: 'is invalid')
1391
+ expect(iban.errors).to_not include(:bank_code)
1392
+ end
1393
+ end
1382
1394
  end
1383
1395
 
1384
- it { is_expected.to eq(false) }
1396
+ context 'with a bank code that does not match' do
1397
+ let(:arg) do
1398
+ {
1399
+ country_code: 'SE',
1400
+ bank_code: '902',
1401
+ account_number: '00000054391024039'
1402
+ }
1403
+ end
1385
1404
 
1386
- context 'locale en', locale: :en do
1387
- specify do
1388
- iban.valid?
1389
- expect(iban.errors).to include(account_number: 'is invalid')
1390
- expect(iban.errors).to_not include(:bank_code)
1405
+ it { is_expected.to eq(false) }
1406
+
1407
+ context 'locale en', locale: :en do
1408
+ specify do
1409
+ iban.valid_swedish_details?
1410
+ expect(iban.errors).to eq(account_number: 'is invalid')
1411
+ end
1391
1412
  end
1392
1413
  end
1393
1414
  end
1394
1415
 
1395
- context 'with a bad bank code' do
1396
- let(:arg) do
1397
- {
1398
- country_code: 'SE',
1399
- bank_code: '902',
1400
- account_number: '00000054391024039'
1401
- }
1416
+ context 'with local details' do
1417
+ context 'with good details' do
1418
+ let(:arg) do
1419
+ {
1420
+ country_code: 'SE',
1421
+ account_number: '5439-0240391'
1422
+ }
1423
+ end
1424
+
1425
+ it { is_expected.to eq(true) }
1402
1426
  end
1403
1427
 
1404
- it { is_expected.to eq(false) }
1428
+ context 'with a clearing code that is too long' do
1429
+ let(:arg) do
1430
+ {
1431
+ country_code: 'SE',
1432
+ branch_code: '54391',
1433
+ account_number: '0240391'
1434
+ }
1435
+ end
1405
1436
 
1406
- context 'locale en', locale: :en do
1407
- specify do
1408
- iban.valid_swedish_details?
1409
- expect(iban.errors).to eq(bank_code: 'is invalid')
1437
+ it { is_expected.to eq(false) }
1438
+
1439
+ context 'locale en', locale: :en do
1440
+ specify do
1441
+ iban.valid_swedish_details?
1442
+ expect(iban.errors).to eq(branch_code: 'is invalid')
1443
+ end
1444
+ end
1445
+ end
1446
+
1447
+ context 'with a serial number that is too long' do
1448
+ let(:arg) do
1449
+ {
1450
+ country_code: 'SE',
1451
+ branch_code: '5439',
1452
+ account_number: '024039111'
1453
+ }
1454
+ end
1455
+
1456
+ it { is_expected.to eq(false) }
1457
+
1458
+ context 'locale en', locale: :en do
1459
+ specify do
1460
+ iban.valid_swedish_details?
1461
+ expect(iban.errors).to eq(account_number: 'is invalid')
1462
+ end
1410
1463
  end
1411
1464
  end
1412
1465
  end