absa-h2h 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|