Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ nbproject/*
coverage/*
rdoc/*
pkg/*
tmp/*
.DS_Store
Gemfile*.lock
# tmp disable
Expand Down
1 change: 1 addition & 0 deletions lib/sepa_king/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Account
convert :name, to: :text

validates_length_of :name, within: 1..70
validates_presence_of :iban
validates_with BICValidator, IBANValidator, message: "%{value} is invalid"

def initialize(attributes = {})
Expand Down
4 changes: 4 additions & 0 deletions lib/sepa_king/account/creditor_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module SEPA
class CreditorAccount < Account
attr_accessor :creditor_identifier

def identifier
creditor_identifier
end

validates_with CreditorIdentifierValidator, message: "%{value} is invalid"
end
end
7 changes: 7 additions & 0 deletions lib/sepa_king/account/debtor_account.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# encoding: utf-8
module SEPA
class DebtorAccount < Account
attr_accessor :debtor_identifier

def identifier
debtor_identifier
end

validates_with DebtorIdentifierValidator, message: "%{value} is invalid"
end
end
7 changes: 5 additions & 2 deletions lib/sepa_king/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,13 @@ def build_group_header(builder)
builder.Id do
builder.OrgId do
builder.Othr do
builder.Id(account.creditor_identifier)
builder.Id(account.identifier)
builder.SchmeNm do
builder.Cd('CUST')
end
end
end
end if account.respond_to? :creditor_identifier
end if account.identifier
end
end
end
Expand Down
58 changes: 50 additions & 8 deletions lib/sepa_king/message/credit_transfer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ class CreditTransfer < Message
# Find groups of transactions which share the same values of some attributes
def transaction_group(transaction)
{ requested_date: transaction.requested_date,
batch_booking: transaction.batch_booking,
service_level: transaction.service_level,
category_purpose: transaction.category_purpose
batch_booking: transaction.batch_booking,
service_level: transaction.service_level,
category_purpose: transaction.category_purpose,
account: transaction.debtor_account || account
}
end

Expand Down Expand Up @@ -41,17 +42,27 @@ def build_payment_informations(builder)
end
builder.ReqdExctnDt(group[:requested_date].iso8601)
builder.Dbtr do
builder.Nm(account.name)
builder.Nm(group[:account].name)
builder.Id do
builder.OrgId do
builder.Othr do
builder.Id(group[:account].debtor_identifier)
builder.SchmeNm do
builder.Cd('CUST')
end
end
end
end if group[:account].debtor_identifier
end
builder.DbtrAcct do
builder.Id do
builder.IBAN(account.iban)
builder.IBAN(group[:account].iban)
end
end
builder.DbtrAgt do
builder.FinInstnId do
if account.bic
builder.BIC(account.bic)
if group[:account].bic
builder.BIC(group[:account].bic)
else
builder.Othr do
builder.Id('NOTPROVIDED')
Expand Down Expand Up @@ -88,6 +99,20 @@ def build_transaction(builder, transaction)
end
end
end

if transaction.clearing_bank_identifier
builder.CdtrAgt do
builder.FinInstnId do
builder.ClrSysMmbId do
builder.ClrSysId do
builder.Cd(transaction.clearing_code)
end
builder.MmbId(transaction.clearing_bank_identifier)
end
end
end
end

builder.Cdtr do
builder.Nm(transaction.name)
if transaction.creditor_address
Expand Down Expand Up @@ -129,9 +154,26 @@ def build_transaction(builder, transaction)
end
end
end

builder.CdtrAcct do
builder.Id do
builder.IBAN(transaction.iban)
if transaction.iban
builder.IBAN(transaction.iban)
end

if transaction.bban
builder.Othr do
builder.Id(transaction.bban)
builder.SchmeNm do
if transaction.bban_proprietary
builder.Prtry(transaction.bban_proprietary)
else
builder.Cd("BBAN")
end
end
end
end

end
end
if transaction.remittance_information
Expand Down
6 changes: 6 additions & 0 deletions lib/sepa_king/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class Transaction
attr_accessor :name,
:iban,
:bic,
:bban, # bankgiro 5748964
:bban_proprietary, # BGNR
:clearing_code, # SESBA
:clearing_bank_identifier, # 9960
:amount,
:instruction,
:reference,
Expand All @@ -31,6 +35,8 @@ class Transaction
validates_presence_of :requested_date
validates_inclusion_of :batch_booking, :in => [true, false]
validates_with BICValidator, IBANValidator, message: "%{value} is invalid"
validates :iban, presence: true, unless: :bban
validates :bban, presence: true, unless: :iban

def initialize(attributes = {})
attributes.each do |name, value|
Expand Down
11 changes: 9 additions & 2 deletions lib/sepa_king/transaction/credit_transfer_transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ module SEPA
class CreditTransferTransaction < Transaction
attr_accessor :service_level,
:creditor_address,
:category_purpose
:category_purpose,
:debtor_account

validates_inclusion_of :service_level, :in => %w(SEPA URGP), :allow_nil => true
validates_length_of :category_purpose, within: 1..4, allow_nil: true

validate { |t| t.validate_requested_date_after(Date.today) }
validate do |t|
t.validate_requested_date_after(Date.today)

if debtor_account
errors.add(:debtor_account, 'is not correct') unless debtor_account.valid?
end
end

def initialize(attributes = {})
super
Expand Down
21 changes: 19 additions & 2 deletions lib/sepa_king/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ def validate(record)
field_name = options[:field_name] || :iban
value = record.send(field_name).to_s

unless IBANTools::IBAN.valid?(value) && value.match?(REGEX)
record.errors.add(field_name, :invalid, message: options[:message])
if value.present?
unless IBANTools::IBAN.valid?(value) && value.match?(REGEX)
record.errors.add(field_name, :invalid, message: options[:message])
end
end
end
end
Expand Down Expand Up @@ -58,6 +60,21 @@ def valid?(creditor_identifier)
end
end

class DebtorIdentifierValidator < ActiveModel::Validator
def validate(record)
field_name = options[:field_name] || :debtor_identifier
value = record.send(field_name)

unless valid?(value)
record.errors.add(field_name, :invalid, message: options[:message])
end
end

def valid?(debtor_identifier)
debtor_identifier.to_s.length <= 35 # Field is Max35Text
end
end

class MandateIdentifierValidator < ActiveModel::Validator
REGEX = %r{\A[A-Za-z0-9 +?/:().,'-]{1,35}\z}

Expand Down
135 changes: 135 additions & 0 deletions spec/credit_transfer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,114 @@
end
end

context 'with a bankgiro creditor account' do
subject do
sct = credit_transfer

sct.add_transaction name: 'Telekomiker AG',
bban: '123456',
clearing_bank_identifier: '9900', # bankgiro
clearing_code: 'SESBA',
bban_proprietary: 'BGNR',
amount: 102.50,
reference: 'XYZ-1234/123',
remittance_information: 'Rechnung vom 22.08.2013'

sct.to_xml(SEPA::PAIN_001_001_03)
end

it 'should validate against pain.001.001.03' do
expect(subject).to validate_against('pain.001.001.03.xsd')
end

it 'should contain <CdtrAcct/Id/Othr> with expected <Id> and <SchmeNm/Prtry>' do
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAcct/Id/Othr/Id', '123456')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAcct/Id/Othr/SchmeNm/Prtry', 'BGNR')
end

it 'should contain <ClrSysMmbId> with expected <MmbId> and <ClrSysId/Cd>' do
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAgt/FinInstnId/ClrSysMmbId/MmbId', '9900')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAgt/FinInstnId/ClrSysMmbId/ClrSysId/Cd', 'SESBA')
end
end

context 'with a plusgiro creditor account' do
subject do
sct = credit_transfer

sct.add_transaction name: 'Telekomiker AG',
bban: '123456',
clearing_bank_identifier: '9960', # plusgiro
clearing_code: 'SESBA',
amount: 102.50,
reference: 'XYZ-1234/123',
remittance_information: 'Rechnung vom 22.08.2013'

sct.to_xml(SEPA::PAIN_001_001_03)
end

it 'should validate against pain.001.001.03' do
expect(subject).to validate_against('pain.001.001.03.xsd')
end

it 'should contain <CdtrAcct/Id/Othr> with expected <Id> and <SchmeNm/Cd>' do
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAcct/Id/Othr/Id', '123456')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAcct/Id/Othr/SchmeNm/Cd', 'BBAN')
end

it 'should contain <ClrSysMmbId> with expected <MmbId> and <ClrSysId/Cd>' do
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAgt/FinInstnId/ClrSysMmbId/MmbId', '9960')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAgt/FinInstnId/ClrSysMmbId/ClrSysId/Cd', 'SESBA')
end
end

context 'with a bban creditor account' do
subject do
sct = credit_transfer

sct.add_transaction name: 'Telekomiker AG',
bban: '123456',
amount: 102.50,
reference: 'XYZ-1234/123',
remittance_information: 'Rechnung vom 22.08.2013'

sct.to_xml(SEPA::PAIN_001_001_03)
end

it 'should validate against pain.001.001.03' do
expect(subject).to validate_against('pain.001.001.03.xsd')
end

it 'should contain <CdtrAcct/Id/Othr> with expected <Id> and <SchmeNm/Cd>' do
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAcct/Id/Othr/Id', '123456')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf/CdtTrfTxInf[1]/CdtrAcct/Id/Othr/SchmeNm/Cd', 'BBAN')
end
end

context 'with debtor identifier' do
subject do
sct = SEPA::CreditTransfer.new name: 'Schuldner GmbH',
iban: 'DE87200500001234567890',
bic: 'BANKDEFFXXX',
debtor_identifier: 'Debtor Identifier AG'

sct.add_transaction name: 'Telekomiker AG',
bban: '123456',
amount: 102.50

sct.to_xml(SEPA::PAIN_001_001_03)
end

it 'should validate against pain.001.001.03' do
expect(subject).to validate_against('pain.001.001.03.xsd')
end

it 'should contain <GrpHdr/InitgPty/Id/OrgId/Othr> with expected <Id> and <SchmeNm/Cd>' do
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/GrpHdr/InitgPty/Id/OrgId/Othr/SchmeNm/Cd', 'CUST')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/GrpHdr/InitgPty/Id/OrgId/Othr/Id', 'Debtor Identifier AG')
end
end

context 'for valid debtor' do
context 'without BIC (IBAN-only)' do
subject do
Expand Down Expand Up @@ -331,6 +439,33 @@
end
end

context 'with transactions containing different debtor_account' do
subject do
sdd = credit_transfer

debtor_account = SEPA::DebtorAccount.new( name: 'Debtor Inc.',
bic: 'RABONL2U',
iban: 'NL08RABO0135742099',
debtor_identifier: '8001011234'
)

sdd.add_transaction(credit_transfer_transaction)
sdd.add_transaction(credit_transfer_transaction.merge(debtor_account: debtor_account))
sdd.add_transaction(credit_transfer_transaction.merge(debtor_account: debtor_account))

sdd.to_xml
end

it 'should contain two payment_informations with <Cdtr>' do
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf[1]/Dbtr/Nm', 'Schuldner GmbH')
expect(subject).not_to have_xml('//Document/CstmrCdtTrfInitn/PmtInf[1]/Dbtr/Id/OrgId/Othr/Id')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf[2]/Dbtr/Nm', 'Debtor Inc.')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf[2]/Dbtr/Id/OrgId/Othr/Id', '8001011234')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf[2]/Dbtr/Id/OrgId/Othr/SchmeNm/Cd', 'CUST')
expect(subject).to have_xml('//Document/CstmrCdtTrfInitn/PmtInf[2]/CdtTrfTxInf[2]/Cdtr/Nm') # Check that we have two CdtTrfTxInf
end
end

context 'with instruction given' do
subject do
sct = credit_transfer
Expand Down
10 changes: 10 additions & 0 deletions spec/debtor_account_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@
iban: 'DE87200500001234567890'
).to be_valid
end

describe :debtor_identifier do
it 'should accept valid value' do
expect(SEPA::DebtorAccount).to accept('a'*35, for: :debtor_identifier)
end

it 'should not accept invalid value' do
expect(SEPA::DebtorAccount).not_to accept('a'*36, for: :debtor_identifier)
end
end
end
Loading