From a9f481b249c104a329132387f508d940809aa47d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Moal Date: Fri, 11 Aug 2017 07:31:23 +0200 Subject: [PATCH] Add Currency support for debits and credits (#64) --- README.md | 12 +++++- lib/sepa_king/message/credit_transfer.rb | 2 +- lib/sepa_king/message/direct_debit.rb | 2 +- lib/sepa_king/transaction.rb | 4 +- .../credit_transfer_transaction.rb | 6 ++- .../transaction/direct_debit_transaction.rb | 6 ++- spec/credit_transfer_spec.rb | 39 +++++++++++++++++++ spec/credit_transfer_transaction_spec.rb | 15 ++++++- spec/direct_debit_spec.rb | 35 +++++++++++++++++ spec/direct_debit_transaction_spec.rb | 15 ++++++- spec/transaction_spec.rb | 10 +++++ 11 files changed, 133 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index abab78b..167fe49 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,14 @@ sdd.add_transaction( # String, max. 34 chars iban: 'DE21500500009876543210', - # Amount in EUR + # Amount # Number with two decimal digit amount: 39.99, + # OPTIONAL: Currency, EUR by default (ISO 4217 standard) + # String, 3 char + currency: 'EUR', + # OPTIONAL: Instruction Identification, will not be submitted to the debtor # String, max. 35 char instruction: '12345', @@ -166,10 +170,14 @@ sct.add_transaction( # String, max. 34 chars iban: 'DE37112589611964645802', - # Amount in EUR + # Amount # Number with two decimal digit amount: 102.50, + # OPTIONAL: Currency, EUR by default (ISO 4217 standard) + # String, 3 char + currency: 'EUR', + # OPTIONAL: Instruction Identification, will not be submitted to the creditor # String, max. 35 char instruction: '12345', diff --git a/lib/sepa_king/message/credit_transfer.rb b/lib/sepa_king/message/credit_transfer.rb index 0797bac..484cfb7 100644 --- a/lib/sepa_king/message/credit_transfer.rb +++ b/lib/sepa_king/message/credit_transfer.rb @@ -69,7 +69,7 @@ def build_transaction(builder, transaction) builder.EndToEndId(transaction.reference) end builder.Amt do - builder.InstdAmt('%.2f' % transaction.amount, Ccy: 'EUR') + builder.InstdAmt('%.2f' % transaction.amount, Ccy: transaction.currency) end if transaction.bic builder.CdtrAgt do diff --git a/lib/sepa_king/message/direct_debit.rb b/lib/sepa_king/message/direct_debit.rb index 8f322dc..f659bbc 100644 --- a/lib/sepa_king/message/direct_debit.rb +++ b/lib/sepa_king/message/direct_debit.rb @@ -113,7 +113,7 @@ def build_transaction(builder, transaction) end builder.EndToEndId(transaction.reference) end - builder.InstdAmt('%.2f' % transaction.amount, Ccy: 'EUR') + builder.InstdAmt('%.2f' % transaction.amount, Ccy: transaction.currency) builder.DrctDbtTx do builder.MndtRltdInf do builder.MndtId(transaction.mandate_id) diff --git a/lib/sepa_king/transaction.rb b/lib/sepa_king/transaction.rb index f707d44..4fece1f 100644 --- a/lib/sepa_king/transaction.rb +++ b/lib/sepa_king/transaction.rb @@ -6,11 +6,12 @@ class Transaction DEFAULT_REQUESTED_DATE = Date.new(1999, 1, 1).freeze - attr_accessor :name, :iban, :bic, :amount, :instruction, :reference, :remittance_information, :requested_date, :batch_booking + attr_accessor :name, :iban, :bic, :amount, :instruction, :reference, :remittance_information, :requested_date, :batch_booking, :currency convert :name, :instruction, :reference, :remittance_information, to: :text convert :amount, to: :decimal validates_length_of :name, within: 1..70 + validates_length_of :currency, is: 3 validates_length_of :instruction, within: 1..35, allow_nil: true validates_length_of :reference, within: 1..35, allow_nil: true validates_length_of :remittance_information, within: 1..140, allow_nil: true @@ -27,6 +28,7 @@ def initialize(attributes = {}) self.requested_date ||= DEFAULT_REQUESTED_DATE self.reference ||= 'NOTPROVIDED' self.batch_booking = true if self.batch_booking.nil? + self.currency ||= 'EUR' end protected diff --git a/lib/sepa_king/transaction/credit_transfer_transaction.rb b/lib/sepa_king/transaction/credit_transfer_transaction.rb index e701b43..e3b62dd 100644 --- a/lib/sepa_king/transaction/credit_transfer_transaction.rb +++ b/lib/sepa_king/transaction/credit_transfer_transaction.rb @@ -14,10 +14,12 @@ def initialize(attributes = {}) def schema_compatible?(schema_name) case schema_name - when PAIN_001_001_03, PAIN_001_002_03 + when PAIN_001_001_03 self.bic.present? && self.service_level == 'SEPA' + when PAIN_001_002_03 + self.bic.present? && self.service_level == 'SEPA' && self.currency == 'EUR' when PAIN_001_003_03 - true + self.currency == 'EUR' end end end diff --git a/lib/sepa_king/transaction/direct_debit_transaction.rb b/lib/sepa_king/transaction/direct_debit_transaction.rb index 9ee36ee..87d3777 100644 --- a/lib/sepa_king/transaction/direct_debit_transaction.rb +++ b/lib/sepa_king/transaction/direct_debit_transaction.rb @@ -34,8 +34,10 @@ def initialize(attributes = {}) def schema_compatible?(schema_name) case schema_name when PAIN_008_002_02 - self.bic.present? && %w(CORE B2B).include?(self.local_instrument) - when PAIN_008_003_02, PAIN_008_001_02 + self.bic.present? && %w(CORE B2B).include?(self.local_instrument) && self.currency == 'EUR' + when PAIN_008_003_02 + self.currency == 'EUR' + when PAIN_008_001_02 true end end diff --git a/spec/credit_transfer_spec.rb b/spec/credit_transfer_spec.rb index 36d3f25..3a0cc73 100644 --- a/spec/credit_transfer_spec.rb +++ b/spec/credit_transfer_spec.rb @@ -288,6 +288,45 @@ expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/PmtId/InstrId', '1234/ABC') end end + + context 'with a different currency given' do + subject do + sct = credit_transfer + + sct.add_transaction name: 'Telekomiker AG', + iban: 'DE37112589611964645802', + bic: 'PBNKDEFF370', + amount: 102.50, + currency: 'CHF' + + sct + end + + it 'should validate against pain.001.001.03' do + expect(subject.to_xml('pain.001.001.03')).to validate_against('pain.001.001.03.xsd') + end + + it 'should have a CHF Ccy' do + doc = Nokogiri::XML(subject.to_xml('pain.001.001.03')) + doc.remove_namespaces! + + nodes = doc.xpath('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/Amt/InstdAmt') + expect(nodes.length).to eql(1) + expect(nodes.first.attribute('Ccy').value).to eql('CHF') + end + + it 'should fail for pain.001.002.03' do + expect { + subject.to_xml(SEPA::PAIN_001_002_03) + }.to raise_error(RuntimeError) + end + + it 'should fail for pain.001.003.03' do + expect { + subject.to_xml(SEPA::PAIN_001_003_03) + }.to raise_error(RuntimeError) + end + end end end end diff --git a/spec/credit_transfer_transaction_spec.rb b/spec/credit_transfer_transaction_spec.rb index ebc09c0..7f79b7a 100644 --- a/spec/credit_transfer_transaction_spec.rb +++ b/spec/credit_transfer_transaction_spec.rb @@ -17,19 +17,30 @@ describe :schema_compatible? do context 'for pain.001.003.03' do - it 'should success' do + it 'should succeed' do expect(SEPA::CreditTransferTransaction.new({})).to be_schema_compatible('pain.001.003.03') end + + it 'should fail for invalid attributes' do + expect(SEPA::CreditTransferTransaction.new(:currency => 'CHF')).not_to be_schema_compatible('pain.001.003.03') + end end context 'pain.001.002.03' do - it 'should success for valid attributes' do + it 'should succeed for valid attributes' do expect(SEPA::CreditTransferTransaction.new(:bic => 'SPUEDE2UXXX', :service_level => 'SEPA')).to be_schema_compatible('pain.001.002.03') end it 'should fail for invalid attributes' do expect(SEPA::CreditTransferTransaction.new(:bic => nil)).not_to be_schema_compatible('pain.001.002.03') expect(SEPA::CreditTransferTransaction.new(:bic => 'SPUEDE2UXXX', :service_level => 'URGP')).not_to be_schema_compatible('pain.001.002.03') + expect(SEPA::CreditTransferTransaction.new(:bic => 'SPUEDE2UXXX', :currency => 'CHF')).not_to be_schema_compatible('pain.001.002.03') + end + end + + context 'for pain.001.001.03' do + it 'should succeed for valid attributes' do + expect(SEPA::CreditTransferTransaction.new(:bic => 'SPUEDE2UXXX', :currency => 'CHF')).to be_schema_compatible('pain.001.001.03') end end end diff --git a/spec/direct_debit_spec.rb b/spec/direct_debit_spec.rb index b4cf381..d208dc8 100644 --- a/spec/direct_debit_spec.rb +++ b/spec/direct_debit_spec.rb @@ -429,6 +429,41 @@ expect(subject).to have_xml('//Document/CstmrDrctDbtInitn/PmtInf/DrctDbtTxInf[1]/PmtId/InstrId', '1234/ABC') end end + + context 'with a different currency given' do + subject do + sct = direct_debit + + sct.add_transaction(direct_debt_transaction.merge(instruction: '1234/ABC', currency: 'SEK')) + + sct + end + + it 'should validate against pain.001.001.03' do + expect(subject.to_xml(SEPA::PAIN_008_001_02)).to validate_against('pain.008.001.02.xsd') + end + + it 'should have a CHF Ccy' do + doc = Nokogiri::XML(subject.to_xml('pain.008.001.02')) + doc.remove_namespaces! + + nodes = doc.xpath('//Document/CstmrDrctDbtInitn/PmtInf/DrctDbtTxInf[1]/InstdAmt') + expect(nodes.length).to eql(1) + expect(nodes.first.attribute('Ccy').value).to eql('SEK') + end + + it 'should fail for pain.008.002.02' do + expect { + subject.to_xml(SEPA::PAIN_008_002_02) + }.to raise_error(RuntimeError) + end + + it 'should fail for pain.008.003.02' do + expect { + subject.to_xml(SEPA::PAIN_008_003_02) + }.to raise_error(RuntimeError) + end + end end end end diff --git a/spec/direct_debit_transaction_spec.rb b/spec/direct_debit_transaction_spec.rb index 498579d..5d1883c 100644 --- a/spec/direct_debit_transaction_spec.rb +++ b/spec/direct_debit_transaction_spec.rb @@ -19,19 +19,30 @@ describe :schema_compatible? do context 'for pain.008.003.02' do - it 'should success' do + it 'should succeed' do expect(SEPA::DirectDebitTransaction.new({})).to be_schema_compatible('pain.008.003.02') end + + it 'should fail for invalid attributes' do + expect(SEPA::DirectDebitTransaction.new(:currency => 'CHF')).not_to be_schema_compatible('pain.001.003.03') + end end context 'for pain.008.002.02' do - it 'should success for valid attributes' do + it 'should succeed for valid attributes' do expect(SEPA::DirectDebitTransaction.new(:bic => 'SPUEDE2UXXX', :local_instrument => 'CORE')).to be_schema_compatible('pain.008.002.02') end it 'should fail for invalid attributes' do expect(SEPA::DirectDebitTransaction.new(:bic => nil)).not_to be_schema_compatible('pain.008.002.02') expect(SEPA::DirectDebitTransaction.new(:bic => 'SPUEDE2UXXX', :local_instrument => 'COR1')).not_to be_schema_compatible('pain.008.002.02') + expect(SEPA::DirectDebitTransaction.new(:bic => 'SPUEDE2UXXX', :currency => 'CHF')).not_to be_schema_compatible('pain.008.002.02') + end + end + + context 'for pain.008.001.02' do + it 'should succeed for valid attributes' do + expect(SEPA::DirectDebitTransaction.new(:bic => 'SPUEDE2UXXX', :currency => 'CHF')).to be_schema_compatible('pain.008.001.02') end end end diff --git a/spec/transaction_spec.rb b/spec/transaction_spec.rb index dfb98c8..d9ac82a 100644 --- a/spec/transaction_spec.rb +++ b/spec/transaction_spec.rb @@ -75,4 +75,14 @@ expect(SEPA::Transaction).not_to accept('', 'X' * 141, for: :remittance_information) end end + + context 'Currency' do + it 'should allow valid values' do + expect(SEPA::Transaction).to accept('EUR', 'CHF', 'SEK', for: :currency) + end + + it 'should not allow invalid values' do + expect(SEPA::Transaction).not_to accept('', 'EURO', 'ABCDEF', for: :currency) + end + end end