LitleOnline 8.16.0 → 8.17.0

Sign up to get free protection for your applications and to get access to all the features.
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