bankserv 0.1.3
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 +6 -0
- data/.rvmrc +1 -0
- data/Gemfile +16 -0
- data/Rakefile +8 -0
- data/bankserv.gemspec +31 -0
- data/config.ru +7 -0
- data/lib/bankserv.rb +48 -0
- data/lib/bankserv/account_holder_verification.rb +90 -0
- data/lib/bankserv/bank_account.rb +19 -0
- data/lib/bankserv/configuration.rb +67 -0
- data/lib/bankserv/credit.rb +70 -0
- data/lib/bankserv/debit.rb +94 -0
- data/lib/bankserv/eft.rb +83 -0
- data/lib/bankserv/engine.rb +183 -0
- data/lib/bankserv/engine/engine_configuration.rb +12 -0
- data/lib/bankserv/engine/engine_process.rb +3 -0
- data/lib/bankserv/request.rb +27 -0
- data/lib/bankserv/transaction.rb +22 -0
- data/lib/bankserv/transmission/document.rb +43 -0
- data/lib/bankserv/transmission/input_document.rb +84 -0
- data/lib/bankserv/transmission/output_document.rb +27 -0
- data/lib/bankserv/transmission/record.rb +13 -0
- data/lib/bankserv/transmission/reply_document.rb +27 -0
- data/lib/bankserv/transmission/set.rb +120 -0
- data/lib/bankserv/transmission/set/account_holder_verification.rb +81 -0
- data/lib/bankserv/transmission/set/account_holder_verification_output.rb +17 -0
- data/lib/bankserv/transmission/set/credit.rb +44 -0
- data/lib/bankserv/transmission/set/debit.rb +44 -0
- data/lib/bankserv/transmission/set/document.rb +45 -0
- data/lib/bankserv/transmission/set/eft.rb +228 -0
- data/lib/bankserv/transmission/set/eft_output.rb +12 -0
- data/lib/bankserv/transmission/set/eft_redirect.rb +18 -0
- data/lib/bankserv/transmission/set/eft_unpaid.rb +18 -0
- data/lib/bankserv/transmission/set/reply.rb +47 -0
- data/lib/bankserv/transmission/statement.rb +46 -0
- data/lib/bankserv/version.rb +3 -0
- data/lib/config/ahv.yml +9 -0
- data/lib/core_extensions.rb +37 -0
- data/lib/generators/active_record/bankserv_generator.rb +30 -0
- data/lib/generators/active_record/templates/migration.rb +141 -0
- data/spec/examples/ahv_input_file.txt +7 -0
- data/spec/examples/ahv_output_file.txt +7 -0
- data/spec/examples/credit_eft_input.txt +166 -0
- data/spec/examples/debit_eft_input_file.txt +20 -0
- data/spec/examples/eft_input_with_2_sets.txt +8 -0
- data/spec/examples/eft_output_file.txt +35 -0
- data/spec/examples/host2host/tmp.log +1 -0
- data/spec/examples/reply/rejected_transmission.txt +7 -0
- data/spec/examples/reply/reply_file.txt +5 -0
- data/spec/examples/statement4_unpacked.dat +51 -0
- data/spec/examples/tmp/OUTPUT0412153500.txt +35 -0
- data/spec/examples/tmp/REPLY0412153000.txt +5 -0
- data/spec/factories.rb +90 -0
- data/spec/internal/config/database.yml.sample +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +138 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/bankserv/account_holder_verification_spec.rb +104 -0
- data/spec/lib/bankserv/configuration_spec.rb +34 -0
- data/spec/lib/bankserv/core_ext_spec.rb +43 -0
- data/spec/lib/bankserv/credit_spec.rb +92 -0
- data/spec/lib/bankserv/debit_spec.rb +187 -0
- data/spec/lib/bankserv/engine/engine_spec.rb +142 -0
- data/spec/lib/bankserv/transaction_spec.rb +32 -0
- data/spec/lib/bankserv/transmission/input_document_spec.rb +207 -0
- data/spec/lib/bankserv/transmission/output_document_spec.rb +210 -0
- data/spec/lib/bankserv/transmission/reply_document_spec.rb +117 -0
- data/spec/lib/bankserv/transmission/set/account_holder_verification_spec.rb +108 -0
- data/spec/lib/bankserv/transmission/set/credit_spec.rb +96 -0
- data/spec/lib/bankserv/transmission/set/debit_spec.rb +103 -0
- data/spec/lib/bankserv/transmission/statement_spec.rb +109 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/helpers.rb +207 -0
- metadata +223 -0
data/lib/bankserv/eft.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Bankserv
|
2
|
+
module Eft
|
3
|
+
# This module is tightly coupled to the Debit and Credit class.
|
4
|
+
# Any change here will ripple down...
|
5
|
+
attr_accessor :request_id
|
6
|
+
|
7
|
+
def request(options)
|
8
|
+
Request.create!(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_request(options)
|
12
|
+
Request.create!(options.merge(test: true))
|
13
|
+
end
|
14
|
+
|
15
|
+
def build!(options)
|
16
|
+
@request_id = options[:bankserv_request_id]
|
17
|
+
|
18
|
+
options[:batches].each do |batch|
|
19
|
+
build_batch! batch
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_batch!(options)
|
24
|
+
batch_id = next_batch_id
|
25
|
+
if self.partial_class_name == "Debit"
|
26
|
+
build_standard!(batch_id, options[:debit])
|
27
|
+
build_contra!(batch_id, options[:credit])
|
28
|
+
else
|
29
|
+
build_standard!(batch_id, options[:credit])
|
30
|
+
build_contra!(batch_id, options[:debit])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def partial_class_name
|
35
|
+
self.name.split("::")[-1]
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_contra!(batch_id, options)
|
39
|
+
ba_options = options.filter_attributes(BankAccount)
|
40
|
+
options = options.filter_attributes(self).merge(bank_account: BankAccount.new(ba_options), record_type: "contra", batch_id: batch_id, bankserv_request_id: @request_id)
|
41
|
+
|
42
|
+
create!(options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_standard!(batch_id, options)
|
46
|
+
if options.is_a? Array
|
47
|
+
options.each do |debit|
|
48
|
+
create_standard!(batch_id, debit)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
create_standard!(batch_id, options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_standard!(batch_id, options)
|
56
|
+
ba_options = options.filter_attributes(BankAccount)
|
57
|
+
options = options.filter_attributes(self).merge(bank_account: BankAccount.new(ba_options), record_type: "standard", batch_id: batch_id, bankserv_request_id: @request_id)
|
58
|
+
|
59
|
+
create!(options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def next_batch_id
|
63
|
+
maximum('batch_id').nil? ? 1 : maximum('batch_id') + 1
|
64
|
+
end
|
65
|
+
|
66
|
+
def has_work?
|
67
|
+
unprocessed.select{|item| not item.request.test?}.any?
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_test_work?
|
71
|
+
unprocessed.select{|item| item.request.test?}.any?
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.for_reference(reference)
|
75
|
+
Debit.for_reference(reference) + Credit.for_reference(reference)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.for_internal_reference(reference)
|
79
|
+
Debit.for_internal_reference(reference) + Credit.for_internal_reference(reference)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module Bankserv
|
2
|
+
class Engine
|
3
|
+
|
4
|
+
attr_accessor :process, :logs, :success
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@logs = {
|
8
|
+
reply_files: [],
|
9
|
+
output_files: [],
|
10
|
+
input_files: []
|
11
|
+
}
|
12
|
+
|
13
|
+
@success = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def process!
|
17
|
+
self.start!
|
18
|
+
self.process_reply_files
|
19
|
+
self.process_output_files
|
20
|
+
self.process_input_files
|
21
|
+
self.finish!
|
22
|
+
# self.perform_post_checks!
|
23
|
+
end
|
24
|
+
|
25
|
+
def start!
|
26
|
+
@process = EngineProcess.create!(running: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_reply_files
|
30
|
+
begin
|
31
|
+
Engine.reply_files.each do |file|
|
32
|
+
@logs[:reply_files] << "Processing #{file}."
|
33
|
+
|
34
|
+
contents = File.open("#{Bankserv::Engine.output_directory}/#{file}", "rb").read
|
35
|
+
document = Bankserv::ReplyDocument.store(contents)
|
36
|
+
document.process!
|
37
|
+
|
38
|
+
@logs[:reply_files] << "Processing #{file}. Complete."
|
39
|
+
|
40
|
+
self.archive_file!("#{Bankserv::Engine.output_directory}/#{file}")
|
41
|
+
@logs[:reply_files] << "#{file} Archived."
|
42
|
+
end
|
43
|
+
rescue Exception => e
|
44
|
+
@logs[:reply_files] << "Error occured! #{e.message}"
|
45
|
+
@success = false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_output_files
|
50
|
+
begin
|
51
|
+
Engine.output_files.each do |file|
|
52
|
+
@logs[:output_files] << "Processing #{file}."
|
53
|
+
|
54
|
+
contents = File.open("#{Bankserv::Engine.output_directory}/#{file}", "rb").read
|
55
|
+
document = Bankserv::OutputDocument.store(contents)
|
56
|
+
document.process!
|
57
|
+
|
58
|
+
@logs[:output_files] << "Processing #{file}. Complete."
|
59
|
+
|
60
|
+
self.archive_file!("#{Bankserv::Engine.output_directory}/#{file}")
|
61
|
+
@logs[:output_files] << "#{file} Archived."
|
62
|
+
end
|
63
|
+
rescue Exception => e
|
64
|
+
@logs[:output_files] << "Error occured! #{e.message}"
|
65
|
+
@success = false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def process_input_files
|
70
|
+
unless self.expecting_reply_file?
|
71
|
+
begin
|
72
|
+
document = Bankserv::InputDocument.generate!(
|
73
|
+
client_code: Bankserv::Configuration.client_code,
|
74
|
+
client_name: Bankserv::Configuration.client_name,
|
75
|
+
th_for_use_of_ld_user: ""
|
76
|
+
)
|
77
|
+
@logs[:input_files] << "Input Document created with id: #{document.id}"
|
78
|
+
rescue Exception => e
|
79
|
+
@logs[:input_files] << "Error occured! #{e.message}"
|
80
|
+
@success = false
|
81
|
+
end
|
82
|
+
|
83
|
+
if self.write_file!(document)
|
84
|
+
document.mark_processed!
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def write_file!(document)
|
90
|
+
begin
|
91
|
+
transmission = Absa::H2h::Transmission::Document.build([document.to_hash])
|
92
|
+
file_name = "INPUT.#{Time.now.strftime('%y%m%d%H%M%S')}.txt"
|
93
|
+
File.open("#{Bankserv::Engine.input_directory}/#{file_name}", 'w') { |f|
|
94
|
+
f.puts transmission
|
95
|
+
}
|
96
|
+
@logs[:input_files] << "Input Document File created. File name: #{file_name}"
|
97
|
+
rescue Exception => e
|
98
|
+
@logs[:input_files] << "Error occured. #{e.message}"
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
def archive_file!(file)
|
106
|
+
year, month = Date.today.year, Date.today.month
|
107
|
+
|
108
|
+
Dir::mkdir("#{Bankserv::Engine.archive_directory}/#{year}") unless File.directory?("#{Bankserv::Engine.archive_directory}/#{year}")
|
109
|
+
Dir::mkdir("#{Bankserv::Engine.archive_directory}/#{year}/#{month}") unless File.directory?("#{Bankserv::Engine.archive_directory}/#{year}/#{month}")
|
110
|
+
FileUtils.mv(file, "#{Bankserv::Engine.archive_directory}/#{year}/#{month}/")
|
111
|
+
end
|
112
|
+
|
113
|
+
def expecting_reply_file?
|
114
|
+
Bankserv::Document.where(type: 'input', reply_status: nil, processed: true).count > 0
|
115
|
+
end
|
116
|
+
|
117
|
+
def finish!
|
118
|
+
@process.update_attributes!(running: false, completed_at: Time.now, success: @success, response: @logs)
|
119
|
+
end
|
120
|
+
|
121
|
+
def running?
|
122
|
+
@process.running?
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.start
|
126
|
+
return true if self.running?
|
127
|
+
|
128
|
+
if Date.today.business_day?
|
129
|
+
queue = Bankserv::Engine.new
|
130
|
+
queue.process!
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.running?
|
135
|
+
EngineProcess.where(running: true).count > 0
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.config
|
139
|
+
EngineConfiguration.to_hash
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.interval
|
143
|
+
config[:interval]
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.input_directory
|
147
|
+
config[:input_directory]
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.output_directory
|
151
|
+
config[:output_directory]
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.archive_directory
|
155
|
+
config[:archive_directory]
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.interval=(interval)
|
159
|
+
EngineConfiguration.last.update_attributes!(interval_in_minutes: interval)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.input_directory=(dir)
|
163
|
+
EngineConfiguration.last.update_attributes!(input_directory: dir)
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.output_directory=(dir)
|
167
|
+
EngineConfiguration.last.update_attributes!(output_directory: dir)
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.archive_directory=(dir)
|
171
|
+
EngineConfiguration.last.update_attributes!(archive_directory: dir)
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.reply_files
|
175
|
+
Dir.entries(output_directory).select {|file| file.upcase.starts_with? "REPLY" }
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.output_files
|
179
|
+
Dir.entries(output_directory).select {|file| file.upcase.starts_with? "OUTPUT" }
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Bankserv::EngineConfiguration < ActiveRecord::Base
|
2
|
+
|
3
|
+
def self.to_hash
|
4
|
+
{
|
5
|
+
interval_in_minutes: last.interval_in_minutes,
|
6
|
+
input_directory: last.input_directory,
|
7
|
+
output_directory: last.output_directory,
|
8
|
+
archive_directory: last.archive_directory
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Bankserv
|
2
|
+
|
3
|
+
class Request < ActiveRecord::Base
|
4
|
+
serialize :data
|
5
|
+
|
6
|
+
self.inheritance_column = :_type_disabled
|
7
|
+
|
8
|
+
after_create :delegate!
|
9
|
+
|
10
|
+
def self.process!
|
11
|
+
self.where(:processed => false).each{|request| request.process!}
|
12
|
+
end
|
13
|
+
|
14
|
+
def delegate!
|
15
|
+
case type
|
16
|
+
when 'ahv'
|
17
|
+
AccountHolderVerification.build! data.merge(bankserv_request_id: self.id)
|
18
|
+
when 'debit'
|
19
|
+
Debit.build! data.merge(bankserv_request_id: self.id)
|
20
|
+
when 'credit'
|
21
|
+
Credit.build! data.merge(bankserv_request_id: self.id)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Bankserv
|
2
|
+
|
3
|
+
class Transaction < ActiveRecord::Base
|
4
|
+
|
5
|
+
belongs_to :statement, :foreign_key => 'bankserv_statement_id'
|
6
|
+
|
7
|
+
serialize :data
|
8
|
+
|
9
|
+
scope :unprocessed, where(processed: false)
|
10
|
+
|
11
|
+
def self.for_client_code(client_code)
|
12
|
+
where(client_code: client_code)
|
13
|
+
end
|
14
|
+
|
15
|
+
def processed!
|
16
|
+
self.processed = true
|
17
|
+
self.save!
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Bankserv
|
2
|
+
|
3
|
+
class Document < ActiveRecord::Base
|
4
|
+
self.inheritance_column = :_type_disabled
|
5
|
+
|
6
|
+
belongs_to :set
|
7
|
+
serialize :error
|
8
|
+
|
9
|
+
def mark_processed!
|
10
|
+
self.update_attributes!(processed: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
set.to_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def input?
|
18
|
+
type == 'input'
|
19
|
+
end
|
20
|
+
|
21
|
+
def output?
|
22
|
+
type == 'output'
|
23
|
+
end
|
24
|
+
|
25
|
+
def reply?
|
26
|
+
type == 'reply'
|
27
|
+
end
|
28
|
+
|
29
|
+
def sets
|
30
|
+
set.contained_sets
|
31
|
+
end
|
32
|
+
|
33
|
+
def records # unordered flat array records
|
34
|
+
sets.map(&:records).flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_with_generation_number(generation_number)
|
38
|
+
sets.select{|set| set.generation_number == generation_number}.first
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class Bankserv::InputDocument < Bankserv::Document
|
2
|
+
|
3
|
+
def self.document_type
|
4
|
+
'input'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.store(string)
|
8
|
+
options = Absa::H2h::Transmission::Document.hash_from_s(string, 'input')
|
9
|
+
|
10
|
+
raise "Expected a document set" unless options[:type] == "document"
|
11
|
+
|
12
|
+
document = new(type: 'input', transmission_number: options[:data][0][:data][:transmission_no], transmission_status: options[:data][0][:data][:rec_status])
|
13
|
+
document.set = Bankserv::Set.from_hash(options)
|
14
|
+
document.set.document = document # whaaaaaa?
|
15
|
+
document.save!
|
16
|
+
document
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.fetch_next_transmission_number
|
20
|
+
transmission_status = Bankserv::Configuration.live_env? ? "L" : "T"
|
21
|
+
where(type: 'input', reply_status: 'ACCEPTED', transmission_status: transmission_status).maximum(:transmission_number)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.sets_with_work
|
25
|
+
defined_input_sets.select(&:has_work?)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.sets_with_test_work
|
29
|
+
defined_input_sets.select(&:has_test_work?)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.has_work?
|
33
|
+
sets_with_work.any?
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.has_test_work?
|
37
|
+
sets_with_test_work.any?
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.generate_test!(options = {})
|
41
|
+
build!(options.merge(rec_status: "T")) if has_test_work?
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.generate!(options = {})
|
45
|
+
build!(options.merge(rec_status: "L")) if has_work?
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.build!(options = {}) # move to private
|
49
|
+
options[:transmission_no] ||= fetch_next_transmission_number
|
50
|
+
|
51
|
+
transmission_status = Bankserv::Configuration.live_env? ? "L" : "T"
|
52
|
+
|
53
|
+
document = new(transmission_status: transmission_status, rec_status: options[:rec_status], type: 'input', transmission_number: options[:transmission_no])
|
54
|
+
document.set = Bankserv::Transmission::UserSet::Document.generate(options.merge(rec_status: document.rec_status))
|
55
|
+
document.set.document = document # whaaaaaa?
|
56
|
+
|
57
|
+
input_sets = if document.rec_status == "L"
|
58
|
+
sets_with_work
|
59
|
+
else
|
60
|
+
sets_with_test_work
|
61
|
+
end
|
62
|
+
|
63
|
+
input_sets.each do |set|
|
64
|
+
document.set.sets << set.generate(rec_status: document.rec_status)
|
65
|
+
document.set.sets[-1].set = document.set # whaaaaaa?
|
66
|
+
end
|
67
|
+
|
68
|
+
document.save!
|
69
|
+
document
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.defined_input_sets
|
73
|
+
[
|
74
|
+
Bankserv::Transmission::UserSet::AccountHolderVerification,
|
75
|
+
Bankserv::Transmission::UserSet::Debit,
|
76
|
+
Bankserv::Transmission::UserSet::Credit
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.for_transmission_number(transmission_number)
|
81
|
+
where(type: 'input', transmission_number: transmission_number).first
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|