VantivCnp 8.31.0

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 (120) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +139 -0
  3. data/CONTRIBUTORS +2 -0
  4. data/DESCRIPTION +5 -0
  5. data/LICENSE +22 -0
  6. data/README.md +72 -0
  7. data/Rakefile +89 -0
  8. data/SETUP.md +46 -0
  9. data/bin/Setup.rb +124 -0
  10. data/bin/sample_batch_driver.rb +123 -0
  11. data/bin/sample_driver.rb +49 -0
  12. data/lib/Communications.rb +85 -0
  13. data/lib/Configuration.rb +67 -0
  14. data/lib/EnvironmentVariables.rb +22 -0
  15. data/lib/LitleBatchRequest.rb +562 -0
  16. data/lib/LitleListeners.rb +142 -0
  17. data/lib/LitleOnline.rb +62 -0
  18. data/lib/LitleOnlineRequest.rb +297 -0
  19. data/lib/LitleRequest.rb +494 -0
  20. data/lib/LitleTransaction.rb +463 -0
  21. data/lib/LitleXmlMapper.rb +64 -0
  22. data/lib/XMLFields.rb +1869 -0
  23. data/lib/cacert.pem +3331 -0
  24. data/samples/Auth/LitleAuthReversalTransaction.rb +15 -0
  25. data/samples/Auth/LitleAuthorizationTransaction.rb +31 -0
  26. data/samples/Auth/LitlePaymentFullLifeCycle.rb +47 -0
  27. data/samples/Batch/AccountUpdate.rb +64 -0
  28. data/samples/Batch/SampleBatchDriver.rb +94 -0
  29. data/samples/Capture/LitleCaptureGivenAuthTransaction.rb +30 -0
  30. data/samples/Capture/LitleCaptureTransaction.rb +14 -0
  31. data/samples/Capture/LitleForceCaptureTransaction.rb +26 -0
  32. data/samples/Capture/LitlePartialCapture.rb +16 -0
  33. data/samples/Credit/LitleCreditTransaction.rb +16 -0
  34. data/samples/Credit/LitleRefundTransaction.rb +29 -0
  35. data/samples/Other/LitleAvsTransaction.rb +34 -0
  36. data/samples/Other/LitleVoidTransaction.rb +18 -0
  37. data/samples/Paypage/FullPaypageLifeCycle.rb +74 -0
  38. data/samples/Run_all.rb +17 -0
  39. data/samples/Sale/LitleSaleTransaction.rb +29 -0
  40. data/samples/Sale/SampleSaleTransaction.rb +24 -0
  41. data/test/certification/certTest1_base.rb +945 -0
  42. data/test/certification/certTest2_authenhanced.rb +573 -0
  43. data/test/certification/certTest3_authreversal.rb +185 -0
  44. data/test/certification/certTest4_echeck.rb +278 -0
  45. data/test/certification/certTest5_token.rb +204 -0
  46. data/test/certification/certTest_batchAll.rb +337 -0
  47. data/test/certification/ts_all.rb +33 -0
  48. data/test/functional/test_activate.rb +100 -0
  49. data/test/functional/test_activateReversal.rb +56 -0
  50. data/test/functional/test_auth.rb +298 -0
  51. data/test/functional/test_authReversal.rb +69 -0
  52. data/test/functional/test_balanceInquiry.rb +80 -0
  53. data/test/functional/test_batch.rb +164 -0
  54. data/test/functional/test_batchStream.rb +145 -0
  55. data/test/functional/test_cancelSubscription.rb +55 -0
  56. data/test/functional/test_capture.rb +84 -0
  57. data/test/functional/test_captureGivenAuth.rb +235 -0
  58. data/test/functional/test_configuration.rb +89 -0
  59. data/test/functional/test_createPlan.rb +85 -0
  60. data/test/functional/test_credit.rb +174 -0
  61. data/test/functional/test_deactivate.rb +80 -0
  62. data/test/functional/test_deactivateReversal.rb +56 -0
  63. data/test/functional/test_depositReversal.rb +56 -0
  64. data/test/functional/test_echeckCredit.rb +134 -0
  65. data/test/functional/test_echeckRedeposit.rb +88 -0
  66. data/test/functional/test_echeckSale.rb +177 -0
  67. data/test/functional/test_echeckVerification.rb +127 -0
  68. data/test/functional/test_echeckVoid.rb +41 -0
  69. data/test/functional/test_forceCapture.rb +183 -0
  70. data/test/functional/test_litle_requests.rb +356 -0
  71. data/test/functional/test_load.rb +82 -0
  72. data/test/functional/test_loadReversal.rb +56 -0
  73. data/test/functional/test_override.rb +64 -0
  74. data/test/functional/test_refundReversal.rb +56 -0
  75. data/test/functional/test_sale.rb +259 -0
  76. data/test/functional/test_token.rb +115 -0
  77. data/test/functional/test_unload.rb +82 -0
  78. data/test/functional/test_unloadReversal.rb +56 -0
  79. data/test/functional/test_updateCardValidationNumOnToken.rb +43 -0
  80. data/test/functional/test_updatePlan.rb +58 -0
  81. data/test/functional/test_updateSubscription.rb +76 -0
  82. data/test/functional/test_xmlfields.rb +427 -0
  83. data/test/functional/ts_all.rb +66 -0
  84. data/test/unit/test_LitleAUBatch.rb +216 -0
  85. data/test/unit/test_LitleBatchRequest.rb +643 -0
  86. data/test/unit/test_LitleOnlineRequest.rb +295 -0
  87. data/test/unit/test_LitleRequest.rb +316 -0
  88. data/test/unit/test_LitleTransaction.rb +397 -0
  89. data/test/unit/test_activate.rb +92 -0
  90. data/test/unit/test_activateReversal.rb +44 -0
  91. data/test/unit/test_auth.rb +421 -0
  92. data/test/unit/test_authReversal.rb +82 -0
  93. data/test/unit/test_balanceInquiry.rb +52 -0
  94. data/test/unit/test_cancelSubscription.rb +43 -0
  95. data/test/unit/test_capture.rb +73 -0
  96. data/test/unit/test_captureGivenAuth.rb +188 -0
  97. data/test/unit/test_createPlan.rb +52 -0
  98. data/test/unit/test_credit.rb +342 -0
  99. data/test/unit/test_deactivate.rb +52 -0
  100. data/test/unit/test_deactivateReversal.rb +44 -0
  101. data/test/unit/test_depositReversal.rb +44 -0
  102. data/test/unit/test_echeckCredit.rb +71 -0
  103. data/test/unit/test_echeckRedeposit.rb +94 -0
  104. data/test/unit/test_echeckSale.rb +71 -0
  105. data/test/unit/test_echeckVerification.rb +71 -0
  106. data/test/unit/test_echeckVoid.rb +54 -0
  107. data/test/unit/test_forceCapture.rb +145 -0
  108. data/test/unit/test_load.rb +53 -0
  109. data/test/unit/test_loadReversal.rb +44 -0
  110. data/test/unit/test_refundReversal.rb +44 -0
  111. data/test/unit/test_sale.rb +465 -0
  112. data/test/unit/test_token.rb +144 -0
  113. data/test/unit/test_unload.rb +53 -0
  114. data/test/unit/test_unloadReversal.rb +44 -0
  115. data/test/unit/test_updateCardValidationNumOnToken.rb +80 -0
  116. data/test/unit/test_updatePlan.rb +45 -0
  117. data/test/unit/test_updateSubscription.rb +172 -0
  118. data/test/unit/test_xmlfields.rb +2930 -0
  119. data/test/unit/ts_unit.rb +65 -0
  120. metadata +224 -0
@@ -0,0 +1,494 @@
1
+ # Copyright (c) 2011 Litle & Co.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation
5
+ # files (the "Software"), to deal in the Software without
6
+ # restriction, including without limitation the rights to use,
7
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the
9
+ # Software is furnished to do so, subject to the following
10
+ # conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ # OTHER DEALINGS IN THE SOFTWARE.
23
+ require_relative 'Configuration'
24
+ require 'net/sftp'
25
+ require 'libxml'
26
+ require 'crack/xml'
27
+ require 'socket'
28
+
29
+ include Socket::Constants
30
+ #
31
+ # This class handles sending the Litle Request (which is actually a series of batches!)
32
+ #
33
+
34
+ module LitleOnline
35
+ class LitleRequest
36
+ include XML::Mapping
37
+ def initialize(options = {})
38
+ # load configuration data
39
+ @config_hash = Configuration.new.config
40
+ @num_batch_requests = 0
41
+ @path_to_request = ''
42
+ @path_to_batches = ''
43
+ @num_total_transactions = 0
44
+ @MAX_NUM_TRANSACTIONS = 500_000
45
+ @options = options
46
+ # current time out set to 2 mins
47
+ # this value is in seconds
48
+ @RESPONSE_TIME_OUT = 360
49
+ @POLL_DELAY = 0
50
+ @responses_expected = 0
51
+ end
52
+
53
+ # Creates the necessary files for the LitleRequest at the path specified. path/request_(TIMESTAMP) will be
54
+ # the final XML markup and path/request_(TIMESTAMP) will hold intermediary XML markup
55
+ # Params:
56
+ # +path+:: A +String+ containing the path to the folder on disc to write the files to
57
+ def create_new_litle_request(path)
58
+ ts = Time.now.to_i.to_s
59
+ begin
60
+ ts += Time.now.nsec.to_s
61
+ rescue NoMethodError # ruby 1.8.7 fix
62
+ ts += Time.now.usec.to_s
63
+ end
64
+
65
+ raise 'Entered a file not a path.' if File.file?(path)
66
+
67
+ path += File::SEPARATOR if path[-1, 1] != '/' && path[-1, 1] != '\\'
68
+
69
+ Dir.mkdir(path) unless File.directory?(path)
70
+
71
+ @path_to_request = path + 'request_' + ts
72
+ @path_to_batches = @path_to_request + '_batches'
73
+
74
+ if File.file?(@path_to_request) || File.file?(@path_to_batches)
75
+ create_new_litle_request(path)
76
+ return
77
+ end
78
+
79
+ File.open(@path_to_request, 'a+') do |file|
80
+ file.write('')
81
+ end
82
+ File.open(@path_to_batches, 'a+') do |file|
83
+ file.write('')
84
+ end
85
+ end
86
+
87
+ # Adds a batch to the LitleRequest. If the batch is open when passed, it will be closed prior to being added.
88
+ # Params:
89
+ # +arg+:: a +LitleBatchRequest+ containing the transactions you wish to send or a +String+ specifying the
90
+ # path to the batch file
91
+ def commit_batch(arg)
92
+ path_to_batch = ''
93
+ # they passed a batch
94
+ if arg.is_a?(LitleBatchRequest)
95
+ path_to_batch = arg.get_batch_name
96
+ if (au = arg.get_au_batch) != nil
97
+ # also commit the account updater batch
98
+ commit_batch(au)
99
+ end
100
+ elsif arg.is_a?(LitleAUBatch)
101
+ path_to_batch = arg.get_batch_name
102
+ elsif arg.is_a?(String)
103
+ path_to_batch = arg
104
+ else
105
+ raise 'You entered neither a path nor a batch. Game over :('
106
+ end
107
+ # the batch isn't closed. let's help a brother out
108
+ if (ind = path_to_batch.index(/\.closed/)).nil?
109
+ if arg.is_a?(String)
110
+ new_batch = LitleBatchRequest.new
111
+ new_batch.open_existing_batch(path_to_batch)
112
+ new_batch.close_batch
113
+ path_to_batch = new_batch.get_batch_name
114
+ # if we passed a path to an AU batch, then new_batch will be a new, empty batch and the batch we passed
115
+ # will be in the AU batch variable. thus, we wanna grab that file name and remove the empty batch.
116
+ unless new_batch.get_au_batch.nil?
117
+ File.remove(path_to_batch)
118
+ path_to_batch = new_batch.get_au_batch.get_batch_name
119
+ end
120
+ elsif arg.is_a?(LitleBatchRequest)
121
+ arg.close_batch
122
+ path_to_batch = arg.get_batch_name
123
+ elsif arg.is_a?(LitleAUBatch)
124
+ arg.close_batch
125
+ path_to_batch = arg.get_batch_name
126
+ end
127
+ ind = path_to_batch.index(/\.closed/)
128
+ end
129
+ transactions_in_batch = path_to_batch[ind + 8..path_to_batch.length].to_i
130
+
131
+ # if the litle request would be too big, let's make another!
132
+ if (@num_total_transactions + transactions_in_batch) > @MAX_NUM_TRANSACTIONS
133
+ finish_request
134
+ initialize(@options)
135
+ create_new_litle_request
136
+ else # otherwise, let's add it line by line to the request doc
137
+ # @num_batch_requests += 1
138
+ # how long we wnat to wait around for the FTP server to get us a response
139
+ @RESPONSE_TIME_OUT += 90 + (transactions_in_batch * 0.25)
140
+ # don't start looking until there could possibly be a response
141
+ @POLL_DELAY += 30 + (transactions_in_batch * 0.02)
142
+ @num_total_transactions += transactions_in_batch
143
+ # Don't add empty batches
144
+ @num_batch_requests += 1 unless transactions_in_batch.eql?(0)
145
+ File.open(@path_to_batches, 'a+') do |fo|
146
+ File.foreach(path_to_batch) do |li|
147
+ fo.puts li
148
+ end
149
+ end
150
+
151
+ File.delete(path_to_batch)
152
+ end
153
+ end
154
+
155
+ # Adds an RFRRequest to the LitleRequest.
156
+ # params:
157
+ # +options+:: a required +Hash+ containing configuration info for the RFRRequest. If the RFRRequest is for a batch, then the
158
+ # litleSessionId is required as a key/val pair. If the RFRRequest is for account updater, then merchantId and postDay are required
159
+ # as key/val pairs.
160
+ # +path+:: optional path to save the new litle request containing the RFRRequest at
161
+ def add_rfr_request(options, path = File.dirname(@path_to_batches))
162
+ rfrrequest = LitleRFRRequest.new
163
+ if options['litleSessionId']
164
+ rfrrequest.litleSessionId = options['litleSessionId']
165
+ elsif options['merchantId'] && options['postDay']
166
+ accountUpdate = AccountUpdateFileRequestData.new
167
+ accountUpdate.merchantId = options['merchantId']
168
+ accountUpdate.postDay = options['postDay']
169
+ rfrrequest.accountUpdateFileRequestData = accountUpdate
170
+ else
171
+ raise ArgumentError, "For an RFR Request, you must specify either a litleSessionId for an RFRRequest for batch or a merchantId
172
+ and a postDay for an RFRRequest for account updater."
173
+ end
174
+
175
+ litleRequest = LitleRequestForRFR.new
176
+ litleRequest.rfrRequest = rfrrequest
177
+
178
+ authentication = Authentication.new
179
+ authentication.user = get_config(:user, options)
180
+ authentication.password = get_config(:password, options)
181
+
182
+ litleRequest.authentication = authentication
183
+ litleRequest.numBatchRequests = '0'
184
+
185
+ litleRequest.version = '8.31'
186
+ litleRequest.xmlns = 'http://www.litle.com/schema'
187
+
188
+ xml = litleRequest.save_to_xml.to_s
189
+
190
+ ts = Time.now.to_i.to_s
191
+ begin
192
+ ts += Time.now.nsec.to_s
193
+ rescue NoMethodError # ruby 1.8.7 fix
194
+ ts += Time.now.usec.to_s
195
+ end
196
+ raise 'Entered a file not a path.' if File.file?(path)
197
+
198
+ path += File::SEPARATOR if path[-1, 1] != '/' && path[-1, 1] != '\\'
199
+
200
+ Dir.mkdir(path) unless File.directory?(path)
201
+
202
+ path_to_request = path + 'request_' + ts
203
+
204
+ File.open(path_to_request, 'a+') do |file|
205
+ file.write xml
206
+ end
207
+ File.rename(path_to_request, path_to_request + '.complete')
208
+ @RESPONSE_TIME_OUT += 90
209
+ end
210
+
211
+ # FTPs all previously unsent LitleRequests located in the folder denoted by path to the server
212
+ # Params:
213
+ # +path+:: A +String+ containing the path to the folder on disc where LitleRequests are located.
214
+ # This should be the same location where the LitleRequests were written to. If no path is explicitly
215
+ # provided, then we use the directory where the current working batches file is stored.
216
+ # +options+:: An (option) +Hash+ containing the username, password, and URL to attempt to sFTP to.
217
+ # If not provided, the values will be populated from the configuration file.
218
+ def send_to_litle(path = File.dirname(@path_to_batches), options = {})
219
+ username = get_config(:sftp_username, options)
220
+ password = get_config(:sftp_password, options)
221
+
222
+ url = get_config(:sftp_url, options)
223
+
224
+ if username.nil? || password.nil? || url.nil?
225
+ raise ArgumentError, 'You are not configured to use sFTP for batch processing. Please run /bin/Setup.rb again!'
226
+ end
227
+
228
+ path += File::SEPARATOR if path[-1, 1] != '/' && path[-1, 1] != '\\'
229
+
230
+ begin
231
+ Net::SFTP.start(url, username, password: password) do |sftp|
232
+ # our folder is /SHORTNAME/SHORTNAME/INBOUND
233
+ Dir.foreach(path) do |filename|
234
+ # we have a complete report according to filename regex
235
+ if (filename =~ /request_\d+.complete\z/) != nil
236
+ # adding .prg extension per the XML
237
+ File.rename(path + filename, path + filename + '.prg')
238
+ end
239
+ end
240
+
241
+ @responses_expected = 0
242
+ Dir.foreach(path) do |filename|
243
+ if (filename =~ /request_\d+.complete.prg\z/) != nil
244
+ # upload the file
245
+ sftp.upload!(path + filename, '/inbound/' + filename)
246
+ @responses_expected += 1
247
+ # rename now that we're done
248
+ sftp.rename!('/inbound/' + filename, '/inbound/' + filename.gsub('prg', 'asc'))
249
+ File.rename(path + filename, path + filename.gsub('prg', 'sent'))
250
+ end
251
+ end
252
+ end
253
+ rescue Net::SSH::AuthenticationFailed
254
+ raise ArgumentError, 'The sFTP credentials provided were incorrect. Try again!'
255
+ end
256
+ end
257
+
258
+ # Sends all previously unsent LitleRequests in the specified directory to the Litle server
259
+ # by use of fast batch. All results will be written to disk as we get them. Note that use
260
+ # of fastbatch is strongly discouraged!
261
+ def send_to_litle_stream(options = {}, path = File.dirname(@path_to_batches))
262
+ url = get_config(:fast_url, options)
263
+ port = get_config(:fast_port, options)
264
+
265
+ if url.nil? || url == ''
266
+ raise ArgumentError, 'A URL for fastbatch was not specified in the config file or passed options. Reconfigure and try again.'
267
+ end
268
+
269
+ if port == '' || port.nil?
270
+ raise ArgumentError, 'A port number for fastbatch was not specified in the config file or passed options. Reconfigure and try again.'
271
+ end
272
+
273
+ path += File::SEPARATOR if path[-1, 1] != '/' && path[-1, 1] != '\\'
274
+
275
+ Dir.mkdir(path + 'responses/') unless File.directory?(path + 'responses/')
276
+
277
+ Dir.foreach(path) do |filename|
278
+ if (filename =~ /request_\d+.complete\z/) != nil
279
+ begin
280
+ socket = TCPSocket.open(url, port.to_i)
281
+ ssl_context = OpenSSL::SSL::SSLContext.new
282
+ ssl_context.ssl_version = :SSLv23
283
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
284
+ ssl_socket.sync_close = true
285
+ ssl_socket.connect
286
+ rescue => e
287
+ raise "A connection couldn't be established. Are you sure you have the correct credentials? Exception: " + e.message
288
+ end
289
+
290
+ File.foreach(path + filename) do |li|
291
+ ssl_socket.puts li
292
+ end
293
+ File.rename(path + filename, path + filename + '.sent')
294
+ File.open(path + 'responses/' + (filename + '.asc.received').gsub('request', 'response'), 'a+') do |fo|
295
+ while line = ssl_socket.gets
296
+ fo.puts(line)
297
+ end
298
+ end
299
+
300
+ end
301
+ end
302
+ end
303
+
304
+ # Grabs response files over SFTP from Litle.
305
+ # Params:
306
+ # +args+:: An (optional) +Hash+ containing values for the number of responses expected, the
307
+ # path to the folder on disk to write the responses from the Litle server to, the username and
308
+ # password with which to connect ot the sFTP server, and the URL to connect over sFTP. Values not
309
+ # provided in the hash will be populate automatically based on our best guess
310
+ def get_responses_from_server(args = {})
311
+ @responses_expected = args[:responses_expected] ||= @responses_expected
312
+ response_path = args[:response_path] ||= (File.dirname(@path_to_batches) + '/responses/')
313
+ username = get_config(:sftp_username, args)
314
+ password = get_config(:sftp_password, args)
315
+ url = get_config(:sftp_url, args)
316
+
317
+ if username.nil? || password.nil? || url.nil?
318
+ raise ConfigurationException, 'You are not configured to use sFTP for batch processing. Please run /bin/Setup.rb again!'
319
+ end
320
+
321
+ if response_path[-1, 1] != '/' && response_path[-1, 1] != '\\'
322
+ response_path += File::SEPARATOR
323
+ end
324
+
325
+ Dir.mkdir(response_path) unless File.directory?(response_path)
326
+ begin
327
+ responses_grabbed = 0
328
+ Net::SFTP.start(url, username, password: password) do |sftp|
329
+ # clear out the sFTP outbound dir prior to checking for new files, avoids leaving files on the server
330
+ # if files are left behind we are not counting then towards the expected total
331
+ sftp.dir.foreach('/outbound/') do |entry|
332
+ if (entry.name =~ /request_\d+.complete.asc\z/) != nil
333
+ sftp.download!('/outbound/' + entry.name, response_path + entry.name.gsub('request', 'response') + '.received')
334
+ 3.times do
335
+ begin
336
+ sftp.remove!('/outbound/' + entry.name)
337
+ break
338
+ rescue Net::SFTP::StatusException
339
+ # try, try, try again
340
+ puts "We couldn't remove it! Try again"
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
346
+ # wait until a response has a possibility of being there
347
+ sleep(@POLL_DELAY)
348
+ time_begin = Time.now
349
+ Net::SFTP.start(url, username, password: password) do |sftp|
350
+ while (Time.now - time_begin) < @RESPONSE_TIME_OUT && responses_grabbed < @responses_expected
351
+ # sleep for 60 seconds, ¿no es bueno?
352
+ sleep(60)
353
+ sftp.dir.foreach('/outbound/') do |entry|
354
+ if (entry.name =~ /request_\d+.complete.asc\z/) != nil
355
+ sftp.download!('/outbound/' + entry.name, response_path + entry.name.gsub('request', 'response') + '.received')
356
+ responses_grabbed += 1
357
+ 3.times do
358
+ begin
359
+ sftp.remove!('/outbound/' + entry.name)
360
+ break
361
+ rescue Net::SFTP::StatusException
362
+ # try, try, try again
363
+ puts "We couldn't remove it! Try again"
364
+ end
365
+ end
366
+ end
367
+ end
368
+ end
369
+ # if our timeout timed out, we're having problems
370
+ if responses_grabbed < @responses_expected
371
+ raise 'We timed out in waiting for a response from the server. :('
372
+ end
373
+ end
374
+ rescue Net::SSH::AuthenticationFailed
375
+ raise ArgumentError, 'The sFTP credentials provided were incorrect. Try again!'
376
+ end
377
+ end
378
+
379
+ # Params:
380
+ # +args+:: A +Hash+ containing arguments for the processing process. This hash MUST contain an entry
381
+ # for a transaction listener (see +DefaultLitleListener+). It may also include a batch listener and a
382
+ # custom path where response files from the server are located (if it is not provided, we'll guess the position)
383
+ def process_responses(args)
384
+ # the transaction listener is required
385
+ unless args.key?(:transaction_listener)
386
+ raise ArgumentError, 'The arguments hash must contain an entry for transaction listener!'
387
+ end
388
+
389
+ transaction_listener = args[:transaction_listener]
390
+ batch_listener = args[:batch_listener] ||= nil
391
+ path_to_responses = args[:path_to_responses] ||= (File.dirname(@path_to_batches) + '/responses/')
392
+
393
+ Dir.foreach(path_to_responses) do |filename|
394
+ if (filename =~ /response_\d+.complete.asc.received\z/) != nil
395
+ process_response(path_to_responses + filename, transaction_listener, batch_listener)
396
+ File.rename(path_to_responses + filename, path_to_responses + filename + '.processed')
397
+ end
398
+ end
399
+ end
400
+
401
+ # Params:
402
+ # +path_to_response+:: The path to a specific .asc file to process
403
+ # +transaction_listener+:: A listener to be applied to the hash of each transaction
404
+ # (see +DefaultLitleListener+)
405
+ # +batch_listener+:: An (optional) listener to be applied to the hash of each batch.
406
+ # Note that this will om-nom-nom quite a bit of memory
407
+ def process_response(path_to_response, transaction_listener, _batch_listener = nil)
408
+ reader = LibXML::XML::Reader.file(path_to_response)
409
+ reader.read # read into the root node
410
+ # if the response attribute is nil, we're dealing with an RFR and everything is a-okay
411
+ if reader.get_attribute('response') != '0' && !reader.get_attribute('response').nil?
412
+ raise 'Error parsing Litle Request: ' + reader.get_attribute('message')
413
+ end
414
+
415
+ reader.read
416
+ count = 0
417
+ while true && count < 500_001
418
+
419
+ count += 1
420
+ return false if reader.node.nil?
421
+
422
+ case reader.node.name.to_s
423
+ when 'batchResponse'
424
+ reader.read
425
+ when 'litleResponse'
426
+ return false
427
+ when 'text'
428
+ reader.read
429
+ else
430
+ xml = reader.read_outer_xml
431
+ duck = Crack::XML.parse(xml)
432
+ duck[duck.keys[0]]['type'] = duck.keys[0]
433
+ duck = duck[duck.keys[0]]
434
+ transaction_listener.apply(duck)
435
+ reader.next
436
+ end
437
+ end
438
+ end
439
+
440
+ def get_path_to_batches
441
+ @path_to_batches
442
+ end
443
+
444
+ # Called when you wish to finish adding batches to your request, this method rewrites the aggregate
445
+ # batch file to the final LitleRequest xml doc with the appropos LitleRequest tags.
446
+ def finish_request
447
+ File.open(@path_to_request, 'w') do |f|
448
+ # jam dat header in there
449
+ f.puts(build_request_header)
450
+ # read into the request file from the batches file
451
+ File.foreach(@path_to_batches) do |li|
452
+ f.puts li
453
+ end
454
+ # finally, let's poot in a header, for old time's sake
455
+ f.puts '</litleRequest>'
456
+ end
457
+
458
+ # rename the requests file
459
+ File.rename(@path_to_request, @path_to_request + '.complete')
460
+ # we don't need the master batch file anymore
461
+ File.delete(@path_to_batches)
462
+ end
463
+
464
+ private
465
+
466
+ def build_request_header(options = @options)
467
+ litle_request = self
468
+
469
+ authentication = Authentication.new
470
+ authentication.user = get_config(:user, options)
471
+ authentication.password = get_config(:password, options)
472
+
473
+ litle_request.authentication = authentication
474
+ litle_request.version = '8.31'
475
+ litle_request.xmlns = 'http://www.litle.com/schema'
476
+ # litle_request.id = options['sessionId'] #grab from options; okay if nil
477
+ litle_request.numBatchRequests = @num_batch_requests
478
+
479
+ xml = litle_request.save_to_xml.to_s
480
+ xml[/<\/litleRequest>/] = ''
481
+ xml
482
+ end
483
+
484
+ def get_config(field, options)
485
+ if options[field.to_s].nil? && options[field].nil?
486
+ @config_hash[field.to_s]
487
+ elsif !options[field.to_s].nil?
488
+ options[field.to_s]
489
+ else
490
+ options[field]
491
+ end
492
+ end
493
+ end
494
+ end