sepa_rator 0.15.0 → 0.16.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 +4 -4
- data/lib/sepa_rator/account/address.rb +1 -2
- data/lib/sepa_rator/account/contact_details.rb +1 -2
- data/lib/sepa_rator/account.rb +4 -23
- data/lib/sepa_rator/concerns/schema_validation.rb +5 -5
- data/lib/sepa_rator/nested_model_validator.rb +13 -0
- data/lib/sepa_rator/transaction/credit_transfer_transaction.rb +3 -7
- data/lib/sepa_rator/transaction/direct_debit_transaction.rb +5 -13
- data/lib/sepa_rator/transaction.rb +3 -18
- data/lib/sepa_rator/validator.rb +35 -8
- data/lib/sepa_rator/version.rb +1 -1
- data/lib/sepa_rator.rb +2 -2
- metadata +5 -5
- data/lib/sepa_rator/concerns/attribute_initializer.rb +0 -40
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83ba8734459eaf10832606aa2c4c894cad0fde36f221a88e4664d55ec929d1d6
|
|
4
|
+
data.tar.gz: 54c0ad9c0f54bcfa88b2912cd1f93b5fd2574d15581574dd5bd2760941adef10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2570e0b6dc02a36accc1d6caeeeb19b064f6da7395f43a1d13af1629de401f67b94d07fcbd45dd82b5e6bc473860ede0c8a6e41f811eca5e66f1fda15398444c
|
|
7
|
+
data.tar.gz: eb2e7d4a6b6809f4a4eeec006de9816773407845c3a74a002fe082d7fb4190052389ea007b7357bf65651638d83c9cbafbc15135faba6decf145de16d0f48558
|
data/lib/sepa_rator/account.rb
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module SEPA
|
|
4
4
|
class Account
|
|
5
|
-
include ActiveModel::
|
|
6
|
-
include AttributeInitializer
|
|
5
|
+
include ActiveModel::Model
|
|
7
6
|
extend Converter
|
|
8
7
|
|
|
9
8
|
attr_accessor :name, :iban, :bic, :address, :agent_lei, :contact_details
|
|
@@ -11,28 +10,10 @@ module SEPA
|
|
|
11
10
|
convert :name, to: :text
|
|
12
11
|
|
|
13
12
|
validates_length_of :name, within: 1..70
|
|
14
|
-
validates_with BICValidator,
|
|
13
|
+
validates_with BICValidator, message: 'is invalid'
|
|
14
|
+
validates_with IBANValidator
|
|
15
15
|
validates_with LEIValidator, field_name: :agent_lei, message: 'is invalid'
|
|
16
|
-
|
|
17
|
-
validate do |record|
|
|
18
|
-
next unless record.address
|
|
19
|
-
|
|
20
|
-
unless record.address.valid?
|
|
21
|
-
record.address.errors.each do |error|
|
|
22
|
-
record.errors.add(:address, error.full_message)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
validate do |record|
|
|
28
|
-
next unless record.contact_details
|
|
29
|
-
|
|
30
|
-
unless record.contact_details.valid?
|
|
31
|
-
record.contact_details.errors.each do |error|
|
|
32
|
-
record.errors.add(:contact_details, error.full_message)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
16
|
+
validates :address, :contact_details, nested_model: true, allow_nil: true
|
|
36
17
|
|
|
37
18
|
def initiating_party_id(builder, schema_name); end
|
|
38
19
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
3
5
|
module SEPA
|
|
4
6
|
module SchemaValidation
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
5
9
|
SCHEMA_DIR = File.expand_path('../../schema', __dir__).freeze
|
|
6
10
|
SCHEMA_CACHE_MUTEX = Mutex.new
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
base.extend ClassMethods
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
module ClassMethods
|
|
12
|
+
class_methods do
|
|
13
13
|
def schema_cache
|
|
14
14
|
@schema_cache ||= {}
|
|
15
15
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Validates nested ActiveModel objects and propagates their errors.
|
|
4
|
+
# Defined at root level so ActiveModel's const_get lookup finds it from any namespace.
|
|
5
|
+
# Usage: validates :address, :contact_details, nested_model: true
|
|
6
|
+
class NestedModelValidator < ActiveModel::EachValidator
|
|
7
|
+
def validate_each(record, attribute, value)
|
|
8
|
+
return unless value
|
|
9
|
+
return if value.valid?
|
|
10
|
+
|
|
11
|
+
value.errors.each { |error| record.errors.add(attribute, error.full_message) }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -21,7 +21,8 @@ module SEPA
|
|
|
21
21
|
:credit_transfer_mandate_id,
|
|
22
22
|
:credit_transfer_mandate_date_of_signature,
|
|
23
23
|
:credit_transfer_mandate_frequency,
|
|
24
|
-
:creditor_contact_details
|
|
24
|
+
:creditor_contact_details,
|
|
25
|
+
:creditor_address
|
|
25
26
|
|
|
26
27
|
CHARGE_BEARERS = %w[DEBT CRED SHAR SLEV].freeze
|
|
27
28
|
EPC_ONLY_SCHEMAS = %w[pain.001.002.03 pain.001.003.03].freeze
|
|
@@ -39,7 +40,7 @@ module SEPA
|
|
|
39
40
|
validates_inclusion_of :service_level, in: %w[SEPA URGP], allow_nil: true
|
|
40
41
|
validates_length_of :category_purpose, within: 1..4, allow_nil: true
|
|
41
42
|
validates_inclusion_of :charge_bearer, in: CHARGE_BEARERS, allow_nil: true
|
|
42
|
-
|
|
43
|
+
validates :creditor_address, :creditor_contact_details, nested_model: true, allow_nil: true
|
|
43
44
|
|
|
44
45
|
convert :debtor_agent_instruction, :instruction_for_debtor_agent,
|
|
45
46
|
:credit_transfer_mandate_id, to: :text
|
|
@@ -50,11 +51,6 @@ module SEPA
|
|
|
50
51
|
validates_length_of :credit_transfer_mandate_id, within: 1..35, allow_nil: true
|
|
51
52
|
validates_inclusion_of :credit_transfer_mandate_frequency, in: FREQUENCY_CODES, allow_nil: true
|
|
52
53
|
|
|
53
|
-
validate do |t|
|
|
54
|
-
next unless t.creditor_contact_details && !t.creditor_contact_details.valid?
|
|
55
|
-
|
|
56
|
-
t.creditor_contact_details.errors.each { |error| t.errors.add(:creditor_contact_details, error.full_message) }
|
|
57
|
-
end
|
|
58
54
|
validate { |t| t.validate_requested_date_after(Date.today) }
|
|
59
55
|
validate :validate_instructions_for_creditor_agent
|
|
60
56
|
validate :validate_regulatory_reportings
|
|
@@ -16,7 +16,8 @@ module SEPA
|
|
|
16
16
|
:original_debtor_account,
|
|
17
17
|
:same_mandate_new_debtor_agent,
|
|
18
18
|
:original_creditor_account,
|
|
19
|
-
:debtor_contact_details
|
|
19
|
+
:debtor_contact_details,
|
|
20
|
+
:debtor_address
|
|
20
21
|
|
|
21
22
|
CHARGE_BEARERS = %w[DEBT CRED SHAR SLEV].freeze
|
|
22
23
|
|
|
@@ -25,23 +26,14 @@ module SEPA
|
|
|
25
26
|
validates_inclusion_of :local_instrument, in: LOCAL_INSTRUMENTS
|
|
26
27
|
validates_inclusion_of :sequence_type, in: SEQUENCE_TYPES
|
|
27
28
|
validates_inclusion_of :charge_bearer, in: CHARGE_BEARERS, allow_nil: true
|
|
28
|
-
|
|
29
|
-
validate do |t|
|
|
30
|
-
next unless t.debtor_contact_details && !t.debtor_contact_details.valid?
|
|
31
|
-
|
|
32
|
-
t.debtor_contact_details.errors.each { |error| t.errors.add(:debtor_contact_details, error.full_message) }
|
|
33
|
-
end
|
|
29
|
+
validates :debtor_address, :debtor_contact_details, :creditor_account, nested_model: true, allow_nil: true
|
|
34
30
|
validate { |t| t.validate_requested_date_after(Date.today.next) }
|
|
35
31
|
|
|
36
32
|
validate do |t|
|
|
37
33
|
errors.add(:original_mandate_id, 'is invalid') if original_mandate_id && !original_mandate_id.to_s.match?(MandateIdentifierValidator::REGEX)
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if original_debtor_account && !original_debtor_account.to_s.empty?
|
|
42
|
-
iban_str = original_debtor_account.to_s
|
|
43
|
-
errors.add(:original_debtor_account, 'is not a valid IBAN') unless
|
|
44
|
-
IBANTools::IBAN.valid?(iban_str) && iban_str.match?(IBANValidator::REGEX)
|
|
35
|
+
if original_debtor_account && !original_debtor_account.to_s.empty? && !IBANValidator.valid_iban?(original_debtor_account)
|
|
36
|
+
errors.add(:original_debtor_account, 'is not a valid IBAN')
|
|
45
37
|
end
|
|
46
38
|
|
|
47
39
|
if t.mandate_date_of_signature.is_a?(Date)
|
|
@@ -2,25 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module SEPA
|
|
4
4
|
class Transaction
|
|
5
|
-
include ActiveModel::
|
|
6
|
-
include AttributeInitializer
|
|
5
|
+
include ActiveModel::Model
|
|
7
6
|
extend Converter
|
|
8
7
|
|
|
9
|
-
# DSL to declare and validate address fields on subclasses (ISP-compliant).
|
|
10
|
-
# Each subclass declares only the address it actually uses.
|
|
11
|
-
def self.validates_address(*fields)
|
|
12
|
-
fields.each do |field|
|
|
13
|
-
attr_accessor field
|
|
14
|
-
|
|
15
|
-
validate do |t|
|
|
16
|
-
address = t.public_send(field)
|
|
17
|
-
next unless address && !address.valid?
|
|
18
|
-
|
|
19
|
-
address.errors.each { |error| t.errors.add(field, error.full_message) }
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
8
|
# Convention SEPA: 1999-01-01 signifies "execute as soon as possible" (ASAP).
|
|
25
9
|
# When no specific date is requested, this sentinel value tells the bank
|
|
26
10
|
# to process the payment at the earliest opportunity.
|
|
@@ -70,7 +54,8 @@ module SEPA
|
|
|
70
54
|
UETR_REGEX = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\z/
|
|
71
55
|
validates_format_of :uetr, with: UETR_REGEX, allow_nil: true
|
|
72
56
|
validates_inclusion_of :batch_booking, in: [true, false]
|
|
73
|
-
validates_with BICValidator,
|
|
57
|
+
validates_with BICValidator, message: 'is invalid'
|
|
58
|
+
validates_with IBANValidator
|
|
74
59
|
validates_with LEIValidator, field_name: :agent_lei, message: 'is invalid'
|
|
75
60
|
|
|
76
61
|
validate do |t|
|
data/lib/sepa_rator/validator.rb
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SEPA
|
|
4
|
+
# ISO 7064 Mod 97-10 checksum used by IBAN, LEI, and Creditor Identifier
|
|
5
|
+
def self.mod97_valid?(alphanumeric_string)
|
|
6
|
+
numeric = alphanumeric_string.gsub(/[A-Z]/i) { |c| c.upcase.ord - 55 }
|
|
7
|
+
numeric.to_i % 97 == 1
|
|
8
|
+
end
|
|
9
|
+
|
|
4
10
|
class IBANValidator < ActiveModel::Validator
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
def self.valid_iban?(value)
|
|
12
|
+
iban = Ibandit::IBAN.new(value.to_s)
|
|
13
|
+
iban.valid? && value.to_s == iban.iban
|
|
14
|
+
end
|
|
7
15
|
|
|
8
16
|
def validate(record)
|
|
9
17
|
field_name = options[:field_name] || :iban
|
|
10
18
|
value = record.public_send(field_name).to_s
|
|
11
19
|
|
|
12
|
-
|
|
20
|
+
iban = Ibandit::IBAN.new(value)
|
|
21
|
+
unless iban.valid?
|
|
22
|
+
record.errors.add(field_name, :invalid, message: options[:message] || iban_error_message(iban))
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
return if value == iban.iban
|
|
13
26
|
|
|
14
|
-
record.errors.add(field_name, :invalid,
|
|
27
|
+
record.errors.add(field_name, :invalid,
|
|
28
|
+
message: options[:message] || 'is invalid (must be uppercase with no spaces)')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def iban_error_message(iban)
|
|
34
|
+
details = iban.errors.map { |key, msg| "#{key} #{msg}" }.join(', ')
|
|
35
|
+
details.empty? ? 'is invalid' : "is invalid (#{details})"
|
|
15
36
|
end
|
|
16
37
|
end
|
|
17
38
|
|
|
@@ -63,9 +84,7 @@ module SEPA
|
|
|
63
84
|
# Strip non-alphanumeric chars from national id before check (the spec allows +?/:().,'-
|
|
64
85
|
# but they are ignored for mod-97 computation)
|
|
65
86
|
check_base = creditor_identifier[0..3] + creditor_identifier[7..].gsub(/[^A-Za-z0-9]/, '')
|
|
66
|
-
|
|
67
|
-
numeric = rearranged.gsub(/[A-Z]/i) { |c| c.upcase.ord - 55 }
|
|
68
|
-
numeric.to_i % 97 == 1
|
|
87
|
+
SEPA.mod97_valid?(check_base[4..] + check_base[0..3])
|
|
69
88
|
end
|
|
70
89
|
end
|
|
71
90
|
|
|
@@ -91,9 +110,17 @@ module SEPA
|
|
|
91
110
|
value = record.public_send(field_name)
|
|
92
111
|
|
|
93
112
|
return unless value
|
|
94
|
-
return if value.to_s
|
|
113
|
+
return if valid_lei?(value.to_s)
|
|
95
114
|
|
|
96
115
|
record.errors.add(field_name, :invalid, message: options[:message])
|
|
97
116
|
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def valid_lei?(value)
|
|
121
|
+
return false unless value.match?(REGEX)
|
|
122
|
+
|
|
123
|
+
SEPA.mod97_valid?(value)
|
|
124
|
+
end
|
|
98
125
|
end
|
|
99
126
|
end
|
data/lib/sepa_rator/version.rb
CHANGED
data/lib/sepa_rator.rb
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
require 'active_model'
|
|
4
4
|
require 'bigdecimal'
|
|
5
5
|
require 'nokogiri'
|
|
6
|
-
require '
|
|
6
|
+
require 'ibandit'
|
|
7
7
|
|
|
8
8
|
require 'sepa_rator/error'
|
|
9
9
|
require 'sepa_rator/converter'
|
|
10
10
|
require 'sepa_rator/validator'
|
|
11
|
-
require 'sepa_rator/
|
|
11
|
+
require 'sepa_rator/nested_model_validator'
|
|
12
12
|
require 'sepa_rator/concerns/schema_validation'
|
|
13
13
|
require 'sepa_rator/concerns/xml_builder'
|
|
14
14
|
require 'sepa_rator/concerns/regulatory_reporting_validator'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sepa_rator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Georg Leciejewski
|
|
@@ -32,19 +32,19 @@ dependencies:
|
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '9'
|
|
34
34
|
- !ruby/object:Gem::Dependency
|
|
35
|
-
name:
|
|
35
|
+
name: ibandit
|
|
36
36
|
requirement: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
40
|
+
version: '1.0'
|
|
41
41
|
type: :runtime
|
|
42
42
|
prerelease: false
|
|
43
43
|
version_requirements: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
47
|
+
version: '1.0'
|
|
48
48
|
- !ruby/object:Gem::Dependency
|
|
49
49
|
name: nokogiri
|
|
50
50
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,7 +86,6 @@ files:
|
|
|
86
86
|
- lib/sepa_rator/account/creditor_address.rb
|
|
87
87
|
- lib/sepa_rator/account/debtor_account.rb
|
|
88
88
|
- lib/sepa_rator/account/debtor_address.rb
|
|
89
|
-
- lib/sepa_rator/concerns/attribute_initializer.rb
|
|
90
89
|
- lib/sepa_rator/concerns/regulatory_reporting_validator.rb
|
|
91
90
|
- lib/sepa_rator/concerns/schema_validation.rb
|
|
92
91
|
- lib/sepa_rator/concerns/xml_builder.rb
|
|
@@ -95,6 +94,7 @@ files:
|
|
|
95
94
|
- lib/sepa_rator/message.rb
|
|
96
95
|
- lib/sepa_rator/message/credit_transfer.rb
|
|
97
96
|
- lib/sepa_rator/message/direct_debit.rb
|
|
97
|
+
- lib/sepa_rator/nested_model_validator.rb
|
|
98
98
|
- lib/sepa_rator/transaction.rb
|
|
99
99
|
- lib/sepa_rator/transaction/credit_transfer_transaction.rb
|
|
100
100
|
- lib/sepa_rator/transaction/direct_debit_transaction.rb
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SEPA
|
|
4
|
-
module AttributeInitializer
|
|
5
|
-
def self.included(base)
|
|
6
|
-
base.include ActiveModel::AttributeAssignment
|
|
7
|
-
base.extend ClassMethods
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
module ClassMethods
|
|
11
|
-
def permitted_attributes
|
|
12
|
-
@permitted_attributes ||= begin
|
|
13
|
-
own_setters = instance_methods(false)
|
|
14
|
-
.select { |m| m.to_s.end_with?('=') }
|
|
15
|
-
.to_set { |m| m.to_s.chomp('=') }
|
|
16
|
-
|
|
17
|
-
if superclass.respond_to?(:permitted_attributes)
|
|
18
|
-
own_setters | superclass.permitted_attributes
|
|
19
|
-
else
|
|
20
|
-
own_setters
|
|
21
|
-
end
|
|
22
|
-
end.freeze
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def initialize(attributes = {})
|
|
27
|
-
assign_attributes(attributes)
|
|
28
|
-
rescue ActiveModel::UnknownAttributeError => e
|
|
29
|
-
raise ArgumentError, "Unknown attribute: #{e.attribute}"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def _assign_attribute(key, value)
|
|
35
|
-
raise ArgumentError, "Unknown attribute: #{key}" unless self.class.permitted_attributes.include?(key.to_s)
|
|
36
|
-
|
|
37
|
-
public_send("#{key}=", value)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|