ibandit 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53b3af5d1ef9ac7b61de43b1cc268380af1e1242
4
- data.tar.gz: 751d53c25ff31c8a44f7cd334f76dac3389d8c0e
3
+ metadata.gz: 0bc26110597f8350ef5b91b0b2fbee330c21bc7b
4
+ data.tar.gz: 3b1d66ea7ceb6b58f275d164e4e873c34416ddd4
5
5
  SHA512:
6
- metadata.gz: a8c6fa27a0efa6909f0783d5257bcce17ade3e21a1030078056854e8f3638e27568c61fe27485ab594f17bb9a0ce818206b521a463821e45f711fb23a50d1af8
7
- data.tar.gz: bf770b81e770a5ed0037ae3b8ef810e84043f9355eadef160fe8cfcde8fd8321c34a8d2be3392a39dc97ebdb15fd879b4907fef7196571a5523b691ec4d1cc79
6
+ metadata.gz: 32ad174ca626b5f73fff4472fc42699146b57d194a871b480cd30765efe7b74d7c918f9f92e75500b2367243521f128282730220818369d6105482ef257f8360
7
+ data.tar.gz: 9440bd30011a7d61f2dd2beec9ee115ab27c0ca9af6cea75b2022d48ac3e25dfc49e3bfcdeb44665e2c0fe3ce1c3978494e9cd4233c80d25a51c280e596938ee
@@ -12,7 +12,7 @@ LineLength:
12
12
  Max: 80
13
13
 
14
14
  ClassLength:
15
- Max: 300
15
+ Max: 400
16
16
 
17
17
  # Avoid single-line methods.
18
18
  SingleLineMethods:
@@ -39,5 +39,9 @@ Style/AsciiComments:
39
39
  Metrics/MethodLength:
40
40
  Max: 25
41
41
 
42
+ # Configuration parameters: CountComments.
43
+ Metrics/ModuleLength:
44
+ Max: 400
45
+
42
46
  Style/DotPosition:
43
47
  EnforcedStyle: 'trailing'
@@ -1,3 +1,7 @@
1
+ ## 0.8.0 - August 14, 2015
2
+ - Return local details for Sweden
3
+ - Introduce pseudo-IBANs for Sweden
4
+
1
5
  ## 0.7.0 - August 11, 2015
2
6
  - Remove all unused `CheckDigit` methods
3
7
 
data/README.md CHANGED
@@ -97,13 +97,13 @@ county combines them:
97
97
  `check_digits`
98
98
  : Two digits calculated using part of the ISO/IEC 7064:2003 standard
99
99
 
100
- `bank_code`
100
+ `swift_bank_code`
101
101
  : The SWIFT identifier for the bank to which the IBAN refers
102
102
 
103
- `branch_code`
103
+ `swift_branch_code`
104
104
  : The SWIFT identifer for the branch to which the IBAN refers (not used in all countries)
105
105
 
106
- `account_number`
106
+ `swift_account_number`
107
107
  : The account number for the account
108
108
 
109
109
  `iban_national_id`
@@ -116,9 +116,9 @@ iban = Ibandit::IBAN.new("GB82 WEST 1234 5698 7654 32")
116
116
 
117
117
  iban.country_code # => "GB"
118
118
  iban.check_digits # => "82"
119
- iban.bank_code # => "WEST"
120
- iban.branch_code # => "123456"
121
- iban.account_number # => "98765432"
119
+ iban.swift_bank_code # => "WEST"
120
+ iban.swift_branch_code # => "123456"
121
+ iban.swift_account_number # => "98765432"
122
122
  iban.iban_national_id # => "WEST123456"
123
123
  ```
124
124
 
@@ -131,6 +131,23 @@ iban = Ibandit::IBAN.new("ES12 1234 5678 9112 3456 7890")
131
131
  iban.local_check_digits # => "91"
132
132
  ```
133
133
 
134
+ In some countries, the SWIFT-defined details differ from the local details that
135
+ customers are familiar with. For this reason, there are also `bank_code`,
136
+ `branch_code` and `account_number` methods on an `IBAN` object. At present,
137
+ these only differ from the `swift_` equivalents for Swedish bank accounts.
138
+
139
+ ```ruby
140
+ iban = Ibandit::IBAN.new(
141
+ country_code: 'SE',
142
+ account_number: '7507-1211203'
143
+ )
144
+ iban.swift_account_number # => "75071211203"
145
+ iban.account_number # => "1211203"
146
+
147
+ iban.swift_branch_code # => nil
148
+ iban.branch_code # => "7507"
149
+ ```
150
+
134
151
  ### Initializing Ibandit
135
152
 
136
153
  The UK and Ireland both use part of the BIC as the `bank_code` in their IBANs.
@@ -420,6 +437,31 @@ iban = Ibandit::IBAN.new(
420
437
  iban.iban # => "GB60BARC20000055779911"
421
438
  ```
422
439
 
440
+ ### Pseudo-IBANs
441
+
442
+ In some countries, it is not possible to recover local banking details from an
443
+ IBAN. For this reason, Ibandit has a concept of a *pseudo-IBAN*. This is a
444
+ string with a similar structure to an IBAN that can be decomposed into local
445
+ banking details. Pseudo-IBANs can be recognized by the fact that they have `ZZ`
446
+ as the third and fourth characters (these would be check digits for a regular
447
+ IBAN).
448
+
449
+ ```
450
+ iban = Ibandit::IBAN.new(
451
+ country_code: 'SE',
452
+ branch_code: '7507',
453
+ account_number: '1211203'
454
+ )
455
+ iban.pseudo_iban # => "SEZZX7507XXX1211203"
456
+
457
+ iban = Ibandit::IBAN.new('SEZZX7507XXX1211203')
458
+ iban.country_code # => "SE"
459
+ iban.branch_code # => "7507"
460
+ iban.account_number # => "1211203"
461
+ ```
462
+
463
+ At present, pseudo-IBANs are only available for Swedish bank accounts.
464
+
423
465
  ## Other libraries
424
466
 
425
467
  Another gem, [iban-tools](https://github.com/alphasights/iban-tools), also
@@ -76,6 +76,10 @@ QA:
76
76
  SA:
77
77
  :bank_code_format: \d{2}
78
78
  :account_number_format: '[A-Z0-9]{18}'
79
+ SE:
80
+ :pseudo_iban_bank_code_length: 0
81
+ :pseudo_iban_branch_code_length: 5
82
+ :pseudo_iban_account_number_length: 10
79
83
  SI:
80
84
  :bank_code_format: \d{5}
81
85
  :account_number_format: \d{8}\d{2}
@@ -736,6 +736,9 @@ SE:
736
736
  :bban_format: \d{3}\d{16}\d{1}
737
737
  :bank_code_format: \d{3}
738
738
  :account_number_format: \d{16}\d{1}
739
+ :pseudo_iban_bank_code_length: 0
740
+ :pseudo_iban_branch_code_length: 5
741
+ :pseudo_iban_account_number_length: 10
739
742
  SI:
740
743
  :bank_code_position: 5
741
744
  :bank_code_length: 5
@@ -1,10 +1,10 @@
1
1
  require File.expand_path('../lib/ibandit/version', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |gem|
4
- gem.add_development_dependency 'rspec', '~> 3.1'
5
- gem.add_development_dependency 'rspec-its', '~> 1.1'
6
- gem.add_development_dependency 'rubocop', '~> 0.30.1'
7
- gem.add_development_dependency 'sax-machine', '~> 1.1'
4
+ gem.add_development_dependency 'rspec', '~> 3.3'
5
+ gem.add_development_dependency 'rspec-its', '~> 1.2'
6
+ gem.add_development_dependency 'rubocop', '~> 0.33.0'
7
+ gem.add_development_dependency 'sax-machine', '~> 1.3'
8
8
  gem.add_development_dependency 'nokogiri', '~> 1.6'
9
9
 
10
10
  gem.add_runtime_dependency 'i18n', '~> 0.7.0'
@@ -1,11 +1,14 @@
1
1
  require 'i18n'
2
2
  require 'ibandit/version'
3
3
  require 'ibandit/errors'
4
+ require 'ibandit/constants'
4
5
  require 'ibandit/iban'
5
6
  require 'ibandit/german_details_converter'
6
7
  require 'ibandit/swedish_details_converter'
7
8
  require 'ibandit/iban_splitter'
8
9
  require 'ibandit/iban_assembler'
10
+ require 'ibandit/pseudo_iban_assembler'
11
+ require 'ibandit/pseudo_iban_splitter'
9
12
  require 'ibandit/local_details_cleaner'
10
13
  require 'ibandit/check_digit'
11
14
 
@@ -0,0 +1,10 @@
1
+ module Ibandit
2
+ module Constants
3
+ SUPPORTED_COUNTRY_CODES = %w(AT BE BG CY CZ DE DK EE ES FI FR GB GR HR HU IE
4
+ IS IT LT LU LV MC MT NL NO PL PT RO SE SI SK
5
+ SM).freeze
6
+
7
+ PSEUDO_IBAN_COUNTRY_CODES = %w(SE).freeze
8
+ PSEUDO_IBAN_CHECK_DIGITS = 'ZZ'.freeze
9
+ end
10
+ end
@@ -2,13 +2,21 @@ require 'yaml'
2
2
 
3
3
  module Ibandit
4
4
  class IBAN
5
- attr_reader :errors, :iban, :country_code, :check_digits, :bank_code,
6
- :branch_code, :account_number
5
+ attr_reader :errors, :iban, :country_code, :check_digits, :bank_code,
6
+ :branch_code, :account_number, :swift_bank_code,
7
+ :swift_branch_code, :swift_account_number
7
8
 
8
9
  def initialize(argument)
9
10
  if argument.is_a?(String)
10
- @iban = argument.to_s.gsub(/\s+/, '').upcase
11
- extract_local_details_from_iban!
11
+ input = argument.to_s.gsub(/\s+/, '').upcase
12
+
13
+ if pseudo_iban?(input)
14
+ local_details = PseudoIBANSplitter.new(input).split
15
+ build_iban_from_local_details(local_details)
16
+ else
17
+ @iban = input
18
+ extract_swift_details_from_iban!
19
+ end
12
20
  elsif argument.is_a?(Hash)
13
21
  build_iban_from_local_details(argument)
14
22
  else
@@ -33,8 +41,8 @@ module Ibandit
33
41
  def iban_national_id
34
42
  return unless decomposable?
35
43
 
36
- national_id = bank_code.to_s
37
- national_id += branch_code.to_s
44
+ national_id = swift_bank_code.to_s
45
+ national_id += swift_branch_code.to_s
38
46
  national_id.slice(0, structure[:iban_national_id_length])
39
47
  end
40
48
 
@@ -51,6 +59,15 @@ module Ibandit
51
59
  iban[4..-1] unless iban.nil?
52
60
  end
53
61
 
62
+ def pseudo_iban
63
+ @pseudo_iban ||= PseudoIBANAssembler.new(
64
+ country_code: country_code,
65
+ bank_code: bank_code,
66
+ branch_code: branch_code,
67
+ account_number: account_number
68
+ ).assemble
69
+ end
70
+
54
71
  ###############
55
72
  # Validations #
56
73
  ###############
@@ -115,12 +132,12 @@ module Ibandit
115
132
  def valid_bank_code_length?
116
133
  return unless valid_country_code?
117
134
 
118
- if bank_code.nil? || bank_code.length == 0
135
+ if swift_bank_code.nil? || swift_bank_code.length == 0
119
136
  @errors[:bank_code] = Ibandit.translate(:is_required)
120
137
  return false
121
138
  end
122
139
 
123
- return true if bank_code.length == structure[:bank_code_length]
140
+ return true if swift_bank_code.length == structure[:bank_code_length]
124
141
 
125
142
  @errors[:bank_code] =
126
143
  Ibandit.translate(:wrong_length, expected: structure[:bank_code_length])
@@ -129,12 +146,14 @@ module Ibandit
129
146
 
130
147
  def valid_branch_code_length?
131
148
  return unless valid_country_code?
132
- return true if branch_code.to_s.length == structure[:branch_code_length]
149
+ if swift_branch_code.to_s.length == structure[:branch_code_length]
150
+ return true
151
+ end
133
152
 
134
153
  if structure[:branch_code_length] == 0
135
154
  @errors[:branch_code] = Ibandit.translate(:not_used_in_country,
136
155
  country_code: country_code)
137
- elsif branch_code.nil? || branch_code.length == 0
156
+ elsif swift_branch_code.nil? || swift_branch_code.length == 0
138
157
  @errors[:branch_code] = Ibandit.translate(:is_required)
139
158
  else
140
159
  @errors[:branch_code] =
@@ -147,12 +166,14 @@ module Ibandit
147
166
  def valid_account_number_length?
148
167
  return unless valid_country_code?
149
168
 
150
- if account_number.nil?
169
+ if swift_account_number.nil?
151
170
  @errors[:account_number] = Ibandit.translate(:is_required)
152
171
  return false
153
172
  end
154
173
 
155
- return true if account_number.length == structure[:account_number_length]
174
+ if swift_account_number.length == structure[:account_number_length]
175
+ return true
176
+ end
156
177
 
157
178
  @errors[:account_number] =
158
179
  Ibandit.translate(:wrong_length,
@@ -187,7 +208,7 @@ module Ibandit
187
208
  def valid_bank_code_format?
188
209
  return unless valid_bank_code_length?
189
210
 
190
- if bank_code =~ Regexp.new(structure[:bank_code_format])
211
+ if swift_bank_code =~ Regexp.new(structure[:bank_code_format])
191
212
  true
192
213
  else
193
214
  @errors[:bank_code] = Ibandit.translate(:is_invalid)
@@ -199,7 +220,7 @@ module Ibandit
199
220
  return unless valid_branch_code_length?
200
221
  return true unless structure[:branch_code_format]
201
222
 
202
- if branch_code =~ Regexp.new(structure[:branch_code_format])
223
+ if swift_branch_code =~ Regexp.new(structure[:branch_code_format])
203
224
  true
204
225
  else
205
226
  @errors[:branch_code] = Ibandit.translate(:is_invalid)
@@ -210,7 +231,7 @@ module Ibandit
210
231
  def valid_account_number_format?
211
232
  return unless valid_account_number_length?
212
233
 
213
- if account_number =~ Regexp.new(structure[:account_number_format])
234
+ if swift_account_number =~ Regexp.new(structure[:account_number_format])
214
235
  true
215
236
  else
216
237
  @errors[:account_number] = Ibandit.translate(:is_invalid)
@@ -242,8 +263,8 @@ module Ibandit
242
263
  begin
243
264
  GermanDetailsConverter.convert(
244
265
  country_code: country_code,
245
- bank_code: bank_code,
246
- account_number: account_number
266
+ bank_code: swift_bank_code,
267
+ account_number: swift_account_number
247
268
  )
248
269
  true
249
270
  rescue UnsupportedAccountDetails
@@ -255,7 +276,10 @@ module Ibandit
255
276
  def valid_swedish_details?
256
277
  return true unless country_code == 'SE'
257
278
 
258
- bank_details = { bank_code: bank_code, account_number: account_number }
279
+ bank_details = {
280
+ bank_code: swift_bank_code,
281
+ account_number: swift_account_number
282
+ }
259
283
 
260
284
  unless SwedishDetailsConverter.valid_bank_code?(bank_details)
261
285
  bank_code_field = bank_code.nil? ? :account_number : :bank_code
@@ -279,28 +303,41 @@ module Ibandit
279
303
  private
280
304
 
281
305
  def decomposable?
282
- [iban, country_code, bank_code, account_number].none?(&:nil?)
306
+ [iban, country_code, swift_bank_code, swift_account_number].none?(&:nil?)
283
307
  end
284
308
 
285
309
  def build_iban_from_local_details(details_hash)
286
310
  local_details = LocalDetailsCleaner.clean(details_hash)
287
311
 
288
- @country_code = try_dup(local_details[:country_code])
289
- @account_number = try_dup(local_details[:account_number])
290
- @branch_code = try_dup(local_details[:branch_code])
291
- @bank_code = try_dup(local_details[:bank_code])
292
- @iban = IBANAssembler.assemble(local_details)
293
- @check_digits = @iban.slice(2, 2) unless @iban.nil?
312
+ @country_code = try_dup(local_details[:country_code])
313
+ @account_number = try_dup(local_details[:account_number])
314
+ @branch_code = try_dup(local_details[:branch_code])
315
+ @bank_code = try_dup(local_details[:bank_code])
316
+
317
+ @swift_account_number = try_dup(local_details[:swift_account_number])
318
+ @swift_branch_code = try_dup(local_details[:swift_branch_code])
319
+ @swift_bank_code = try_dup(local_details[:swift_bank_code])
320
+
321
+ @iban = IBANAssembler.assemble(swift_details)
322
+ @check_digits = @iban.slice(2, 2) unless @iban.nil?
294
323
  end
295
324
 
296
- def extract_local_details_from_iban!
297
- local_details = IBANSplitter.split(@iban)
325
+ def extract_swift_details_from_iban!
326
+ swift_details = IBANSplitter.split(@iban)
327
+
328
+ @country_code = swift_details[:country_code]
329
+ @check_digits = swift_details[:check_digits]
298
330
 
299
- @country_code = local_details[:country_code]
300
- @check_digits = local_details[:check_digits]
301
- @bank_code = local_details[:bank_code]
302
- @branch_code = local_details[:branch_code]
303
- @account_number = local_details[:account_number]
331
+ @swift_bank_code = swift_details[:bank_code]
332
+ @swift_branch_code = swift_details[:branch_code]
333
+ @swift_account_number = swift_details[:account_number]
334
+
335
+ return if Constants::PSEUDO_IBAN_COUNTRY_CODES.
336
+ include?(@country_code)
337
+
338
+ @bank_code = swift_details[:bank_code]
339
+ @branch_code = swift_details[:branch_code]
340
+ @account_number = swift_details[:account_number]
304
341
  end
305
342
 
306
343
  def try_dup(object)
@@ -339,5 +376,18 @@ module Ibandit
339
376
  :bank_code
340
377
  end
341
378
  end
379
+
380
+ def swift_details
381
+ {
382
+ country_code: @country_code,
383
+ account_number: @swift_account_number,
384
+ branch_code: @swift_branch_code,
385
+ bank_code: @swift_bank_code
386
+ }
387
+ end
388
+
389
+ def pseudo_iban?(input)
390
+ input.slice(2, 2) == Constants::PSEUDO_IBAN_CHECK_DIGITS
391
+ end
342
392
  end
343
393
  end
@@ -1,9 +1,5 @@
1
1
  module Ibandit
2
2
  module IBANAssembler
3
- SUPPORTED_COUNTRY_CODES = %w(AT BE BG CY CZ DE DK EE ES FI FR GB GR HR HU IE
4
- IS IT LT LU LV MC MT NL NO PL PT RO SE SI SK
5
- SM).freeze
6
-
7
3
  EXCEPTION_COUNTRY_CODES = %w(IT SM BE).freeze
8
4
 
9
5
  def self.assemble(local_details)
@@ -67,8 +63,11 @@ module Ibandit
67
63
  ##################
68
64
 
69
65
  def self.can_assemble?(local_details)
70
- SUPPORTED_COUNTRY_CODES.include?(local_details[:country_code]) &&
71
- valid_arguments?(local_details)
66
+ supported_country_code?(local_details) && valid_arguments?(local_details)
67
+ end
68
+
69
+ def self.supported_country_code?(local_details)
70
+ Constants::SUPPORTED_COUNTRY_CODES.include?(local_details[:country_code])
72
71
  end
73
72
 
74
73
  def self.valid_arguments?(local_details)
@@ -1,12 +1,12 @@
1
1
  module Ibandit
2
2
  module LocalDetailsCleaner
3
- SUPPORTED_COUNTRY_CODES = %w(AT BE BG CY CZ DE DK EE ES FI FR GB GR HR HU IE
4
- IS IT LT LU LV MC MT NL NO PL PT RO SE SI SK
5
- SM).freeze
6
-
7
3
  def self.clean(local_details)
8
4
  country_code = local_details[:country_code]
9
5
 
6
+ unless explicit_swift_details?(country_code)
7
+ local_details = swift_details_for(local_details).merge(local_details)
8
+ end
9
+
10
10
  return local_details unless can_clean?(country_code, local_details)
11
11
 
12
12
  local_details.merge(
@@ -18,10 +18,14 @@ module Ibandit
18
18
  ###########
19
19
 
20
20
  def self.can_clean?(country_code, local_details)
21
- SUPPORTED_COUNTRY_CODES.include?(country_code) &&
21
+ Constants::SUPPORTED_COUNTRY_CODES.include?(country_code) &&
22
22
  fields_for?(country_code, local_details)
23
23
  end
24
24
 
25
+ def self.explicit_swift_details?(country_code)
26
+ Constants::PSEUDO_IBAN_COUNTRY_CODES.include?(country_code)
27
+ end
28
+
25
29
  def self.fields_for?(country_code, opts)
26
30
  required_fields(country_code).all? { |argument| opts[argument] }
27
31
  end
@@ -149,7 +153,7 @@ module Ibandit
149
153
 
150
154
  {
151
155
  bank_code: bank_code.rjust(4, '0'),
152
- account_number: account_number.gsub('-', '').rjust(10, '0')
156
+ account_number: account_number.delete('-').rjust(10, '0')
153
157
  }
154
158
  end
155
159
 
@@ -437,14 +441,19 @@ module Ibandit
437
441
  end
438
442
 
439
443
  def self.clean_se_details(local_details)
440
- converted_details =
441
- SwedishDetailsConverter.convert(local_details[:account_number])
444
+ converted_details = SwedishDetailsConverter.new(
445
+ branch_code: local_details[:branch_code],
446
+ account_number: local_details[:account_number]
447
+ ).convert
442
448
 
443
- bank_code = local_details[:bank_code] || converted_details[:bank_code]
449
+ bank_code = local_details[:bank_code] ||
450
+ converted_details[:swift_bank_code]
444
451
 
445
452
  {
446
- bank_code: bank_code,
447
- account_number: converted_details[:account_number]
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]
448
457
  }
449
458
  end
450
459
 
@@ -477,5 +486,14 @@ module Ibandit
477
486
  hufo + reikningsnumer + kennitala
478
487
  end
479
488
  private_class_method :pad_is_account_number
489
+
490
+ def self.swift_details_for(local_details)
491
+ {
492
+ swift_bank_code: local_details[:bank_code],
493
+ swift_branch_code: local_details[:branch_code],
494
+ swift_account_number: local_details[:account_number]
495
+ }
496
+ end
497
+ private_class_method :swift_details_for
480
498
  end
481
499
  end