ibandit 0.8.1 → 0.8.2

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.
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