activemerchant 1.0.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 (55) hide show
  1. data/CHANGELOG +40 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +93 -0
  4. data/lib/active_merchant.rb +59 -0
  5. data/lib/active_merchant/billing/base.rb +52 -0
  6. data/lib/active_merchant/billing/credit_card.rb +217 -0
  7. data/lib/active_merchant/billing/gateway.rb +100 -0
  8. data/lib/active_merchant/billing/gateways.rb +15 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +236 -0
  10. data/lib/active_merchant/billing/gateways/bogus.rb +92 -0
  11. data/lib/active_merchant/billing/gateways/eway.rb +235 -0
  12. data/lib/active_merchant/billing/gateways/linkpoint.rb +445 -0
  13. data/lib/active_merchant/billing/gateways/moneris.rb +205 -0
  14. data/lib/active_merchant/billing/gateways/payflow.rb +84 -0
  15. data/lib/active_merchant/billing/gateways/payflow/f73e89fd.0 +17 -0
  16. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +190 -0
  17. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +30 -0
  18. data/lib/active_merchant/billing/gateways/payflow_express.rb +123 -0
  19. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +9 -0
  20. data/lib/active_merchant/billing/gateways/payflow_uk.rb +17 -0
  21. data/lib/active_merchant/billing/gateways/paypal.rb +90 -0
  22. data/lib/active_merchant/billing/gateways/paypal/api_cert_chain.crt +35 -0
  23. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +208 -0
  24. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +30 -0
  25. data/lib/active_merchant/billing/gateways/paypal_express.rb +115 -0
  26. data/lib/active_merchant/billing/gateways/psigate.rb +265 -0
  27. data/lib/active_merchant/billing/gateways/trust_commerce.rb +330 -0
  28. data/lib/active_merchant/billing/gateways/usa_epay.rb +189 -0
  29. data/lib/active_merchant/billing/integrations.rb +12 -0
  30. data/lib/active_merchant/billing/integrations/action_view_helper.rb +65 -0
  31. data/lib/active_merchant/billing/integrations/bogus.rb +17 -0
  32. data/lib/active_merchant/billing/integrations/bogus/helper.rb +17 -0
  33. data/lib/active_merchant/billing/integrations/bogus/notification.rb +11 -0
  34. data/lib/active_merchant/billing/integrations/chronopay.rb +17 -0
  35. data/lib/active_merchant/billing/integrations/chronopay/helper.rb +81 -0
  36. data/lib/active_merchant/billing/integrations/chronopay/notification.rb +156 -0
  37. data/lib/active_merchant/billing/integrations/gestpay.rb +21 -0
  38. data/lib/active_merchant/billing/integrations/gestpay/common.rb +42 -0
  39. data/lib/active_merchant/billing/integrations/gestpay/helper.rb +72 -0
  40. data/lib/active_merchant/billing/integrations/gestpay/notification.rb +83 -0
  41. data/lib/active_merchant/billing/integrations/helper.rb +79 -0
  42. data/lib/active_merchant/billing/integrations/nochex.rb +21 -0
  43. data/lib/active_merchant/billing/integrations/nochex/helper.rb +68 -0
  44. data/lib/active_merchant/billing/integrations/nochex/notification.rb +101 -0
  45. data/lib/active_merchant/billing/integrations/notification.rb +52 -0
  46. data/lib/active_merchant/billing/integrations/paypal.rb +35 -0
  47. data/lib/active_merchant/billing/integrations/paypal/helper.rb +103 -0
  48. data/lib/active_merchant/billing/integrations/paypal/notification.rb +187 -0
  49. data/lib/active_merchant/billing/response.rb +28 -0
  50. data/lib/active_merchant/lib/country.rb +297 -0
  51. data/lib/active_merchant/lib/posts_data.rb +21 -0
  52. data/lib/active_merchant/lib/requires_parameters.rb +17 -0
  53. data/lib/active_merchant/lib/validateable.rb +76 -0
  54. data/lib/tasks/cia.rb +90 -0
  55. metadata +129 -0
@@ -0,0 +1,445 @@
1
+ # Portions of the LinkPoint Gateway by Ryan Heneise
2
+ #--
3
+ # Copyright (c) 2005 Tobias Luetke
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following 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 OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ require 'rexml/document'
26
+
27
+ module ActiveMerchant #:nodoc:
28
+ module Billing #:nodoc:
29
+
30
+ # Initialization Options
31
+ # :login Your store number
32
+ # :pem The text of your linkpoint PEM file. Note
33
+ # this is not the path to file, but its
34
+ # contents. If you are only using one PEM
35
+ # file on your site you can declare it
36
+ # globally and then you won't need to
37
+ # include this option
38
+ #
39
+ #
40
+ # A valid store number is required. Unfortunately, with LinkPoint
41
+ # YOU CAN'T JUST USE ANY OLD STORE NUMBER. Also, you can't just
42
+ # generate your own PEM file. You'll need to use a special PEM file
43
+ # provided by LinkPoint.
44
+ #
45
+ # Go to http://www.linkpoint.com/support/sup_teststore.asp to set up
46
+ # a test account and obtain your PEM file.
47
+ #
48
+ # Declaring PEM file Globally
49
+ # ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' )
50
+ #
51
+ #
52
+ # Valid Order Options
53
+ # :result =>
54
+ # LIVE Production mode
55
+ # GOOD Approved response in test mode
56
+ # DECLINE Declined response in test mode
57
+ # DUPLICATE Duplicate response in test mode
58
+ #
59
+ # :ponumber Order number
60
+ #
61
+ # :transactionorigin => Source of the transaction
62
+ # ECI Email or Internet
63
+ # MAIL Mail order
64
+ # MOTO Mail order/Telephone
65
+ # TELEPHONE Telephone
66
+ # RETAIL Face-to-face
67
+ #
68
+ # :ordertype =>
69
+ # SALE Real live sale
70
+ # PREAUTH Authorize only
71
+ # POSTAUTH Forced Ticket or Ticket Only transaction
72
+ # VOID
73
+ # CREDIT
74
+ # CALCSHIPPING For shipping charges calculations
75
+ # CALCTAX For sales tax calculations
76
+ #
77
+ # Recurring Options
78
+ # :action =>
79
+ # SUBMIT
80
+ # MODIFY
81
+ # CANCEL
82
+ #
83
+ # :installments Identifies how many recurring payments to charge the customer
84
+ # :startdate Date to begin charging the recurring payments. Format: YYYYMMDD or "immediate"
85
+ # :periodicity =>
86
+ # MONTHLY
87
+ # BIMONTHLY
88
+ # WEEKLY
89
+ # BIWEEKLY
90
+ # YEARLY
91
+ # DAILY
92
+ # :threshold Tells how many times to retry the transaction (if it fails) before contacting the merchant.
93
+ # :comments Uh... comments
94
+ #
95
+ #
96
+ # For reference:
97
+ #
98
+ # https://www.linkpointcentral.com/lpc/docs/Help/APIHelp/lpintguide.htm
99
+ #
100
+ # Entities = {
101
+ # :payment => [:subtotal, :tax, :vattax, :shipping, :chargetotal],
102
+ # :billing => [:name, :address1, :address2, :city, :state, :zip, :country, :email, :phone, :fax, :addrnum],
103
+ # :shipping => [:name, :address1, :address2, :city, :state, :zip, :country, :weight, :items, :carrier, :total],
104
+ # :creditcard => [:cardnumber, :cardexpmonth, :cardexpyear, :cvmvalue, :track],
105
+ # :telecheck => [:routing, :account, :checknumber, :bankname, :bankstate, :dl, :dlstate, :void, :accounttype, :ssn],
106
+ # :transactiondetails => [:transactionorigin, :oid, :ponumber, :taxexempt, :terminaltype, :ip, :reference_number, :recurring, :tdate],
107
+ # :periodic => [:action, :installments, :threshold, :startdate, :periodicity, :comments],
108
+ # :notes => [:comments, :referred]
109
+ # }
110
+ #
111
+ #
112
+ # IMPORTANT NOTICE:
113
+ #
114
+ # LinkPoint's Items entity is not yet supported in this module.
115
+ #
116
+ class LinkpointGateway < Gateway
117
+ attr_reader :response
118
+ attr_reader :options
119
+
120
+ # Your global PEM file. This will be assigned to you by linkpoint
121
+ #
122
+ # Example:
123
+ #
124
+ # ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' )
125
+ #
126
+ cattr_accessor :pem_file
127
+
128
+ TEST_URL = 'https://staging.linkpt.net:1129/'
129
+ LIVE_URL = 'https://secure.linkpt.net:1129/'
130
+
131
+ # @options = {
132
+ # :store_number => options[:login],
133
+ # :result => test? ? "GOOD" : "LIVE"
134
+ # }.update(options)
135
+ def initialize(options = {})
136
+ requires!(options, :login)
137
+
138
+ @options = {
139
+ :result => 'LIVE'
140
+ }.update(options)
141
+
142
+ @pem = @options[:pem] || LinkpointGateway.pem_file
143
+
144
+ raise ArgumentError, "You need to pass in your pem file using the :pem parameter or set it globally using ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) or similar" if @pem.nil?
145
+ end
146
+
147
+ # Send a purchase request with periodic options
148
+ # Recurring Options
149
+ # :action =>
150
+ # SUBMIT
151
+ # MODIFY
152
+ # CANCEL
153
+ #
154
+ # :installments Identifies how many recurring payments to charge the customer
155
+ # :startdate Date to begin charging the recurring payments. Format: YYYYMMDD or "immediate"
156
+ # :periodicity =>
157
+ # :monthly
158
+ # :bimonthly
159
+ # :weekly
160
+ # :biweekly
161
+ # :yearly
162
+ # :daily
163
+ # :threshold Tells how many times to retry the transaction (if it fails) before contacting the merchant.
164
+ # :comments Uh... comments
165
+ #
166
+ def recurring(money, creditcard, options={})
167
+ requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily], :installments, :order_id )
168
+
169
+ options.update(
170
+ :ordertype => "SALE",
171
+ :action => "SUBMIT",
172
+ :installments => options[:installments] || 12,
173
+ :startdate => options[:startdate] || "immediate",
174
+ :periodicity => options[:periodicity].to_s || "monthly",
175
+ :comments => options[:comments] || nil,
176
+ :threshold => options[:threshold] || 3
177
+ )
178
+ commit(money, creditcard, options)
179
+ end
180
+
181
+ # Buy the thing
182
+ def purchase(money, creditcard, options={})
183
+ requires!(options, :order_id)
184
+ options.update(
185
+ :ordertype => "SALE"
186
+ )
187
+ commit(money, creditcard, options)
188
+ end
189
+
190
+ #
191
+ # Authorize the transaction
192
+ #
193
+ # Reserves the funds on the customer's credit card, but does not charge the card.
194
+ #
195
+ def authorize(money, creditcard, options = {})
196
+ requires!(options, :order_id)
197
+ options.update(
198
+ :ordertype => "PREAUTH"
199
+ )
200
+ commit(money, creditcard, options)
201
+ end
202
+
203
+ #
204
+ # Post an authorization.
205
+ #
206
+ # Captures the funds from an authorized transaction.
207
+ # Order_id must be a valid order id from a prior authorized transaction.
208
+ #
209
+ def capture(money, authorization, options = {})
210
+ options.update(
211
+ :order_id => authorization,
212
+ :ordertype => "POSTAUTH"
213
+ )
214
+ commit(money, nil, options)
215
+ end
216
+
217
+ # Void a previous transaction
218
+ def void(identification, options = {})
219
+ options.update(
220
+ :order_id => identification,
221
+ :ordertype => "VOID"
222
+ )
223
+ commit(nil, nil, options)
224
+ end
225
+
226
+ #
227
+ # Refund an order
228
+ #
229
+ # identification must be a valid order id previously submitted by SALE
230
+ #
231
+ def credit(money, identification, options = {})
232
+ options.update(
233
+ :ordertype => "CREDIT",
234
+ :order_id => identification
235
+ )
236
+ commit(money, nil, options)
237
+ end
238
+
239
+ def self.supported_cardtypes
240
+ [:visa, :master, :discover, :american_express]
241
+ end
242
+
243
+ def test?
244
+ @options[:test] || Base.gateway_mode == :test
245
+ end
246
+
247
+ private
248
+
249
+ # Commit the transaction by posting the XML file to the LinkPoint server
250
+ def commit(money, creditcard, options = {})
251
+ parameters = parameters(money, creditcard, options)
252
+
253
+ if creditcard and result = test_result_from_cc_number(parameters[:creditcard][:cardnumber])
254
+ return result
255
+ end
256
+
257
+ data = ssl_post post_data(parameters)
258
+
259
+ @response = parse(data)
260
+
261
+ success = (@response[:approved] == "APPROVED")
262
+ message = response[:message]
263
+
264
+ Response.new(success, message, @response,
265
+ :test => test?,
266
+ :authorization => response[:ordernum]
267
+ )
268
+ end
269
+
270
+ # Build the XML file
271
+ def post_data(parameters = {})
272
+ xml = REXML::Document.new
273
+ order = xml.add_element("order")
274
+
275
+ # Merchant Info
276
+ merchantinfo = order.add_element("merchantinfo")
277
+ merchantinfo.add_element("configfile").text = @options[:login]
278
+
279
+ # Loop over the parameters hash to construct the XML string
280
+ for key, value in parameters
281
+ elem = order.add_element(key.to_s)
282
+ for k, v in parameters[key]
283
+ elem.add_element(k.to_s).text = parameters[key][k].to_s if parameters[key][k]
284
+ end
285
+ # Linkpoint doesn't understand empty elements:
286
+ order.delete(elem) if elem.size == 0
287
+ end
288
+
289
+ return xml.to_s
290
+ end
291
+
292
+ # Set up the parameters hash just once so we don't have to do it
293
+ # for every action.
294
+ def parameters(money, creditcard, options = {})
295
+
296
+ params = {
297
+ :payment => {
298
+ :subtotal => amount(options[:subtotal]),
299
+ :tax => amount(options[:tax]),
300
+ :vattax => amount(options[:vattax]),
301
+ :shipping => amount(options[:shipping]),
302
+ :chargetotal => amount(money)
303
+ },
304
+ :transactiondetails => {
305
+ :transactionorigin => options[:transactionorigin] || "ECI",
306
+ :oid => options[:order_id],
307
+ :ponumber => options[:ponumber],
308
+ :taxexempt => options[:taxexempt],
309
+ :terminaltype => options[:terminaltype],
310
+ :ip => options[:ip],
311
+ :reference_number => options[:reference_number],
312
+ :recurring => options[:recurring] || "NO", #DO NOT USE if you are using the periodic billing option.
313
+ :tdate => options[:tdate]
314
+ },
315
+ :orderoptions => {
316
+ :ordertype => options[:ordertype],
317
+ :result => @options[:result]
318
+ },
319
+ :periodic => {
320
+ :action => options[:action],
321
+ :installments => options[:installments],
322
+ :threshold => options[:threshold],
323
+ :startdate => options[:startdate],
324
+ :periodicity => options[:periodicity],
325
+ :comments => options[:comments]
326
+ },
327
+ :telecheck => {
328
+ :routing => options[:telecheck_routing],
329
+ :account => options[:telecheck_account],
330
+ :checknumber => options[:telecheck_checknumber],
331
+ :bankname => options[:telecheck_bankname],
332
+ :dl => options[:telecheck_dl],
333
+ :dlstate => options[:telecheck_dlstate],
334
+ :void => options[:telecheck_void],
335
+ :accounttype => options[:telecheck_accounttype],
336
+ :ssn => options[:telecheck_ssn],
337
+ }
338
+ }
339
+
340
+ if creditcard
341
+ params[:creditcard] = {
342
+ :cardnumber => creditcard.number,
343
+ :cardexpmonth => creditcard.month,
344
+ :cardexpyear => format_creditcard_expiry_year(creditcard.year),
345
+ :track => nil
346
+ }
347
+
348
+ if creditcard.verification_value?
349
+ params[:creditcard][:cvmvalue] = creditcard.verification_value
350
+ params[:creditcard][:cvmindicator] = 'provided'
351
+ else
352
+ params[:creditcard][:cvmindicator] = 'not_provided'
353
+ end
354
+ end
355
+
356
+ if billing_address = options[:billing_address] || options[:address]
357
+
358
+ params[:billing] = {}
359
+ params[:billing][:name] = billing_address[:name] || creditcard ? creditcard.name : nil
360
+ params[:billing][:address1] = billing_address[:address1] unless billing_address[:address1].blank?
361
+ params[:billing][:address2] = billing_address[:address2] unless billing_address[:address2].blank?
362
+ params[:billing][:city] = billing_address[:city] unless billing_address[:city].blank?
363
+ params[:billing][:state] = billing_address[:state] unless billing_address[:state].blank?
364
+ params[:billing][:zip] = billing_address[:zip] unless billing_address[:zip].blank?
365
+ params[:billing][:country] = billing_address[:country] unless billing_address[:country].blank?
366
+ params[:billing][:company] = billing_address[:company] unless billing_address[:company].blank?
367
+ params[:billing][:phone] = billing_address[:phone] unless billing_address[:phone].blank?
368
+ params[:billing][:email] = options[:email] unless options[:email].blank?
369
+ end
370
+
371
+ if shipping_address = options[:shipping_address] || billing_address
372
+
373
+ params[:shipping] = {}
374
+ params[:shipping][:name] = shipping_address[:name] || creditcard ? creditcard.name : nil
375
+ params[:shipping][:address1] = shipping_address[:address1] unless shipping_address[:address1].blank?
376
+ params[:shipping][:address2] = shipping_address[:address2] unless shipping_address[:address2].blank?
377
+ params[:shipping][:city] = shipping_address[:city] unless shipping_address[:city].blank?
378
+ params[:shipping][:state] = shipping_address[:state] unless shipping_address[:state].blank?
379
+ params[:shipping][:zip] = shipping_address[:zip] unless shipping_address[:zip].blank?
380
+ params[:shipping][:country] = shipping_address[:country] unless shipping_address[:country].blank?
381
+ end
382
+
383
+ return params
384
+ end
385
+
386
+ def parse(xml)
387
+
388
+ # For reference, a typical response...
389
+ # <r_csp></r_csp>
390
+ # <r_time></r_time>
391
+ # <r_ref></r_ref>
392
+ # <r_error></r_error>
393
+ # <r_ordernum></r_ordernum>
394
+ # <r_message>This is a test transaction and will not show up in the Reports</r_message>
395
+ # <r_code></r_code>
396
+ # <r_tdate>Thu Feb 2 15:40:21 2006</r_tdate>
397
+ # <r_score></r_score>
398
+ # <r_authresponse></r_authresponse>
399
+ # <r_approved>APPROVED</r_approved>
400
+ # <r_avs></r_avs>
401
+
402
+ response = {:message => "Global Error Receipt", :complete => false}
403
+
404
+ xml = REXML::Document.new("<response>#{xml}</response>")
405
+ xml.root.elements.each do |node|
406
+ response[node.name.downcase.sub(/^r_/, '').to_sym] = normalize(node.text)
407
+ end unless xml.root.nil?
408
+
409
+ response
410
+ end
411
+
412
+ # Redefine ssl_post to use our PEM file
413
+ def ssl_post(data)
414
+
415
+ raise "PEM file invalid or missing!" unless @pem =~ %r{RSA.*CERTIFICATE}m
416
+
417
+ uri = URI.parse(test? ? TEST_URL : LIVE_URL)
418
+
419
+ http = Net::HTTP.new(uri.host, uri.port)
420
+
421
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @ssl_strict
422
+ http.use_ssl = true
423
+ http.cert = OpenSSL::X509::Certificate.new(@pem)
424
+ http.key = OpenSSL::PKey::RSA.new(@pem)
425
+
426
+ http.post(uri.path, data).body
427
+ end
428
+
429
+ # Make a ruby type out of the response string
430
+ def normalize(field)
431
+ case field
432
+ when "true" then true
433
+ when "false" then false
434
+ when "" then nil
435
+ when "null" then nil
436
+ else field
437
+ end
438
+ end
439
+
440
+ def format_creditcard_expiry_year(year)
441
+ sprintf("%.4i", year)[-2..-1]
442
+ end
443
+ end
444
+ end
445
+ end
@@ -0,0 +1,205 @@
1
+ require 'rexml/document'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+
6
+ class MonerisGateway < Gateway
7
+ attr_reader :url
8
+ attr_reader :response
9
+ attr_reader :options
10
+
11
+ TEST_URL = 'https://esqa.moneris.com/gateway2/servlet/MpgRequest'
12
+ LIVE_URL = 'https://www3.moneris.com/gateway2/servlet/MpgRequest'
13
+
14
+ # login is your Store ID
15
+ # password is your API Token
16
+ def initialize(options = {})
17
+ requires!(options, :login, :password)
18
+
19
+ @options = {
20
+ :strict_ssl => true,
21
+ :crypt_type => 7
22
+ }.update(options)
23
+
24
+ @url = test? ? TEST_URL : LIVE_URL
25
+
26
+ super
27
+ end
28
+
29
+ def authorize(money, creditcard, options = {})
30
+ requires!(options, :order_id)
31
+
32
+ parameters = {
33
+ :order_id => options[:order_id],
34
+ :cust_id => options[:customer],
35
+ :amount => amount(money),
36
+ :pan => creditcard.number,
37
+ :expdate => expdate(creditcard),
38
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
39
+ }
40
+
41
+ commit('preauth', parameters)
42
+ end
43
+
44
+ # Pass in <tt>order_id</tt> and optionally a <tt>customer</tt> parameter
45
+ def purchase(money, creditcard, options = {})
46
+ requires!(options, :order_id)
47
+
48
+ parameters = {
49
+ :order_id => options[:order_id],
50
+ :cust_id => options[:customer],
51
+ :amount => amount(money),
52
+ :pan => creditcard.number,
53
+ :expdate => expdate(creditcard),
54
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
55
+ }
56
+
57
+ commit('purchase', parameters)
58
+ end
59
+
60
+ # Moneris requires both the order_id and the transaction number of
61
+ # the original authorization. To maintain the same interface as the other
62
+ # gateways the two numbers are concatenated together with an _ separator as
63
+ # the authorization number returned by authorization
64
+ def capture(money, authorization, options = {})
65
+ txn_number, order_id = authorization.split(';')
66
+
67
+ parameters = {
68
+ :txn_number => txn_number,
69
+ :order_id => order_id,
70
+ :comp_amount => amount(money),
71
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
72
+ }
73
+
74
+ commit('completion', parameters)
75
+ end
76
+
77
+ def void(authorization, options = {})
78
+ txn_number, order_id = authorization.split(';')
79
+
80
+ parameters = {
81
+ :txn_number => txn_number,
82
+ :order_id => order_id,
83
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
84
+ }
85
+
86
+ commit('purchasecorrection', parameters)
87
+ end
88
+
89
+ # We support visa and master card
90
+ def self.supported_cardtypes
91
+ [:visa, :master]
92
+ end
93
+
94
+ private
95
+
96
+ def expdate(creditcard)
97
+ year = sprintf("%.4i", creditcard.year)
98
+ month = sprintf("%.2i", creditcard.month)
99
+
100
+ "#{year[-2..-1]}#{month}"
101
+ end
102
+
103
+ def commit(action, parameters)
104
+ if result = test_result_from_cc_number(parameters[:pan])
105
+ return result
106
+ end
107
+
108
+ data = ssl_post @url, post_data(action, parameters)
109
+ @response = parse(data)
110
+
111
+ success = (response[:response_code] and response[:complete] and (0..49).include?(response[:response_code].to_i) )
112
+ message = message_form(response[:message])
113
+ authorization = "#{response[:trans_id]};#{response[:receipt_id]}" if response[:trans_id] && response[:receipt_id]
114
+
115
+ Response.new(success, message, @response,
116
+ :test => test?,
117
+ :authorization => authorization
118
+ )
119
+ end
120
+
121
+ # Parse moneris response xml into a convinient hash
122
+ def parse(xml)
123
+ # "<?xml version=\"1.0\"?><response><receipt>".
124
+ # "<ReceiptId>Global Error Receipt</ReceiptId>".
125
+ # "<ReferenceNum>null</ReferenceNum>
126
+ # <ResponseCode>null</ResponseCode>".
127
+ # "<ISO>null</ISO>
128
+ # <AuthCode>null</AuthCode>
129
+ # <TransTime>null</TransTime>".
130
+ # "<TransDate>null</TransDate>
131
+ # <TransType>null</TransType>
132
+ # <Complete>false</Complete>".
133
+ # "<Message>null</Message>
134
+ # <TransAmount>null</TransAmount>".
135
+ # "<CardType>null</CardType>".
136
+ # "<TransID>null</TransID>
137
+ # <TimedOut>null</TimedOut>".
138
+ # "</receipt></response>
139
+
140
+ response = {:message => "Global Error Receipt", :complete => false}
141
+
142
+ xml = REXML::Document.new(xml)
143
+
144
+ xml.elements.each('//receipt/*') do |node|
145
+
146
+ response[node.name.underscore.to_sym] = normalize(node.text)
147
+
148
+ end unless xml.root.nil?
149
+
150
+ response
151
+ end
152
+
153
+ def post_data(action, parameters = {})
154
+ xml = REXML::Document.new
155
+ root = xml.add_element("request")
156
+ root.add_element("store_id").text = options[:login]
157
+ root.add_element("api_token").text = options[:password]
158
+ transaction = root.add_element(action)
159
+
160
+ # Must add the elements in the correct order
161
+ actions[action].each do |key|
162
+ transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank?
163
+ end
164
+
165
+ xml.to_s
166
+ end
167
+
168
+ def message_form(message)
169
+ return 'Unspecified error' if message.blank?
170
+ message.gsub(/[^\w]/, ' ').split.join(" ").capitalize
171
+ end
172
+
173
+ # Make a ruby type out of the response string
174
+ def normalize(field)
175
+ case field
176
+ when "true" then true
177
+ when "false" then false
178
+ when "" then nil
179
+ when "null" then nil
180
+ else field
181
+ end
182
+ end
183
+
184
+ def actions
185
+ ACTIONS
186
+ end
187
+
188
+ ACTIONS = {
189
+ "purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
190
+ "preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
191
+ "command" => [:order_id],
192
+ "refund" => [:order_id, :amount, :txn_number, :crypt_type],
193
+ "indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
194
+ "completion" => [:order_id, :comp_amount, :txn_number, :crypt_type],
195
+ "purchasecorrection" => [:order_id, :txn_number, :crypt_type],
196
+ "cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav],
197
+ "cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
198
+ "transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
199
+ "Batchcloseall" => [],
200
+ "opentotals" => [:ecr_number],
201
+ "batchclose" => [:ecr_number],
202
+ }
203
+ end
204
+ end
205
+ end