LitleOnline 8.16.0 → 8.17.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 (43) hide show
  1. data/CHANGELOG +7 -0
  2. data/DESCRIPTION +1 -1
  3. data/README.md +2 -2
  4. data/Rakefile +6 -3
  5. data/bin/Setup.rb +14 -0
  6. data/bin/sample_batch_driver.rb +122 -0
  7. data/lib/LitleBatchRequest.rb +466 -0
  8. data/lib/LitleListeners.rb +142 -0
  9. data/lib/LitleOnline.rb +4 -0
  10. data/lib/LitleOnlineRequest.rb +25 -144
  11. data/lib/LitleRequest.rb +513 -0
  12. data/lib/LitleTransaction.rb +295 -0
  13. data/lib/XMLFields.rb +114 -4
  14. data/test/certification/certTest_batchAll.rb +337 -0
  15. data/test/certification/ts_all.rb +2 -0
  16. data/test/functional/test_batch.rb +164 -0
  17. data/test/functional/test_credit.rb +0 -18
  18. data/test/functional/test_litle_requests.rb +355 -0
  19. data/test/functional/test_updateCardValidationNumOnToken.rb +1 -1
  20. data/test/functional/test_xmlfields.rb +0 -19
  21. data/test/functional/ts_all.rb +2 -0
  22. data/test/unit/test_LitleAUBatch.rb +217 -0
  23. data/test/unit/test_LitleBatchRequest.rb +599 -0
  24. data/test/unit/test_LitleOnlineRequest.rb +9 -57
  25. data/test/unit/test_LitleRequest.rb +320 -0
  26. data/test/unit/test_LitleTransaction.rb +398 -0
  27. data/test/unit/test_auth.rb +25 -2
  28. data/test/unit/test_authReversal.rb +28 -5
  29. data/test/unit/test_capture.rb +24 -1
  30. data/test/unit/test_captureGivenAuth.rb +23 -1
  31. data/test/unit/test_credit.rb +92 -19
  32. data/test/unit/test_echeckCredit.rb +1 -1
  33. data/test/unit/test_echeckRedeposit.rb +1 -1
  34. data/test/unit/test_echeckSale.rb +1 -1
  35. data/test/unit/test_echeckVerification.rb +1 -1
  36. data/test/unit/test_echeckVoid.rb +1 -1
  37. data/test/unit/test_forceCapture.rb +24 -1
  38. data/test/unit/test_sale.rb +25 -2
  39. data/test/unit/test_token.rb +1 -1
  40. data/test/unit/test_updateCardValidationNumOnToken.rb +1 -1
  41. data/test/unit/test_xmlfields.rb +1 -1
  42. data/test/unit/ts_unit.rb +4 -0
  43. metadata +59 -14
data/CHANGELOG CHANGED
@@ -1,5 +1,12 @@
1
1
  = LitleOnline CHANGELOG
2
2
 
3
+ == Version 8.17.0 (June 13, 2013)
4
+ * Feature: Batch support - see https://gist.github.com/litleSDK/5687345, https://gist.github.com/litleSDK/5714803 and https://gist.github.com/litleSDK/5714847
5
+ * Feature: Add support for surcharging credit card transactions
6
+ * Feature: Add support for specifying the terminalId on a pos transaction
7
+ * Feature: Add support for pos transactions on tied refunds
8
+ * Cleanup: Drop support for 1.8.6
9
+
3
10
  == Version 8.16.0 (March 29, 2013)
4
11
 
5
12
  * Feature: Merged pull request #7 from pierre to add external logging support
data/DESCRIPTION CHANGED
@@ -1,4 +1,4 @@
1
- Litle Online Ruby SDK created for version 8.16 of Litle XML online format, see the XSD schema for specific fields that are supported by this format.
1
+ Litle Online Ruby SDK created for version 8.17 of Litle XML format, see the XSD schema for specific fields that are supported by this format.
2
2
 
3
3
  This gem contains an application interface in the Ruby programming language created by Litle & Co.
4
4
 
data/README.md CHANGED
@@ -66,6 +66,6 @@ puts "Litle Transaction ID: "+ response.saleResponse.litleTxnId
66
66
  Message: Valid Format
67
67
  Litle Transaction ID: <your-numeric-litle-txn-id>
68
68
 
69
- More examples can be found here [Ruby Gists](https://gist.github.com/gists/search?q=Litle+Ruby+SDK&page=1)
69
+ More examples (including batch processing with sFTP) can be found here [Ruby Gists](https://gist.github.com/litleSDK)
70
70
 
71
- Please contact Lilte & Co. with any further questions. You can reach us at sdksupport@litle.com.
71
+ Please contact Litle & Co. with any further questions. You can reach us at sdksupport@litle.com.
data/Rakefile CHANGED
@@ -30,22 +30,25 @@ require 'rake/clean'
30
30
  spec = Gem::Specification.new do |s|
31
31
  FileUtils.rm_rf('pkg')
32
32
  s.name = "LitleOnline"
33
- s.summary = "Ruby SDK produced by Litle & Co. for online transaction processing using Litle XML format v8.16"
33
+ s.summary = "Ruby SDK produced by Litle & Co. for transaction processing using Litle XML format v8.17"
34
34
  s.description = File.read(File.join(File.dirname(__FILE__), 'DESCRIPTION'))
35
35
  s.requirements =
36
36
  [ 'Contact sdksupport@litle.com for more information' ]
37
- s.version = "8.16.0"
37
+ s.version = "8.17.0"
38
38
  s.author = "Litle & Co"
39
39
  s.email = "sdksupport@litle.com"
40
40
  s.homepage = "http://www.litle.com/developers"
41
41
  s.platform = Gem::Platform::RUBY
42
- s.required_ruby_version = '>=1.8.6'
42
+ s.required_ruby_version = '>=1.8.7'
43
43
  s.files = Dir['**/**']
44
44
  s.executables = [ 'sample_driver.rb', 'Setup.rb' ]
45
45
  s.test_files = Dir["test/unit/ts_unit.rb"]
46
46
  s.has_rdoc = true
47
47
  s.add_dependency('xml-object')
48
48
  s.add_dependency('xml-mapping')
49
+ s.add_dependency('net-sftp')
50
+ s.add_dependency('libxml-ruby')
51
+ s.add_dependency('crack')
49
52
  s.add_development_dependency('mocha')
50
53
  end
51
54
 
data/bin/Setup.rb CHANGED
@@ -41,17 +41,31 @@ class Setup
41
41
  puts "Please input your password:"
42
42
  f.puts "password: " + gets
43
43
  puts "Please input your merchantId:"
44
+
44
45
  f.puts "currency_merchant_map:"
45
46
  f.puts " DEFAULT: " + gets
46
47
  f.puts "default_report_group: 'Default Report Group'"
48
+
47
49
  puts "Please choose Litle url from the following list (example: 'cert') or directly input another URL: \nsandbox => https://www.testlitle.com/sandbox/communicator/online \ncert => https://cert.litle.com/vap/communicator/online \nprecert => https://precert.litle.com/vap/communicator/online \nproduction1 => https://payments.litle.com/vap/communicator/online \nproduction2 => https://payments2.litle.com/vap/communicator/online"
48
50
  f.puts "url: " + Setup.choice(gets)
49
51
  puts "Please input the proxy address, if no proxy hit enter key: "
50
52
  f.puts "proxy_addr: " + gets
51
53
  puts "Please input the proxy port, if no proxy hit enter key: "
52
54
  f.puts "proxy_port: " + gets
55
+ puts "Please input your sFTP username for batch processing; if no sFTP, hit enter key: "
56
+ f.puts "sftp_username: " + gets
57
+ puts "Please input your sFTP password for batch processing; if no sFTP, hit enter key: "
58
+ f.puts "sftp_password: " + gets
59
+ puts "Please input your sFTP url for batch processing; if no sFTP, hit enter key: "
60
+ f.puts "sftp_url: " + gets
61
+ puts "Please input your url for fast batch processing; if no fast batch, hit enter key: "
62
+ f.puts "fast_url: " + gets
63
+ puts "Please input your port for fast batch processing; if no fast batch, hit enter key: "
64
+ f.puts "fast_port: " + gets
65
+
53
66
  f.puts "printxml: false"
54
67
  f.puts "timeout: 65"
68
+
55
69
  end
56
70
  end
57
71
 
@@ -0,0 +1,122 @@
1
+ =begin
2
+ Copyright (c) 2011 Litle & Co.
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
24
+ =end
25
+
26
+ #Sample Driver
27
+ require_relative '../lib/LitleOnline'
28
+
29
+ saleHash = {
30
+ 'reportGroup'=>'Planets',
31
+ 'id' => '006',
32
+ 'orderId'=>'12344',
33
+ 'amount'=>'6000',
34
+ 'orderSource'=>'ecommerce',
35
+ 'card'=>{
36
+ 'type'=>'VI',
37
+ 'number' =>'4100000000000001',
38
+ 'expDate' =>'1210'
39
+ }}
40
+
41
+ accountUpdateHash = {
42
+ 'reportGroup'=>'Planets',
43
+ 'id'=>'12345',
44
+ 'customerId'=>'0987',
45
+ 'orderId'=>'1234',
46
+ 'card'=>{
47
+ 'type'=>'VI',
48
+ 'number' =>'4100000000000001',
49
+ 'expDate' =>'1210'
50
+ }}
51
+
52
+ rfrHash = {
53
+ 'merchantId'=>101,
54
+ 'postDay'=>'2013-06-04'
55
+ }
56
+
57
+ path = Dir.pwd
58
+
59
+ request = LitleOnline::LitleRequest.new({'sessionId'=>'8675309'})
60
+
61
+ request.create_new_litle_request(path)
62
+ puts "Created new LitleRequest at location: " + path
63
+ start = Time::now
64
+ #create five batches, each with 10 sales
65
+ #5.times{
66
+ batch = LitleOnline::LitleBatchRequest.new
67
+ batch.create_new_batch(path)
68
+
69
+ #add the same sale ten times
70
+ #10.times{
71
+ batch.sale(saleHash)
72
+ #}
73
+ batch.account_update(accountUpdateHash)
74
+
75
+ #close the batch, indicating we plan to add no more transactions
76
+ batch.close_batch()
77
+ #add the batch to the LitleRequest
78
+ request.commit_batch(batch)
79
+ #}
80
+
81
+ request.add_rfr_request(rfrHash)
82
+
83
+
84
+
85
+ # puts "Finished adding batches to LitleRequest at " + request.get_path_to_batches
86
+ #finish the Litle Request, indicating we plan to add no more batches
87
+ request.finish_request
88
+ puts "Generated final XML markup of the LitleRequest"
89
+
90
+ #send the batch files at the given directory over sFTP
91
+ request.send_to_litle
92
+ puts "Dropped off the XML of the LitleRequest over FTP"
93
+ #grab the expected number of responses from the sFTP server and save them to the given path
94
+ request.get_responses_from_server()
95
+ puts "Received the LitleRequest responses from the server"
96
+ #process the responses from the server with a listener which applies the given block
97
+ start = Time::now
98
+ request.process_responses({:transaction_listener => LitleOnline::DefaultLitleListener.new do |transaction|
99
+ type = transaction["type"]
100
+ #if we're dealing with a saleResponse (check the Litle XML Reference Guide!)
101
+ if(type == "saleResponse") then
102
+ #grab an attribute of the parent of the response
103
+ puts "Report Group: " + transaction["reportGroup"]
104
+
105
+ #grab some child elements of the transaction
106
+ puts "Litle Txn Id: " + transaction["litleTxnId"]
107
+ puts "Order Id: " + transaction["orderId"]
108
+ puts "Response: " + transaction["response"]
109
+
110
+ #grab a child element of a child element of the transation
111
+ puts "AVS Result: " + transaction["fraudResult"]["avsResult"]
112
+ puts "Token Response Message: " + transaction["tokenResponse"]["tokenMessage"]
113
+ end
114
+
115
+ if(type == "RFRResponse") then
116
+ puts "RFR Response Code: " + transaction["response"]
117
+ puts "RFR Response Message: " + transaction["message"]
118
+ end
119
+ end})
120
+ stop = Time::now
121
+ puts "Total time: " + (stop - start).to_s
122
+
@@ -0,0 +1,466 @@
1
+ =begin
2
+ Copyright (c) 2011 Litle & Co.
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
24
+ =end
25
+ require_relative 'Configuration'
26
+
27
+ #
28
+ # This class creates a new batch to which Litle XML transactions are added.
29
+ # The batch is stored in the local file system until it is ready to be sent
30
+ # to Litle.
31
+ #
32
+ module LitleOnline
33
+
34
+ class LitleBatchRequest
35
+ include XML::Mapping
36
+ def initialize
37
+ #load configuration data
38
+ @config_hash = Configuration.new.config
39
+
40
+ @txn_counts = { :id=>nil,
41
+ :merchantId=>nil,
42
+ :auth=>{ :numAuths=>0, :authAmount=>0 },
43
+ :sale=>{ :numSales=>0, :saleAmount=>0 },
44
+ :credit=>{ :numCredits=>0, :creditAmount=>0 },
45
+ :numTokenRegistrations=>0,
46
+ :captureGivenAuth=>{ :numCaptureGivenAuths=>0, :captureGivenAuthAmount=>0 },
47
+ :forceCapture=>{ :numForceCaptures=>0, :forceCaptureAmount=>0 },
48
+ :authReversal=>{ :numAuthReversals=>0, :authReversalAmount=>0 },
49
+ :capture=>{ :numCaptures=>0, :captureAmount=>0 },
50
+ :echeckVerification=>{ :numEcheckVerification=>0, :echeckVerificationAmount=>0 },
51
+ :echeckCredit=>{ :numEcheckCredit=>0, :echeckCreditAmount=>0 },
52
+ :numEcheckRedeposit=>0,
53
+ :echeckSale=>{ :numEcheckSales=>0, :echeckSalesAmount=>0 },
54
+ :numUpdateCardValidationNumOnTokens=>0,
55
+ :numAccountUpdates=>0,
56
+ :total=>0
57
+ }
58
+ @litle_txn = LitleTransaction.new
59
+ @path_to_batch = nil
60
+ @txn_file = nil
61
+ @MAX_TXNS_IN_BATCH = 100000
62
+ @au_batch = nil
63
+ end
64
+
65
+ def create_new_batch(path)
66
+ ts = Time::now.to_i.to_s
67
+ begin
68
+ ts += Time::now.nsec.to_s
69
+ rescue NoMethodError # ruby 1.8.7 fix
70
+ ts += Time::now.usec.to_s
71
+ end
72
+ if(File.file?(path)) then
73
+ raise ArgumentError, "Entered a file not a path."
74
+ end
75
+
76
+ if(path[-1,1] != '/' && path[-1,1] != '\\') then
77
+ path = path + File::SEPARATOR
78
+ end
79
+ if(!File.directory?(path)) then
80
+ Dir.mkdir(path)
81
+ end
82
+
83
+ @path_to_batch = path + 'batch_' + ts
84
+ @txn_file = @path_to_batch + '_txns'
85
+ if(File.file?(@path_to_batch)) then
86
+ create_new_batch(path)
87
+ return
88
+ end
89
+ File.open(@path_to_batch, 'a+') do |file|
90
+ file.write("")
91
+ end
92
+ File.open(@txn_file, 'a+') do |file|
93
+ file.write("")
94
+ end
95
+ end
96
+
97
+ def open_existing_batch(pathToBatchFile)
98
+ if(!File.file?(pathToBatchFile)) then
99
+ raise ArgumentError, "No batch file exists at the passed location!"
100
+ end
101
+
102
+ if((pathToBatchFile =~ /batch_\d+.closed-\d+\z/) != nil) then
103
+ raise ArgumentError, "The passed batch file is closed!"
104
+ end
105
+
106
+ @txn_file = pathToBatchFile + '_txns'
107
+ @path_to_batch = pathToBatchFile
108
+ temp_counts = File.open(@path_to_batch, "rb") { |f| Marshal.load(f) }
109
+ # woops, they opened an AU batch
110
+ if(temp_counts[:numAccountUpdates] != 0) then
111
+ au_batch = LitleAUBatch.new
112
+ au_batch.open_existing_batch(pathToBatchFile)
113
+ initialize()
114
+ create_new_batch(File.dirname(pathToBatchFile))
115
+ @au_batch = au_batch
116
+ elsif
117
+ @txn_counts = temp_counts
118
+ end
119
+ end
120
+
121
+ def close_batch(txn_location = @txn_file)
122
+ header = build_batch_header(@txn_counts)
123
+ File.rename(@path_to_batch, @path_to_batch + '.closed-' + @txn_counts[:total].to_s)
124
+ @path_to_batch = @path_to_batch + '.closed-' + @txn_counts[:total].to_s
125
+ File.open(@path_to_batch, 'w') do |fo|
126
+ fo.puts header
127
+ File.foreach(txn_location) do |li|
128
+ fo.puts li
129
+ end
130
+ fo.puts('</batchRequest>')
131
+ end
132
+ File.delete(txn_location)
133
+ if(@au_batch != nil) then
134
+ @au_batch.close_batch
135
+ end
136
+ end
137
+
138
+ def authorization(options)
139
+ transaction = @litle_txn.authorization(options)
140
+ @txn_counts[:auth][:numAuths] += 1
141
+ @txn_counts[:auth][:authAmount] += options['amount'].to_i
142
+
143
+ add_txn_to_batch(transaction, :authorization, options)
144
+ end
145
+
146
+ def sale(options)
147
+ transaction = @litle_txn.sale(options)
148
+ @txn_counts[:sale][:numSales] += 1
149
+ @txn_counts[:sale][:saleAmount] += options['amount'].to_i
150
+
151
+ add_txn_to_batch(transaction, :sale, options)
152
+ end
153
+
154
+ def credit(options)
155
+ transaction = @litle_txn.credit(options)
156
+ @txn_counts[:credit][:numCredits] += 1
157
+ @txn_counts[:credit][:creditAmount] += options['amount'].to_i
158
+
159
+ add_txn_to_batch(transaction, :credit, options)
160
+ end
161
+
162
+ def auth_reversal(options)
163
+ transaction = @litle_txn.auth_reversal(options)
164
+ @txn_counts[:authReversal][:numAuthReversals] += 1
165
+ @txn_counts[:authReversal][:authReversalAmount] += options['amount'].to_i
166
+
167
+ add_txn_to_batch(transaction, :authReversal, options)
168
+ end
169
+
170
+ def register_token_request(options)
171
+ transaction = @litle_txn.register_token_request(options)
172
+ @txn_counts[:numTokenRegistrations] += 1
173
+
174
+ add_txn_to_batch(transaction, :numTokenRegistrations, options)
175
+ end
176
+
177
+ def update_card_validation_num_on_token(options)
178
+ transaction = @litle_txn.update_card_validation_num_on_token(options)
179
+ @txn_counts[:numUpdateCardValidationNumOnTokens] += 1
180
+
181
+ add_txn_to_batch(transaction, :numUpdateCardValidationNumOnTokens, options)
182
+ end
183
+
184
+ def force_capture(options)
185
+ transaction = @litle_txn.force_capture(options)
186
+ @txn_counts[:forceCapture][:numForceCaptures] += 1
187
+ @txn_counts[:forceCapture][:forceCaptureAmount] += options['amount'].to_i
188
+
189
+ add_txn_to_batch(transaction, :forceCapture, options)
190
+ end
191
+
192
+ def capture(options)
193
+ transaction = @litle_txn.capture(options)
194
+ @txn_counts[:capture][:numCaptures] += 1
195
+ @txn_counts[:capture][:captureAmount] += options['amount'].to_i
196
+
197
+ add_txn_to_batch(transaction, :capture, options)
198
+ end
199
+
200
+ def capture_given_auth(options)
201
+ transaction = @litle_txn.capture_given_auth(options)
202
+ @txn_counts[:captureGivenAuth][:numCaptureGivenAuths] += 1
203
+ @txn_counts[:captureGivenAuth][:captureGivenAuthAmount] += options['amount'].to_i
204
+
205
+ add_txn_to_batch(transaction, :captureGivenAuth, options)
206
+ end
207
+
208
+ def echeck_verification(options)
209
+ transaction = @litle_txn.echeck_verification(options)
210
+ @txn_counts[:echeckVerification][:numEcheckVerification] += 1
211
+ @txn_counts[:echeckVerification][:echeckVerificationAmount] += options['amount'].to_i
212
+
213
+ add_txn_to_batch(transaction, :echeckVerification, options)
214
+ end
215
+
216
+ def echeck_credit(options)
217
+ transaction = @litle_txn.echeck_credit(options)
218
+ @txn_counts[:echeckCredit][:numEcheckCredit] += 1
219
+ @txn_counts[:echeckCredit][:echeckCreditAmount] += options['amount'].to_i
220
+
221
+ add_txn_to_batch(transaction, :echeckCredit, options)
222
+ end
223
+
224
+ def echeck_redeposit(options)
225
+ transaction = @litle_txn.echeck_redeposit(options)
226
+ @txn_counts[:numEcheckRedeposit] += 1
227
+
228
+ add_txn_to_batch(transaction, :echeckRedeposit, options)
229
+ end
230
+
231
+ def echeck_sale(options)
232
+ transaction = @litle_txn.echeck_sale(options)
233
+ @txn_counts[:echeckSale][:numEcheckSales] += 1
234
+ @txn_counts[:echeckSale][:echeckSalesAmount] += options['amount'].to_i
235
+
236
+ add_txn_to_batch(transaction, :echeckSale, options)
237
+ end
238
+
239
+ def account_update(options)
240
+
241
+ if(@au_batch == nil) then
242
+ @au_batch = LitleAUBatch.new
243
+ @au_batch.create_new_batch(File.dirname(@path_to_batch))
244
+ end
245
+ @au_batch.account_update(options)
246
+ end
247
+
248
+ def get_counts_and_amounts
249
+ return @txn_counts
250
+ end
251
+ def get_batch_name
252
+ return @path_to_batch
253
+ end
254
+ def get_au_batch
255
+ return @au_batch
256
+ end
257
+
258
+
259
+ private
260
+
261
+ def add_txn_to_batch(transaction, type, options)
262
+ @txn_counts[:total] += 1
263
+ xml = transaction.save_to_xml.to_s
264
+ File.open(@txn_file, 'a+') do |file|
265
+ file.write(xml)
266
+ end
267
+ # save counts and amounts to batch file
268
+ File.open(@path_to_batch, 'wb'){|f| Marshal.dump(@txn_counts, f)}
269
+ if(@txn_counts[:total] >= @MAX_TXNS_IN_BATCH) then
270
+ close_batch()
271
+ path = File.dirname(@path_to_batch)
272
+ initialize
273
+ create_new_batch(path)
274
+ end
275
+ end
276
+
277
+ def build_batch_header(options)
278
+ request = BatchRequest.new
279
+
280
+ request.numAuths = @txn_counts[:auth][:numAuths]
281
+ request.authAmount = @txn_counts[:auth][:authAmount]
282
+ request.numSales = @txn_counts[:sale][:numSales]
283
+ request.saleAmount = @txn_counts[:sale][:saleAmount]
284
+ request.numCredits = @txn_counts[:credit][:numCredits]
285
+ request.creditAmount = @txn_counts[:credit][:creditAmount]
286
+ request.numTokenRegistrations = @txn_counts[:numTokenRegistrations]
287
+ request.numCaptureGivenAuths = @txn_counts[:captureGivenAuth][:numCaptureGivenAuths]
288
+ request.captureGivenAuthAmount = @txn_counts[:captureGivenAuth][:captureGivenAuthAmount]
289
+ request.numForceCaptures = @txn_counts[:forceCapture][:numForceCaptures]
290
+ request.forceCaptureAmount = @txn_counts[:forceCapture][:forceCaptureAmount]
291
+ request.numAuthReversals = @txn_counts[:authReversal][:numAuthReversals]
292
+ request.authReversalAmount = @txn_counts[:authReversal][:authReversalAmount]
293
+ request.numCaptures = @txn_counts[:capture][:numCaptures]
294
+ request.captureAmount = @txn_counts[:capture][:captureAmount]
295
+ request.numEcheckSales = @txn_counts[:echeckSale][:numEcheckSales]
296
+ request.echeckSalesAmount = @txn_counts[:echeckSale][:echeckSalesAmount]
297
+ request.numEcheckRedeposit = @txn_counts[:numEcheckRedeposit]
298
+ request.numEcheckCredit = @txn_counts[:echeckCredit][:numEcheckCredit]
299
+ request.echeckCreditAmount = @txn_counts[:echeckCredit][:echeckCreditAmount]
300
+ request.numEcheckVerification = @txn_counts[:echeckVerification][:numEcheckVerification]
301
+ request.echeckVerificationAmount = @txn_counts[:echeckVerification][:echeckVerificationAmount]
302
+ request.numAccountUpdates = @txn_counts[:numAccountUpdates]
303
+ request.merchantId = get_merchant_id(options)
304
+ request.id = @txn_counts[:id]
305
+ request.numUpdateCardValidationNumOnTokens = @txn_counts[:numUpdateCardValidationNumOnTokens]
306
+
307
+ header = request.save_to_xml.to_s
308
+ header['/>']= '>'
309
+
310
+ return header
311
+ end
312
+
313
+ def get_config(field, options)
314
+ options[field.to_s] == nil ? @config_hash[field.to_s] : options[field.to_s]
315
+ end
316
+
317
+ def get_merchant_id(options)
318
+ options['merchantId'] || @config_hash['currency_merchant_map']['DEFAULT']
319
+ end
320
+ end
321
+
322
+ private
323
+
324
+ # IF YOU ARE A MERCHANT, DON'T LOOK HERE. IT'S SCARY!
325
+
326
+ class LitleAUBatch
327
+ include XML::Mapping
328
+ def initialize
329
+ #load configuration data
330
+ @config_hash = Configuration.new.config
331
+
332
+ @txn_counts = { :id=>nil,
333
+ :merchantId=>nil,
334
+ :numAccountUpdates=>0,
335
+ :total=>0
336
+ }
337
+ @litle_txn = LitleTransaction.new
338
+ @path_to_batch = nil
339
+ @txn_file = nil
340
+ @MAX_TXNS_IN_BATCH = 100000
341
+ end
342
+
343
+ def create_new_batch(path)
344
+ ts = Time::now.to_i.to_s
345
+ begin
346
+ ts += Time::now.nsec.to_s
347
+ rescue NoMethodError # ruby 1.8.7 fix
348
+ ts += Time::now.usec.to_s
349
+ end
350
+
351
+ if(File.file?(path)) then
352
+ raise ArgumentError, "Entered a file not a path."
353
+ end
354
+
355
+ if(path[-1,1] != '/' && path[-1,1] != '\\') then
356
+ path = path + File::SEPARATOR
357
+ end
358
+ if(!File.directory?(path)) then
359
+ Dir.mkdir(path)
360
+ end
361
+
362
+ @path_to_batch = path + 'batch_' + ts
363
+ @txn_file = @path_to_batch + '_txns'
364
+ if(File.file?(@path_to_batch)) then
365
+ create_new_batch(path)
366
+ return
367
+ end
368
+ File.open(@path_to_batch, 'a+') do |file|
369
+ file.write("")
370
+ end
371
+ File.open(@txn_file, 'a+') do |file|
372
+ file.write("")
373
+ end
374
+ end
375
+
376
+ def open_existing_batch(pathToBatchFile)
377
+ if(!File.file?(pathToBatchFile)) then
378
+ raise ArgumentError, "No batch file exists at the passed location!"
379
+ end
380
+
381
+ if((pathToBatchFile =~ /batch_\d+.closed-\d+\z/) != nil) then
382
+ raise ArgumentError, "The passed batch file is closed!"
383
+ end
384
+
385
+ @txn_file = pathToBatchFile + '_txns'
386
+ @path_to_batch = pathToBatchFile
387
+ temp_counts = File.open(@path_to_batch, "rb") { |f| Marshal.load(f) }
388
+ if(temp_counts.keys.size > 4) then
389
+ raise RuntimeException, "Tried to open an AU batch with a non-AU batch file"
390
+ end
391
+
392
+ @txn_counts[:id] = temp_counts[:id]
393
+ @txn_counts[:merchantId] = temp_counts[:merchantId]
394
+ @txn_counts[:numAccountUpdates] = temp_counts[:numAccountUpdates]
395
+ @txn_counts[:total] = temp_counts[:total]
396
+ end
397
+
398
+ def close_batch(txn_location = @txn_file)
399
+ header = build_batch_header(@txn_counts)
400
+
401
+ File.rename(@path_to_batch, @path_to_batch + '.closed-' + @txn_counts[:total].to_s)
402
+ @path_to_batch = @path_to_batch + '.closed-' + @txn_counts[:total].to_s
403
+ File.open(@path_to_batch, 'w') do |fo|
404
+ fo.puts header
405
+ File.foreach(txn_location) do |li|
406
+ fo.puts li
407
+ end
408
+ fo.puts('</batchRequest>')
409
+ end
410
+ File.delete(txn_location)
411
+ end
412
+
413
+ def account_update(options)
414
+ transaction = @litle_txn.account_update(options)
415
+ @txn_counts[:numAccountUpdates] += 1
416
+
417
+ add_txn_to_batch(transaction, :authorization, options)
418
+ end
419
+
420
+ def get_counts_and_amounts
421
+ return @txn_counts
422
+ end
423
+ def get_batch_name
424
+ return @path_to_batch
425
+ end
426
+
427
+ private
428
+
429
+ def add_txn_to_batch(transaction, type, options)
430
+ @txn_counts[:total] += 1
431
+ xml = transaction.save_to_xml.to_s
432
+ File.open(@txn_file, 'a+') do |file|
433
+ file.write(xml)
434
+ end
435
+ # save counts and amounts to batch file
436
+ File.open(@path_to_batch, 'wb'){|f| Marshal.dump(@txn_counts, f)}
437
+ if(@txn_counts[:total] >= @MAX_TXNS_IN_BATCH) then
438
+ close_batch()
439
+ path = File.dirname(@path_to_batch)
440
+ initialize
441
+ create_new_batch(path)
442
+ end
443
+ end
444
+
445
+ def build_batch_header(options)
446
+ request = BatchRequest.new
447
+
448
+ request.numAccountUpdates = @txn_counts[:numAccountUpdates]
449
+ request.merchantId = get_merchant_id(options)
450
+ request.id = @txn_counts[:id]
451
+
452
+ header = request.save_to_xml.to_s
453
+ header['/>']= '>'
454
+
455
+ return header
456
+ end
457
+
458
+ def get_config(field, options)
459
+ options[field.to_s] == nil ? @config_hash[field.to_s] : options[field.to_s]
460
+ end
461
+
462
+ def get_merchant_id(options)
463
+ options['merchantId'] || @config_hash['currency_merchant_map']['DEFAULT']
464
+ end
465
+ end
466
+ end