activemerchant 1.45.0 → 1.46.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 +3 -1
- data.tar.gz.sig +0 -0
- data/CHANGELOG +25 -0
- data/CONTRIBUTORS +9 -0
- data/README.md +6 -20
- data/lib/active_merchant.rb +9 -50
- data/lib/active_merchant/billing.rb +1 -0
- data/lib/active_merchant/billing/gateway.rb +10 -1
- data/lib/active_merchant/billing/gateways.rb +6 -11
- data/lib/active_merchant/billing/gateways/authorize_net.rb +58 -47
- data/lib/active_merchant/billing/gateways/beanstream.rb +1 -1
- data/lib/active_merchant/billing/gateways/beanstream_interac.rb +8 -8
- data/lib/active_merchant/billing/gateways/braintree.rb +2 -2
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +69 -22
- data/lib/active_merchant/billing/gateways/braintree_orange.rb +2 -2
- data/lib/active_merchant/billing/gateways/checkout.rb +7 -2
- data/lib/active_merchant/billing/gateways/cyber_source.rb +25 -9
- data/lib/active_merchant/billing/gateways/elavon.rb +1 -1
- data/lib/active_merchant/billing/gateways/eway_rapid.rb +6 -3
- data/lib/active_merchant/billing/gateways/finansbank.rb +1 -1
- data/lib/active_merchant/billing/gateways/hps.rb +0 -1
- data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +1 -1
- data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +0 -0
- data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +1 -1
- data/lib/active_merchant/billing/gateways/instapay.rb +0 -0
- data/lib/active_merchant/billing/gateways/ipp.rb +175 -0
- data/lib/active_merchant/billing/gateways/mercury.rb +4 -11
- data/lib/active_merchant/billing/gateways/migs.rb +1 -1
- data/lib/active_merchant/billing/gateways/modern_payments.rb +1 -1
- data/lib/active_merchant/billing/gateways/orbital.rb +2 -2
- data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +26 -0
- data/lib/active_merchant/billing/gateways/payflow.rb +3 -3
- data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +16 -5
- data/lib/active_merchant/billing/gateways/payflow_express.rb +3 -3
- data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +2 -2
- data/lib/active_merchant/billing/gateways/payflow_uk.rb +4 -4
- data/lib/active_merchant/billing/gateways/paypal.rb +15 -3
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +10 -1
- data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +1 -1
- data/lib/active_merchant/billing/gateways/paypal_ca.rb +1 -1
- data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +3 -3
- data/lib/active_merchant/billing/gateways/paypal_express.rb +4 -4
- data/lib/active_merchant/billing/gateways/pin.rb +10 -1
- data/lib/active_merchant/billing/gateways/quickbooks.rb +278 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +2 -2
- data/lib/active_merchant/billing/gateways/sage.rb +3 -3
- data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +1 -1
- data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +1 -1
- data/lib/active_merchant/billing/gateways/secure_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/skip_jack.rb +0 -1
- data/lib/active_merchant/billing/gateways/stripe.rb +13 -2
- data/lib/active_merchant/billing/gateways/wirecard.rb +0 -1
- data/lib/active_merchant/billing/payment_token.rb +1 -1
- data/lib/active_merchant/connection.rb +169 -0
- data/lib/active_merchant/network_connection_retries.rb +78 -0
- data/lib/active_merchant/post_data.rb +24 -0
- data/lib/active_merchant/posts_data.rb +78 -0
- data/lib/active_merchant/version.rb +1 -1
- data/lib/certs/cacert.pem +3866 -0
- metadata +22 -44
- metadata.gz.sig +0 -0
- data/lib/active_merchant/offsite_payments_shim.rb +0 -19
@@ -18,7 +18,7 @@ module ActiveMerchant #:nodoc:
|
|
18
18
|
|
19
19
|
self.homepage_url = 'http://www.mercurypay.com'
|
20
20
|
self.display_name = 'Mercury'
|
21
|
-
self.supported_countries = ['US']
|
21
|
+
self.supported_countries = ['US','CA']
|
22
22
|
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
|
23
23
|
self.default_currency = 'USD'
|
24
24
|
|
@@ -72,13 +72,6 @@ module ActiveMerchant #:nodoc:
|
|
72
72
|
def void(authorization, options={})
|
73
73
|
requires!(options, :credit_card) unless @use_tokenization
|
74
74
|
|
75
|
-
if options[:try_reversal]
|
76
|
-
request = build_authorized_request('VoidSale', nil, authorization, options[:credit_card], options.merge(:reversal => true))
|
77
|
-
response = commit('VoidSale', request)
|
78
|
-
|
79
|
-
return response if response.success?
|
80
|
-
end
|
81
|
-
|
82
75
|
request = build_authorized_request('VoidSale', nil, authorization, options[:credit_card], options)
|
83
76
|
commit('VoidSale', request)
|
84
77
|
end
|
@@ -115,7 +108,7 @@ module ActiveMerchant #:nodoc:
|
|
115
108
|
xml = Builder::XmlMarkup.new
|
116
109
|
|
117
110
|
invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount = split_authorization(authorization)
|
118
|
-
ref_no = "1" if
|
111
|
+
ref_no = "1" if ref_no.blank?
|
119
112
|
|
120
113
|
xml.tag! "TStream" do
|
121
114
|
xml.tag! "Transaction" do
|
@@ -132,8 +125,8 @@ module ActiveMerchant #:nodoc:
|
|
132
125
|
add_address(xml, options)
|
133
126
|
xml.tag! 'TranInfo' do
|
134
127
|
xml.tag! "AuthCode", auth_code
|
135
|
-
xml.tag! "AcqRefData", acq_ref_data
|
136
|
-
xml.tag! "ProcessData", process_data
|
128
|
+
xml.tag! "AcqRefData", acq_ref_data
|
129
|
+
xml.tag! "ProcessData", process_data
|
137
130
|
end
|
138
131
|
end
|
139
132
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/orbital/orbital_soft_descriptors'
|
2
2
|
require "rexml/document"
|
3
3
|
|
4
4
|
module ActiveMerchant #:nodoc:
|
@@ -240,7 +240,7 @@ module ActiveMerchant #:nodoc:
|
|
240
240
|
|
241
241
|
|
242
242
|
# ==== Customer Profiles
|
243
|
-
# :customer_ref_num should be set unless
|
243
|
+
# :customer_ref_num should be set unless you're happy with Orbital providing one
|
244
244
|
#
|
245
245
|
# :customer_profile_order_override_ind can be set to map
|
246
246
|
# the CustomerRefNum to OrderID or Comments. Defaults to 'NO' - no mapping
|
@@ -177,6 +177,13 @@ module ActiveMerchant #:nodoc:
|
|
177
177
|
def capture(money, authorization, options = {})
|
178
178
|
action = 'settletx'
|
179
179
|
|
180
|
+
options.merge!(:money => money, :authorization => authorization)
|
181
|
+
commit_capture(action, authorization, build_request(action, options))
|
182
|
+
end
|
183
|
+
|
184
|
+
def refund(money, authorization, options={})
|
185
|
+
action = 'refundtx'
|
186
|
+
|
180
187
|
options.merge!(:money => money, :authorization => authorization)
|
181
188
|
commit(action, build_request(action, options))
|
182
189
|
end
|
@@ -201,6 +208,10 @@ module ActiveMerchant #:nodoc:
|
|
201
208
|
money = options.delete(:money)
|
202
209
|
authorization = options.delete(:authorization)
|
203
210
|
build_capture(protocol, money, authorization, options)
|
211
|
+
when 'refundtx'
|
212
|
+
money = options.delete(:money)
|
213
|
+
authorization = options.delete(:authorization)
|
214
|
+
build_refund(protocol, money, authorization, options)
|
204
215
|
else
|
205
216
|
raise "no action specified for build_request"
|
206
217
|
end
|
@@ -228,6 +239,13 @@ module ActiveMerchant #:nodoc:
|
|
228
239
|
}
|
229
240
|
end
|
230
241
|
|
242
|
+
def build_refund(xml, money, authorization, options={})
|
243
|
+
xml.tag! 'refundtx', {
|
244
|
+
:tid => authorization,
|
245
|
+
:amt => amount(money)
|
246
|
+
}
|
247
|
+
end
|
248
|
+
|
231
249
|
def parse(action, body)
|
232
250
|
hash = {}
|
233
251
|
xml = REXML::Document.new(body)
|
@@ -252,6 +270,14 @@ module ActiveMerchant #:nodoc:
|
|
252
270
|
)
|
253
271
|
end
|
254
272
|
|
273
|
+
def commit_capture(action, authorization, request)
|
274
|
+
response = parse(action, ssl_post(self.live_url, request))
|
275
|
+
Response.new(successful?(response), message_from(response), response,
|
276
|
+
:test => test?,
|
277
|
+
:authorization => authorization
|
278
|
+
)
|
279
|
+
end
|
280
|
+
|
255
281
|
def message_from(response)
|
256
282
|
(response[:rdesc] || response[:edesc])
|
257
283
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/payflow/payflow_common_api'
|
2
|
+
require 'active_merchant/billing/gateways/payflow/payflow_response'
|
3
|
+
require 'active_merchant/billing/gateways/payflow_express'
|
4
4
|
|
5
5
|
module ActiveMerchant #:nodoc:
|
6
6
|
module Billing #:nodoc:
|
@@ -197,13 +197,24 @@ module ActiveMerchant #:nodoc:
|
|
197
197
|
|
198
198
|
response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
|
199
199
|
|
200
|
-
build_response(
|
201
|
-
|
202
|
-
|
203
|
-
:
|
204
|
-
:
|
200
|
+
build_response(
|
201
|
+
success_for(response),
|
202
|
+
response[:message], response,
|
203
|
+
test: test?,
|
204
|
+
authorization: response[:pn_ref] || response[:rp_ref],
|
205
|
+
cvv_result: CVV_CODE[response[:cv_result]],
|
206
|
+
avs_result: { code: response[:avs_result] },
|
207
|
+
fraud_review: under_fraud_review?(response)
|
205
208
|
)
|
206
209
|
end
|
210
|
+
|
211
|
+
def success_for(response)
|
212
|
+
%w(0 126).include?(response[:result])
|
213
|
+
end
|
214
|
+
|
215
|
+
def under_fraud_review?(response)
|
216
|
+
(response[:result] == "126")
|
217
|
+
end
|
207
218
|
end
|
208
219
|
end
|
209
220
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/payflow/payflow_common_api'
|
2
|
+
require 'active_merchant/billing/gateways/payflow/payflow_express_response'
|
3
|
+
require 'active_merchant/billing/gateways/paypal_express_common'
|
4
4
|
|
5
5
|
module ActiveMerchant #:nodoc:
|
6
6
|
module Billing #:nodoc:
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/payflow_express'
|
2
2
|
|
3
3
|
module ActiveMerchant #:nodoc:
|
4
4
|
module Billing #:nodoc:
|
5
5
|
class PayflowExpressUkGateway < PayflowExpressGateway
|
6
6
|
self.default_currency = 'GBP'
|
7
7
|
self.partner = 'PayPalUk'
|
8
|
-
|
8
|
+
|
9
9
|
self.supported_countries = ['GB']
|
10
10
|
self.homepage_url = 'https://www.paypal.com/uk/cgi-bin/webscr?cmd=_additional-payment-overview-outside'
|
11
11
|
self.display_name = 'PayPal Express Checkout (UK)'
|
@@ -1,16 +1,16 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/payflow'
|
2
|
+
require 'active_merchant/billing/gateways/payflow_express_uk'
|
3
3
|
|
4
4
|
module ActiveMerchant #:nodoc:
|
5
5
|
module Billing #:nodoc:
|
6
6
|
class PayflowUkGateway < PayflowGateway
|
7
7
|
self.default_currency = 'GBP'
|
8
8
|
self.partner = 'PayPalUk'
|
9
|
-
|
9
|
+
|
10
10
|
def express
|
11
11
|
@express ||= PayflowExpressUkGateway.new(@options)
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :solo, :switch]
|
15
15
|
self.supported_countries = ['GB']
|
16
16
|
self.homepage_url = 'https://www.paypal.com/uk/webapps/mpp/pro'
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/paypal/paypal_common_api'
|
2
|
+
require 'active_merchant/billing/gateways/paypal/paypal_recurring_api'
|
3
|
+
require 'active_merchant/billing/gateways/paypal_express'
|
4
4
|
|
5
5
|
module ActiveMerchant #:nodoc:
|
6
6
|
module Billing #:nodoc:
|
@@ -38,6 +38,18 @@ module ActiveMerchant #:nodoc:
|
|
38
38
|
@express ||= PaypalExpressGateway.new(@options)
|
39
39
|
end
|
40
40
|
|
41
|
+
def supports_scrubbing?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def scrub(transcript)
|
46
|
+
transcript.
|
47
|
+
gsub(%r((<n1:Password>).+(</n1:Password>)), '\1[FILTERED]\2').
|
48
|
+
gsub(%r((<n1:Username>).+(</n1:Username>)), '\1[FILTERED]\2').
|
49
|
+
gsub(%r((<n2:CreditCardNumber>).+(</n2:CreditCardNumber)), '\1[FILTERED]\2').
|
50
|
+
gsub(%r((<n2:CVV2>).+(</n2:CVV2)), '\1[FILTERED]\2')
|
51
|
+
end
|
52
|
+
|
41
53
|
private
|
42
54
|
|
43
55
|
def define_transaction_type(transaction_arg)
|
@@ -2,6 +2,8 @@ module ActiveMerchant #:nodoc:
|
|
2
2
|
module Billing #:nodoc:
|
3
3
|
# This module is included in both PaypalGateway and PaypalExpressGateway
|
4
4
|
module PaypalCommonAPI
|
5
|
+
include Empty
|
6
|
+
|
5
7
|
API_VERSION = '72'
|
6
8
|
|
7
9
|
URLS = {
|
@@ -573,7 +575,7 @@ module ActiveMerchant #:nodoc:
|
|
573
575
|
xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank?
|
574
576
|
|
575
577
|
xml.tag! 'n2:InvoiceID', (options[:order_id] || options[:invoice_id]) unless (options[:order_id] || options[:invoice_id]).blank?
|
576
|
-
xml
|
578
|
+
add_button_source(xml)
|
577
579
|
|
578
580
|
# The notify URL applies only to DoExpressCheckoutPayment.
|
579
581
|
# This value is ignored when set in SetExpressCheckout or GetExpressCheckoutDetails
|
@@ -593,6 +595,13 @@ module ActiveMerchant #:nodoc:
|
|
593
595
|
end
|
594
596
|
end
|
595
597
|
|
598
|
+
def add_button_source(xml)
|
599
|
+
button_source = (@options[:button_source] || application_id)
|
600
|
+
if !empty?(button_source)
|
601
|
+
xml.tag! 'n2:ButtonSource', button_source.to_s.slice(0, 32)
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
596
605
|
def add_express_only_payment_details(xml, options = {})
|
597
606
|
add_optional_fields(xml,
|
598
607
|
%w{n2:NoteText n2:SoftDescriptor
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/paypal/paypal_common_api'
|
2
|
+
require 'active_merchant/billing/gateways/paypal/paypal_express_response'
|
3
|
+
require 'active_merchant/billing/gateways/paypal_express_common'
|
4
4
|
|
5
5
|
module ActiveMerchant #:nodoc:
|
6
6
|
module Billing #:nodoc:
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require 'active_merchant/billing/gateways/paypal/paypal_common_api'
|
2
|
+
require 'active_merchant/billing/gateways/paypal/paypal_express_response'
|
3
|
+
require 'active_merchant/billing/gateways/paypal/paypal_recurring_api'
|
4
|
+
require 'active_merchant/billing/gateways/paypal_express_common'
|
5
5
|
|
6
6
|
module ActiveMerchant #:nodoc:
|
7
7
|
module Billing #:nodoc:
|
@@ -148,9 +148,12 @@ module ActiveMerchant #:nodoc:
|
|
148
148
|
url = "#{test? ? test_url : live_url}/#{action}"
|
149
149
|
|
150
150
|
begin
|
151
|
-
|
151
|
+
raw_response = ssl_request(method, url, post_data(params), headers(options))
|
152
|
+
body = parse(raw_response)
|
152
153
|
rescue ResponseError => e
|
153
154
|
body = parse(e.response.body)
|
155
|
+
rescue JSON::ParserError
|
156
|
+
return unparsable_response(raw_response)
|
154
157
|
end
|
155
158
|
|
156
159
|
if body["response"]
|
@@ -181,6 +184,12 @@ module ActiveMerchant #:nodoc:
|
|
181
184
|
)
|
182
185
|
end
|
183
186
|
|
187
|
+
def unparsable_response(raw_response)
|
188
|
+
message = "Invalid JSON response received from Pin Payments. Please contact support@pin.net.au if you continue to receive this message."
|
189
|
+
message += " (The raw response returned by the API was #{raw_response.inspect})"
|
190
|
+
return Response.new(false, message)
|
191
|
+
end
|
192
|
+
|
184
193
|
def token(response)
|
185
194
|
response['token']
|
186
195
|
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
class QuickbooksGateway < Gateway
|
4
|
+
self.test_url = 'https://sandbox.api.intuit.com'
|
5
|
+
self.live_url = 'https://api.intuit.com'
|
6
|
+
|
7
|
+
self.supported_countries = ['US']
|
8
|
+
self.default_currency = 'USD'
|
9
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners]
|
10
|
+
|
11
|
+
self.homepage_url = 'http://payments.intuit.com'
|
12
|
+
self.display_name = 'QuickBooks Payments'
|
13
|
+
ENDPOINT = "/quickbooks/v4/payments/charges"
|
14
|
+
OAUTH_ENDPOINTS = {
|
15
|
+
site: 'https://oauth.intuit.com',
|
16
|
+
request_token_path: '/oauth/v1/get_request_token',
|
17
|
+
authorize_url: 'https://appcenter.intuit.com/Connect/Begin',
|
18
|
+
access_token_path: '/oauth/v1/get_access_token'
|
19
|
+
}
|
20
|
+
|
21
|
+
# https://developer.intuit.com/docs/0150_payments/0300_developer_guides/error_handling
|
22
|
+
|
23
|
+
STANDARD_ERROR_CODE_MAPPING = {
|
24
|
+
# Fraud Warnings
|
25
|
+
'PMT-1000' => STANDARD_ERROR_CODE[:processing_error], # payment was accepted, but refund was unsuccessful
|
26
|
+
'PMT-1001' => STANDARD_ERROR_CODE[:invalid_cvc], # payment processed, but cvc was invalid
|
27
|
+
'PMT-1002' => STANDARD_ERROR_CODE[:incorrect_address], # payment processed, incorrect address info
|
28
|
+
'PMT-1003' => STANDARD_ERROR_CODE[:processing_error], # payment processed, address info couldn't be validated
|
29
|
+
|
30
|
+
# Fraud Errors
|
31
|
+
'PMT-2000' => STANDARD_ERROR_CODE[:incorrect_cvc], # Incorrect CVC
|
32
|
+
'PMT-2001' => STANDARD_ERROR_CODE[:invalid_cvc], # CVC check unavaliable
|
33
|
+
'PMT-2002' => STANDARD_ERROR_CODE[:incorrect_address], # Incorrect address
|
34
|
+
'PMT-2003' => STANDARD_ERROR_CODE[:incorrect_address], # Address info unavailable
|
35
|
+
|
36
|
+
'PMT-3000' => STANDARD_ERROR_CODE[:processing_error], # Merchant account could not be validated
|
37
|
+
|
38
|
+
# Invalid Request
|
39
|
+
'PMT-4000' => STANDARD_ERROR_CODE[:processing_error], # Object is invalid
|
40
|
+
'PMT-4001' => STANDARD_ERROR_CODE[:processing_error], # Object not found
|
41
|
+
'PMT-4002' => STANDARD_ERROR_CODE[:processing_error], # Object is required
|
42
|
+
|
43
|
+
# Transaction Declined
|
44
|
+
'PMT-5000' => STANDARD_ERROR_CODE[:card_declined], # Request was declined
|
45
|
+
'PMT-5001' => STANDARD_ERROR_CODE[:card_declined], # Merchant does not support given payment method
|
46
|
+
|
47
|
+
# System Error
|
48
|
+
'PMT-6000' => STANDARD_ERROR_CODE[:processing_error], # A temporary Issue prevented this request from being processed.
|
49
|
+
}
|
50
|
+
|
51
|
+
FRAUD_WARNING_CODES = ['PMT-1000','PMT-1001','PMT-1002','PMT-1003']
|
52
|
+
|
53
|
+
def initialize(options = {})
|
54
|
+
requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm)
|
55
|
+
@options = options
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
def purchase(money, payment, options = {})
|
60
|
+
post = {}
|
61
|
+
add_amount(post, money, options)
|
62
|
+
add_charge_data(post, payment, options)
|
63
|
+
post[:capture] = "true"
|
64
|
+
|
65
|
+
commit(ENDPOINT, post)
|
66
|
+
end
|
67
|
+
|
68
|
+
def authorize(money, payment, options = {})
|
69
|
+
post = {}
|
70
|
+
add_amount(post, money, options)
|
71
|
+
add_charge_data(post, payment, options)
|
72
|
+
post[:capture] = "false"
|
73
|
+
|
74
|
+
commit(ENDPOINT, post)
|
75
|
+
end
|
76
|
+
|
77
|
+
def capture(money, authorization, options = {})
|
78
|
+
post = {}
|
79
|
+
capture_uri = "#{ENDPOINT}/#{CGI.escape(authorization)}/capture"
|
80
|
+
post[:amount] = localized_amount(money, currency(money))
|
81
|
+
|
82
|
+
commit(capture_uri, post)
|
83
|
+
end
|
84
|
+
|
85
|
+
def refund(money, authorization, options = {})
|
86
|
+
post = {}
|
87
|
+
post[:amount] = localized_amount(money, currency(money))
|
88
|
+
|
89
|
+
commit(refund_uri(authorization), post)
|
90
|
+
end
|
91
|
+
|
92
|
+
def verify(credit_card, options = {})
|
93
|
+
authorize(1.00, credit_card, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def supports_scrubbing?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def scrub(transcript)
|
101
|
+
transcript.
|
102
|
+
gsub(%r((realm=\")\w+), '\1[FILTERED]').
|
103
|
+
gsub(%r((oauth_consumer_key=\")\w+), '\1[FILTERED]').
|
104
|
+
gsub(%r((oauth_nonce=\")\w+), '\1[FILTERED]').
|
105
|
+
gsub(%r((oauth_signature=\")[a-zA-Z%0-9]+), '\1[FILTERED]').
|
106
|
+
gsub(%r((oauth_token=\")\w+), '\1[FILTERED]').
|
107
|
+
gsub(%r((\"card\":{\"number\":\")\d+), '\1[FILTERED]').
|
108
|
+
gsub(%r((\"cvc\":\")\d+), '\1[FILTERED]')
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def add_charge_data(post, payment, options = {})
|
114
|
+
add_payment(post, payment, options)
|
115
|
+
add_address(post, options)
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_address(post, options)
|
119
|
+
return unless post[:card] && post[:card].kind_of?(Hash)
|
120
|
+
|
121
|
+
card_address = {}
|
122
|
+
if address = options[:billing_address] || options[:address]
|
123
|
+
card_address[:streetAddress] = address[:address1]
|
124
|
+
card_address[:city] = address[:city]
|
125
|
+
card_address[:region] = address[:state] || address[:region]
|
126
|
+
card_address[:country] = address[:country]
|
127
|
+
card_address[:postalCode] = address[:zip] if address[:zip]
|
128
|
+
end
|
129
|
+
post[:card][:address] = card_address
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_amount(post, money, options = {})
|
133
|
+
currency = options[:currency] || currency(money)
|
134
|
+
post[:amount] = localized_amount(money, currency)
|
135
|
+
post[:currency] = currency.upcase
|
136
|
+
end
|
137
|
+
|
138
|
+
def add_payment(post, payment, options = {})
|
139
|
+
add_creditcard(post, payment, options)
|
140
|
+
end
|
141
|
+
|
142
|
+
def add_creditcard(post, creditcard, options = {})
|
143
|
+
card = {}
|
144
|
+
card[:number] = creditcard.number
|
145
|
+
card[:expMonth] = "%02d" % creditcard.month
|
146
|
+
card[:expYear] = creditcard.year
|
147
|
+
card[:cvc] = creditcard.verification_value if creditcard.verification_value?
|
148
|
+
card[:name] = creditcard.name if creditcard.name
|
149
|
+
card[:commercialCardCode] = options[:card_code] if options[:card_code]
|
150
|
+
|
151
|
+
post[:card] = card
|
152
|
+
end
|
153
|
+
|
154
|
+
def parse(body)
|
155
|
+
JSON.parse(body)
|
156
|
+
end
|
157
|
+
|
158
|
+
def commit(uri, body = {}, method = :post)
|
159
|
+
endpoint = gateway_url + uri
|
160
|
+
# The QuickBooks API returns HTTP 4xx on failed transactions, which causes a
|
161
|
+
# ResponseError raise, so we have to inspect the response and discern between
|
162
|
+
# a legitimate HTTP error and an actual gateway transactional error.
|
163
|
+
response = begin
|
164
|
+
case method
|
165
|
+
when :post
|
166
|
+
ssl_post(endpoint, post_data(body), headers(:post, endpoint))
|
167
|
+
when :get
|
168
|
+
ssl_request(:get, endpoint, nil, headers(:get, endpoint))
|
169
|
+
else
|
170
|
+
raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get"
|
171
|
+
end
|
172
|
+
rescue ResponseError => e
|
173
|
+
extract_response_body_or_raise(e)
|
174
|
+
end
|
175
|
+
|
176
|
+
response_object(response)
|
177
|
+
end
|
178
|
+
|
179
|
+
def response_object(raw_response)
|
180
|
+
parsed_response = parse(raw_response)
|
181
|
+
|
182
|
+
Response.new(
|
183
|
+
success?(parsed_response),
|
184
|
+
message_from(parsed_response),
|
185
|
+
parsed_response,
|
186
|
+
authorization: authorization_from(parsed_response),
|
187
|
+
test: test?,
|
188
|
+
cvv_result: cvv_code_from(parsed_response),
|
189
|
+
error_code: errors_from(parsed_response),
|
190
|
+
fraud_review: fraud_review_status_from(parsed_response)
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
def gateway_url
|
195
|
+
test? ? test_url : live_url
|
196
|
+
end
|
197
|
+
|
198
|
+
def post_data(data = {})
|
199
|
+
data.to_json
|
200
|
+
end
|
201
|
+
|
202
|
+
def headers(method, uri)
|
203
|
+
raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" unless [:post, :get].include?(method)
|
204
|
+
request_uri = URI.parse(uri)
|
205
|
+
|
206
|
+
# Following the guidelines from http://nouncer.com/oauth/authentication.html
|
207
|
+
oauth_parameters = {
|
208
|
+
oauth_nonce: generate_unique_id,
|
209
|
+
oauth_timestamp: Time.now.to_i.to_s,
|
210
|
+
oauth_signature_method: 'HMAC-SHA1',
|
211
|
+
oauth_version: "1.0",
|
212
|
+
oauth_consumer_key: @options[:consumer_key],
|
213
|
+
oauth_token: @options[:access_token]
|
214
|
+
}
|
215
|
+
|
216
|
+
# prepare components for signature
|
217
|
+
oauth_signature_base_string = [method.to_s.upcase, request_uri.to_s, oauth_parameters.to_param].map{|v| CGI.escape(v) }.join('&')
|
218
|
+
oauth_signing_key = [@options[:consumer_secret], @options[:token_secret]].map{|v| CGI.escape(v)}.join('&')
|
219
|
+
hmac_signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), oauth_signing_key, oauth_signature_base_string)
|
220
|
+
|
221
|
+
# append signature to required OAuth parameters
|
222
|
+
oauth_parameters[:oauth_signature] = CGI.escape(Base64.encode64(hmac_signature).chomp.gsub(/\n/, ''))
|
223
|
+
|
224
|
+
# prepare Authorization header string
|
225
|
+
oauth_parameters = Hash[oauth_parameters.sort_by {|k, _| k}]
|
226
|
+
oauth_headers = ["OAuth realm=\"#{@options[:realm]}\""]
|
227
|
+
oauth_headers += oauth_parameters.map {|k, v| "#{k}=\"#{v}\""}
|
228
|
+
|
229
|
+
{
|
230
|
+
"Content-type" => "application/json",
|
231
|
+
"Request-Id" => generate_unique_id,
|
232
|
+
"Authorization" => oauth_headers.join(', ')
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
def cvv_code_from(response)
|
237
|
+
if response['errors'].present?
|
238
|
+
FRAUD_WARNING_CODES.include?(response['errors'].first['code']) ? 'I' : ''
|
239
|
+
else
|
240
|
+
success?(response) ? 'M' : ''
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def success?(response)
|
245
|
+
response['errors'].present? ? FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) : true
|
246
|
+
end
|
247
|
+
|
248
|
+
def message_from(response)
|
249
|
+
response['errors'].present? ? response["errors"].map {|error_hash| error_hash["message"] }.join(" ") : "Transaction Approved"
|
250
|
+
end
|
251
|
+
|
252
|
+
def errors_from(response)
|
253
|
+
response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response["errors"].first["code"]] : ""
|
254
|
+
end
|
255
|
+
|
256
|
+
def authorization_from(response)
|
257
|
+
response['id']
|
258
|
+
end
|
259
|
+
|
260
|
+
def fraud_review_status_from(response)
|
261
|
+
response['errors'] && FRAUD_WARNING_CODES.include?(response['errors'].first['code'])
|
262
|
+
end
|
263
|
+
|
264
|
+
def extract_response_body_or_raise(response_error)
|
265
|
+
begin
|
266
|
+
parse(response_error.response.body)
|
267
|
+
rescue JSON::ParserError
|
268
|
+
raise response_error
|
269
|
+
end
|
270
|
+
response_error.response.body
|
271
|
+
end
|
272
|
+
|
273
|
+
def refund_uri(authorization)
|
274
|
+
"#{ENDPOINT}/#{CGI.escape(authorization)}/refunds"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|