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.
Files changed (49) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +7 -0
  4. data/README +0 -0
  5. data/Rakefile +7 -0
  6. data/absa-h2h.gemspec +23 -0
  7. data/lib/absa-h2h/account_holder_verification.rb +22 -0
  8. data/lib/absa-h2h/account_holder_verification_output.rb +22 -0
  9. data/lib/absa-h2h/eft/rejection_code.rb +30 -0
  10. data/lib/absa-h2h/eft.rb +200 -0
  11. data/lib/absa-h2h/eft_output.rb +12 -0
  12. data/lib/absa-h2h/eft_redirect.rb +13 -0
  13. data/lib/absa-h2h/eft_unpaid.rb +13 -0
  14. data/lib/absa-h2h/reply.rb +27 -0
  15. data/lib/absa-h2h/transmission/document.rb +26 -0
  16. data/lib/absa-h2h/transmission/record.rb +18 -0
  17. data/lib/absa-h2h/transmission/set.rb +168 -0
  18. data/lib/absa-h2h/version.rb +5 -0
  19. data/lib/absa-h2h.rb +22 -0
  20. data/lib/config/account_holder_verification.yml +196 -0
  21. data/lib/config/account_holder_verification_output.yml +182 -0
  22. data/lib/config/document.yml +54 -0
  23. data/lib/config/eft.yml +284 -0
  24. data/lib/config/eft_output.yml +65 -0
  25. data/lib/config/eft_redirect.yml +129 -0
  26. data/lib/config/eft_rejection_codes.yml +321 -0
  27. data/lib/config/eft_unpaid.yml +125 -0
  28. data/lib/config/reply.yml +222 -0
  29. data/lib/tmp/test.txt +0 -0
  30. data/spec/examples/ahv_input_file.txt +7 -0
  31. data/spec/examples/ahv_output_file.txt +7 -0
  32. data/spec/examples/eft_input_credit_file.txt +166 -0
  33. data/spec/examples/eft_input_credit_file2.txt +0 -0
  34. data/spec/examples/eft_input_file.txt +20 -0
  35. data/spec/examples/eft_output_file.txt +35 -0
  36. data/spec/examples/reply_file.txt +5 -0
  37. data/spec/examples/transmission_header_file.txt +1 -0
  38. data/spec/lib/account_holder_verification_spec.rb +157 -0
  39. data/spec/lib/eft/rejection_code_spec.rb +37 -0
  40. data/spec/lib/eft_output_spec.rb +9 -0
  41. data/spec/lib/eft_spec.rb +369 -0
  42. data/spec/lib/eft_transaction_standard_spec.rb +88 -0
  43. data/spec/lib/string_spec.rb +9 -0
  44. data/spec/lib/transmission/document_spec.rb +152 -0
  45. data/spec/lib/transmission/header_spec.rb +28 -0
  46. data/spec/lib/transmission/record_spec.rb +24 -0
  47. data/spec/lib/transmission/trailer_spec.rb +26 -0
  48. data/spec/spec_helper.rb +9 -0
  49. metadata +135 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ build.sh
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in absa-h2h.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec'
7
+ gem 'strata', '0.0.1', :git => 'git://github.com/tehtorq/strata.git'
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ # If you want to make this the default task
7
+ task :default => :spec
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
@@ -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,12 @@
1
+ module Absa
2
+ module H2h
3
+ module Transmission
4
+ class EftOutput < Set
5
+
6
+ class Header < Record; end
7
+ class Trailer < Record; end
8
+
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module Absa
2
+ module H2h
3
+ module Transmission
4
+ class EftRedirect < Set
5
+
6
+ class Header < Record; end
7
+ class Trailer < Record; end
8
+ class Transaction < Record; end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Absa
2
+ module H2h
3
+ module Transmission
4
+ class EftUnpaid < Set
5
+
6
+ class Header < Record; end
7
+ class Trailer < Record; end
8
+ class Transaction < Record; end
9
+
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,5 @@
1
+ module Absa
2
+ module H2h
3
+ VERSION = "0.0.11"
4
+ end
5
+ 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'