Class: CnpOnline::CnpRequest

Inherits:
Object
  • Object
show all
Includes:
XML::Mapping
Defined in:
lib/XMLFields.rb,
lib/CnpRequest.rb

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ CnpRequest

Returns a new instance of CnpRequest.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/CnpRequest.rb', line 55

def initialize(options = {})
  #load configuration data
  @config_hash = Configuration.new.config
  @num_batch_requests = 0
  @path_to_request = ""
  @path_to_batches = ""
  @num_total_transactions = 0
  @MAX_NUM_TRANSACTIONS = 500000
  @options = options
  # current time out set to 2 mins
  # this value is in seconds
  @RESPONSE_TIME_OUT = 5200
  @POLL_DELAY = 0
  @responses_expected = 0
end

Instance Method Details

#add_rfr_request(options, path = (File.dirname(@path_to_batches))) ⇒ Object

Adds an RFRRequest to the CnpRequest. params:

options

a required Hash containing configuration info for the RFRRequest. If the RFRRequest is for a batch, then the

cnpSessionId is required as a key/val pair. If the RFRRequest is for account updater, then merchantId and postDay are required as key/val pairs.

path

optional path to save the new cnp request containing the RFRRequest at



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/CnpRequest.rb', line 185

def add_rfr_request(options, path = (File.dirname(@path_to_batches)))
 
  rfrrequest = CnpRFRRequest.new
  if(options['cnpSessionId']) then
    rfrrequest.cnpSessionId = options['cnpSessionId']
  elsif(options['merchantId'] and options['postDay']) then
    accountUpdate = AccountUpdateFileRequestData.new
    accountUpdate.merchantId = options['merchantId']
    accountUpdate.postDay = options['postDay']
    rfrrequest.accountUpdateFileRequestData = accountUpdate
  else
    raise ArgumentError, "For an RFR Request, you must specify either a cnpSessionId for an RFRRequest for batch or a merchantId
    and a postDay for an RFRRequest for account updater."
  end 
  
  cnpRequest = CnpRequestForRFR.new
  cnpRequest.rfrRequest = rfrrequest
  
  authentication = Authentication.new
  authentication.user = get_config(:user, options)
  authentication.password = get_config(:password, options)

  cnpRequest.authentication = authentication
  cnpRequest.numBatchRequests = "0"
  
  cnpRequest.version         = '12.8'
  cnpRequest.xmlns           = "http://www.vantivcnp.com/schema"

  
  xml = cnpRequest.save_to_xml.to_s
  
  ts = Time::now.to_i.to_s
  begin
    ts += Time::now.nsec.to_s
  rescue NoMethodError # ruby 1.8.7 fix
    ts += Time::now.usec.to_s
  end
  if(File.file?(path)) then
    raise RuntimeError, "Entered a file not a path."
  end

  if(path[-1,1] != '/' and path[-1,1] != '\\') then
    path = path + File::SEPARATOR
  end

  if !File.directory?(path) then
    Dir.mkdir(path)
  end
  
  path_to_request = path + REQUEST_FILE_PREFIX + ts

  File.open(path_to_request, 'a+') do |file|
    file.write xml
  end
  File.rename(path_to_request, path_to_request + COMPLETE_FILE_SUFFIX)
  @RESPONSE_TIME_OUT += 90   
end

#commit_batch(arg) ⇒ Object

Adds a batch to the CnpRequest. If the batch is open when passed, it will be closed prior to being added. Params:

arg

a CnpBatchRequest containing the transactions you wish to send or a String specifying the

path to the batch file



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/CnpRequest.rb', line 115

def commit_batch(arg)
  path_to_batch = ""
  #they passed a batch
  if arg.kind_of?(CnpBatchRequest) then
    path_to_batch = arg.get_batch_name
    if((au = arg.get_au_batch) != nil) then 
      # also commit the account updater batch
      commit_batch(au)
    end
  elsif arg.kind_of?(CnpAUBatch) then
    path_to_batch = arg.get_batch_name
  elsif arg.kind_of?(String) then
    path_to_batch = arg
  else
    raise RuntimeError, "You entered neither a path nor a batch. Game over :("
  end
  #the batch isn't closed. let's help a brother out
  if (ind = path_to_batch.index(/\.closed/)) == nil then
    if arg.kind_of?(String) then
      new_batch = CnpBatchRequest.new
      new_batch.open_existing_batch(path_to_batch)
      new_batch.close_batch()
      path_to_batch = new_batch.get_batch_name
      # if we passed a path to an AU batch, then new_batch will be a new, empty batch and the batch we passed
      # will be in the AU batch variable. thus, we wanna grab that file name and remove the empty batch.
      if(new_batch.get_au_batch != nil) then
        File.remove(path_to_batch)
        path_to_batch = new_batch.get_au_batch.get_batch_name
      end 
    elsif arg.kind_of?(CnpBatchRequest) then
      arg.close_batch()
      path_to_batch = arg.get_batch_name
    elsif arg.kind_of?(CnpAUBatch) then
      arg.close_batch()
      path_to_batch = arg.get_batch_name 
    end
    ind = path_to_batch.index(/\.closed/)
  end
  transactions_in_batch = path_to_batch[ind+8..path_to_batch.length].to_i

  # if the cnp request would be too big, let's make another!
  if (@num_total_transactions + transactions_in_batch) > @MAX_NUM_TRANSACTIONS then
    finish_request
    initialize(@options)
    create_new_cnp_request
  else #otherwise, let's add it line by line to the request doc
   # @num_batch_requests += 1
    #how long we wnat to wait around for the FTP server to get us a response
    @RESPONSE_TIME_OUT += 90 + (transactions_in_batch * 0.25)
    #don't start looking until there could possibly be a response
    @POLL_DELAY += 30 +(transactions_in_batch  * 0.02)
    @num_total_transactions += transactions_in_batch
     # Don't add empty batches
   @num_batch_requests += 1 unless transactions_in_batch.eql?(0)
    File.open(@path_to_batches, 'a+') do |fo|
      File.foreach(path_to_batch) do |li|
        fo.puts li
      end
    end
    
    File.delete(path_to_batch)
  end
end

#create_new_cnp_request(path) ⇒ Object

Creates the necessary files for the CnpRequest at the path specified. path/request_(TIMESTAMP) will be the final XML markup and path/request_(TIMESTAMP) will hold intermediary XML markup Params:

path

A String containing the path to the folder on disc to write the files to



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/CnpRequest.rb', line 75

def create_new_cnp_request(path)
  ts = Time::now.to_i.to_s
  begin
    ts += Time::now.nsec.to_s
  rescue NoMethodError # ruby 1.8.7 fix
    ts += Time::now.usec.to_s
  end 
  
  if(File.file?(path)) then
    raise RuntimeError, "Entered a file not a path."
  end

  if(path[-1,1] != '/' and path[-1,1] != '\\') then
    path = path + File::SEPARATOR
  end

  if !File.directory?(path) then
    Dir.mkdir(path)
  end
  
  @path_to_request = path + REQUEST_FILE_PREFIX + ts
  @path_to_batches = @path_to_request + '_batches'

  if File.file?(@path_to_request) or File.file?(@path_to_batches) then
    create_new_cnp_request(path)
    return
  end

  File.open(@path_to_request, 'a+') do |file|
    file.write("")
  end
  File.open(@path_to_batches, 'a+') do |file|
    file.write("")
  end
end

#finish_requestObject

Called when you wish to finish adding batches to your request, this method rewrites the aggregate batch file to the final CnpRequest xml doc with the appropos CnpRequest tags.



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/CnpRequest.rb', line 444

def finish_request
  File.open(@path_to_request, 'w') do |f|
    #jam dat header in there
    f.puts(build_request_header())
    #read into the request file from the batches file
    File.foreach(@path_to_batches) do |li|
      f.puts li
    end
    #finally, let's poot in a header, for old time's sake
    f.puts '</cnpRequest>'
  end

  #rename the requests file
  File.rename(@path_to_request, @path_to_request + COMPLETE_FILE_SUFFIX)
  #we don't need the master batch file anymore
  File.delete(@path_to_batches)
end

#get_path_to_batchesObject



438
439
440
# File 'lib/CnpRequest.rb', line 438

def get_path_to_batches
  return @path_to_batches
end

#get_responses_from_server(args = {}) ⇒ Object

Grabs response files over SFTP from Cnp. Params:

args

An (optional) Hash containing values for the number of responses expected, the

path to the folder on disk to write the responses from the Cnp server to, the username and password with which to connect ot the sFTP server, and the URL to connect over sFTP. Values not provided in the hash will be populate automatically based on our best guess



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/CnpRequest.rb', line 343

def get_responses_from_server(args = {})
  use_encryption = get_config(SFTP_USE_ENCRYPTION_CONFIG_NAME, args)
  @responses_expected = args[:responses_expected] ||= @responses_expected
  response_path = args[:response_path] ||= (File.dirname(@path_to_batches) + '/' + RESPONSE_PATH_DIR)
  username = get_config(SFTP_USERNAME_CONFIG_NAME, args)
  password = get_config(SFTP_PASSWORD_CONFIG_NAME, args)
  url = get_config(SFTP_URL_CONFIG_NAME, args)

  if(username == nil or password == nil or url == nil) then
    raise ArgumentError, "You are not configured to use sFTP for batch processing. Please run /bin/Setup.rb again!"
  end
  response_path = prepare_for_sftp(response_path, use_encryption)
  if use_encryption
    response_path += ENCRYPTED_PATH_DIR
  end


  download_from_sftp(response_path, url, username, password)

  if use_encryption
    decrypt_response_files(response_path, args)
  end
end

#process_response(path_to_response, transaction_listener, batch_listener = nil) ⇒ Object

Params:

path_to_response

The path to a specific .asc file to process

transaction_listener

A listener to be applied to the hash of each transaction

(see DefaultCnpListener)

batch_listener

An (optional) listener to be applied to the hash of each batch.

Note that this will om-nom-nom quite a bit of memory



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/CnpRequest.rb', line 402

def process_response(path_to_response, transaction_listener, batch_listener = nil)
  reader = LibXML::XML::Reader.file(path_to_response)

  reader.read # read into the root node
  #if the response attribute is nil, we're dealing with an RFR and everything is a-okay
  if reader.get_attribute('response') != "0" and reader.get_attribute('response') != nil then
    raise RuntimeError,  "Error parsing Cnp Request: " + reader.get_attribute("message")
  end
  
  reader.read
  count = 0
  while true and count < 500001 do
    
    count += 1
    if(reader.node == nil) then
      return false
    end 
    
    case reader.node.name.to_s
    when "batchResponse"
      reader.read
    when "cnpResponse"
      return false
    when "text"
      reader.read
    else
      xml = reader.read_outer_xml
      duck = Crack::XML.parse(xml)
      duck[duck.keys[0]]["type"] = duck.keys[0]
      duck = duck[duck.keys[0]]
      transaction_listener.apply(duck)
      reader.next
    end
  end
end

#process_responses(args) ⇒ Object

Params:

args

A Hash containing arguments for the processing process. This hash MUST contain an entry

for a transaction listener (see DefaultCnpListener). It may also include a batch listener and a custom path where response files from the server are located (if it is not provided, we’ll guess the position)



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/CnpRequest.rb', line 371

def process_responses(args)

  #the transaction listener is required
  if(!args.has_key?(:transaction_listener)) then
    raise ArgumentError, "The arguments hash must contain an entry for transaction listener!"
  end
  
  transaction_listener = args[:transaction_listener]
  batch_listener = args[:batch_listener] ||= nil
  path_to_responses = args[:path_to_responses] ||= (File.dirname(@path_to_batches) + '/' + RESPONSE_PATH_DIR)
  delete_batch_files = args[:deleteBatchFiles] ||= get_config(:deleteBatchFiles, args)
  #deleteBatchFiles = get_config(:deleteBatchFiles, args)

  Dir.foreach(path_to_responses) do |filename|
    if ((filename =~ /#{RESPONSE_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}.asc#{RECEIVED_FILE_SUFFIX}\z/) != nil) then
      process_response(path_to_responses + filename, transaction_listener, batch_listener)
      File.rename(path_to_responses + filename, path_to_responses + filename + '.processed')
    end 
  end

  if delete_batch_files
    delete_files_in_path(path_to_responses, /#{RESPONSE_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}.asc#{RECEIVED_FILE_SUFFIX}.processed\z/)
  end
end

#send_to_cnp(path = (File.dirname(@path_to_batches)), options = {}) ⇒ Object

FTPs all previously unsent CnpRequests located in the folder denoted by path to the server Params:

path

A String containing the path to the folder on disc where CnpRequests are located.

This should be the same location where the CnpRequests were written to. If no path is explicitly provided, then we use the directory where the current working batches file is stored.

options

An (option) Hash containing the username, password, and URL to attempt to sFTP to.

If not provided, the values will be populated from the configuration file.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/CnpRequest.rb', line 250

def send_to_cnp(path = (File.dirname(@path_to_batches)), options = {})

  use_encryption = get_config(SFTP_USE_ENCRYPTION_CONFIG_NAME, options)
  username = get_config(SFTP_USERNAME_CONFIG_NAME, options)
  password = get_config(SFTP_PASSWORD_CONFIG_NAME, options)
  delete_batch_files = get_config(SFTP_DELETE_BATCH_FILES_CONFIG_NAME, options)
  url = get_config(SFTP_URL_CONFIG_NAME, options)



  if(username == nil or password == nil or url == nil) then
    raise ArgumentError, "You are not configured to use sFTP for batch processing. Please run /bin/Setup.rb again!"
  end
  path = path_to_requests = prepare_for_sftp(path, use_encryption)


  if use_encryption
    encrypted_path = path_to_requests + ENCRYPTED_PATH_DIR
    encrypt_request_files(path, encrypted_path, options)
    path_to_requests = encrypted_path
  end



  @responses_expected = upload_to_sftp(path_to_requests, url, username, password, use_encryption)

  if delete_batch_files
    delete_files_in_path(path_to_requests, /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}#{ENCRYPTED_FILE_SUFFIX}?#{SENT_FILE_SUFFIX}\z/)
    if use_encryption
      delete_files_in_path(path, /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}#{SENT_FILE_SUFFIX}\z/)
    end
  end
end

#send_to_cnp_stream(options = {}, path = (File.dirname(@path_to_batches))) ⇒ Object

Sends all previously unsent CnpRequests in the specified directory to the Cnp server by use of fast batch. All results will be written to disk as we get them. Note that use of fastbatch is strongly discouraged!



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/CnpRequest.rb', line 287

def send_to_cnp_stream(options = {}, path = (File.dirname(@path_to_batches)))
  url = get_config(:fast_url, options)
  port = get_config(:fast_port, options)

  
  if(url == nil or url == "") then
    raise ArgumentError, "A URL for fastbatch was not specified in the config file or passed options. Reconfigure and try again."
  end 
    
  if(port == "" or port == nil) then
    raise ArgumentError, "A port number for fastbatch was not specified in the config file or passed options. Reconfigure and try again."
  end        
  
  if(path[-1,1] != '/' && path[-1,1] != '\\') then
    path = path + File::SEPARATOR
  end
  
  if (!File.directory?(path + RESPONSE_PATH_DIR)) then
    Dir.mkdir(path + RESPONSE_PATH_DIR)
  end
      
  Dir.foreach(path) do |filename|
    if((filename =~ /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}\z/) != nil) then
      begin
        socket = TCPSocket.open(url,port.to_i)
        ssl_context = OpenSSL::SSL::SSLContext.new()
        ssl_context.ssl_version = :SSLv23
        ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
        ssl_socket.sync_close = true
        ssl_socket.connect
             
       rescue => e 
        raise "A connection couldn't be established. Are you sure you have the correct credentials? Exception: " + e.message
      end
        
        File.foreach(path + filename) do |li|
          ssl_socket.puts li
          
        end
        File.rename(path + filename, path + filename + SENT_FILE_SUFFIX)
        File.open(path + RESPONSE_PATH_DIR + (filename + '.asc' + RECEIVED_FILE_SUFFIX).gsub("request", "response"), 'a+') do |fo|
        while line = ssl_socket.gets
             fo.puts(line)
        end
       end
    end
  end    
end