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.
- 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
|