sepa_king 0.0.7 → 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.
@@ -5,20 +5,15 @@ module SEPA
5
5
  self.account_class = DebtorAccount
6
6
  self.transaction_class = CreditTransferTransaction
7
7
  self.xml_main_tag = 'CstmrCdtTrfInitn'
8
+ self.known_schemas = [ PAIN_001_003_03, PAIN_001_002_03 ]
8
9
 
9
10
  private
10
- # @return {Hash<Symbol=>String>} xml schema information used in output xml
11
- def xml_schema
12
- { :xmlns => 'urn:iso:std:iso:20022:tech:xsd:pain.001.002.03',
13
- :'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
14
- :'xsi:schemaLocation' => 'urn:iso:std:iso:20022:tech:xsd:pain.001.002.03 pain.001.002.03.xsd' }
15
- end
16
-
17
11
  # Find groups of transactions which share the same values of some attributes
18
12
  def grouped_transactions
19
13
  transactions.group_by do |transaction|
20
14
  { requested_date: transaction.requested_date,
21
- batch_booking: transaction.batch_booking
15
+ batch_booking: transaction.batch_booking,
16
+ service_level: transaction.service_level
22
17
  }
23
18
  end
24
19
  end
@@ -35,7 +30,7 @@ module SEPA
35
30
  builder.CtrlSum('%.2f' % amount_total(transactions))
36
31
  builder.PmtTpInf do
37
32
  builder.SvcLvl do
38
- builder.Cd('SEPA')
33
+ builder.Cd(group[:service_level])
39
34
  end
40
35
  end
41
36
  builder.ReqdExctnDt(group[:requested_date].iso8601)
@@ -49,7 +44,13 @@ module SEPA
49
44
  end
50
45
  builder.DbtrAgt do
51
46
  builder.FinInstnId do
52
- builder.BIC(account.bic)
47
+ if account.bic
48
+ builder.BIC(account.bic)
49
+ else
50
+ builder.Othr do
51
+ builder.Id('NOTPROVIDED')
52
+ end
53
+ end
53
54
  end
54
55
  end
55
56
  builder.ChrgBr('SLEV')
@@ -69,9 +70,11 @@ module SEPA
69
70
  builder.Amt do
70
71
  builder.InstdAmt('%.2f' % transaction.amount, Ccy: 'EUR')
71
72
  end
72
- builder.CdtrAgt do
73
- builder.FinInstnId do
74
- builder.BIC(transaction.bic)
73
+ if transaction.bic
74
+ builder.CdtrAgt do
75
+ builder.FinInstnId do
76
+ builder.BIC(transaction.bic)
77
+ end
75
78
  end
76
79
  end
77
80
  builder.Cdtr do
@@ -5,21 +5,15 @@ module SEPA
5
5
  self.account_class = CreditorAccount
6
6
  self.transaction_class = DirectDebitTransaction
7
7
  self.xml_main_tag = 'CstmrDrctDbtInitn'
8
+ self.known_schemas = [ PAIN_008_003_02, PAIN_008_002_02 ]
8
9
 
9
10
  validate do |record|
10
11
  if record.transactions.map(&:local_instrument).uniq.size > 1
11
- errors.add(:base, 'CORE and B2B must not be mixed in one message!')
12
+ errors.add(:base, 'CORE, COR1 AND B2B must not be mixed in one message!')
12
13
  end
13
14
  end
14
15
 
15
16
  private
16
- # @return {Hash<Symbol=>String>} xml schema information used in output xml
17
- def xml_schema
18
- { :xmlns => 'urn:iso:std:iso:20022:tech:xsd:pain.008.002.02',
19
- :'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
20
- :'xsi:schemaLocation' => 'urn:iso:std:iso:20022:tech:xsd:pain.008.002.02 pain.008.002.02.xsd' }
21
- end
22
-
23
17
  # Find groups of transactions which share the same values of some attributes
24
18
  def grouped_transactions
25
19
  transactions.group_by do |transaction|
@@ -61,7 +55,13 @@ module SEPA
61
55
  end
62
56
  builder.CdtrAgt do
63
57
  builder.FinInstnId do
64
- builder.BIC(group[:account].bic)
58
+ if group[:account].bic
59
+ builder.BIC(group[:account].bic)
60
+ else
61
+ builder.Othr do
62
+ builder.Id('NOTPROVIDED')
63
+ end
64
+ end
65
65
  end
66
66
  end
67
67
  builder.ChrgBr('SLEV')
@@ -99,7 +99,13 @@ module SEPA
99
99
  end
100
100
  builder.DbtrAgt do
101
101
  builder.FinInstnId do
102
- builder.BIC(transaction.bic)
102
+ if transaction.bic
103
+ builder.BIC(transaction.bic)
104
+ else
105
+ builder.Othr do
106
+ builder.Id('NOTPROVIDED')
107
+ end
108
+ end
103
109
  end
104
110
  end
105
111
  builder.Dbtr do
@@ -1,6 +1,11 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module SEPA
4
+ PAIN_008_002_02 = 'pain.008.002.02'
5
+ PAIN_008_003_02 = 'pain.008.003.02'
6
+ PAIN_001_002_03 = 'pain.001.002.03'
7
+ PAIN_001_003_03 = 'pain.001.003.03'
8
+
4
9
  class Message
5
10
  include ActiveModel::Validations
6
11
 
@@ -11,7 +16,7 @@ module SEPA
11
16
  record.errors.add(:account, 'is invalid') unless record.account.valid?
12
17
  end
13
18
 
14
- class_attribute :account_class, :transaction_class, :xml_main_tag
19
+ class_attribute :account_class, :transaction_class, :xml_main_tag, :known_schemas
15
20
 
16
21
  def initialize(account_options={})
17
22
  @transactions = []
@@ -25,12 +30,13 @@ module SEPA
25
30
  end
26
31
 
27
32
  # @return [String] xml
28
- def to_xml
33
+ def to_xml(schema_name=self.known_schemas.first)
29
34
  raise RuntimeError.new(errors.full_messages.join("\n")) unless valid?
35
+ raise RuntimeError.new("Incompatible with schema #{schema_name}!") unless schema_compatible?(schema_name)
30
36
 
31
37
  builder = Builder::XmlMarkup.new indent: 2
32
38
  builder.instruct!
33
- builder.Document(xml_schema) do
39
+ builder.Document(xml_schema(schema_name)) do
34
40
  builder.__send__(xml_main_tag) do
35
41
  build_group_header(builder)
36
42
  build_payment_informations(builder)
@@ -42,7 +48,25 @@ module SEPA
42
48
  selected_transactions.inject(0) { |sum, t| sum + t.amount }
43
49
  end
44
50
 
51
+ def schema_compatible?(schema_name)
52
+ raise ArgumentError.new("Schema #{schema_name} is unknown!") unless self.known_schemas.include?(schema_name)
53
+
54
+ case schema_name
55
+ when PAIN_001_002_03, PAIN_008_002_02
56
+ account.bic.present? && transactions.all? { |t| t.schema_compatible?(schema_name) }
57
+ when PAIN_001_003_03, PAIN_008_003_02
58
+ transactions.all? { |t| t.schema_compatible?(schema_name) }
59
+ end
60
+ end
61
+
45
62
  private
63
+ # @return {Hash<Symbol=>String>} xml schema information used in output xml
64
+ def xml_schema(schema_name)
65
+ { :xmlns => "urn:iso:std:iso:20022:tech:xsd:#{schema_name}",
66
+ :'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
67
+ :'xsi:schemaLocation' => "urn:iso:std:iso:20022:tech:xsd:#{schema_name} #{schema_name}.xsd" }
68
+ end
69
+
46
70
  def build_group_header(builder)
47
71
  builder.GrpHdr do
48
72
  builder.MsgId(message_identification)
@@ -1,5 +1,22 @@
1
1
  # encoding: utf-8
2
2
  module SEPA
3
3
  class CreditTransferTransaction < Transaction
4
+ attr_accessor :service_level
5
+
6
+ validates_inclusion_of :service_level, :in => %w(SEPA URGP)
7
+
8
+ def initialize(attributes = {})
9
+ super
10
+ self.service_level ||= 'SEPA'
11
+ end
12
+
13
+ def schema_compatible?(schema_name)
14
+ case schema_name
15
+ when PAIN_001_002_03
16
+ self.bic.present? && self.service_level == 'SEPA'
17
+ when PAIN_001_003_03
18
+ true
19
+ end
20
+ end
4
21
  end
5
22
  end
@@ -5,7 +5,7 @@ module SEPA
5
5
 
6
6
  validates_length_of :mandate_id, within: 1..35
7
7
  validates_presence_of :mandate_date_of_signature
8
- validates_inclusion_of :local_instrument, :in => %w(CORE B2B)
8
+ validates_inclusion_of :local_instrument, :in => %w(CORE COR1 B2B)
9
9
  validates_inclusion_of :sequence_type, :in => %w(FRST OOFF RCUR FNAL)
10
10
 
11
11
  validate do |t|
@@ -25,5 +25,14 @@ module SEPA
25
25
  self.local_instrument ||= 'CORE'
26
26
  self.sequence_type ||= 'OOFF'
27
27
  end
28
+
29
+ def schema_compatible?(schema_name)
30
+ case schema_name
31
+ when PAIN_008_002_02
32
+ self.bic.present? && %w(CORE B2B).include?(self.local_instrument)
33
+ when PAIN_008_003_02
34
+ true
35
+ end
36
+ end
28
37
  end
29
38
  end
@@ -3,15 +3,17 @@ module SEPA
3
3
  class IBANValidator < ActiveModel::Validator
4
4
  def validate(record)
5
5
  unless IBANTools::IBAN.valid?(record.iban.to_s)
6
- record.errors.add(:iban, 'is invalid')
6
+ record.errors.add(:iban, :invalid)
7
7
  end
8
8
  end
9
9
  end
10
10
 
11
11
  class BICValidator < ActiveModel::Validator
12
12
  def validate(record)
13
- unless record.bic.to_s.match /[A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}/
14
- record.errors.add(:bic, 'is invalid')
13
+ if record.bic
14
+ unless record.bic.to_s.match /[A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}/
15
+ record.errors.add(:bic, :invalid)
16
+ end
15
17
  end
16
18
  end
17
19
  end
@@ -19,7 +21,7 @@ module SEPA
19
21
  class CreditorIdentifierValidator < ActiveModel::Validator
20
22
  def validate(record)
21
23
  unless valid?(record.creditor_identifier)
22
- record.errors.add(:creditor_identifier, 'is invalid')
24
+ record.errors.add(:creditor_identifier, :invalid)
23
25
  end
24
26
  end
25
27
 
@@ -1,3 +1,3 @@
1
1
  module SEPA
2
- VERSION = '0.0.7'
2
+ VERSION = '0.1.0'
3
3
  end
data/spec/account_spec.rb CHANGED
@@ -36,7 +36,7 @@ describe SEPA::Account do
36
36
  end
37
37
 
38
38
  it 'should not accept invalid value' do
39
- SEPA::Account.should_not accept(nil, '', 'invalid', for: :bic)
39
+ SEPA::Account.should_not accept('', 'invalid', for: :bic)
40
40
  end
41
41
  end
42
42
  end
@@ -17,6 +17,13 @@ describe SEPA::Converter do
17
17
  convert_text('üöäÜÖÄß').should == 'ueoeaeUEOEAEss'
18
18
  end
19
19
 
20
+ it 'should convert line breaks' do
21
+ convert_text("one\ntwo") .should == 'one two'
22
+ convert_text("one\ntwo\n") .should == 'one two'
23
+ convert_text("\none\ntwo\n").should == 'one two'
24
+ convert_text("one\n\ntwo") .should == 'one two'
25
+ end
26
+
20
27
  it 'should convert number' do
21
28
  convert_text(1234).should == '1234'
22
29
  end
@@ -42,6 +42,55 @@ describe SEPA::CreditTransfer do
42
42
  end
43
43
 
44
44
  context 'for valid debtor' do
45
+ context 'without BIC (IBAN-only)' do
46
+ subject do
47
+ sct = SEPA::CreditTransfer.new name: 'Schuldner GmbH',
48
+ iban: 'DE87200500001234567890'
49
+
50
+ sct.add_transaction name: 'Telekomiker AG',
51
+ bic: 'PBNKDEFF370',
52
+ iban: 'DE37112589611964645802',
53
+ amount: 102.50,
54
+ reference: 'XYZ-1234/123',
55
+ remittance_information: 'Rechnung vom 22.08.2013'
56
+
57
+ sct
58
+ end
59
+
60
+ it 'should create valid XML file' do
61
+ expect(subject.to_xml).to validate_against('pain.001.003.03.xsd')
62
+ end
63
+
64
+ it 'should fail for pain.001.002.03' do
65
+ expect {
66
+ subject.to_xml(SEPA::PAIN_001_002_03)
67
+ }.to raise_error(RuntimeError)
68
+ end
69
+ end
70
+
71
+ context 'with BIC' do
72
+ subject do
73
+ sct = credit_transfer
74
+
75
+ sct.add_transaction name: 'Telekomiker AG',
76
+ bic: 'PBNKDEFF370',
77
+ iban: 'DE37112589611964645802',
78
+ amount: 102.50,
79
+ reference: 'XYZ-1234/123',
80
+ remittance_information: 'Rechnung vom 22.08.2013'
81
+
82
+ sct
83
+ end
84
+
85
+ it 'should validate against pain.001.002.03' do
86
+ expect(subject.to_xml('pain.001.002.03')).to validate_against('pain.001.002.03.xsd')
87
+ end
88
+
89
+ it 'should validate against pain.001.003.03' do
90
+ expect(subject.to_xml('pain.001.003.03')).to validate_against('pain.001.003.03.xsd')
91
+ end
92
+ end
93
+
45
94
  context 'without requested_date given' do
46
95
  subject do
47
96
  sct = credit_transfer
@@ -54,7 +103,6 @@ describe SEPA::CreditTransfer do
54
103
  remittance_information: 'Rechnung vom 22.08.2013'
55
104
 
56
105
  sct.add_transaction name: 'Amazonas GmbH',
57
- bic: 'TUBDDEDDXXX',
58
106
  iban: 'DE27793589132923472195',
59
107
  amount: 59.00,
60
108
  reference: 'XYZ-5678/456',
@@ -64,7 +112,7 @@ describe SEPA::CreditTransfer do
64
112
  end
65
113
 
66
114
  it 'should create valid XML file' do
67
- expect(subject).to validate_against('pain.001.002.03.xsd')
115
+ expect(subject).to validate_against('pain.001.003.03.xsd')
68
116
  end
69
117
 
70
118
  it 'should have message_identification' do
@@ -117,9 +165,9 @@ describe SEPA::CreditTransfer do
117
165
  subject.should have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[2]/Amt/InstdAmt', '59.00')
118
166
  end
119
167
 
120
- it 'should contain <CdtrAgt>' do
168
+ it 'should contain <CdtrAgt> for every BIC given' do
121
169
  subject.should have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAgt/FinInstnId/BIC', 'PBNKDEFF370')
122
- subject.should have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[2]/CdtrAgt/FinInstnId/BIC', 'TUBDDEDDXXX')
170
+ subject.should_not have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[2]/CdtrAgt')
123
171
  end
124
172
 
125
173
  it 'should contain <Cdtr>' do
@@ -2,14 +2,35 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe SEPA::CreditTransferTransaction do
5
- it 'should initialize a new transaction' do
6
- expect(
7
- SEPA::CreditTransferTransaction.new name: 'Telekomiker AG',
8
- iban: 'DE37112589611964645802',
9
- bic: 'PBNKDEFF370',
10
- amount: 102.50,
11
- reference: 'XYZ-1234/123',
12
- remittance_information: 'Rechnung 123 vom 22.08.2013'
13
- ).to be_valid
5
+ describe :initialize do
6
+ it 'should initialize a valid transaction' do
7
+ expect(
8
+ SEPA::CreditTransferTransaction.new name: 'Telekomiker AG',
9
+ iban: 'DE37112589611964645802',
10
+ bic: 'PBNKDEFF370',
11
+ amount: 102.50,
12
+ reference: 'XYZ-1234/123',
13
+ remittance_information: 'Rechnung 123 vom 22.08.2013'
14
+ ).to be_valid
15
+ end
16
+ end
17
+
18
+ describe :schema_compatible? do
19
+ context 'for pain.001.003.03' do
20
+ it 'should success' do
21
+ SEPA::CreditTransferTransaction.new({}).should be_schema_compatible('pain.001.003.03')
22
+ end
23
+ end
24
+
25
+ context 'pain.001.002.03' do
26
+ it 'should success for valid attributes' do
27
+ SEPA::CreditTransferTransaction.new(:bic => 'SPUEDE2UXXX', :service_level => 'SEPA').should be_schema_compatible('pain.001.002.03')
28
+ end
29
+
30
+ it 'should fail for invalid attributes' do
31
+ SEPA::CreditTransferTransaction.new(:bic => nil).should_not be_schema_compatible('pain.001.002.03')
32
+ SEPA::CreditTransferTransaction.new(:bic => 'SPUEDE2UXXX', :service_level => 'URGP').should_not be_schema_compatible('pain.001.002.03')
33
+ end
34
+ end
14
35
  end
15
36
  end
@@ -43,6 +43,60 @@ describe SEPA::DirectDebit do
43
43
  end
44
44
 
45
45
  context 'for valid creditor' do
46
+ context 'without BIC (IBAN-only)' do
47
+ subject do
48
+ sdd = SEPA::DirectDebit.new name: 'Gläubiger GmbH',
49
+ iban: 'DE87200500001234567890',
50
+ creditor_identifier: 'DE98ZZZ09999999999'
51
+
52
+ sdd.add_transaction name: 'Zahlemann & Söhne GbR',
53
+ bic: 'SPUEDE2UXXX',
54
+ iban: 'DE21500500009876543210',
55
+ amount: 39.99,
56
+ reference: 'XYZ/2013-08-ABO/12345',
57
+ remittance_information: 'Unsere Rechnung vom 10.08.2013',
58
+ mandate_id: 'K-02-2011-12345',
59
+ mandate_date_of_signature: Date.new(2011,1,25)
60
+
61
+ sdd
62
+ end
63
+
64
+ it 'should create valid XML file' do
65
+ expect(subject.to_xml).to validate_against('pain.008.003.02.xsd')
66
+ end
67
+
68
+ it 'should fail for pain.008.002.02' do
69
+ expect {
70
+ subject.to_xml(SEPA::PAIN_008_002_02)
71
+ }.to raise_error(RuntimeError)
72
+ end
73
+ end
74
+
75
+ context 'with BIC' do
76
+ subject do
77
+ sdd = direct_debit
78
+
79
+ sdd.add_transaction name: 'Zahlemann & Söhne GbR',
80
+ bic: 'SPUEDE2UXXX',
81
+ iban: 'DE21500500009876543210',
82
+ amount: 39.99,
83
+ reference: 'XYZ/2013-08-ABO/12345',
84
+ remittance_information: 'Unsere Rechnung vom 10.08.2013',
85
+ mandate_id: 'K-02-2011-12345',
86
+ mandate_date_of_signature: Date.new(2011,1,25)
87
+
88
+ sdd
89
+ end
90
+
91
+ it 'should validate against pain.008.002.02' do
92
+ expect(subject.to_xml('pain.008.002.02')).to validate_against('pain.008.002.02.xsd')
93
+ end
94
+
95
+ it 'should validate against pain.008.003.02' do
96
+ expect(subject.to_xml('pain.008.003.02')).to validate_against('pain.008.003.02.xsd')
97
+ end
98
+ end
99
+
46
100
  context 'without requested_date given' do
47
101
  subject do
48
102
  sdd = direct_debit
@@ -57,7 +111,6 @@ describe SEPA::DirectDebit do
57
111
  mandate_date_of_signature: Date.new(2011,1,25)
58
112
 
59
113
  sdd.add_transaction name: 'Meier & Schulze oHG',
60
- bic: 'GENODEF1JEV',
61
114
  iban: 'DE68210501700012345678',
62
115
  amount: 750.00,
63
116
  reference: 'XYZ/2013-08-ABO/6789',
@@ -69,7 +122,7 @@ describe SEPA::DirectDebit do
69
122
  end
70
123
 
71
124
  it 'should create valid XML file' do
72
- expect(subject).to validate_against('pain.008.002.02.xsd')
125
+ expect(subject).to validate_against('pain.008.003.02.xsd')
73
126
  end
74
127
 
75
128
  it 'should have message_identification' do
@@ -138,7 +191,7 @@ describe SEPA::DirectDebit do
138
191
 
139
192
  it 'should contain <DbtrAgt>' do
140
193
  subject.should have_xml('//Document/CstmrDrctDbtInitn/PmtInf/DrctDbtTxInf[1]/DbtrAgt/FinInstnId/BIC', 'SPUEDE2UXXX')
141
- subject.should have_xml('//Document/CstmrDrctDbtInitn/PmtInf/DrctDbtTxInf[2]/DbtrAgt/FinInstnId/BIC', 'GENODEF1JEV')
194
+ subject.should have_xml('//Document/CstmrDrctDbtInitn/PmtInf/DrctDbtTxInf[2]/DbtrAgt/FinInstnId/Othr/Id', 'NOTPROVIDED')
142
195
  end
143
196
 
144
197
  it 'should contain <Dbtr>' do
@@ -2,17 +2,38 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe SEPA::DirectDebitTransaction do
5
- it 'should initialize a new transaction' do
6
- expect(
7
- SEPA::DirectDebitTransaction.new name: 'Zahlemann & Söhne Gbr',
8
- bic: 'SPUEDE2UXXX',
9
- iban: 'DE21500500009876543210',
10
- amount: 39.99,
11
- reference: 'XYZ-1234/123',
12
- remittance_information: 'Vielen Dank für Ihren Einkauf!',
13
- mandate_id: 'K-02-2011-12345',
14
- mandate_date_of_signature: Date.new(2011,1,25)
15
- ).to be_valid
5
+ describe :initialize do
6
+ it 'should create a valid transaction' do
7
+ expect(
8
+ SEPA::DirectDebitTransaction.new name: 'Zahlemann & Söhne Gbr',
9
+ bic: 'SPUEDE2UXXX',
10
+ iban: 'DE21500500009876543210',
11
+ amount: 39.99,
12
+ reference: 'XYZ-1234/123',
13
+ remittance_information: 'Vielen Dank für Ihren Einkauf!',
14
+ mandate_id: 'K-02-2011-12345',
15
+ mandate_date_of_signature: Date.new(2011,1,25)
16
+ ).to be_valid
17
+ end
18
+ end
19
+
20
+ describe :schema_compatible? do
21
+ context 'for pain.008.003.02' do
22
+ it 'should success' do
23
+ SEPA::DirectDebitTransaction.new({}).should be_schema_compatible('pain.008.003.02')
24
+ end
25
+ end
26
+
27
+ context 'for pain.008.002.02' do
28
+ it 'should success for valid attributes' do
29
+ SEPA::DirectDebitTransaction.new(:bic => 'SPUEDE2UXXX', :local_instrument => 'CORE').should be_schema_compatible('pain.008.002.02')
30
+ end
31
+
32
+ it 'should fail for invalid attributes' do
33
+ SEPA::DirectDebitTransaction.new(:bic => nil).should_not be_schema_compatible('pain.008.002.02')
34
+ SEPA::DirectDebitTransaction.new(:bic => 'SPUEDE2UXXX', :local_instrument => 'COR1').should_not be_schema_compatible('pain.008.002.02')
35
+ end
36
+ end
16
37
  end
17
38
 
18
39
  context 'Mandate Date of Signature' do