absa-h2h 0.0.11
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.
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/Gemfile +7 -0
- data/README +0 -0
- data/Rakefile +7 -0
- data/absa-h2h.gemspec +23 -0
- data/lib/absa-h2h/account_holder_verification.rb +22 -0
- data/lib/absa-h2h/account_holder_verification_output.rb +22 -0
- data/lib/absa-h2h/eft/rejection_code.rb +30 -0
- data/lib/absa-h2h/eft.rb +200 -0
- data/lib/absa-h2h/eft_output.rb +12 -0
- data/lib/absa-h2h/eft_redirect.rb +13 -0
- data/lib/absa-h2h/eft_unpaid.rb +13 -0
- data/lib/absa-h2h/reply.rb +27 -0
- data/lib/absa-h2h/transmission/document.rb +26 -0
- data/lib/absa-h2h/transmission/record.rb +18 -0
- data/lib/absa-h2h/transmission/set.rb +168 -0
- data/lib/absa-h2h/version.rb +5 -0
- data/lib/absa-h2h.rb +22 -0
- data/lib/config/account_holder_verification.yml +196 -0
- data/lib/config/account_holder_verification_output.yml +182 -0
- data/lib/config/document.yml +54 -0
- data/lib/config/eft.yml +284 -0
- data/lib/config/eft_output.yml +65 -0
- data/lib/config/eft_redirect.yml +129 -0
- data/lib/config/eft_rejection_codes.yml +321 -0
- data/lib/config/eft_unpaid.yml +125 -0
- data/lib/config/reply.yml +222 -0
- data/lib/tmp/test.txt +0 -0
- data/spec/examples/ahv_input_file.txt +7 -0
- data/spec/examples/ahv_output_file.txt +7 -0
- data/spec/examples/eft_input_credit_file.txt +166 -0
- data/spec/examples/eft_input_credit_file2.txt +0 -0
- data/spec/examples/eft_input_file.txt +20 -0
- data/spec/examples/eft_output_file.txt +35 -0
- data/spec/examples/reply_file.txt +5 -0
- data/spec/examples/transmission_header_file.txt +1 -0
- data/spec/lib/account_holder_verification_spec.rb +157 -0
- data/spec/lib/eft/rejection_code_spec.rb +37 -0
- data/spec/lib/eft_output_spec.rb +9 -0
- data/spec/lib/eft_spec.rb +369 -0
- data/spec/lib/eft_transaction_standard_spec.rb +88 -0
- data/spec/lib/string_spec.rb +9 -0
- data/spec/lib/transmission/document_spec.rb +152 -0
- data/spec/lib/transmission/header_spec.rb +28 -0
- data/spec/lib/transmission/record_spec.rb +24 -0
- data/spec/lib/transmission/trailer_spec.rb +26 -0
- data/spec/spec_helper.rb +9 -0
- metadata +135 -0
data/.rspec
ADDED
data/Gemfile
ADDED
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
data/absa-h2h.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "absa-h2h/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "absa-h2h"
|
7
|
+
s.version = Absa::H2h::VERSION
|
8
|
+
s.authors = ["Jeffrey van Aswegen, Douglas Anderson"]
|
9
|
+
s.email = ["jeffmess@gmail.com, i.am.douglas.anderson@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A ruby interface to commumicate with the ABSA Host 2 Host platform}
|
12
|
+
s.description = %q{The interface supports Account holder verifications, EFT payments, Debit orders, collecting statements.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "absa-h2h"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "activesupport"
|
22
|
+
s.add_dependency "i18n"
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Absa
|
2
|
+
module H2h
|
3
|
+
module Transmission
|
4
|
+
class AccountHolderVerification < Set
|
5
|
+
class Header < Record; end
|
6
|
+
class Trailer < Record; end
|
7
|
+
class InternalAccountDetail < Record; end
|
8
|
+
class ExternalAccountDetail < Record; end
|
9
|
+
|
10
|
+
def validate!
|
11
|
+
unless trailer.no_det_recs == transactions.length.to_s
|
12
|
+
raise "no_det_recs mismatch: expected #{trailer.no_det_recs}, got #{transactions.length}"
|
13
|
+
end
|
14
|
+
|
15
|
+
unless transactions.map {|t| t.seq_no} == (1..(transactions.length)).map(&:to_s).to_a
|
16
|
+
raise "seq_no mismatch: #{transactions.map {|t| t.seq_no}}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Absa
|
2
|
+
module H2h
|
3
|
+
module Transmission
|
4
|
+
class AccountHolderVerificationOutput < Set
|
5
|
+
class Header < Record; end
|
6
|
+
class Trailer < Record; end
|
7
|
+
class InternalAccountDetail < Record; end
|
8
|
+
class ExternalAccountDetail < Record; end
|
9
|
+
|
10
|
+
def validate!
|
11
|
+
unless trailer.no_det_recs == transactions.length.to_s
|
12
|
+
raise "no_det_recs mismatch: expected #{trailer.no_det_recs}, got #{transactions.length}"
|
13
|
+
end
|
14
|
+
|
15
|
+
unless transactions.map {|t| t.seq_no} == (1..(transactions.length)).map(&:to_s).to_a
|
16
|
+
raise "seq_no mismatch: #{transactions.map {|t| t.seq_no}}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Absa
|
2
|
+
module H2h
|
3
|
+
module Eft
|
4
|
+
class RejectionCode
|
5
|
+
|
6
|
+
def self.reasons
|
7
|
+
self.config['reasons']
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.qualifiers
|
11
|
+
self.config['qualifiers']
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.reason_for_code(code)
|
15
|
+
self.reasons[code]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.qualifier_for_code(code)
|
19
|
+
self.qualifiers[code]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.config
|
23
|
+
file_name = "#{Absa::H2h::CONFIG_DIR}/eft_rejection_codes.yml"
|
24
|
+
YAML.load(File.open(file_name))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/absa-h2h/eft.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
module Absa
|
2
|
+
module H2h
|
3
|
+
module Transmission
|
4
|
+
class Eft < Set
|
5
|
+
|
6
|
+
def validate_standard_transactions!
|
7
|
+
if transactions.first.user_sequence_number != header.first_sequence_number
|
8
|
+
raise "user_sequence_number: 1st Standard transactions user sequence number and the headers first sequence number must be equal."
|
9
|
+
end
|
10
|
+
|
11
|
+
raise "user_sequence_number: Duplicate user sequence number. Transactions must have unique sequence numbers!" unless transactions.map(&:user_sequence_number).uniq.length == transactions.length
|
12
|
+
|
13
|
+
unless transactions.map(&:user_sequence_number) == ((transactions.first.user_sequence_number.to_i)..(transactions.first.user_sequence_number.to_i + transactions.length-1)).map(&:to_s).to_a
|
14
|
+
raise "user_sequence_number: Transactions must increment sequentially. Got: #{transactions.map(&:user_sequence_number)}"
|
15
|
+
end
|
16
|
+
|
17
|
+
transactions.each do |transaction|
|
18
|
+
first_action_date = Date.strptime(header.first_action_date, "%y%m%d")
|
19
|
+
last_action_date = Date.strptime(header.last_action_date, "%y%m%d")
|
20
|
+
action_date = Date.strptime(transaction.action_date, "%y%m%d")
|
21
|
+
|
22
|
+
raise "action_date: Must be within the range of the headers first_action_date and last_action_date" unless (first_action_date..last_action_date).cover?(action_date)
|
23
|
+
raise "rec_status: Transaction and Header record status must be equal" if header.rec_status != transaction.rec_status
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_header_trailer!
|
28
|
+
raise "rec_status: Trailer and Header record status must be equal" if header.rec_status != trailer.rec_status
|
29
|
+
raise "bankserv_user_code: Trailer and Header user code must be equal." if header.bankserv_user_code != trailer.bankserv_user_code
|
30
|
+
raise "first_sequence_number: Trailer and Header sequence number must be equal." if header.first_sequence_number != trailer.first_sequence_number
|
31
|
+
raise "first_action_date: Trailer and Header first action date must be equal." if header.first_action_date != trailer.first_action_date
|
32
|
+
raise "last_action_date: Trailer and Header last action date must be equal." if header.last_action_date != trailer.last_action_date
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_contra_records!
|
36
|
+
transactions.select{|t| t.contra_record? }.each do |transaction|
|
37
|
+
# Loop used to validate contra records against the standard records
|
38
|
+
unless calculate_contra_record_total(transaction) == transaction.amount.to_i
|
39
|
+
raise "amount: Contra record amount must be the sum amount of all preceeding transactions. Expected #{calculate_contra_record_total(transaction)}. Got #{transaction.amount}."
|
40
|
+
end
|
41
|
+
|
42
|
+
transactions = transactions_for_contra_record(transaction)
|
43
|
+
|
44
|
+
if transactions.map(&:action_date).uniq.length > 1
|
45
|
+
raise "action_date: Contra records action date must be equal to all preceeding standard transactions action date. Got #{transactions_for_contra_record(transaction).map(&:action_date)}."
|
46
|
+
end
|
47
|
+
|
48
|
+
if (transactions.map(&:user_nominated_account).uniq.length > 1) or (transactions.map(&:user_nominated_account).first != transaction.user_nominated_account)
|
49
|
+
raise "user_nominated_account: Contra records user nominated account must match all preceeding standard transactions user nominated accounts. Got #{transactions.map(&:user_nominated_account)}."
|
50
|
+
end
|
51
|
+
|
52
|
+
if (transactions.map(&:user_branch).uniq.length > 1) or (transactions.map(&:user_branch).first != transaction.user_branch)
|
53
|
+
raise "user_branch_code: Contra records user branch must match all preceeding standard transactions user branch. Got #{transactions.map(&:user_branch)}."
|
54
|
+
end
|
55
|
+
|
56
|
+
raise "user_code: Contra records user code must match the headers users code. Got #{transaction.user_code}. Expected #{header.bankserv_user_code}." unless transaction.user_code == header.bankserv_user_code
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_trailer_transactions!
|
61
|
+
unless trailer.last_sequence_number == transactions.last.user_sequence_number
|
62
|
+
raise "last_sequence_number: Trailer records last sequence number must match the last contra records sequence number. Got #{trailer.last_sequence_number}. Expected #{transactions.last.user_sequence_number}"
|
63
|
+
end
|
64
|
+
|
65
|
+
debit_records = transactions.select {|t| t.bankserv_record_identifier.to_i == 50 }
|
66
|
+
credit_records = transactions.select {|t| t.bankserv_record_identifier.to_i == 10 }
|
67
|
+
|
68
|
+
unless trailer.no_debit_records.to_i == self.debit_records.count + self.credit_contra_records.count
|
69
|
+
raise "no_debit_records: Trailer records number of debit records must match the number of debit records. Expected #{debit_records.count}. Got #{trailer.no_debit_records}."
|
70
|
+
end
|
71
|
+
|
72
|
+
unless trailer.no_credit_records.to_i == self.credit_records.count + self.debit_contra_records.count
|
73
|
+
raise "no_credit_records: Trailer records number of credit records must match the number of credit records and contra debit records. Expected #{self.credit_records.count + self.debit_contra_records.count}. Got #{trailer.no_credit_records}."
|
74
|
+
end
|
75
|
+
|
76
|
+
unless trailer.no_contra_records.to_i == self.contra_records.count
|
77
|
+
raise "no_contra_records: Trailer records number of contra records must match the number of contra records. Expected #{self.contra_records.count}. Got #{trailer.no_contra_records}."
|
78
|
+
end
|
79
|
+
|
80
|
+
unless trailer.total_debit_value.to_i == self.total_debit_transactions
|
81
|
+
raise "total_debit_value: Trailer records total debit value must equal the sum amount of all transactions and credit contra records. Expected #{self.total_debit_transactions}. Got #{trailer.total_debit_value.to_i}."
|
82
|
+
end
|
83
|
+
|
84
|
+
unless trailer.total_credit_value.to_i == self.total_credit_transactions
|
85
|
+
raise "total_credit_value: Trailer records total credit value must equal the sum amount of all transactions and debit contra records. Expected #{self.total_credit_transactions}. Got #{trailer.total_credit_value.to_i}."
|
86
|
+
end
|
87
|
+
|
88
|
+
unless trailer.hash_total_of_homing_account_numbers.to_i == self.homing_numbers_hash_total
|
89
|
+
raise "hash_total_of_homing_account_numbers: Trailers hash total of homing account numbers does not match. Expected #{self.homing_numbers_hash_total}. Got #{trailer.hash_total_of_homing_account_numbers}."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate!
|
94
|
+
validate_standard_transactions!
|
95
|
+
validate_header_trailer!
|
96
|
+
validate_contra_records!
|
97
|
+
validate_trailer_transactions!
|
98
|
+
end
|
99
|
+
|
100
|
+
def contra_records
|
101
|
+
transactions.select {|t| t.contra_record? }
|
102
|
+
end
|
103
|
+
|
104
|
+
def standard_records
|
105
|
+
transactions.select {|t| !t.contra_record? }
|
106
|
+
end
|
107
|
+
|
108
|
+
def debit_records
|
109
|
+
# Standard records only
|
110
|
+
transactions.select {|t| t.bankserv_record_identifier.to_i == 50 }
|
111
|
+
end
|
112
|
+
|
113
|
+
def credit_records
|
114
|
+
# Standard records only
|
115
|
+
transactions.select {|t| t.bankserv_record_identifier.to_i == 10 }
|
116
|
+
end
|
117
|
+
|
118
|
+
def debit_contra_records
|
119
|
+
transactions.select {|t| t.contra_record? && t.bankserv_record_identifier.to_i == 52}
|
120
|
+
end
|
121
|
+
|
122
|
+
def credit_contra_records
|
123
|
+
transactions.select {|t| t.contra_record? && t.bankserv_record_identifier.to_i == 12}
|
124
|
+
end
|
125
|
+
|
126
|
+
def total_debit_transactions
|
127
|
+
# including credit contra records
|
128
|
+
ccr = self.credit_contra_records == [] ? 0 : self.credit_contra_records.map(&:amount).map(&:to_i).inject(&:+)
|
129
|
+
(self.debit_records.map(&:amount).map(&:to_i).inject(&:+) || 0) + ccr
|
130
|
+
end
|
131
|
+
|
132
|
+
def total_credit_transactions
|
133
|
+
# including debit contra records
|
134
|
+
dcr = self.debit_contra_records == [] ? 0 : self.debit_contra_records.map(&:amount).map(&:to_i).inject(&:+)
|
135
|
+
(self.credit_records.map(&:amount).map(&:to_i).inject(&:+) || 0) + dcr
|
136
|
+
end
|
137
|
+
|
138
|
+
def calculate_contra_record_total(contra_record)
|
139
|
+
transactions_for_contra_record(contra_record).map(&:amount).map(&:to_i).inject(&:+)
|
140
|
+
end
|
141
|
+
|
142
|
+
def transactions_for_contra_record(contra_record)
|
143
|
+
contra_records = self.contra_records.map(&:user_sequence_number)
|
144
|
+
sequence = transactions.map(&:user_sequence_number)
|
145
|
+
|
146
|
+
if contra_records.index(contra_record.user_sequence_number) == 0 # First contra record in user set
|
147
|
+
previous_contra_record = contra_record.user_sequence_number
|
148
|
+
start_point = 0
|
149
|
+
else
|
150
|
+
previous_contra_record = contra_records[contra_records.index(contra_record.user_sequence_number)-1]
|
151
|
+
start_point = sequence.index(previous_contra_record) + 1
|
152
|
+
end
|
153
|
+
|
154
|
+
end_point = sequence.index(contra_record.user_sequence_number)-1
|
155
|
+
transactions[start_point..end_point]
|
156
|
+
end
|
157
|
+
|
158
|
+
def homing_numbers_hash_total
|
159
|
+
ns_homing_account_number_total = self.standard_records.map(&:non_standard_homing_account_number).empty? ? 0 : self.standard_records.map(&:non_standard_homing_account_number).map(&:to_i).inject(&:+)
|
160
|
+
field9 = transactions.map(&:homing_account_number).map(&:to_i).inject(&:+) + ns_homing_account_number_total
|
161
|
+
field9.to_s.reverse[0,12].reverse.to_i
|
162
|
+
end
|
163
|
+
|
164
|
+
class Header < Record; end
|
165
|
+
class ContraRecord < Record
|
166
|
+
|
167
|
+
def contra_record?
|
168
|
+
true
|
169
|
+
end
|
170
|
+
|
171
|
+
def validate!(options={})
|
172
|
+
super(options)
|
173
|
+
raise "homing_branch: Should match the user branch. Got #{@homing_branch}. Expected #{@user_branch}." unless @homing_branch == @user_branch
|
174
|
+
raise "homing_account_number: Should match the user nominated account number. Got #{@homing_account_number}. Expected #{@user_nominated_account}." unless @homing_account_number == @user_nominated_account
|
175
|
+
raise "user_ref: Position 1 - 10 is compulsory. Please provide users abbreviated name." if @user_ref[0..9].blank?
|
176
|
+
raise "user_ref: Position 11 - 16 is compulsory and must be set to 'CONTRA'. Got #{@user_ref[10..15]}" if @user_ref[10..15] != "CONTRA"
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
class StandardRecord < Record
|
182
|
+
|
183
|
+
def contra_record?
|
184
|
+
false
|
185
|
+
end
|
186
|
+
|
187
|
+
def validate!(options={})
|
188
|
+
super(options)
|
189
|
+
|
190
|
+
raise "user_ref: Position 1 - 10 is compulsory. Please provide users abbreviated name." if @user_ref[0..11].blank?
|
191
|
+
raise "homing_account_name: Not to be left blank." if @homing_account_name.blank?
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
class Trailer < Record; end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Absa
|
2
|
+
module H2h
|
3
|
+
module Transmission
|
4
|
+
class Reply < Set
|
5
|
+
|
6
|
+
class TransmissionStatus < Record; end
|
7
|
+
class TransmissionRejectedReason < Record; end
|
8
|
+
class EftStatus < Record; end
|
9
|
+
class AcceptedReportReply < Record; end
|
10
|
+
class RejectedMessage < Record; end
|
11
|
+
|
12
|
+
def self.hash_from_s(string, transmission_type)
|
13
|
+
set_info = {type: self.partial_class_name.underscore, data: []}
|
14
|
+
|
15
|
+
string.split(/^/).each do |line|
|
16
|
+
if Set.for_record(line, transmission_type) == self
|
17
|
+
set_info[:data] << self.process_record(line[0, 198])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
set_info
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Absa
|
2
|
+
module H2h
|
3
|
+
module Transmission
|
4
|
+
|
5
|
+
class Document < Set
|
6
|
+
|
7
|
+
class Header < Record; end
|
8
|
+
class Trailer < Record; end
|
9
|
+
|
10
|
+
def self.from_s(string, transmission_type)
|
11
|
+
options = self.hash_from_s(string, transmission_type)
|
12
|
+
self.build(options[:data])
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_file!(filename)
|
16
|
+
File.open(destination, 'w') {|file| file.write(self.to_s) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_file!(filename)
|
20
|
+
self.from_s(File.open(filename, "rb").read)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Absa::H2h::Transmission
|
2
|
+
|
3
|
+
class Record
|
4
|
+
include Strata::RecordWriter
|
5
|
+
extend Strata::RecordWriter::ClassMethods
|
6
|
+
|
7
|
+
set_record_length 198
|
8
|
+
set_delimiter "\r\n"
|
9
|
+
set_allowed_characters ('A'..'Z').to_a + ('a'..'z').to_a + (0..9).to_a.map(&:to_s) + ['.','/','-','&','*',',','(',')','<','+','$',';','>','=',"'",' '] # move to config file
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
set_layout_variables(options)
|
13
|
+
validate! options
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module Absa::H2h::Transmission
|
2
|
+
class Set
|
3
|
+
|
4
|
+
attr_accessor :records
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
self.records = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.build(data)
|
11
|
+
set = self.new
|
12
|
+
|
13
|
+
data.each do |hash|
|
14
|
+
puts hash.inspect
|
15
|
+
if hash[:data].is_a? Array
|
16
|
+
klass = "Absa::H2h::Transmission::#{hash[:type].capitalize.camelize}".constantize
|
17
|
+
set.records << klass.build(hash[:data])
|
18
|
+
else
|
19
|
+
klass = "Absa::H2h::Transmission::#{self.partial_class_name}::#{hash[:type].capitalize.camelize}".constantize
|
20
|
+
set.records << klass.new(hash[:data])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
set.validate!
|
25
|
+
set
|
26
|
+
end
|
27
|
+
|
28
|
+
def header
|
29
|
+
records[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
def trailer
|
33
|
+
records[-1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def transactions
|
37
|
+
records[1..-2]
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate!
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
string = ""
|
46
|
+
records.each {|record| string += record.to_s }
|
47
|
+
string
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.for_record(record, transmission_type) # move this logic to yml file
|
51
|
+
record_id = record[0..2]
|
52
|
+
|
53
|
+
case record_id
|
54
|
+
when '000','999'
|
55
|
+
return Absa::H2h::Transmission::Document
|
56
|
+
when '030','031','039'
|
57
|
+
return (transmission_type == "output") ? Absa::H2h::Transmission::AccountHolderVerificationOutput : Absa::H2h::Transmission::AccountHolderVerification
|
58
|
+
when '001', '020'
|
59
|
+
return Absa::H2h::Transmission::Eft
|
60
|
+
when '010','019'
|
61
|
+
return Absa::H2h::Transmission::EftOutput
|
62
|
+
when '011','013','014'
|
63
|
+
return Absa::H2h::Transmission::EftUnpaid
|
64
|
+
when '016','017','018'
|
65
|
+
return Absa::H2h::Transmission::EftRedirect
|
66
|
+
when '900','901','902','903'
|
67
|
+
return Absa::H2h::Transmission::Reply
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.trailer_id(klass)
|
72
|
+
case klass.name
|
73
|
+
when 'Absa::H2h::Transmission::Document'
|
74
|
+
return '999'
|
75
|
+
when 'Absa::H2h::Transmission::AccountHolderVerification'
|
76
|
+
return '039'
|
77
|
+
when 'Absa::H2h::Transmission::AccountHolderVerificationOutput'
|
78
|
+
return '039'
|
79
|
+
when 'Absa::H2h::Transmission::EftOutput'
|
80
|
+
return '019'
|
81
|
+
when 'Absa::H2h::Transmission::EftUnpaid'
|
82
|
+
return '014'
|
83
|
+
when 'Absa::H2h::Transmission::EftRedirect'
|
84
|
+
return '018'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.is_trailer_record?(set, record)
|
89
|
+
record_id = record[0,3]
|
90
|
+
return true if set == Absa::H2h::Transmission::Eft and (["001","020"].include? record_id) and record[4,2] == "92"
|
91
|
+
self.trailer_id(set) == record_id
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.process_record(record)
|
95
|
+
record_info = {}
|
96
|
+
|
97
|
+
self.record_types.each do |record_type|
|
98
|
+
klass = "#{self.name}::#{record_type.camelize}".constantize
|
99
|
+
|
100
|
+
if klass.matches_definition?(record)
|
101
|
+
options = klass.string_to_hash(record)
|
102
|
+
record_info = {type: record_type, data: options}
|
103
|
+
break
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
record_info
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.hash_from_s(string, transmission_type)
|
111
|
+
set_info = {type: self.partial_class_name.underscore, data: []}
|
112
|
+
lines = string.split(/^/)
|
113
|
+
|
114
|
+
# look for rec_ids, split into chunks, and pass each related class a piece of string
|
115
|
+
|
116
|
+
buffer = []
|
117
|
+
current_set = nil
|
118
|
+
subset = nil
|
119
|
+
|
120
|
+
lines.each do |line|
|
121
|
+
if Set.for_record(line, transmission_type) == self
|
122
|
+
if subset && (buffer.length > 0)
|
123
|
+
set_info[:data] << subset.hash_from_s(buffer.join, transmission_type)
|
124
|
+
buffer = []
|
125
|
+
subset = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
record = line[0, 198]
|
129
|
+
set_info[:data] << self.process_record(record)
|
130
|
+
else
|
131
|
+
subset = Set.for_record(line, transmission_type) unless subset
|
132
|
+
buffer << line
|
133
|
+
|
134
|
+
if self.is_trailer_record?(subset, line)
|
135
|
+
set_info[:data] << subset.hash_from_s(buffer.join, transmission_type)
|
136
|
+
buffer = []
|
137
|
+
subset = nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
set_info
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.record_types
|
146
|
+
self.layout_rules.map {|k,v| k}
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.module_name
|
150
|
+
self.name.split("::")[0..-1].join("::")
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.partial_class_name
|
154
|
+
self.name.split("::")[-1]
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.layout_rules
|
158
|
+
file_name = "#{Absa::H2h::CONFIG_DIR}/#{self.partial_class_name.underscore}.yml"
|
159
|
+
|
160
|
+
YAML.load(File.open(file_name))
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.record_type(record_type)
|
164
|
+
"#{self.name}::#{record_type.camelize}".constantize
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
data/lib/absa-h2h.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "absa-h2h/version"
|
2
|
+
require "active_support/core_ext/string"
|
3
|
+
require "yaml"
|
4
|
+
#require 'strata'
|
5
|
+
|
6
|
+
module Absa
|
7
|
+
module H2h
|
8
|
+
CONFIG_DIR = File.expand_path(File.dirname(__FILE__)) + "/config"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'absa-h2h/transmission/set'
|
13
|
+
require 'absa-h2h/transmission/record'
|
14
|
+
require 'absa-h2h/transmission/document'
|
15
|
+
require 'absa-h2h/account_holder_verification'
|
16
|
+
require 'absa-h2h/account_holder_verification_output'
|
17
|
+
require 'absa-h2h/eft'
|
18
|
+
require 'absa-h2h/eft_output'
|
19
|
+
require 'absa-h2h/eft_unpaid'
|
20
|
+
require 'absa-h2h/eft_redirect'
|
21
|
+
require 'absa-h2h/eft/rejection_code'
|
22
|
+
require 'absa-h2h/reply'
|