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.
Files changed (75) hide show
  1. data/.gitignore +6 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +16 -0
  4. data/Rakefile +8 -0
  5. data/bankserv.gemspec +31 -0
  6. data/config.ru +7 -0
  7. data/lib/bankserv.rb +48 -0
  8. data/lib/bankserv/account_holder_verification.rb +90 -0
  9. data/lib/bankserv/bank_account.rb +19 -0
  10. data/lib/bankserv/configuration.rb +67 -0
  11. data/lib/bankserv/credit.rb +70 -0
  12. data/lib/bankserv/debit.rb +94 -0
  13. data/lib/bankserv/eft.rb +83 -0
  14. data/lib/bankserv/engine.rb +183 -0
  15. data/lib/bankserv/engine/engine_configuration.rb +12 -0
  16. data/lib/bankserv/engine/engine_process.rb +3 -0
  17. data/lib/bankserv/request.rb +27 -0
  18. data/lib/bankserv/transaction.rb +22 -0
  19. data/lib/bankserv/transmission/document.rb +43 -0
  20. data/lib/bankserv/transmission/input_document.rb +84 -0
  21. data/lib/bankserv/transmission/output_document.rb +27 -0
  22. data/lib/bankserv/transmission/record.rb +13 -0
  23. data/lib/bankserv/transmission/reply_document.rb +27 -0
  24. data/lib/bankserv/transmission/set.rb +120 -0
  25. data/lib/bankserv/transmission/set/account_holder_verification.rb +81 -0
  26. data/lib/bankserv/transmission/set/account_holder_verification_output.rb +17 -0
  27. data/lib/bankserv/transmission/set/credit.rb +44 -0
  28. data/lib/bankserv/transmission/set/debit.rb +44 -0
  29. data/lib/bankserv/transmission/set/document.rb +45 -0
  30. data/lib/bankserv/transmission/set/eft.rb +228 -0
  31. data/lib/bankserv/transmission/set/eft_output.rb +12 -0
  32. data/lib/bankserv/transmission/set/eft_redirect.rb +18 -0
  33. data/lib/bankserv/transmission/set/eft_unpaid.rb +18 -0
  34. data/lib/bankserv/transmission/set/reply.rb +47 -0
  35. data/lib/bankserv/transmission/statement.rb +46 -0
  36. data/lib/bankserv/version.rb +3 -0
  37. data/lib/config/ahv.yml +9 -0
  38. data/lib/core_extensions.rb +37 -0
  39. data/lib/generators/active_record/bankserv_generator.rb +30 -0
  40. data/lib/generators/active_record/templates/migration.rb +141 -0
  41. data/spec/examples/ahv_input_file.txt +7 -0
  42. data/spec/examples/ahv_output_file.txt +7 -0
  43. data/spec/examples/credit_eft_input.txt +166 -0
  44. data/spec/examples/debit_eft_input_file.txt +20 -0
  45. data/spec/examples/eft_input_with_2_sets.txt +8 -0
  46. data/spec/examples/eft_output_file.txt +35 -0
  47. data/spec/examples/host2host/tmp.log +1 -0
  48. data/spec/examples/reply/rejected_transmission.txt +7 -0
  49. data/spec/examples/reply/reply_file.txt +5 -0
  50. data/spec/examples/statement4_unpacked.dat +51 -0
  51. data/spec/examples/tmp/OUTPUT0412153500.txt +35 -0
  52. data/spec/examples/tmp/REPLY0412153000.txt +5 -0
  53. data/spec/factories.rb +90 -0
  54. data/spec/internal/config/database.yml.sample +3 -0
  55. data/spec/internal/config/routes.rb +3 -0
  56. data/spec/internal/db/schema.rb +138 -0
  57. data/spec/internal/log/.gitignore +1 -0
  58. data/spec/internal/public/favicon.ico +0 -0
  59. data/spec/lib/bankserv/account_holder_verification_spec.rb +104 -0
  60. data/spec/lib/bankserv/configuration_spec.rb +34 -0
  61. data/spec/lib/bankserv/core_ext_spec.rb +43 -0
  62. data/spec/lib/bankserv/credit_spec.rb +92 -0
  63. data/spec/lib/bankserv/debit_spec.rb +187 -0
  64. data/spec/lib/bankserv/engine/engine_spec.rb +142 -0
  65. data/spec/lib/bankserv/transaction_spec.rb +32 -0
  66. data/spec/lib/bankserv/transmission/input_document_spec.rb +207 -0
  67. data/spec/lib/bankserv/transmission/output_document_spec.rb +210 -0
  68. data/spec/lib/bankserv/transmission/reply_document_spec.rb +117 -0
  69. data/spec/lib/bankserv/transmission/set/account_holder_verification_spec.rb +108 -0
  70. data/spec/lib/bankserv/transmission/set/credit_spec.rb +96 -0
  71. data/spec/lib/bankserv/transmission/set/debit_spec.rb +103 -0
  72. data/spec/lib/bankserv/transmission/statement_spec.rb +109 -0
  73. data/spec/spec_helper.rb +28 -0
  74. data/spec/support/helpers.rb +207 -0
  75. metadata +223 -0
@@ -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,3 @@
1
+ class Bankserv::EngineProcess < ActiveRecord::Base
2
+ serialize :response
3
+ 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