activemerchant 1.44.1 → 1.45.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 (58) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -3
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG +48 -0
  5. data/CONTRIBUTORS +12 -0
  6. data/README.md +15 -5
  7. data/lib/active_merchant/billing.rb +2 -0
  8. data/lib/active_merchant/billing/apple_pay_payment_token.rb +22 -0
  9. data/lib/active_merchant/billing/gateway.rb +36 -4
  10. data/lib/active_merchant/billing/gateways/adyen.rb +6 -2
  11. data/lib/active_merchant/billing/gateways/authorize_net.rb +332 -255
  12. data/lib/active_merchant/billing/gateways/bank_frick.rb +225 -0
  13. data/lib/active_merchant/billing/gateways/bogus.rb +9 -9
  14. data/lib/active_merchant/billing/gateways/borgun.rb +0 -1
  15. data/lib/active_merchant/billing/gateways/braintree_blue.rb +8 -0
  16. data/lib/active_merchant/billing/gateways/cashnet.rb +17 -10
  17. data/lib/active_merchant/billing/gateways/checkout.rb +213 -0
  18. data/lib/active_merchant/billing/gateways/conekta.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/cyber_source.rb +1 -1
  20. data/lib/active_merchant/billing/gateways/elavon.rb +3 -3
  21. data/lib/active_merchant/billing/gateways/eway_rapid.rb +114 -13
  22. data/lib/active_merchant/billing/gateways/finansbank.rb +1 -1
  23. data/lib/active_merchant/billing/gateways/global_transport.rb +183 -0
  24. data/lib/active_merchant/billing/gateways/hps.rb +27 -20
  25. data/lib/active_merchant/billing/gateways/iats_payments.rb +68 -35
  26. data/lib/active_merchant/billing/gateways/litle.rb +36 -1
  27. data/lib/active_merchant/billing/gateways/merchant_one.rb +0 -1
  28. data/lib/active_merchant/billing/gateways/merchant_ware.rb +8 -4
  29. data/lib/active_merchant/billing/gateways/mercury.rb +17 -10
  30. data/lib/active_merchant/billing/gateways/moneris.rb +11 -6
  31. data/lib/active_merchant/billing/gateways/moneris_us.rb +126 -33
  32. data/lib/active_merchant/billing/gateways/money_movers.rb +0 -1
  33. data/lib/active_merchant/billing/gateways/net_registry.rb +6 -1
  34. data/lib/active_merchant/billing/gateways/network_merchants.rb +5 -5
  35. data/lib/active_merchant/billing/gateways/nmi.rb +241 -5
  36. data/lib/active_merchant/billing/gateways/openpay.rb +1 -0
  37. data/lib/active_merchant/billing/gateways/optimal_payment.rb +6 -1
  38. data/lib/active_merchant/billing/gateways/orbital.rb +6 -4
  39. data/lib/active_merchant/billing/gateways/pay_junction.rb +9 -5
  40. data/lib/active_merchant/billing/gateways/payex.rb +19 -9
  41. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +2 -2
  42. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
  43. data/lib/active_merchant/billing/gateways/payscout.rb +0 -2
  44. data/lib/active_merchant/billing/gateways/pin.rb +1 -1
  45. data/lib/active_merchant/billing/gateways/psigate.rb +1 -2
  46. data/lib/active_merchant/billing/gateways/redsys.rb +37 -40
  47. data/lib/active_merchant/billing/gateways/secure_pay.rb +181 -9
  48. data/lib/active_merchant/billing/gateways/stripe.rb +106 -31
  49. data/lib/active_merchant/billing/gateways/tns.rb +227 -0
  50. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +38 -10
  51. data/lib/active_merchant/billing/gateways/webpay.rb +14 -0
  52. data/lib/active_merchant/billing/payment_token.rb +21 -0
  53. data/lib/active_merchant/billing/response.rb +2 -1
  54. data/lib/active_merchant/country.rb +6 -1
  55. data/lib/active_merchant/version.rb +1 -1
  56. metadata +8 -3
  57. metadata.gz.sig +0 -0
  58. data/lib/active_merchant/billing/gateways/samurai.rb +0 -130
@@ -91,6 +91,7 @@ module ActiveMerchant #:nodoc:
91
91
  post[:method] = 'card'
92
92
  post[:description] = options[:description]
93
93
  post[:order_id] = options[:order_id]
94
+ post[:device_session_id] = options[:device_session_id]
94
95
  add_creditcard(post, creditcard, options)
95
96
  post
96
97
  end
@@ -96,7 +96,7 @@ module ActiveMerchant #:nodoc:
96
96
  else
97
97
  raise 'Unknown Action'
98
98
  end
99
- txnRequest = URI.encode(xml)
99
+ txnRequest = escape_uri(xml)
100
100
  response = parse(ssl_post(test? ? self.test_url : self.live_url, "txnMode=#{action}&txnRequest=#{txnRequest}"))
101
101
 
102
102
  Response.new(successful?(response), message_from(response), hash_from_xml(response),
@@ -107,6 +107,11 @@ module ActiveMerchant #:nodoc:
107
107
  )
108
108
  end
109
109
 
110
+ # The upstream is picky and so we can't use CGI.escape like we want to
111
+ def escape_uri(uri)
112
+ URI::DEFAULT_PARSER.escape(uri)
113
+ end
114
+
110
115
  def successful?(response)
111
116
  REXML::XPath.first(response, '//decision').text == 'ACCEPTED' rescue false
112
117
  end
@@ -189,7 +189,7 @@ module ActiveMerchant #:nodoc:
189
189
  add_creditcard(xml, creditcard, options[:currency])
190
190
  add_address(xml, creditcard, options)
191
191
  if @options[:customer_profiles]
192
- add_customer_data(xml, options)
192
+ add_customer_data(xml, creditcard, options)
193
193
  add_managed_billing(xml, options)
194
194
  end
195
195
  end
@@ -202,7 +202,7 @@ module ActiveMerchant #:nodoc:
202
202
  add_creditcard(xml, creditcard, options[:currency])
203
203
  add_address(xml, creditcard, options)
204
204
  if @options[:customer_profiles]
205
- add_customer_data(xml, options)
205
+ add_customer_data(xml, creditcard, options)
206
206
  add_managed_billing(xml, options)
207
207
  end
208
208
  end
@@ -294,12 +294,14 @@ module ActiveMerchant #:nodoc:
294
294
  authorization.split(';')
295
295
  end
296
296
 
297
- def add_customer_data(xml, options)
297
+ def add_customer_data(xml, creditcard, options)
298
298
  if options[:profile_txn]
299
299
  xml.tag! :CustomerRefNum, options[:customer_ref_num]
300
300
  else
301
301
  if options[:customer_ref_num]
302
- xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM
302
+ if creditcard
303
+ xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM
304
+ end
303
305
  xml.tag! :CustomerRefNum, options[:customer_ref_num]
304
306
  else
305
307
  xml.tag! :CustomerProfileFromOrderInd, AUTO_GENERATE
@@ -297,11 +297,15 @@ module ActiveMerchant #:nodoc:
297
297
 
298
298
  # add fields for credit card
299
299
  def add_creditcard(params, creditcard)
300
- params[:name] = creditcard.name
301
- params[:number] = creditcard.number
302
- params[:expiration_month] = creditcard.month
303
- params[:expiration_year] = creditcard.year
304
- params[:verification_number] = creditcard.verification_value if creditcard.verification_value?
300
+ if creditcard.respond_to?(:track_data) && creditcard.track_data.present?
301
+ params[:track] = creditcard.track_data
302
+ else
303
+ params[:name] = creditcard.name
304
+ params[:number] = creditcard.number
305
+ params[:expiration_month] = creditcard.month
306
+ params[:expiration_year] = creditcard.year
307
+ params[:verification_number] = creditcard.verification_value if creditcard.verification_value?
308
+ end
305
309
  end
306
310
 
307
311
  # add field for "instant" transaction, using previous transaction id
@@ -1,9 +1,15 @@
1
1
  require "nokogiri"
2
+
2
3
  module ActiveMerchant #:nodoc:
3
4
  module Billing #:nodoc:
4
5
  class PayexGateway < Gateway
5
- self.live_url = 'https://external.payex.com/'
6
- self.test_url = 'https://test-external.payex.com/'
6
+ class_attribute :live_external_url, :test_external_url, :live_confined_url, :test_confined_url
7
+
8
+ self.live_external_url = 'https://external.payex.com/'
9
+ self.test_external_url = 'https://test-external.payex.com/'
10
+
11
+ self.live_confined_url = 'https://confined.payex.com/'
12
+ self.test_confined_url = 'https://test-confined.payex.com/'
7
13
 
8
14
  self.money_format = :cents
9
15
  self.supported_countries = ['DK', 'FI', 'NO', 'SE']
@@ -12,9 +18,6 @@ module ActiveMerchant #:nodoc:
12
18
  self.display_name = 'Payex'
13
19
  self.default_currency = "EUR"
14
20
 
15
- # NOTE: the PurchaseCC uses a different url for test transactions
16
- TEST_CONFINED_URL = 'https://test-confined.payex.com/'
17
-
18
21
  TRANSACTION_STATUS = {
19
22
  sale: '0',
20
23
  initialize: '1',
@@ -206,8 +209,8 @@ module ActiveMerchant #:nodoc:
206
209
  orderRef: order_ref,
207
210
  transactionType: 1, # online payment
208
211
  cardNumber: payment_method.number,
209
- cardNumberExpireMonth: "%02d" % payment_method.month,
210
- cardNumberExpireYear: "%02d" % payment_method.year,
212
+ cardNumberExpireMonth: format(payment_method.month, :two_digits),
213
+ cardNumberExpireYear: format(payment_method.year, :two_digits),
211
214
  cardHolderName: payment_method.name,
212
215
  cardNumberCVC: payment_method.verification_value
213
216
  }
@@ -319,8 +322,15 @@ module ActiveMerchant #:nodoc:
319
322
  end
320
323
 
321
324
  def url_for(soap_action)
322
- base_url = test? ? (soap_action[:confined] ? TEST_CONFINED_URL : test_url) : live_url
323
- File.join(base_url, soap_action[:url])
325
+ File.join(base_url(soap_action), soap_action[:url])
326
+ end
327
+
328
+ def base_url(soap_action)
329
+ if soap_action[:confined]
330
+ test? ? test_confined_url : live_confined_url
331
+ else
332
+ test? ? test_external_url : live_external_url
333
+ end
324
334
  end
325
335
 
326
336
  # this will add a hash to the passed in properties as required by Payex requests
@@ -7,7 +7,7 @@ module ActiveMerchant #:nodoc:
7
7
  URLS = {
8
8
  :test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',
9
9
  :signature => 'https://api-3t.sandbox.paypal.com/2.0/' },
10
- :live => { :certificate => 'https://api-aa.paypal.com/2.0/',
10
+ :live => { :certificate => 'https://api.paypal.com/2.0/',
11
11
  :signature => 'https://api-3t.paypal.com/2.0/' }
12
12
  }
13
13
 
@@ -659,7 +659,7 @@ module ActiveMerchant #:nodoc:
659
659
  end
660
660
 
661
661
  def item_amount(amount, currency_code)
662
- if amount.to_i < 0 && non_fractional_currency?(currency_code)
662
+ if amount.to_i < 0 && Gateway.non_fractional_currency?(currency_code)
663
663
  amount(amount).to_f.floor
664
664
  else
665
665
  localized_amount(amount, currency_code)
@@ -56,6 +56,10 @@ module ActiveMerchant #:nodoc:
56
56
  'name' => shipping['ShippingOptionName']
57
57
  }
58
58
  end
59
+
60
+ def note
61
+ @params['note_text']
62
+ end
59
63
  end
60
64
  end
61
65
  end
@@ -9,8 +9,6 @@ module ActiveMerchant #:nodoc:
9
9
  self.homepage_url = 'http://www.payscout.com/'
10
10
  self.display_name = 'Payscout'
11
11
 
12
- self.ssl_version = 'SSLv3'
13
-
14
12
  def initialize(options = {})
15
13
  requires!(options, :username, :password)
16
14
  super
@@ -9,7 +9,7 @@ module ActiveMerchant #:nodoc:
9
9
  self.supported_countries = ['AU']
10
10
  self.supported_cardtypes = [:visa, :master, :american_express]
11
11
  self.homepage_url = 'http://www.pin.net.au/'
12
- self.display_name = 'Pin'
12
+ self.display_name = 'Pin Payments'
13
13
 
14
14
  def initialize(options = {})
15
15
  requires!(options, :api_key)
@@ -36,13 +36,12 @@ module ActiveMerchant #:nodoc:
36
36
  # )
37
37
  class PsigateGateway < Gateway
38
38
  self.test_url = 'https://dev.psigate.com:7989/Messenger/XMLMessenger'
39
- self.live_url = 'https://secure.psigate.com:7934/Messenger/XMLMessenger'
39
+ self.live_url = 'https://secure.psigate.com:17934/Messenger/XMLMessenger'
40
40
 
41
41
  self.supported_cardtypes = [:visa, :master, :american_express]
42
42
  self.supported_countries = ['CA']
43
43
  self.homepage_url = 'http://www.psigate.com/'
44
44
  self.display_name = 'Psigate'
45
- self.ssl_version = :SSLv3
46
45
 
47
46
  SUCCESS_MESSAGE = 'Success'
48
47
  FAILURE_MESSAGE = 'The transaction was declined'
@@ -9,30 +9,14 @@ module ActiveMerchant #:nodoc:
9
9
  # used by many banks in Spain and is particularly well supported by
10
10
  # Catalunya Caixa's ecommerce department.
11
11
  #
12
- # Standard ActiveMerchant methods are supported, with one notable exception:
13
- # :order_id must be provided and must conform to a very specific format.
12
+ # Redsys requires an order_id be provided with each transaction and it must
13
+ # follow a specific format. The rules are as follows:
14
14
  #
15
- # == Example use:
16
- #
17
- # gateway = ActiveMerchant::Billing::RedsysGateway.new(
18
- # :login => "091358382",
19
- # :secret_key => "qwertyasdf0123456789"
20
- # )
21
- #
22
- # # Run a purchase for 10 euros
23
- # response = gateway.purchase(1000, creditcard, :order_id => "123456")
24
- # puts reponse.success? # => true
25
- #
26
- # # Partially refund the purchase
27
- # response = gateway.refund(500, response.authorization)
28
- #
29
- # Redsys requires an order_id be provided with each transaction of a
30
- # specific format. The rules are as follows:
31
- #
32
- # * Minimum length: 4
33
- # * Maximum length: 12
34
15
  # * First 4 digits must be numerical
35
16
  # * Remaining 8 digits may be alphanumeric
17
+ # * Max length: 12
18
+ #
19
+ # If an invalid order_id is provided, we do our best to clean it up.
36
20
  #
37
21
  # Much of the code for this library is based on the active_merchant_sermepa
38
22
  # integration gateway which uses essentially the same API but with the
@@ -44,18 +28,13 @@ module ActiveMerchant #:nodoc:
44
28
  self.live_url = "https://sis.sermepa.es/sis/operaciones"
45
29
  self.test_url = "https://sis-t.sermepa.es:25443/sis/operaciones"
46
30
 
47
- # Sensible region specific defaults.
48
31
  self.supported_countries = ['ES']
49
32
  self.default_currency = 'EUR'
50
33
  self.money_format = :cents
51
34
 
52
35
  # Not all card types may be activated by the bank!
53
36
  self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club]
54
-
55
- # Homepage URL of the gateway for reference
56
37
  self.homepage_url = "http://www.redsys.es/"
57
-
58
- # What to call this gateway
59
38
  self.display_name = "Redsys"
60
39
 
61
40
  CURRENCY_CODES = {
@@ -75,6 +54,7 @@ module ActiveMerchant #:nodoc:
75
54
  "NZD" => '554',
76
55
  "PEN" => '604',
77
56
  "RUB" => '643',
57
+ "SGD" => '702',
78
58
  "USD" => '840',
79
59
  "UYU" => '858'
80
60
  }
@@ -94,14 +74,12 @@ module ActiveMerchant #:nodoc:
94
74
  # a card has been rejected. Syntax or general request errors
95
75
  # are not covered here.
96
76
  RESPONSE_TEXTS = {
97
- # Accepted Codes
98
77
  0 => "Transaction Approved",
99
78
  400 => "Cancellation Accepted",
100
79
  481 => "Cancellation Accepted",
101
80
  500 => "Reconciliation Accepted",
102
81
  900 => "Refund / Confirmation approved",
103
82
 
104
- # Declined error codes
105
83
  101 => "Card expired",
106
84
  102 => "Card blocked temporarily or under susciption of fraud",
107
85
  104 => "Transaction not permitted",
@@ -121,7 +99,6 @@ module ActiveMerchant #:nodoc:
121
99
  190 => "Refusal with no specific reason",
122
100
  191 => "Expiry date incorrect",
123
101
 
124
- # Declined, and suspected of fraud
125
102
  201 => "Card expired",
126
103
  202 => "Card blocked temporarily or under suscipition of fraud",
127
104
  204 => "Transaction not permitted",
@@ -131,13 +108,11 @@ module ActiveMerchant #:nodoc:
131
108
  280 => "CVV2/CVC2 Error",
132
109
  290 => "Declined with no specific reason",
133
110
 
134
- # More general codes for specific types of transaction
135
111
  480 => "Original transaction not located, or time-out exceeded",
136
112
  501 => "Original transaction not located, or time-out exceeded",
137
113
  502 => "Original transaction not located, or time-out exceeded",
138
114
  503 => "Original transaction not located, or time-out exceeded",
139
115
 
140
- # Declined transactions by the bank
141
116
  904 => "Merchant not registered at FUC",
142
117
  909 => "System error",
143
118
  912 => "Issuer not available",
@@ -192,6 +167,7 @@ module ActiveMerchant #:nodoc:
192
167
  add_amount(data, money, options)
193
168
  add_order(data, options[:order_id])
194
169
  add_creditcard(data, creditcard)
170
+ data[:description] = options[:description]
195
171
 
196
172
  commit data
197
173
  end
@@ -204,6 +180,7 @@ module ActiveMerchant #:nodoc:
204
180
  add_amount(data, money, options)
205
181
  add_order(data, options[:order_id])
206
182
  add_creditcard(data, creditcard)
183
+ data[:description] = options[:description]
207
184
 
208
185
  commit data
209
186
  end
@@ -214,6 +191,7 @@ module ActiveMerchant #:nodoc:
214
191
  add_amount(data, money, options)
215
192
  order_id, _, _ = split_authorization(authorization)
216
193
  add_order(data, order_id)
194
+ data[:description] = options[:description]
217
195
 
218
196
  commit data
219
197
  end
@@ -224,6 +202,7 @@ module ActiveMerchant #:nodoc:
224
202
  order_id, amount, currency = split_authorization(authorization)
225
203
  add_amount(data, amount, :currency => currency)
226
204
  add_order(data, order_id)
205
+ data[:description] = options[:description]
227
206
 
228
207
  commit data
229
208
  end
@@ -234,10 +213,18 @@ module ActiveMerchant #:nodoc:
234
213
  add_amount(data, money, options)
235
214
  order_id, _, _ = split_authorization(authorization)
236
215
  add_order(data, order_id)
216
+ data[:description] = options[:description]
237
217
 
238
218
  commit data
239
219
  end
240
220
 
221
+ def verify(creditcard, options = {})
222
+ MultiResponse.run(:use_first_response) do |r|
223
+ r.process { authorize(100, creditcard, options) }
224
+ r.process(:ignore_result) { void(r.authorization, options) }
225
+ end
226
+ end
227
+
241
228
  private
242
229
 
243
230
  def add_action(data, action)
@@ -250,8 +237,7 @@ module ActiveMerchant #:nodoc:
250
237
  end
251
238
 
252
239
  def add_order(data, order_id)
253
- raise ArgumentError.new("Invalid order_id format") unless(/^\d{4}[\da-zA-Z]{0,8}$/ =~ order_id)
254
- data[:order_id] = order_id
240
+ data[:order_id] = clean_order_id(order_id)
255
241
  end
256
242
 
257
243
  def url
@@ -300,13 +286,14 @@ module ActiveMerchant #:nodoc:
300
286
  xml.DATOSENTRADA do
301
287
  # Basic elements
302
288
  xml.DS_Version 0.1
303
- xml.DS_MERCHANT_CURRENCY data[:currency]
304
- xml.DS_MERCHANT_AMOUNT data[:amount]
305
- xml.DS_MERCHANT_ORDER data[:order_id]
306
- xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
307
- xml.DS_MERCHANT_TERMINAL @options[:terminal]
308
- xml.DS_MERCHANT_MERCHANTCODE @options[:login]
309
- xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data)
289
+ xml.DS_MERCHANT_CURRENCY data[:currency]
290
+ xml.DS_MERCHANT_AMOUNT data[:amount]
291
+ xml.DS_MERCHANT_ORDER data[:order_id]
292
+ xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
293
+ xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description]
294
+ xml.DS_MERCHANT_TERMINAL @options[:terminal]
295
+ xml.DS_MERCHANT_MERCHANTCODE @options[:login]
296
+ xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data)
310
297
 
311
298
  # Only when card is present
312
299
  if data[:card]
@@ -373,6 +360,7 @@ module ActiveMerchant #:nodoc:
373
360
 
374
361
  def currency_code(currency)
375
362
  return currency if currency =~ /^\d+$/
363
+ raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency]
376
364
  CURRENCY_CODES[currency]
377
365
  end
378
366
 
@@ -389,6 +377,15 @@ module ActiveMerchant #:nodoc:
389
377
  def is_success_response?(code)
390
378
  (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i)
391
379
  end
380
+
381
+ def clean_order_id(order_id)
382
+ cleansed = order_id.gsub(/[^\da-zA-Z]/, '')
383
+ if cleansed =~ /^\d{4}/
384
+ cleansed[0..12]
385
+ else
386
+ "%04d%s" % [rand(0..9999), cleansed[0...8]]
387
+ end
388
+ end
392
389
  end
393
390
  end
394
391
  end
@@ -2,24 +2,196 @@ require File.dirname(__FILE__) + '/authorize_net'
2
2
 
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
- class SecurePayGateway < AuthorizeNetGateway
5
+ class SecurePayGateway < Gateway
6
+ API_VERSION = '3.1'
7
+
6
8
  self.live_url = self.test_url = 'https://www.securepay.com/AuthSpayAdapter/process.aspx'
7
9
 
10
+ class_attribute :duplicate_window
11
+
12
+ APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4
13
+
14
+ RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT, AUTHORIZATION_CODE = 0, 2, 3, 4
15
+ AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE, CARDHOLDER_AUTH_CODE = 5, 6, 38, 39
16
+
17
+ self.default_currency = 'USD'
18
+
19
+ self.supported_countries = %w(US CA GB AU)
20
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
8
21
  self.homepage_url = 'http://www.securepay.com/'
9
22
  self.display_name = 'SecurePay'
10
- self.supported_countries = %w(US CA GB AU)
11
23
 
12
- # Limit support to purchase() for the time being
13
- # JRuby chokes here
14
- # undef_method :authorize, :capture, :void, :credit
24
+ CARD_CODE_ERRORS = %w( N S )
25
+ AVS_ERRORS = %w( A E N R W Z )
26
+ AVS_REASON_CODES = %w(27 45)
27
+ TRANSACTION_ALREADY_ACTIONED = %w(310 311)
15
28
 
16
- undef_method :authorize
17
- undef_method :capture
18
- undef_method :void
19
- undef_method :credit
29
+ def initialize(options = {})
30
+ requires!(options, :login, :password)
31
+ super
32
+ end
33
+
34
+ def purchase(money, paysource, options = {})
35
+ post = {}
36
+ add_currency_code(post, money, options)
37
+ add_invoice(post, options)
38
+ add_payment_source(post, paysource, options)
39
+ add_address(post, options)
40
+ add_customer_data(post, options)
41
+ add_duplicate_window(post)
42
+
43
+ commit('AUTH_CAPTURE', money, post)
44
+ end
20
45
 
21
46
  private
22
47
 
48
+ def commit(action, money, parameters)
49
+ parameters[:amount] = amount(money) unless action == 'VOID'
50
+
51
+ url = (test? ? self.test_url : self.live_url)
52
+ data = ssl_post(url, post_data(action, parameters))
53
+
54
+ response = parse(data)
55
+ response[:action] = action
56
+
57
+ message = message_from(response)
58
+
59
+ Response.new(success?(response), message, response,
60
+ :test => test?,
61
+ :authorization => response[:transaction_id],
62
+ :fraud_review => fraud_review?(response),
63
+ :avs_result => { :code => response[:avs_result_code] },
64
+ :cvv_result => response[:card_code]
65
+ )
66
+ end
67
+
68
+ def success?(response)
69
+ response[:response_code] == APPROVED && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code])
70
+ end
71
+
72
+ def fraud_review?(response)
73
+ response[:response_code] == FRAUD_REVIEW
74
+ end
75
+
76
+ def parse(body)
77
+ fields = split(body)
78
+
79
+ results = {
80
+ :response_code => fields[RESPONSE_CODE].to_i,
81
+ :response_reason_code => fields[RESPONSE_REASON_CODE],
82
+ :response_reason_text => fields[RESPONSE_REASON_TEXT],
83
+ :avs_result_code => fields[AVS_RESULT_CODE],
84
+ :transaction_id => fields[TRANSACTION_ID],
85
+ :card_code => fields[CARD_CODE_RESPONSE_CODE],
86
+ :authorization_code => fields[AUTHORIZATION_CODE],
87
+ :cardholder_authentication_code => fields[CARDHOLDER_AUTH_CODE]
88
+ }
89
+ results
90
+ end
91
+
92
+ def post_data(action, parameters = {})
93
+ post = {}
94
+
95
+ post[:version] = API_VERSION
96
+ post[:login] = @options[:login]
97
+ post[:tran_key] = @options[:password]
98
+ post[:relay_response] = "FALSE"
99
+ post[:type] = action
100
+ post[:delim_data] = "TRUE"
101
+ post[:delim_char] = ","
102
+ post[:encap_char] = "$"
103
+ post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant"
104
+
105
+ request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
106
+ request
107
+ end
108
+
109
+ def add_currency_code(post, money, options)
110
+ post[:currency_code] = options[:currency] || currency(money)
111
+ end
112
+
113
+ def add_invoice(post, options)
114
+ post[:invoice_num] = options[:order_id]
115
+ post[:description] = options[:description]
116
+ end
117
+
118
+ def add_creditcard(post, creditcard, options={})
119
+ post[:card_num] = creditcard.number
120
+ post[:card_code] = creditcard.verification_value if creditcard.verification_value?
121
+ post[:exp_date] = expdate(creditcard)
122
+ post[:first_name] = creditcard.first_name
123
+ post[:last_name] = creditcard.last_name
124
+ end
125
+
126
+ def add_payment_source(params, source, options={})
127
+ add_creditcard(params, source, options)
128
+ end
129
+
130
+ def add_customer_data(post, options)
131
+ if options.has_key? :email
132
+ post[:email] = options[:email]
133
+ post[:email_customer] = false
134
+ end
135
+
136
+ if options.has_key? :customer
137
+ post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil
138
+ end
139
+
140
+ if options.has_key? :ip
141
+ post[:customer_ip] = options[:ip]
142
+ end
143
+
144
+ if options.has_key? :cardholder_authentication_value
145
+ post[:cardholder_authentication_value] = options[:cardholder_authentication_value]
146
+ end
147
+
148
+ if options.has_key? :authentication_indicator
149
+ post[:authentication_indicator] = options[:authentication_indicator]
150
+ end
151
+ end
152
+
153
+ # x_duplicate_window won't be sent by default, because sending it changes the response.
154
+ # "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent."
155
+ # (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf
156
+ def add_duplicate_window(post)
157
+ post[:duplicate_window] = duplicate_window if duplicate_window
158
+ end
159
+
160
+ def add_address(post, options)
161
+ if address = options[:billing_address] || options[:address]
162
+ post[:address] = address[:address1].to_s
163
+ post[:company] = address[:company].to_s
164
+ post[:phone] = address[:phone].to_s
165
+ post[:zip] = address[:zip].to_s
166
+ post[:city] = address[:city].to_s
167
+ post[:country] = address[:country].to_s
168
+ post[:state] = address[:state].blank? ? 'n/a' : address[:state]
169
+ end
170
+
171
+ if address = options[:shipping_address]
172
+ post[:ship_to_first_name] = address[:first_name].to_s
173
+ post[:ship_to_last_name] = address[:last_name].to_s
174
+ post[:ship_to_address] = address[:address1].to_s
175
+ post[:ship_to_company] = address[:company].to_s
176
+ post[:ship_to_phone] = address[:phone].to_s
177
+ post[:ship_to_zip] = address[:zip].to_s
178
+ post[:ship_to_city] = address[:city].to_s
179
+ post[:ship_to_country] = address[:country].to_s
180
+ post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state]
181
+ end
182
+ end
183
+
184
+ def message_from(results)
185
+ if results[:response_code] == DECLINED
186
+ return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code])
187
+ if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code])
188
+ return AVSResult.messages[ results[:avs_result_code] ]
189
+ end
190
+ end
191
+
192
+ (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '')
193
+ end
194
+
23
195
  def split(response)
24
196
  response.split(',')
25
197
  end