CnpOnline 12.3.0 → 12.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +12 -0
  3. data/Rakefile +1 -1
  4. data/lib/CnpBatchRequest.rb +17 -8
  5. data/lib/CnpOnline.rb +2 -0
  6. data/lib/CnpOnlineRequest.rb +30 -2
  7. data/lib/CnpRequest.rb +24 -4
  8. data/lib/CnpTransaction.rb +70 -7
  9. data/lib/CommManager.rb +185 -0
  10. data/lib/Communications.rb +7 -3
  11. data/lib/EnvironmentVariables.rb +8 -0
  12. data/lib/RequestTarget.rb +10 -0
  13. data/lib/XMLFields.rb +148 -11
  14. data/out/production/cnp-sdk-for-ruby/CnpBatchRequest.rb +787 -0
  15. data/out/production/cnp-sdk-for-ruby/CnpListeners.rb +265 -0
  16. data/out/production/cnp-sdk-for-ruby/CnpOnline.rb +65 -0
  17. data/out/production/cnp-sdk-for-ruby/CnpOnlineRequest.rb +370 -0
  18. data/out/production/cnp-sdk-for-ruby/CnpRequest.rb +670 -0
  19. data/out/production/cnp-sdk-for-ruby/CnpTransaction.rb +831 -0
  20. data/out/production/cnp-sdk-for-ruby/CnpXmlMapper.rb +71 -0
  21. data/out/production/cnp-sdk-for-ruby/CommManager.rb +185 -0
  22. data/out/production/cnp-sdk-for-ruby/Communications.rb +90 -0
  23. data/out/production/cnp-sdk-for-ruby/Configuration.rb +75 -0
  24. data/out/production/cnp-sdk-for-ruby/EnvironmentVariables.rb +33 -0
  25. data/out/production/cnp-sdk-for-ruby/RequestTarget.rb +10 -0
  26. data/out/production/cnp-sdk-for-ruby/Setup.rb +147 -0
  27. data/out/production/cnp-sdk-for-ruby/XMLFields.rb +2638 -0
  28. data/out/production/cnp-sdk-for-ruby/cacert.pem +3331 -0
  29. data/out/production/cnp-sdk-for-ruby/sample_batch_driver.rb +123 -0
  30. data/out/production/cnp-sdk-for-ruby/sample_driver.rb +49 -0
  31. data/out/test/cnp-sdk-for-ruby/certification/certTest1_base.rb +948 -0
  32. data/out/test/cnp-sdk-for-ruby/certification/certTest2_authenhanced.rb +577 -0
  33. data/out/test/cnp-sdk-for-ruby/certification/certTest3_authreversal.rb +190 -0
  34. data/out/test/cnp-sdk-for-ruby/certification/certTest4_echeck.rb +288 -0
  35. data/out/test/cnp-sdk-for-ruby/certification/certTest5_token.rb +209 -0
  36. data/out/test/cnp-sdk-for-ruby/certification/certTest_batchAll.rb +610 -0
  37. data/out/test/cnp-sdk-for-ruby/certification/ts_all.rb +33 -0
  38. data/out/test/cnp-sdk-for-ruby/functional/test_activate.rb +133 -0
  39. data/out/test/cnp-sdk-for-ruby/functional/test_activateReversal.rb +73 -0
  40. data/out/test/cnp-sdk-for-ruby/functional/test_auth.rb +478 -0
  41. data/out/test/cnp-sdk-for-ruby/functional/test_authReversal.rb +74 -0
  42. data/out/test/cnp-sdk-for-ruby/functional/test_balanceInquiry.rb +107 -0
  43. data/out/test/cnp-sdk-for-ruby/functional/test_batch.rb +264 -0
  44. data/out/test/cnp-sdk-for-ruby/functional/test_batchStream.rb +154 -0
  45. data/out/test/cnp-sdk-for-ruby/functional/test_cancelSubscription.rb +57 -0
  46. data/out/test/cnp-sdk-for-ruby/functional/test_capture.rb +147 -0
  47. data/out/test/cnp-sdk-for-ruby/functional/test_captureGivenAuth.rb +352 -0
  48. data/out/test/cnp-sdk-for-ruby/functional/test_commManager.rb +302 -0
  49. data/out/test/cnp-sdk-for-ruby/functional/test_configuration.rb +89 -0
  50. data/out/test/cnp-sdk-for-ruby/functional/test_createPlan.rb +87 -0
  51. data/out/test/cnp-sdk-for-ruby/functional/test_credit.rb +222 -0
  52. data/out/test/cnp-sdk-for-ruby/functional/test_ctx.rb +124 -0
  53. data/out/test/cnp-sdk-for-ruby/functional/test_deactivate.rb +105 -0
  54. data/out/test/cnp-sdk-for-ruby/functional/test_deactivateReversal.rb +71 -0
  55. data/out/test/cnp-sdk-for-ruby/functional/test_depositReversal.rb +70 -0
  56. data/out/test/cnp-sdk-for-ruby/functional/test_echeckCredit.rb +179 -0
  57. data/out/test/cnp-sdk-for-ruby/functional/test_echeckRedeposit.rb +117 -0
  58. data/out/test/cnp-sdk-for-ruby/functional/test_echeckSale.rb +255 -0
  59. data/out/test/cnp-sdk-for-ruby/functional/test_echeckVerification.rb +160 -0
  60. data/out/test/cnp-sdk-for-ruby/functional/test_echeckVoid.rb +42 -0
  61. data/out/test/cnp-sdk-for-ruby/functional/test_fastAccessFunding.rb +177 -0
  62. data/out/test/cnp-sdk-for-ruby/functional/test_forceCapture.rb +272 -0
  63. data/out/test/cnp-sdk-for-ruby/functional/test_fraudCheck.rb +76 -0
  64. data/out/test/cnp-sdk-for-ruby/functional/test_giftCardAuthReversal.rb +72 -0
  65. data/out/test/cnp-sdk-for-ruby/functional/test_giftCardCapture.rb +72 -0
  66. data/out/test/cnp-sdk-for-ruby/functional/test_giftCardCredit.rb +69 -0
  67. data/out/test/cnp-sdk-for-ruby/functional/test_litle_requests.rb +355 -0
  68. data/out/test/cnp-sdk-for-ruby/functional/test_load.rb +108 -0
  69. data/out/test/cnp-sdk-for-ruby/functional/test_loadReversal.rb +71 -0
  70. data/out/test/cnp-sdk-for-ruby/functional/test_override.rb +68 -0
  71. data/out/test/cnp-sdk-for-ruby/functional/test_pgp_cnp_requests.rb +295 -0
  72. data/out/test/cnp-sdk-for-ruby/functional/test_queryTransaction.rb +154 -0
  73. data/out/test/cnp-sdk-for-ruby/functional/test_refundReversal.rb +71 -0
  74. data/out/test/cnp-sdk-for-ruby/functional/test_sale.rb +459 -0
  75. data/out/test/cnp-sdk-for-ruby/functional/test_token.rb +137 -0
  76. data/out/test/cnp-sdk-for-ruby/functional/test_translateToken.rb +203 -0
  77. data/out/test/cnp-sdk-for-ruby/functional/test_unload.rb +108 -0
  78. data/out/test/cnp-sdk-for-ruby/functional/test_unloadReversal.rb +71 -0
  79. data/out/test/cnp-sdk-for-ruby/functional/test_updateCardValidationNumOnToken.rb +44 -0
  80. data/out/test/cnp-sdk-for-ruby/functional/test_updatePlan.rb +61 -0
  81. data/out/test/cnp-sdk-for-ruby/functional/test_updateSubscription.rb +80 -0
  82. data/out/test/cnp-sdk-for-ruby/functional/test_wallet.rb +74 -0
  83. data/out/test/cnp-sdk-for-ruby/functional/test_xmlfields.rb +429 -0
  84. data/out/test/cnp-sdk-for-ruby/functional/ts_all.rb +69 -0
  85. data/out/test/cnp-sdk-for-ruby/unit/test_LitleAUBatch.rb +244 -0
  86. data/out/test/cnp-sdk-for-ruby/unit/test_LitleBatchRequest.rb +791 -0
  87. data/out/test/cnp-sdk-for-ruby/unit/test_LitleOnlineRequest.rb +253 -0
  88. data/out/test/cnp-sdk-for-ruby/unit/test_LitleRequest.rb +317 -0
  89. data/out/test/cnp-sdk-for-ruby/unit/test_LitleTransaction.rb +426 -0
  90. data/out/test/cnp-sdk-for-ruby/unit/test_LitleXmlMapper.rb +139 -0
  91. data/out/test/cnp-sdk-for-ruby/unit/test_activate.rb +92 -0
  92. data/out/test/cnp-sdk-for-ruby/unit/test_activateReversal.rb +56 -0
  93. data/out/test/cnp-sdk-for-ruby/unit/test_auth.rb +463 -0
  94. data/out/test/cnp-sdk-for-ruby/unit/test_authReversal.rb +82 -0
  95. data/out/test/cnp-sdk-for-ruby/unit/test_balanceInquiry.rb +52 -0
  96. data/out/test/cnp-sdk-for-ruby/unit/test_cancelSubscription.rb +43 -0
  97. data/out/test/cnp-sdk-for-ruby/unit/test_capture.rb +99 -0
  98. data/out/test/cnp-sdk-for-ruby/unit/test_captureGivenAuth.rb +234 -0
  99. data/out/test/cnp-sdk-for-ruby/unit/test_createPlan.rb +52 -0
  100. data/out/test/cnp-sdk-for-ruby/unit/test_credit.rb +355 -0
  101. data/out/test/cnp-sdk-for-ruby/unit/test_deactivate.rb +71 -0
  102. data/out/test/cnp-sdk-for-ruby/unit/test_deactivateReversal.rb +56 -0
  103. data/out/test/cnp-sdk-for-ruby/unit/test_depositReversal.rb +56 -0
  104. data/out/test/cnp-sdk-for-ruby/unit/test_echeckCredit.rb +103 -0
  105. data/out/test/cnp-sdk-for-ruby/unit/test_echeckRedeposit.rb +109 -0
  106. data/out/test/cnp-sdk-for-ruby/unit/test_echeckSale.rb +107 -0
  107. data/out/test/cnp-sdk-for-ruby/unit/test_echeckVerification.rb +74 -0
  108. data/out/test/cnp-sdk-for-ruby/unit/test_echeckVoid.rb +54 -0
  109. data/out/test/cnp-sdk-for-ruby/unit/test_forceCapture.rb +163 -0
  110. data/out/test/cnp-sdk-for-ruby/unit/test_fraudCheck.rb +45 -0
  111. data/out/test/cnp-sdk-for-ruby/unit/test_giftCardAuthReversal.rb +58 -0
  112. data/out/test/cnp-sdk-for-ruby/unit/test_giftCardCapture.rb +57 -0
  113. data/out/test/cnp-sdk-for-ruby/unit/test_giftCardCredit.rb +57 -0
  114. data/out/test/cnp-sdk-for-ruby/unit/test_load.rb +53 -0
  115. data/out/test/cnp-sdk-for-ruby/unit/test_loadReversal.rb +56 -0
  116. data/out/test/cnp-sdk-for-ruby/unit/test_pgp_CnpRequest.rb +139 -0
  117. data/out/test/cnp-sdk-for-ruby/unit/test_queryTransaction.rb +106 -0
  118. data/out/test/cnp-sdk-for-ruby/unit/test_refundReversal.rb +56 -0
  119. data/out/test/cnp-sdk-for-ruby/unit/test_sale.rb +551 -0
  120. data/out/test/cnp-sdk-for-ruby/unit/test_token.rb +159 -0
  121. data/out/test/cnp-sdk-for-ruby/unit/test_unload.rb +53 -0
  122. data/out/test/cnp-sdk-for-ruby/unit/test_unloadReversal.rb +56 -0
  123. data/out/test/cnp-sdk-for-ruby/unit/test_updateCardValidationNumOnToken.rb +80 -0
  124. data/out/test/cnp-sdk-for-ruby/unit/test_updatePlan.rb +45 -0
  125. data/out/test/cnp-sdk-for-ruby/unit/test_updateSubscription.rb +172 -0
  126. data/out/test/cnp-sdk-for-ruby/unit/test_wallet.rb +262 -0
  127. data/out/test/cnp-sdk-for-ruby/unit/test_xmlfields.rb +2930 -0
  128. data/out/test/cnp-sdk-for-ruby/unit/ts_unit.rb +67 -0
  129. data/test/certification/certTest2_authenhanced.rb +28 -27
  130. data/test/functional/test_batch.rb +106 -1
  131. data/test/functional/test_batchStream.rb +3 -2
  132. data/test/functional/test_captureGivenAuth.rb +24 -0
  133. data/test/functional/test_commManager.rb +302 -0
  134. data/test/functional/test_ctx.rb +124 -0
  135. data/test/functional/test_fastAccessFunding.rb +21 -0
  136. data/test/functional/test_forceCapture.rb +21 -0
  137. data/test/functional/test_xmlfields.rb +20 -1
  138. metadata +122 -4
@@ -0,0 +1,670 @@
1
+ =begin
2
+ Copyright (c) 2017 Vantiv eCommerce
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
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
+ OTHER DEALINGS IN THE SOFTWARE.
21
+ =end
22
+ require_relative 'Configuration'
23
+ require 'net/sftp'
24
+ require 'libxml'
25
+ require 'crack/xml'
26
+ require 'socket'
27
+ require 'iostreams'
28
+ require 'open3'
29
+
30
+ include Socket::Constants
31
+ #
32
+ # This class handles sending the Cnp Request (which is actually a series of batches!)
33
+ #
34
+
35
+ SFTP_USERNAME_CONFIG_NAME = :sftp_username
36
+ SFTP_PASSWORD_CONFIG_NAME = :sftp_password
37
+ SFTP_URL_CONFIG_NAME = :sftp_url
38
+ SFTP_USE_ENCRYPTION_CONFIG_NAME = :useEncryption
39
+ SFTP_DELETE_BATCH_FILES_CONFIG_NAME = :deleteBatchFiles
40
+
41
+ REQUEST_PATH_DIR = 'requests/'
42
+ REQUEST_FILE_PREFIX = 'request_'
43
+ ENCRYPTED_PATH_DIR = 'encrypted/'
44
+ ENCRYPTED_FILE_SUFFIX = '.encrypted'
45
+ RESPONSE_PATH_DIR = 'responses/'
46
+ RESPONSE_FILE_PREFIX = 'response_'
47
+
48
+ SENT_FILE_SUFFIX = '.sent'
49
+ RECEIVED_FILE_SUFFIX = '.received'
50
+ COMPLETE_FILE_SUFFIX = '.complete'
51
+
52
+ module CnpOnline
53
+ class CnpRequest
54
+ include XML::Mapping
55
+ def initialize(options = {})
56
+ #load configuration data
57
+ @config_hash = Configuration.new.config
58
+ @num_batch_requests = 0
59
+ @path_to_request = ""
60
+ @path_to_batches = ""
61
+ @num_total_transactions = 0
62
+ @MAX_NUM_TRANSACTIONS = 500000
63
+ @options = options
64
+ # current time out set to 2 mins
65
+ # this value is in seconds
66
+ @RESPONSE_TIME_OUT = 5200
67
+ @POLL_DELAY = 0
68
+ @responses_expected = 0
69
+ end
70
+
71
+ # Creates the necessary files for the CnpRequest at the path specified. path/request_(TIMESTAMP) will be
72
+ # the final XML markup and path/request_(TIMESTAMP) will hold intermediary XML markup
73
+ # Params:
74
+ # +path+:: A +String+ containing the path to the folder on disc to write the files to
75
+ def create_new_cnp_request(path)
76
+ ts = Time::now.to_i.to_s
77
+ begin
78
+ ts += Time::now.nsec.to_s
79
+ rescue NoMethodError # ruby 1.8.7 fix
80
+ ts += Time::now.usec.to_s
81
+ end
82
+
83
+ if(File.file?(path)) then
84
+ raise RuntimeError, "Entered a file not a path."
85
+ end
86
+
87
+ if(path[-1,1] != '/' and path[-1,1] != '\\') then
88
+ path = path + File::SEPARATOR
89
+ end
90
+
91
+ if !File.directory?(path) then
92
+ Dir.mkdir(path)
93
+ end
94
+
95
+ @path_to_request = path + REQUEST_FILE_PREFIX + ts
96
+ @path_to_batches = @path_to_request + '_batches'
97
+
98
+ if File.file?(@path_to_request) or File.file?(@path_to_batches) then
99
+ create_new_cnp_request(path)
100
+ return
101
+ end
102
+
103
+ File.open(@path_to_request, 'a+') do |file|
104
+ file.write("")
105
+ end
106
+ File.open(@path_to_batches, 'a+') do |file|
107
+ file.write("")
108
+ end
109
+ end
110
+
111
+ # Adds a batch to the CnpRequest. If the batch is open when passed, it will be closed prior to being added.
112
+ # Params:
113
+ # +arg+:: a +CnpBatchRequest+ containing the transactions you wish to send or a +String+ specifying the
114
+ # path to the batch file
115
+ def commit_batch(arg)
116
+ path_to_batch = ""
117
+ #they passed a batch
118
+ if arg.kind_of?(CnpBatchRequest) then
119
+ path_to_batch = arg.get_batch_name
120
+ if((au = arg.get_au_batch) != nil) then
121
+ # also commit the account updater batch
122
+ commit_batch(au)
123
+ end
124
+ elsif arg.kind_of?(CnpAUBatch) then
125
+ path_to_batch = arg.get_batch_name
126
+ elsif arg.kind_of?(String) then
127
+ path_to_batch = arg
128
+ else
129
+ raise RuntimeError, "You entered neither a path nor a batch. Game over :("
130
+ end
131
+ #the batch isn't closed. let's help a brother out
132
+ if (ind = path_to_batch.index(/\.closed/)) == nil then
133
+ if arg.kind_of?(String) then
134
+ new_batch = CnpBatchRequest.new
135
+ new_batch.open_existing_batch(path_to_batch)
136
+ new_batch.close_batch()
137
+ path_to_batch = new_batch.get_batch_name
138
+ # if we passed a path to an AU batch, then new_batch will be a new, empty batch and the batch we passed
139
+ # will be in the AU batch variable. thus, we wanna grab that file name and remove the empty batch.
140
+ if(new_batch.get_au_batch != nil) then
141
+ File.remove(path_to_batch)
142
+ path_to_batch = new_batch.get_au_batch.get_batch_name
143
+ end
144
+ elsif arg.kind_of?(CnpBatchRequest) then
145
+ arg.close_batch()
146
+ path_to_batch = arg.get_batch_name
147
+ elsif arg.kind_of?(CnpAUBatch) then
148
+ arg.close_batch()
149
+ path_to_batch = arg.get_batch_name
150
+ end
151
+ ind = path_to_batch.index(/\.closed/)
152
+ end
153
+ transactions_in_batch = path_to_batch[ind+8..path_to_batch.length].to_i
154
+
155
+ # if the cnp request would be too big, let's make another!
156
+ if (@num_total_transactions + transactions_in_batch) > @MAX_NUM_TRANSACTIONS then
157
+ finish_request
158
+ initialize(@options)
159
+ create_new_cnp_request
160
+ else #otherwise, let's add it line by line to the request doc
161
+ # @num_batch_requests += 1
162
+ #how long we wnat to wait around for the FTP server to get us a response
163
+ @RESPONSE_TIME_OUT += 90 + (transactions_in_batch * 0.25)
164
+ #don't start looking until there could possibly be a response
165
+ @POLL_DELAY += 30 +(transactions_in_batch * 0.02)
166
+ @num_total_transactions += transactions_in_batch
167
+ # Don't add empty batches
168
+ @num_batch_requests += 1 unless transactions_in_batch.eql?(0)
169
+ File.open(@path_to_batches, 'a+') do |fo|
170
+ File.foreach(path_to_batch) do |li|
171
+ fo.puts li
172
+ end
173
+ end
174
+
175
+ File.delete(path_to_batch)
176
+ end
177
+ end
178
+
179
+ # Adds an RFRRequest to the CnpRequest.
180
+ # params:
181
+ # +options+:: a required +Hash+ containing configuration info for the RFRRequest. If the RFRRequest is for a batch, then the
182
+ # cnpSessionId is required as a key/val pair. If the RFRRequest is for account updater, then merchantId and postDay are required
183
+ # as key/val pairs.
184
+ # +path+:: optional path to save the new cnp request containing the RFRRequest at
185
+ def add_rfr_request(options, path = (File.dirname(@path_to_batches)))
186
+
187
+ rfrrequest = CnpRFRRequest.new
188
+ if(options['cnpSessionId']) then
189
+ rfrrequest.cnpSessionId = options['cnpSessionId']
190
+ elsif(options['merchantId'] and options['postDay']) then
191
+ accountUpdate = AccountUpdateFileRequestData.new
192
+ accountUpdate.merchantId = options['merchantId']
193
+ accountUpdate.postDay = options['postDay']
194
+ rfrrequest.accountUpdateFileRequestData = accountUpdate
195
+ else
196
+ raise ArgumentError, "For an RFR Request, you must specify either a cnpSessionId for an RFRRequest for batch or a merchantId
197
+ and a postDay for an RFRRequest for account updater."
198
+ end
199
+
200
+ cnpRequest = CnpRequestForRFR.new
201
+ cnpRequest.rfrRequest = rfrrequest
202
+
203
+ authentication = Authentication.new
204
+ authentication.user = get_config(:user, options)
205
+ authentication.password = get_config(:password, options)
206
+
207
+ cnpRequest.authentication = authentication
208
+ cnpRequest.numBatchRequests = "0"
209
+
210
+ cnpRequest.version = '12.8'
211
+ cnpRequest.xmlns = "http://www.vantivcnp.com/schema"
212
+
213
+
214
+ xml = cnpRequest.save_to_xml.to_s
215
+
216
+ ts = Time::now.to_i.to_s
217
+ begin
218
+ ts += Time::now.nsec.to_s
219
+ rescue NoMethodError # ruby 1.8.7 fix
220
+ ts += Time::now.usec.to_s
221
+ end
222
+ if(File.file?(path)) then
223
+ raise RuntimeError, "Entered a file not a path."
224
+ end
225
+
226
+ if(path[-1,1] != '/' and path[-1,1] != '\\') then
227
+ path = path + File::SEPARATOR
228
+ end
229
+
230
+ if !File.directory?(path) then
231
+ Dir.mkdir(path)
232
+ end
233
+
234
+ path_to_request = path + REQUEST_FILE_PREFIX + ts
235
+
236
+ File.open(path_to_request, 'a+') do |file|
237
+ file.write xml
238
+ end
239
+ File.rename(path_to_request, path_to_request + COMPLETE_FILE_SUFFIX)
240
+ @RESPONSE_TIME_OUT += 90
241
+ end
242
+
243
+ # FTPs all previously unsent CnpRequests located in the folder denoted by path to the server
244
+ # Params:
245
+ # +path+:: A +String+ containing the path to the folder on disc where CnpRequests are located.
246
+ # This should be the same location where the CnpRequests were written to. If no path is explicitly
247
+ # provided, then we use the directory where the current working batches file is stored.
248
+ # +options+:: An (option) +Hash+ containing the username, password, and URL to attempt to sFTP to.
249
+ # If not provided, the values will be populated from the configuration file.
250
+ def send_to_cnp(path = (File.dirname(@path_to_batches)), options = {})
251
+
252
+ use_encryption = get_config(SFTP_USE_ENCRYPTION_CONFIG_NAME, options)
253
+ username = get_config(SFTP_USERNAME_CONFIG_NAME, options)
254
+ password = get_config(SFTP_PASSWORD_CONFIG_NAME, options)
255
+ delete_batch_files = get_config(SFTP_DELETE_BATCH_FILES_CONFIG_NAME, options)
256
+ url = get_config(SFTP_URL_CONFIG_NAME, options)
257
+
258
+ print (use_encryption)
259
+ print (username + "\n")
260
+ print (password + "\n")
261
+ print (url + "\n")
262
+
263
+ if(username == nil or password == nil or url == nil) then
264
+ raise ArgumentError, "You are not configured to use sFTP for batch processing. Please run /bin/Setup.rb again!"
265
+ end
266
+ path = path_to_requests = prepare_for_sftp(path, use_encryption)
267
+
268
+
269
+ if use_encryption
270
+ encrypted_path = path_to_requests + ENCRYPTED_PATH_DIR
271
+ encrypt_request_files(path, encrypted_path, options)
272
+ path_to_requests = encrypted_path
273
+ end
274
+
275
+ print (path_to_requests + "\n")
276
+
277
+ @responses_expected = upload_to_sftp(path_to_requests, url, username, password, use_encryption)
278
+
279
+ if delete_batch_files
280
+ delete_files_in_path(path_to_requests, /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}#{ENCRYPTED_FILE_SUFFIX}?#{SENT_FILE_SUFFIX}\z/)
281
+ if use_encryption
282
+ delete_files_in_path(path, /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}#{SENT_FILE_SUFFIX}\z/)
283
+ end
284
+ end
285
+ end
286
+
287
+ # Sends all previously unsent CnpRequests in the specified directory to the Cnp server
288
+ # by use of fast batch. All results will be written to disk as we get them. Note that use
289
+ # of fastbatch is strongly discouraged!
290
+ def send_to_cnp_stream(options = {}, path = (File.dirname(@path_to_batches)))
291
+ url = get_config(:fast_url, options)
292
+ port = get_config(:fast_port, options)
293
+
294
+
295
+ if(url == nil or url == "") then
296
+ raise ArgumentError, "A URL for fastbatch was not specified in the config file or passed options. Reconfigure and try again."
297
+ end
298
+
299
+ if(port == "" or port == nil) then
300
+ raise ArgumentError, "A port number for fastbatch was not specified in the config file or passed options. Reconfigure and try again."
301
+ end
302
+
303
+ if(path[-1,1] != '/' && path[-1,1] != '\\') then
304
+ path = path + File::SEPARATOR
305
+ end
306
+
307
+ if (!File.directory?(path + RESPONSE_PATH_DIR)) then
308
+ Dir.mkdir(path + RESPONSE_PATH_DIR)
309
+ end
310
+
311
+ Dir.foreach(path) do |filename|
312
+ if((filename =~ /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}\z/) != nil) then
313
+ begin
314
+ socket = TCPSocket.open(url,port.to_i)
315
+ ssl_context = OpenSSL::SSL::SSLContext.new()
316
+ ssl_context.ssl_version = :SSLv23
317
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
318
+ ssl_socket.sync_close = true
319
+ ssl_socket.connect
320
+
321
+ rescue => e
322
+ raise "A connection couldn't be established. Are you sure you have the correct credentials? Exception: " + e.message
323
+ end
324
+
325
+ File.foreach(path + filename) do |li|
326
+ ssl_socket.puts li
327
+
328
+ end
329
+ File.rename(path + filename, path + filename + SENT_FILE_SUFFIX)
330
+ File.open(path + RESPONSE_PATH_DIR + (filename + '.asc' + RECEIVED_FILE_SUFFIX).gsub("request", "response"), 'a+') do |fo|
331
+ while line = ssl_socket.gets
332
+ fo.puts(line)
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
338
+
339
+
340
+ # Grabs response files over SFTP from Cnp.
341
+ # Params:
342
+ # +args+:: An (optional) +Hash+ containing values for the number of responses expected, the
343
+ # path to the folder on disk to write the responses from the Cnp server to, the username and
344
+ # password with which to connect ot the sFTP server, and the URL to connect over sFTP. Values not
345
+ # provided in the hash will be populate automatically based on our best guess
346
+ def get_responses_from_server(args = {})
347
+ use_encryption = get_config(SFTP_USE_ENCRYPTION_CONFIG_NAME, args)
348
+ @responses_expected = args[:responses_expected] ||= @responses_expected
349
+ response_path = args[:response_path] ||= (File.dirname(@path_to_batches) + '/' + RESPONSE_PATH_DIR)
350
+ username = get_config(SFTP_USERNAME_CONFIG_NAME, args)
351
+ password = get_config(SFTP_PASSWORD_CONFIG_NAME, args)
352
+ url = get_config(SFTP_URL_CONFIG_NAME, args)
353
+
354
+ if(username == nil or password == nil or url == nil) then
355
+ raise ArgumentError, "You are not configured to use sFTP for batch processing. Please run /bin/Setup.rb again!"
356
+ end
357
+ response_path = prepare_for_sftp(response_path, use_encryption)
358
+ if use_encryption
359
+ response_path += ENCRYPTED_PATH_DIR
360
+ end
361
+
362
+ download_from_sftp(response_path, url, username, password)
363
+
364
+ if use_encryption
365
+ decrypt_response_files(response_path, args)
366
+ end
367
+ end
368
+
369
+ # Params:
370
+ # +args+:: A +Hash+ containing arguments for the processing process. This hash MUST contain an entry
371
+ # for a transaction listener (see +DefaultCnpListener+). It may also include a batch listener and a
372
+ # custom path where response files from the server are located (if it is not provided, we'll guess the position)
373
+ def process_responses(args)
374
+
375
+ #the transaction listener is required
376
+ if(!args.has_key?(:transaction_listener)) then
377
+ raise ArgumentError, "The arguments hash must contain an entry for transaction listener!"
378
+ end
379
+
380
+ transaction_listener = args[:transaction_listener]
381
+ batch_listener = args[:batch_listener] ||= nil
382
+ path_to_responses = args[:path_to_responses] ||= (File.dirname(@path_to_batches) + '/' + RESPONSE_PATH_DIR)
383
+ delete_batch_files = args[:deleteBatchFiles] ||= get_config(:deleteBatchFiles, args)
384
+ #deleteBatchFiles = get_config(:deleteBatchFiles, args)
385
+
386
+ Dir.foreach(path_to_responses) do |filename|
387
+ if ((filename =~ /#{RESPONSE_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}.asc#{RECEIVED_FILE_SUFFIX}\z/) != nil) then
388
+ process_response(path_to_responses + filename, transaction_listener, batch_listener)
389
+ File.rename(path_to_responses + filename, path_to_responses + filename + '.processed')
390
+ end
391
+ end
392
+
393
+ if delete_batch_files
394
+ delete_files_in_path(path_to_responses, /#{RESPONSE_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}.asc#{RECEIVED_FILE_SUFFIX}.processed\z/)
395
+ end
396
+ end
397
+
398
+ # Params:
399
+ # +path_to_response+:: The path to a specific .asc file to process
400
+ # +transaction_listener+:: A listener to be applied to the hash of each transaction
401
+ # (see +DefaultCnpListener+)
402
+ # +batch_listener+:: An (optional) listener to be applied to the hash of each batch.
403
+ # Note that this will om-nom-nom quite a bit of memory
404
+ def process_response(path_to_response, transaction_listener, batch_listener = nil)
405
+ reader = LibXML::XML::Reader.file(path_to_response)
406
+ print path_to_response
407
+ reader.read # read into the root node
408
+ #if the response attribute is nil, we're dealing with an RFR and everything is a-okay
409
+ if reader.get_attribute('response') != "0" and reader.get_attribute('response') != nil then
410
+ raise RuntimeError, "Error parsing Cnp Request: " + reader.get_attribute("message")
411
+ end
412
+
413
+ reader.read
414
+ count = 0
415
+ while true and count < 500001 do
416
+
417
+ count += 1
418
+ if(reader.node == nil) then
419
+ return false
420
+ end
421
+
422
+ case reader.node.name.to_s
423
+ when "batchResponse"
424
+ reader.read
425
+ when "cnpResponse"
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
+ return @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 CnpRequest xml doc with the appropos CnpRequest 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 '</cnpRequest>'
456
+ end
457
+
458
+ #rename the requests file
459
+ File.rename(@path_to_request, @path_to_request + COMPLETE_FILE_SUFFIX)
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
+ cnp_request = self
468
+
469
+ authentication = Authentication.new
470
+ authentication.user = get_config(:user, options)
471
+ authentication.password = get_config(:password, options)
472
+
473
+ cnp_request.authentication = authentication
474
+ cnp_request.version = '12.8'
475
+ cnp_request.xmlns = "http://www.vantivcnp.com/schema"
476
+ # cnp_request.id = options['sessionId'] #grab from options; okay if nil
477
+ cnp_request.numBatchRequests = @num_batch_requests
478
+
479
+ xml = cnp_request.save_to_xml.to_s
480
+ xml[/<\/cnpRequest>/]=''
481
+ return xml
482
+ end
483
+
484
+ def get_config(field, options)
485
+ if options[field.to_s] == nil and options[field] == nil then
486
+ return @config_hash[field.to_s]
487
+ elsif options[field.to_s] != nil then
488
+ return options[field.to_s]
489
+ else
490
+ return options[field]
491
+ end
492
+ end
493
+
494
+
495
+ def delete_files_in_path(path, pattern)
496
+ Dir.foreach(path) do |filename|
497
+ if((filename =~ pattern)) != nil then
498
+ File.delete(path + filename)
499
+ end
500
+ end
501
+ end
502
+
503
+ def prepare_for_sftp(path, use_encryption)
504
+ if(path[-1,1] != '/' && path[-1,1] != '\\') then
505
+ path = path + File::SEPARATOR
506
+ end
507
+
508
+ if(!File.directory?(path)) then
509
+ Dir.mkdir(path)
510
+ end
511
+
512
+ if use_encryption
513
+ encrypted_path = path + ENCRYPTED_PATH_DIR
514
+ if !File.directory?(encrypted_path)
515
+ Dir.mkdir(encrypted_path)
516
+ end
517
+ end
518
+ return path
519
+ end
520
+
521
+
522
+ def encrypt_request_files(path, encrypted_path, options)
523
+ Dir.foreach(path) do |filename|
524
+ if (filename =~ /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}\z/) != nil
525
+ cipher_filename = encrypted_path + filename + ENCRYPTED_FILE_SUFFIX
526
+ plain_filename = path + filename
527
+ encrypt_batch_file_request(cipher_filename, plain_filename, options)
528
+ end
529
+ end
530
+ end
531
+
532
+
533
+ def decrypt_response_files(response_path, args)
534
+ delete_batch_files = get_config(SFTP_DELETE_BATCH_FILES_CONFIG_NAME, args)
535
+
536
+ Dir.foreach(response_path) do |filename|
537
+ if (filename =~ /#{RESPONSE_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}#{ENCRYPTED_FILE_SUFFIX}.asc#{RECEIVED_FILE_SUFFIX}\z/) != nil
538
+ decrypt_batch_file_response(response_path + filename, args)
539
+ end
540
+ end
541
+ if delete_batch_files
542
+ delete_files_in_path(response_path, /#{RESPONSE_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}#{ENCRYPTED_FILE_SUFFIX}.asc#{RECEIVED_FILE_SUFFIX}\z/)
543
+ end
544
+ end
545
+
546
+
547
+ def upload_to_sftp(path_to_requests, url, username, password, use_encryption)
548
+ begin
549
+ responses_expected = 0
550
+ print "uploading attempt\n"
551
+ Net::SFTP.start(url, username, :password => password) do |sftp|
552
+ print "uploading attempt 2\n"
553
+ Dir.foreach(path_to_requests) do |filename|
554
+ if (filename =~ /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}((#{ENCRYPTED_FILE_SUFFIX})?)\z/) != nil
555
+ print "uploading attempt 3\n"
556
+ new_filename = filename + '.prg'
557
+ File.rename(path_to_requests + filename, path_to_requests + new_filename)
558
+ # upload the file
559
+ sftp.upload!(path_to_requests + new_filename, '/inbound/' + new_filename)
560
+ responses_expected += 1
561
+ # rename now that we're done
562
+ sftp.rename!('/inbound/'+ new_filename, '/inbound/' + new_filename.gsub('prg', 'asc'))
563
+ File.rename(path_to_requests + new_filename, path_to_requests + new_filename.gsub('prg','sent'))
564
+ if use_encryption
565
+ # rename the plain text file too
566
+ text_filename = (path_to_requests + filename).gsub(ENCRYPTED_PATH_DIR, '').gsub(ENCRYPTED_FILE_SUFFIX, '')
567
+ File.rename(text_filename, text_filename + SENT_FILE_SUFFIX)
568
+ end
569
+ end
570
+ end
571
+ end
572
+ print "uploading done\n"
573
+ return responses_expected
574
+ rescue Net::SSH::AuthenticationFailed
575
+ raise ArgumentError, "The sFTP credentials provided were incorrect. Try again!"
576
+ end
577
+ end
578
+
579
+ def download_from_sftp(response_path, url, username, password)
580
+ responses_grabbed = 0
581
+ begin
582
+ #wait until a response has a possibility of being there?
583
+ sleep(@POLL_DELAY)
584
+ time_begin = Time.now
585
+ Net::SFTP.start(url, username, :password => password) do |sftp|
586
+ while((Time.now - time_begin) < @RESPONSE_TIME_OUT && responses_grabbed < @responses_expected)
587
+ #sleep for 60 seconds, ¿no es bueno?
588
+ sleep(60)
589
+ sftp.dir.foreach('/outbound/') do |entry|
590
+ if (entry.name =~ /#{REQUEST_FILE_PREFIX}\d+#{COMPLETE_FILE_SUFFIX}((#{ENCRYPTED_FILE_SUFFIX})?).asc\z/) != nil then
591
+ response_filename = response_path + entry.name.gsub(REQUEST_FILE_PREFIX, RESPONSE_FILE_PREFIX) + RECEIVED_FILE_SUFFIX
592
+ sftp.download!('/outbound/' + entry.name, response_filename)
593
+ responses_grabbed += 1
594
+ 3.times{
595
+ begin
596
+ sftp.remove!('/outbound/' + entry.name)
597
+ break
598
+ rescue Net::SFTP::StatusException
599
+ #try, try, try again
600
+ puts "We couldn't remove it! Try again"
601
+ end
602
+ }
603
+ end
604
+ end
605
+ end
606
+ if responses_grabbed < @responses_expected then
607
+ raise RuntimeError, "We timed out in waiting for a response from the server. :("
608
+ end
609
+ end
610
+ rescue Net::SSH::AuthenticationFailed
611
+ raise ArgumentError, "The sFTP credentials provided were incorrect. Try again!"
612
+ end
613
+ end
614
+
615
+
616
+ # Encrypt the request file for a PGP enabled account
617
+ # +cipher_filename+:: Name of File that would contain encrypted batch
618
+ # +plain_filename+:: Name of File containing batch in XML markup
619
+ # +options+:: An (option) +Hash+ containing the public key to attempt to encrypt the file.
620
+ # If not provided, the values will be populated from the configuration file.
621
+ def encrypt_batch_file_request(cipher_filename, plain_filename, options)
622
+ pgpkeyID = get_config(:vantivPublicKeyID, options)
623
+ if pgpkeyID == ""
624
+ raise RuntimeError, "The public key to encrypt batch file requests is missing from the config"
625
+ end
626
+
627
+ IOStreams::Pgp::Writer.open(
628
+ cipher_filename,
629
+ recipient: pgpkeyID
630
+ ) do |output|
631
+ File.open(plain_filename, "r").readlines.each do |line|
632
+ output.puts(line)
633
+ end
634
+ end
635
+
636
+ rescue IOStreams::Pgp::Failure => e
637
+ raise ArgumentError, "Please check if you have entered correct vantivePublicKeyID to config and that " +
638
+ "vantiv's public key is added to your gpg keyring and is trusted. #{e.message}"
639
+ end
640
+
641
+
642
+ # Decrypt the encrypted batch response file
643
+ # +response_filename+:: Filename of encrypted batch response file
644
+ # The decrypted response would be placed in +response_filename+.gsub("encrypted", "")
645
+ # +args+:: An (arg) +Hash+ containing the passphrase to atempt to decrypt the file
646
+ # If not provided, the values will be populated from the configuration file.
647
+ def decrypt_batch_file_response(response_filename, args)
648
+ passphrase = get_config(:passphrase, args)
649
+ if passphrase == ""
650
+ raise RuntimeError, "The passphrase to decrypt the batch file responses is missing from the config"
651
+ end
652
+ decrypted_response_filename = response_filename.gsub(ENCRYPTED_PATH_DIR, '').gsub(ENCRYPTED_FILE_SUFFIX, "")
653
+
654
+ decrypted_file = File.open(decrypted_response_filename, "w")
655
+ IOStreams::Pgp::Reader.open(
656
+ response_filename,
657
+ passphrase: passphrase
658
+ ) do |stream|
659
+ while !stream.eof?
660
+ decrypted_file.puts(stream.readline)
661
+ #puts stream.readline()
662
+ end
663
+ end
664
+ decrypted_file.close
665
+ rescue IOStreams::Pgp::Failure => e
666
+ raise ArgumentError, "Please check if you have entered correct passphrase to config and that your " +
667
+ "merchant private key is added to your gpg keyring and is trusted. #{e.message}"
668
+ end
669
+ end
670
+ end