activemerchant 1.44.1 → 1.45.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -3
- data.tar.gz.sig +0 -0
- data/CHANGELOG +48 -0
- data/CONTRIBUTORS +12 -0
- data/README.md +15 -5
- data/lib/active_merchant/billing.rb +2 -0
- data/lib/active_merchant/billing/apple_pay_payment_token.rb +22 -0
- data/lib/active_merchant/billing/gateway.rb +36 -4
- data/lib/active_merchant/billing/gateways/adyen.rb +6 -2
- data/lib/active_merchant/billing/gateways/authorize_net.rb +332 -255
- data/lib/active_merchant/billing/gateways/bank_frick.rb +225 -0
- data/lib/active_merchant/billing/gateways/bogus.rb +9 -9
- data/lib/active_merchant/billing/gateways/borgun.rb +0 -1
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +8 -0
- data/lib/active_merchant/billing/gateways/cashnet.rb +17 -10
- data/lib/active_merchant/billing/gateways/checkout.rb +213 -0
- data/lib/active_merchant/billing/gateways/conekta.rb +1 -1
- data/lib/active_merchant/billing/gateways/cyber_source.rb +1 -1
- data/lib/active_merchant/billing/gateways/elavon.rb +3 -3
- data/lib/active_merchant/billing/gateways/eway_rapid.rb +114 -13
- data/lib/active_merchant/billing/gateways/finansbank.rb +1 -1
- data/lib/active_merchant/billing/gateways/global_transport.rb +183 -0
- data/lib/active_merchant/billing/gateways/hps.rb +27 -20
- data/lib/active_merchant/billing/gateways/iats_payments.rb +68 -35
- data/lib/active_merchant/billing/gateways/litle.rb +36 -1
- data/lib/active_merchant/billing/gateways/merchant_one.rb +0 -1
- data/lib/active_merchant/billing/gateways/merchant_ware.rb +8 -4
- data/lib/active_merchant/billing/gateways/mercury.rb +17 -10
- data/lib/active_merchant/billing/gateways/moneris.rb +11 -6
- data/lib/active_merchant/billing/gateways/moneris_us.rb +126 -33
- data/lib/active_merchant/billing/gateways/money_movers.rb +0 -1
- data/lib/active_merchant/billing/gateways/net_registry.rb +6 -1
- data/lib/active_merchant/billing/gateways/network_merchants.rb +5 -5
- data/lib/active_merchant/billing/gateways/nmi.rb +241 -5
- data/lib/active_merchant/billing/gateways/openpay.rb +1 -0
- data/lib/active_merchant/billing/gateways/optimal_payment.rb +6 -1
- data/lib/active_merchant/billing/gateways/orbital.rb +6 -4
- data/lib/active_merchant/billing/gateways/pay_junction.rb +9 -5
- data/lib/active_merchant/billing/gateways/payex.rb +19 -9
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +2 -2
- data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
- data/lib/active_merchant/billing/gateways/payscout.rb +0 -2
- data/lib/active_merchant/billing/gateways/pin.rb +1 -1
- data/lib/active_merchant/billing/gateways/psigate.rb +1 -2
- data/lib/active_merchant/billing/gateways/redsys.rb +37 -40
- data/lib/active_merchant/billing/gateways/secure_pay.rb +181 -9
- data/lib/active_merchant/billing/gateways/stripe.rb +106 -31
- data/lib/active_merchant/billing/gateways/tns.rb +227 -0
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +38 -10
- data/lib/active_merchant/billing/gateways/webpay.rb +14 -0
- data/lib/active_merchant/billing/payment_token.rb +21 -0
- data/lib/active_merchant/billing/response.rb +2 -1
- data/lib/active_merchant/country.rb +6 -1
- data/lib/active_merchant/version.rb +1 -1
- metadata +8 -3
- metadata.gz.sig +0 -0
- data/lib/active_merchant/billing/gateways/samurai.rb +0 -130
@@ -96,7 +96,7 @@ module ActiveMerchant #:nodoc:
|
|
96
96
|
else
|
97
97
|
raise 'Unknown Action'
|
98
98
|
end
|
99
|
-
txnRequest =
|
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
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
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
|
-
|
6
|
-
|
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:
|
210
|
-
cardNumberExpireYear:
|
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
|
323
|
-
|
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
|
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)
|
@@ -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:
|
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
|
-
#
|
13
|
-
#
|
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
|
-
|
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
|
304
|
-
xml.DS_MERCHANT_AMOUNT
|
305
|
-
xml.DS_MERCHANT_ORDER
|
306
|
-
xml.DS_MERCHANT_TRANSACTIONTYPE
|
307
|
-
xml.
|
308
|
-
xml.
|
309
|
-
xml.
|
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 <
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|