aba 0.1.0 → 1.0.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 +5 -5
- data/.gitignore +1 -0
- data/.rspec +3 -1
- data/.travis.yml +6 -0
- data/CHANGELOG.md +14 -0
- data/README.md +110 -18
- data/aba.gemspec +5 -5
- data/lib/aba.rb +6 -114
- data/lib/aba/batch.rb +225 -0
- data/lib/aba/entry.rb +17 -0
- data/lib/aba/return.rb +71 -0
- data/lib/aba/transaction.rb +69 -35
- data/lib/aba/validations.rb +79 -14
- data/lib/aba/version.rb +1 -1
- data/spec/lib/aba/batch_spec.rb +133 -0
- data/spec/lib/aba/return_spec.rb +67 -0
- data/spec/lib/aba/transaction_spec.rb +51 -0
- data/spec/lib/aba/validations_spec.rb +211 -0
- data/spec/lib/aba_spec.rb +21 -0
- data/spec/spec_helper.rb +3 -0
- metadata +29 -19
- data/spec/aba_spec.rb +0 -40
- data/spec/transaction_spec.rb +0 -37
- data/spec/validations_spec.rb +0 -47
data/lib/aba/entry.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Aba
|
2
|
+
class Entry
|
3
|
+
def initialize(attrs = {})
|
4
|
+
attrs.each do |key, value|
|
5
|
+
send("#{key}=", value)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def credit?
|
10
|
+
Validations::CREDIT_TRANSACTION_CODES.include?(transaction_code.to_i)
|
11
|
+
end
|
12
|
+
|
13
|
+
def debit?
|
14
|
+
Validations::DEBIT_TRANSACTION_CODES.include?(transaction_code.to_i)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/aba/return.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
class Aba
|
2
|
+
class Return < Entry
|
3
|
+
include Aba::Validations
|
4
|
+
|
5
|
+
attr_accessor :account_number, :transaction_code, :amount, :account_name,
|
6
|
+
:bsb, :trace_bsb, :trace_account_number, :name_of_remitter,
|
7
|
+
:return_code, :lodgement_reference,
|
8
|
+
:original_processing_day, :original_user_id
|
9
|
+
|
10
|
+
# BSB
|
11
|
+
validates_bsb :bsb
|
12
|
+
|
13
|
+
# Account Number
|
14
|
+
validates_account_number :account_number
|
15
|
+
|
16
|
+
# Indicator
|
17
|
+
validates_return_code :return_code
|
18
|
+
|
19
|
+
# Transaction Code
|
20
|
+
validates_transaction_code :transaction_code
|
21
|
+
|
22
|
+
# Amount
|
23
|
+
validates_integer :amount
|
24
|
+
|
25
|
+
# Original Day of Processing
|
26
|
+
validates_integer :original_processing_day, :unsigned
|
27
|
+
|
28
|
+
# Original User Id
|
29
|
+
validates_max_length :original_user_id, 6
|
30
|
+
validates_integer :original_user_id, :unsigned
|
31
|
+
|
32
|
+
# Account Name
|
33
|
+
validates_max_length :account_name, 32
|
34
|
+
validates_becs :account_name
|
35
|
+
|
36
|
+
# Lodgement Reference
|
37
|
+
validates_max_length :lodgement_reference, 18
|
38
|
+
validates_becs :lodgement_reference
|
39
|
+
|
40
|
+
# Trace Record
|
41
|
+
validates_bsb :trace_bsb
|
42
|
+
validates_account_number :trace_account_number
|
43
|
+
|
44
|
+
# Name of Remitter
|
45
|
+
validates_max_length :name_of_remitter, 16
|
46
|
+
validates_becs :name_of_remitter
|
47
|
+
|
48
|
+
# Allow dashes to be input, but remove them from output
|
49
|
+
def account_number
|
50
|
+
@account_number ? @account_number.to_s.delete('-') : nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
raise 'Transaction data is invalid - check the contents of `errors`' unless valid?
|
55
|
+
|
56
|
+
format('2%-7s%9s%1d%2d%010d%-32s%-18s%-7s%9s%-16s%02d%6s',
|
57
|
+
bsb,
|
58
|
+
account_number,
|
59
|
+
return_code,
|
60
|
+
transaction_code,
|
61
|
+
amount.to_i.abs,
|
62
|
+
account_name,
|
63
|
+
lodgement_reference,
|
64
|
+
trace_bsb,
|
65
|
+
trace_account_number,
|
66
|
+
name_of_remitter,
|
67
|
+
original_processing_day.to_i,
|
68
|
+
original_user_id)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/aba/transaction.rb
CHANGED
@@ -1,31 +1,60 @@
|
|
1
1
|
class Aba
|
2
|
-
class Transaction
|
2
|
+
class Transaction < Entry
|
3
3
|
include Aba::Validations
|
4
4
|
|
5
|
-
attr_accessor :account_number, :transaction_code, :amount, :account_name,
|
6
|
-
:bsb, :trace_bsb, :trace_account_number, :name_of_remitter,
|
7
|
-
:indicator, :lodgement_reference
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
5
|
+
attr_accessor :account_number, :transaction_code, :amount, :account_name,
|
6
|
+
:bsb, :trace_bsb, :trace_account_number, :name_of_remitter,
|
7
|
+
:witholding_amount, :indicator, :lodgement_reference
|
8
|
+
|
9
|
+
# BSB
|
10
|
+
validates_bsb :bsb
|
11
|
+
|
12
|
+
# Account Number
|
13
|
+
validates_account_number :account_number
|
14
|
+
|
15
|
+
# Indicator
|
16
|
+
validates_indicator :indicator
|
17
|
+
|
18
|
+
# Transaction Code
|
19
|
+
validates_transaction_code :transaction_code
|
20
|
+
|
21
|
+
# Amount
|
22
|
+
validates_integer :amount
|
23
|
+
|
24
|
+
# Account Name
|
25
|
+
validates_max_length :account_name, 32
|
26
|
+
validates_becs :account_name
|
27
|
+
|
28
|
+
# Lodgement Reference
|
29
|
+
validates_max_length :lodgement_reference, 18
|
30
|
+
validates_becs :lodgement_reference
|
31
|
+
|
32
|
+
# Trace Record
|
33
|
+
validates_bsb :trace_bsb
|
34
|
+
validates_account_number :trace_account_number
|
35
|
+
|
36
|
+
# Name of Remitter
|
37
|
+
validates_max_length :name_of_remitter, 16
|
38
|
+
validates_becs :name_of_remitter
|
39
|
+
|
40
|
+
# Allow dashes to be input, but remove them from output
|
41
|
+
def account_number
|
42
|
+
@account_number ? @account_number.to_s.gsub('-', '') : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Sane default for majority of use cases as per BECS
|
46
|
+
def indicator
|
47
|
+
@indicator || ' '
|
26
48
|
end
|
27
|
-
|
49
|
+
|
50
|
+
# Optional as per BECS
|
51
|
+
def lodgement_reference
|
52
|
+
@lodgement_reference || ''
|
53
|
+
end
|
54
|
+
|
28
55
|
def to_s
|
56
|
+
raise RuntimeError, 'Transaction data is invalid - check the contents of `errors`' unless valid?
|
57
|
+
|
29
58
|
# Record type
|
30
59
|
output = "1"
|
31
60
|
|
@@ -33,44 +62,49 @@ class Aba
|
|
33
62
|
output += bsb
|
34
63
|
|
35
64
|
# Account number
|
65
|
+
#raise RuntimeError, 'Transaction is missing account_number' unless account_number
|
36
66
|
output += account_number.to_s.rjust(9, " ")
|
37
67
|
|
38
68
|
# Withholding Tax Indicator
|
39
69
|
# "N" – for new or varied Bank/State/Branch number or name details, otherwise blank filled.
|
70
|
+
# "T" - for a drawing under a Transaction Negotiation Authority.
|
40
71
|
# "W" – dividend paid to a resident of a country where a double tax agreement is in force.
|
41
72
|
# "X" – dividend paid to a resident of any other country.
|
42
73
|
# "Y" – interest paid to all non-residents.
|
43
74
|
output += indicator.to_s.ljust(1, " ")
|
44
75
|
|
45
76
|
# Transaction Code
|
46
|
-
# 50 General Credit.
|
47
|
-
# 53 Payroll.
|
48
|
-
# 54 Pension.
|
49
|
-
# 56 Dividend.
|
50
|
-
# 57 Debenture Interest.
|
77
|
+
# 50 General Credit.
|
78
|
+
# 53 Payroll.
|
79
|
+
# 54 Pension.
|
80
|
+
# 56 Dividend.
|
81
|
+
# 57 Debenture Interest.
|
51
82
|
# 13 General Debit.
|
52
83
|
output += transaction_code.to_s
|
53
84
|
|
54
85
|
# Amount to be credited or debited
|
55
|
-
output += amount.abs.to_s.rjust(10, "0")
|
86
|
+
output += amount.to_i.abs.to_s.rjust(10, "0")
|
56
87
|
|
57
88
|
# Title of Account
|
58
|
-
|
89
|
+
# Full BECS character set valid
|
90
|
+
output += account_name.to_s.ljust(32, " ")
|
59
91
|
|
60
|
-
# Lodgement Reference Produced on the recipient’s Account Statement.
|
61
|
-
|
92
|
+
# Lodgement Reference Produced on the recipient’s Account Statement.
|
93
|
+
# Full BECS character set valid
|
94
|
+
output += lodgement_reference.to_s.ljust(18, " ")
|
62
95
|
|
63
96
|
# Trace BSB Number
|
64
97
|
output += trace_bsb
|
65
98
|
|
66
|
-
# Trace Account Number
|
99
|
+
# Trace Account Number
|
67
100
|
output += trace_account_number.to_s.rjust(9, " ")
|
68
101
|
|
69
102
|
# Name of Remitter Produced on the recipient’s Account Statement
|
70
|
-
|
103
|
+
# Full BECS character set valid
|
104
|
+
output += name_of_remitter.to_s.ljust(16, " ")
|
71
105
|
|
72
106
|
# Withholding amount in cents
|
73
107
|
output += (witholding_amount || 0).abs.to_s.rjust(8, "0")
|
74
108
|
end
|
75
109
|
end
|
76
|
-
end
|
110
|
+
end
|
data/lib/aba/validations.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
class Aba
|
2
2
|
module Validations
|
3
|
-
attr_accessor :
|
3
|
+
attr_accessor :error_collection
|
4
|
+
|
5
|
+
BECS_PATTERN = /\A[\w\+\-\@\ \$\!\%\&\(\)\*\.\/\#\=\:\;\?\,\'\[\]\_\^]*\Z/
|
6
|
+
INDICATORS = [' ', 'N', 'T', 'W', 'X', 'Y']
|
7
|
+
DEBIT_TRANSACTION_CODES = [13]
|
8
|
+
CREDIT_TRANSACTION_CODES = [50, 51, 52, 53, 54, 55, 56, 57]
|
4
9
|
|
5
10
|
def self.included(base)
|
6
11
|
base.instance_eval do
|
@@ -10,9 +15,21 @@ class Aba
|
|
10
15
|
base.send :extend, ClassMethods
|
11
16
|
end
|
12
17
|
|
13
|
-
|
18
|
+
def self.transaction_codes
|
19
|
+
DEBIT_TRANSACTION_CODES + CREDIT_TRANSACTION_CODES
|
20
|
+
end
|
21
|
+
|
14
22
|
def valid?
|
15
|
-
|
23
|
+
!has_errors?
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method 'errors', 'error_collection'
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Run all validations
|
31
|
+
def has_errors?
|
32
|
+
self.error_collection = []
|
16
33
|
|
17
34
|
self.class.instance_variable_get(:@_validations).each do |attribute, validations|
|
18
35
|
value = send(attribute)
|
@@ -20,35 +37,83 @@ class Aba
|
|
20
37
|
validations.each do |type, param|
|
21
38
|
case type
|
22
39
|
when :presence
|
23
|
-
self.
|
40
|
+
self.error_collection << "#{attribute} is empty" if value.nil? || value.to_s.empty?
|
24
41
|
when :bsb
|
25
|
-
|
42
|
+
unless((param && value.nil?) || value =~ /^\d{3}-\d{3}$/)
|
43
|
+
self.error_collection << "#{attribute} format is incorrect"
|
44
|
+
end
|
26
45
|
when :max_length
|
27
|
-
self.
|
46
|
+
self.error_collection << "#{attribute} length must not exceed #{param} characters" if value.to_s.length > param
|
47
|
+
when :length
|
48
|
+
self.error_collection << "#{attribute} length must be exactly #{param} characters" if value.to_s.length != param
|
49
|
+
when :integer
|
50
|
+
if param
|
51
|
+
self.error_collection << "#{attribute} must be a number" unless value.to_s =~ /\A[+-]?\d+\Z/
|
52
|
+
else
|
53
|
+
self.error_collection << "#{attribute} must be an unsigned number" unless value.to_s =~ /\A\d+\Z/
|
54
|
+
end
|
55
|
+
when :account_number
|
56
|
+
if value.to_s =~ /\A[0\ ]+\Z/ || value.to_s !~ /\A[a-z\d\ ]{1,9}\Z/
|
57
|
+
self.error_collection << "#{attribute} must be a valid account number"
|
58
|
+
end
|
59
|
+
when :becs
|
60
|
+
self.error_collection << "#{attribute} must not contain invalid characters" unless value.to_s =~ BECS_PATTERN
|
61
|
+
when :indicator
|
62
|
+
list = INDICATORS.join('\', \'')
|
63
|
+
self.error_collection << "#{attribute} must be a one of '#{list}'" unless INDICATORS.include?(value.to_s)
|
64
|
+
when :transaction_code
|
65
|
+
self.error_collection << "#{attribute} must be one of #{Validations.transaction_codes.join(', ')}" unless Validations.transaction_codes.include?(value.to_i)
|
28
66
|
end
|
29
67
|
end
|
30
68
|
end
|
31
69
|
|
32
|
-
self.
|
70
|
+
!self.error_collection.empty?
|
33
71
|
end
|
34
|
-
|
72
|
+
|
35
73
|
module ClassMethods
|
36
74
|
def validates_presence_of(*attributes)
|
37
|
-
attributes.each do |a|
|
75
|
+
attributes.each do |a|
|
38
76
|
add_validation_attribute(a, :presence)
|
39
77
|
end
|
40
78
|
end
|
41
79
|
|
42
|
-
def validates_bsb(
|
43
|
-
|
44
|
-
|
45
|
-
end
|
80
|
+
def validates_bsb(attribute, options = {})
|
81
|
+
options[:allow_blank] ||= false
|
82
|
+
add_validation_attribute(attribute, :bsb, options[:allow_blank])
|
46
83
|
end
|
47
84
|
|
48
85
|
def validates_max_length(attribute, length)
|
49
86
|
add_validation_attribute(attribute, :max_length, length)
|
50
87
|
end
|
51
88
|
|
89
|
+
def validates_length(attribute, length)
|
90
|
+
add_validation_attribute(attribute, :length, length)
|
91
|
+
end
|
92
|
+
|
93
|
+
def validates_integer(attribute, signed = true)
|
94
|
+
add_validation_attribute(attribute, :integer, signed)
|
95
|
+
end
|
96
|
+
|
97
|
+
def validates_account_number(attribute)
|
98
|
+
add_validation_attribute(attribute, :account_number)
|
99
|
+
end
|
100
|
+
|
101
|
+
def validates_becs(attribute)
|
102
|
+
add_validation_attribute(attribute, :becs)
|
103
|
+
end
|
104
|
+
|
105
|
+
def validates_indicator(attribute)
|
106
|
+
add_validation_attribute(attribute, :indicator)
|
107
|
+
end
|
108
|
+
|
109
|
+
def validates_transaction_code(attribute)
|
110
|
+
add_validation_attribute(attribute, :transaction_code)
|
111
|
+
end
|
112
|
+
|
113
|
+
def validates_return_code(attribute)
|
114
|
+
add_validation_attribute(attribute, :integer, :return_code)
|
115
|
+
end
|
116
|
+
|
52
117
|
private
|
53
118
|
|
54
119
|
def add_validation_attribute(attribute, type, param = true)
|
@@ -57,4 +122,4 @@ class Aba
|
|
57
122
|
end
|
58
123
|
end
|
59
124
|
end
|
60
|
-
end
|
125
|
+
end
|
data/lib/aba/version.rb
CHANGED
@@ -0,0 +1,133 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Aba::Batch do
|
6
|
+
subject(:batch) do
|
7
|
+
Aba::Batch.new(
|
8
|
+
financial_institution: "WPC",
|
9
|
+
user_name: "John Doe",
|
10
|
+
user_id: "466364",
|
11
|
+
description: "Payroll",
|
12
|
+
process_at: "190615",
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:transactions_attributes) { [{amount: 30, transaction_code: 50}, {amount: 20, transaction_code: 13}] }
|
17
|
+
let(:returns_attributes) { [{amount: 3, transaction_code: 50}, {amount: 2, transaction_code: 13}] }
|
18
|
+
|
19
|
+
before do
|
20
|
+
transactions_attributes.each do |attr|
|
21
|
+
transaction = Aba::Transaction.new(
|
22
|
+
bsb: '342-342',
|
23
|
+
account_number: '3244654',
|
24
|
+
amount: attr[:amount],
|
25
|
+
account_name: 'John Doe',
|
26
|
+
transaction_code: attr[:transaction_code],
|
27
|
+
lodgement_reference: 'R435564',
|
28
|
+
trace_bsb: '453-543',
|
29
|
+
trace_account_number: '45656733',
|
30
|
+
name_of_remitter: 'Remitter',
|
31
|
+
)
|
32
|
+
|
33
|
+
batch.add_transaction(transaction)
|
34
|
+
end
|
35
|
+
|
36
|
+
returns_attributes.map do |attr|
|
37
|
+
ret = Aba::Return.new(
|
38
|
+
bsb: '453-543',
|
39
|
+
account_number: '45656733',
|
40
|
+
amount: attr[:amount],
|
41
|
+
account_name: 'John Doe',
|
42
|
+
transaction_code: attr[:transaction_code],
|
43
|
+
lodgement_reference: 'R435564',
|
44
|
+
trace_bsb: '342-342',
|
45
|
+
trace_account_number: '3244654',
|
46
|
+
name_of_remitter: 'Remitter',
|
47
|
+
return_code: 8,
|
48
|
+
original_user_id: 654321,
|
49
|
+
original_processing_day: 12,
|
50
|
+
)
|
51
|
+
|
52
|
+
batch.add_return(ret)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#to_s" do
|
57
|
+
context 'when descriptive record' do
|
58
|
+
context 'without bsb' do
|
59
|
+
it "should return a string containing the descriptive record without the bsb" do
|
60
|
+
expect(batch.to_s).to include("0 01WPC John Doe 466364Payroll 190615 \r\n")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with bsb' do
|
65
|
+
before { batch.bsb = "123-345" }
|
66
|
+
|
67
|
+
it "should return a string containing the descriptive record with the bsb" do
|
68
|
+
expect(batch.to_s).to include("0123-345 01WPC John Doe 466364Payroll 190615 \r\n")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
context 'when detail record' do
|
75
|
+
it "should contain transaction & return records" do
|
76
|
+
expect(batch.to_s).to include("1342-342 3244654 500000000030John Doe R435564 453-543 45656733Remitter 00000000\r\n")
|
77
|
+
expect(batch.to_s).to include("1342-342 3244654 130000000020John Doe R435564 453-543 45656733Remitter 00000000\r\n")
|
78
|
+
expect(batch.to_s).to include("2453-543 456567338500000000003John Doe R435564 342-342 3244654Remitter 12654321\r\n")
|
79
|
+
expect(batch.to_s).to include("2453-543 456567338130000000002John Doe R435564 342-342 3244654Remitter 12654321\r\n")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when file total record' do
|
84
|
+
context 'with unbalanced transactions' do
|
85
|
+
it "should return a string where the net total is not zero" do
|
86
|
+
expect(batch.to_s).to include("7999-999 000000001100000000330000000022 000004 ")
|
87
|
+
# | Total || Credit || Debit |
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with balanced transactions' do
|
92
|
+
let(:transactions_attributes) do
|
93
|
+
[{amount: 30, transaction_code: 50}, {amount: 30, transaction_code: 13}, {amount: 30, transaction_code: 50}]
|
94
|
+
end
|
95
|
+
let(:returns_attributes) do
|
96
|
+
[{amount: 3, transaction_code: 50}, {amount: 3, transaction_code: 13}, {amount: 30, transaction_code: 13}]
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should return a string where the net total is zero" do
|
100
|
+
expect(batch.to_s).to include("7999-999 000000000000000000630000000063 000006 ")
|
101
|
+
# | Total || Credit || Debit |
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'with negative values supplied for the amounts' do
|
106
|
+
let(:transactions_attributes) { [{amount: -30, transaction_code: 50}, {amount: -20, transaction_code: 13}] }
|
107
|
+
let(:returns_attributes) { [{amount: -3, transaction_code: 50}, {amount: 2, transaction_code: 13}] }
|
108
|
+
|
109
|
+
it 'should return a string where the amount segments rely on the transaction code, not the amount sign' do
|
110
|
+
expect(batch.to_s).to include("7999-999 000000001100000000330000000022 000004 ")
|
111
|
+
# | Total || Credit || Debit |
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#errors" do
|
119
|
+
it "is empty" do
|
120
|
+
expect(batch.errors).to be_nil
|
121
|
+
end
|
122
|
+
|
123
|
+
context "with an invalid amount" do
|
124
|
+
let(:transactions_attributes) do
|
125
|
+
[{amount: 1, transaction_code: 50}, {amount: 'abc', transaction_code: 13}, {amount: 'def', transaction_code: 50}]
|
126
|
+
end
|
127
|
+
|
128
|
+
it "reports the errors" do
|
129
|
+
expect(batch.errors).to eq(:entries => { 1 => ["amount must be a number"], 2 => ["amount must be a number"] })
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|