sepa_king 0.0.7 → 0.1.0

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