activemerchant 1.56.0 → 1.57.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
- data/CHANGELOG +42 -0
- data/lib/active_merchant/billing/gateways/authorize_net.rb +52 -21
- data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +1 -0
- data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +243 -0
- data/lib/active_merchant/billing/gateways/bpoint.rb +1 -1
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +11 -11
- data/lib/active_merchant/billing/gateways/bridge_pay.rb +37 -8
- data/lib/active_merchant/billing/gateways/card_stream.rb +36 -11
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +3 -0
- data/lib/active_merchant/billing/gateways/clearhaus.rb +2 -2
- data/lib/active_merchant/billing/gateways/creditcall.rb +1 -1
- data/lib/active_merchant/billing/gateways/cyber_source.rb +12 -1
- data/lib/active_merchant/billing/gateways/element.rb +335 -0
- data/lib/active_merchant/billing/gateways/forte.rb +8 -0
- data/lib/active_merchant/billing/gateways/in_context_paypal_express.rb +15 -0
- data/lib/active_merchant/billing/gateways/litle.rb +1 -1
- data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +2 -1
- data/lib/active_merchant/billing/gateways/ncr_secure_pay.rb +165 -0
- data/lib/active_merchant/billing/gateways/payeezy.rb +54 -12
- data/lib/active_merchant/billing/gateways/sage.rb +379 -128
- data/lib/active_merchant/billing/gateways/stripe.rb +13 -3
- data/lib/active_merchant/billing/gateways/trans_first.rb +26 -6
- data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +573 -0
- data/lib/active_merchant/billing/gateways/worldpay.rb +7 -0
- data/lib/active_merchant/billing/network_tokenization_credit_card.rb +11 -0
- data/lib/active_merchant/version.rb +1 -1
- metadata +7 -6
- data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +0 -89
- data/lib/active_merchant/billing/gateways/sage/sage_core.rb +0 -115
- data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +0 -149
- data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +0 -97
@@ -23,6 +23,7 @@ module ActiveMerchant #:nodoc:
|
|
23
23
|
def purchase(money, payment_method, options={})
|
24
24
|
post = {}
|
25
25
|
add_amount(post, money, options)
|
26
|
+
add_invoice(post, options)
|
26
27
|
add_payment_method(post, payment_method)
|
27
28
|
add_billing_address(post, payment_method, options)
|
28
29
|
add_shipping_address(post, options)
|
@@ -34,6 +35,7 @@ module ActiveMerchant #:nodoc:
|
|
34
35
|
def authorize(money, payment_method, options={})
|
35
36
|
post = {}
|
36
37
|
add_amount(post, money, options)
|
38
|
+
add_invoice(post, options)
|
37
39
|
add_payment_method(post, payment_method)
|
38
40
|
add_billing_address(post, payment_method, options)
|
39
41
|
add_shipping_address(post, options)
|
@@ -44,6 +46,7 @@ module ActiveMerchant #:nodoc:
|
|
44
46
|
|
45
47
|
def capture(money, authorization, options={})
|
46
48
|
post = {}
|
49
|
+
add_invoice(post, options)
|
47
50
|
post[:transaction_id] = transaction_id_from(authorization)
|
48
51
|
post[:authorization_code] = authorization_code_from(authorization)
|
49
52
|
post[:action] = "capture"
|
@@ -54,6 +57,7 @@ module ActiveMerchant #:nodoc:
|
|
54
57
|
def credit(money, payment_method, options={})
|
55
58
|
post = {}
|
56
59
|
add_amount(post, money, options)
|
60
|
+
add_invoice(post, options)
|
57
61
|
add_payment_method(post, payment_method)
|
58
62
|
add_billing_address(post, payment_method, options)
|
59
63
|
post[:action] = "disburse"
|
@@ -95,6 +99,10 @@ module ActiveMerchant #:nodoc:
|
|
95
99
|
post[:location_id] = "loc_#{@options[:location_id]}"
|
96
100
|
end
|
97
101
|
|
102
|
+
def add_invoice(post, options)
|
103
|
+
post[:order_number] = options[:order_id]
|
104
|
+
end
|
105
|
+
|
98
106
|
def add_amount(post, money, options)
|
99
107
|
post[:authorization_amount] = amount(money)
|
100
108
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
class InContextPaypalExpressGateway < PaypalExpressGateway
|
4
|
+
self.test_redirect_url = 'https://www.sandbox.paypal.com/checkoutnow'
|
5
|
+
self.live_redirect_url = 'https://www.paypal.com/checkoutnow'
|
6
|
+
|
7
|
+
def redirect_url_for(token, options = {})
|
8
|
+
options = {review: true}.update(options)
|
9
|
+
url = "#{redirect_url}?token=#{token}"
|
10
|
+
url += '&useraction=commit' unless options[:review]
|
11
|
+
url
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -243,7 +243,7 @@ module ActiveMerchant #:nodoc:
|
|
243
243
|
def add_order_source(doc, payment_method, options)
|
244
244
|
if options[:order_source]
|
245
245
|
doc.orderSource(options[:order_source])
|
246
|
-
elsif payment_method.is_a?(NetworkTokenizationCreditCard)
|
246
|
+
elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay
|
247
247
|
doc.orderSource('applepay')
|
248
248
|
elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present?
|
249
249
|
doc.orderSource('retail')
|
@@ -99,7 +99,8 @@ module ActiveMerchant #:nodoc:
|
|
99
99
|
|
100
100
|
def add_invoice(post, options)
|
101
101
|
if options.has_key? :order_id
|
102
|
-
|
102
|
+
order_id = options[:order_id].to_s.gsub(/[^\w.]/, '')
|
103
|
+
post[:invoice_number] = truncate(order_id, 17)
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module ActiveMerchant #:nodoc:
|
4
|
+
module Billing #:nodoc:
|
5
|
+
class NcrSecurePayGateway < Gateway
|
6
|
+
self.test_url = 'https://testbox.monetra.com:8665/'
|
7
|
+
self.live_url = 'https://ps.ncrsecurepay.com:8444/'
|
8
|
+
|
9
|
+
self.supported_countries = ['US']
|
10
|
+
self.default_currency = 'USD'
|
11
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
|
12
|
+
|
13
|
+
self.homepage_url = 'http://www.ncrretailonline.com'
|
14
|
+
self.display_name = 'NCR Secure Pay'
|
15
|
+
|
16
|
+
def initialize(options={})
|
17
|
+
requires!(options, :username, :password)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def purchase(money, payment, options={})
|
22
|
+
post = {}
|
23
|
+
add_invoice(post, money, options)
|
24
|
+
add_payment(post, payment)
|
25
|
+
add_address(post, payment, options)
|
26
|
+
|
27
|
+
commit('sale', post)
|
28
|
+
end
|
29
|
+
|
30
|
+
def authorize(money, payment, options={})
|
31
|
+
post = {}
|
32
|
+
add_invoice(post, money, options)
|
33
|
+
add_payment(post, payment)
|
34
|
+
add_address(post, payment, options)
|
35
|
+
|
36
|
+
commit('preauth', post)
|
37
|
+
end
|
38
|
+
|
39
|
+
def capture(money, authorization, options={})
|
40
|
+
post = {}
|
41
|
+
add_reference(post, authorization)
|
42
|
+
add_invoice(post, money, options)
|
43
|
+
|
44
|
+
commit('preauthcomplete', post)
|
45
|
+
end
|
46
|
+
|
47
|
+
def refund(money, authorization, options={})
|
48
|
+
post = {}
|
49
|
+
add_reference(post, authorization)
|
50
|
+
add_invoice(post, money, options)
|
51
|
+
|
52
|
+
commit('credit', post)
|
53
|
+
end
|
54
|
+
|
55
|
+
def void(authorization, options={})
|
56
|
+
post = {}
|
57
|
+
add_reference(post, authorization)
|
58
|
+
commit('void', post)
|
59
|
+
end
|
60
|
+
|
61
|
+
def verify(credit_card, options={})
|
62
|
+
MultiResponse.run(:use_first_response) do |r|
|
63
|
+
r.process { authorize(100, credit_card, options) }
|
64
|
+
r.process(:ignore_result) { void(r.authorization, options) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def supports_scrubbing?
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def scrub(transcript)
|
73
|
+
transcript.gsub(%r((<password>)[^<]*(</password>))i, '\1[FILTERED]\2').
|
74
|
+
gsub(%r((<account>)[^<]*(</account>))i, '\1[FILTERED]\2').
|
75
|
+
gsub(%r((<cv>)[^<]*(</cv>))i, '\1[FILTERED]\2')
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def add_reference(post, reference)
|
81
|
+
post[:ttid] = reference
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_address(post, payment, options)
|
85
|
+
address = options[:billing_address] || options[:address]
|
86
|
+
post[:zip] = address[:zip]
|
87
|
+
post[:street] = address[:address1]
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_invoice(post, money, options)
|
91
|
+
post[:amount] = amount(money)
|
92
|
+
post[:currency] = (options[:currency] || currency(money))
|
93
|
+
post[:descmerch] = options[:merchant] if options[:merchant]
|
94
|
+
post[:ordernum] = options[:order_id] if options[:order_id]
|
95
|
+
post[:comments] = options[:description] if options[:description]
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_payment(post, payment)
|
99
|
+
post[:cardholdername] = payment.name
|
100
|
+
post[:account] = payment.number
|
101
|
+
post[:cv] = payment.verification_value
|
102
|
+
post[:expdate] = expdate(payment)
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse(body)
|
106
|
+
doc = Nokogiri::XML(body)
|
107
|
+
doc.remove_namespaces!
|
108
|
+
response = doc.xpath("/MonetraResp/Resp")[0]
|
109
|
+
resp_params = {}
|
110
|
+
|
111
|
+
response.elements.each do |node|
|
112
|
+
resp_params[node.name.downcase.to_sym] = node.text
|
113
|
+
end
|
114
|
+
resp_params
|
115
|
+
end
|
116
|
+
|
117
|
+
def commit(action, parameters)
|
118
|
+
url = (test? ? test_url : live_url)
|
119
|
+
response = parse(ssl_post(url, request_body(action, parameters)))
|
120
|
+
|
121
|
+
Response.new(
|
122
|
+
success_from(response),
|
123
|
+
message_from(response),
|
124
|
+
response,
|
125
|
+
authorization: authorization_from(response),
|
126
|
+
test: test?,
|
127
|
+
error_code: error_code_from(response)
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
def success_from(response)
|
132
|
+
response[:code] == "AUTH"
|
133
|
+
end
|
134
|
+
|
135
|
+
def message_from(response)
|
136
|
+
response[:verbiage]
|
137
|
+
end
|
138
|
+
|
139
|
+
def authorization_from(response)
|
140
|
+
response[:ttid]
|
141
|
+
end
|
142
|
+
|
143
|
+
def request_body(action, parameters = {})
|
144
|
+
Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml|
|
145
|
+
xml.MonetraTrans do
|
146
|
+
xml.Trans(identifier: parameters.delete(:identifier) || "1") do
|
147
|
+
xml.username(options[:username])
|
148
|
+
xml.password(options[:password])
|
149
|
+
xml.action(action)
|
150
|
+
parameters.each do |name, value|
|
151
|
+
xml.send(name, value)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end.to_xml
|
156
|
+
end
|
157
|
+
|
158
|
+
def error_code_from(response)
|
159
|
+
unless success_from(response)
|
160
|
+
response[:msoft_code] || response[:phard_code]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -30,22 +30,22 @@ module ActiveMerchant
|
|
30
30
|
super
|
31
31
|
end
|
32
32
|
|
33
|
-
def purchase(amount,
|
33
|
+
def purchase(amount, payment_method, options = {})
|
34
34
|
params = {transaction_type: 'purchase'}
|
35
35
|
|
36
36
|
add_invoice(params, options)
|
37
|
-
|
37
|
+
add_payment_method(params, payment_method)
|
38
38
|
add_address(params, options)
|
39
39
|
add_amount(params, amount, options)
|
40
40
|
|
41
41
|
commit(params, options)
|
42
42
|
end
|
43
43
|
|
44
|
-
def authorize(amount,
|
44
|
+
def authorize(amount, payment_method, options = {})
|
45
45
|
params = {transaction_type: 'authorize'}
|
46
46
|
|
47
47
|
add_invoice(params, options)
|
48
|
-
|
48
|
+
add_payment_method(params, payment_method)
|
49
49
|
add_address(params, options)
|
50
50
|
add_amount(params, amount, options)
|
51
51
|
|
@@ -71,7 +71,7 @@ module ActiveMerchant
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def void(authorization, options = {})
|
74
|
-
params = {transaction_type: '
|
74
|
+
params = {transaction_type: 'void'}
|
75
75
|
|
76
76
|
add_authorization_info(params, authorization)
|
77
77
|
add_amount(params, amount_from_authorization(authorization), options)
|
@@ -79,6 +79,26 @@ module ActiveMerchant
|
|
79
79
|
commit(params, options)
|
80
80
|
end
|
81
81
|
|
82
|
+
def verify(credit_card, options={})
|
83
|
+
MultiResponse.run(:use_first_response) do |r|
|
84
|
+
r.process { authorize(100, credit_card, options) }
|
85
|
+
r.process(:ignore_result) { void(r.authorization, options) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def supports_scrubbing?
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
def scrub(transcript)
|
94
|
+
transcript.
|
95
|
+
gsub(%r((Token: )(\w|-)+), '\1[FILTERED]').
|
96
|
+
gsub(%r((\\?"card_number\\?":\\?")\d+), '\1[FILTERED]').
|
97
|
+
gsub(%r((\\?"cvv\\?":\\?")\d+), '\1[FILTERED]').
|
98
|
+
gsub(%r((\\?"account_number\\?":\\?")\d+), '\1[FILTERED]').
|
99
|
+
gsub(%r((\\?"routing_number\\?":\\?")\d+), '\1[FILTERED]')
|
100
|
+
end
|
101
|
+
|
82
102
|
private
|
83
103
|
|
84
104
|
def add_invoice(params, options)
|
@@ -96,6 +116,14 @@ module ActiveMerchant
|
|
96
116
|
params[:method] = method
|
97
117
|
end
|
98
118
|
|
119
|
+
def add_payment_method(params, payment_method)
|
120
|
+
if payment_method.is_a? Check
|
121
|
+
add_echeck(params, payment_method)
|
122
|
+
else
|
123
|
+
add_creditcard(params, payment_method)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
99
127
|
def add_creditcard(params, creditcard)
|
100
128
|
credit_card = {}
|
101
129
|
|
@@ -109,6 +137,19 @@ module ActiveMerchant
|
|
109
137
|
params[:credit_card] = credit_card
|
110
138
|
end
|
111
139
|
|
140
|
+
def add_echeck(params, echeck)
|
141
|
+
tele_check = {}
|
142
|
+
|
143
|
+
tele_check[:check_number] = echeck.number
|
144
|
+
tele_check[:check_type] = "P"
|
145
|
+
tele_check[:routing_number] = echeck.routing_number
|
146
|
+
tele_check[:account_number] = echeck.account_number
|
147
|
+
tele_check[:accountholder_name] = "#{echeck.first_name} #{echeck.last_name}"
|
148
|
+
|
149
|
+
params[:method] = 'tele_check'
|
150
|
+
params[:tele_check] = tele_check
|
151
|
+
end
|
152
|
+
|
112
153
|
def add_address(params, options)
|
113
154
|
address = options[:billing_address]
|
114
155
|
return unless address
|
@@ -141,12 +182,9 @@ module ActiveMerchant
|
|
141
182
|
url = "#{url}/#{transaction_id}"
|
142
183
|
end
|
143
184
|
|
144
|
-
success = false
|
145
185
|
begin
|
146
186
|
body = params.to_json
|
147
|
-
|
148
|
-
response = parse(raw_response)
|
149
|
-
success = (response['transaction_status'] == 'approved')
|
187
|
+
response = parse(ssl_post(url, body, headers(body)))
|
150
188
|
rescue ResponseError => e
|
151
189
|
response = response_error(e.response.body)
|
152
190
|
rescue JSON::ParserError
|
@@ -154,17 +192,21 @@ module ActiveMerchant
|
|
154
192
|
end
|
155
193
|
|
156
194
|
Response.new(
|
157
|
-
|
158
|
-
handle_message(response,
|
195
|
+
success_from(response),
|
196
|
+
handle_message(response, success_from(response)),
|
159
197
|
response,
|
160
198
|
test: test?,
|
161
199
|
authorization: authorization_from(params, response),
|
162
200
|
avs_result: {code: response['avs']},
|
163
201
|
cvv_result: response['cvv2'],
|
164
|
-
error_code: error_code(response,
|
202
|
+
error_code: error_code(response, success_from(response))
|
165
203
|
)
|
166
204
|
end
|
167
205
|
|
206
|
+
def success_from(response)
|
207
|
+
response['transaction_status'] == 'approved'
|
208
|
+
end
|
209
|
+
|
168
210
|
def authorization_from(params, response)
|
169
211
|
[
|
170
212
|
response['transaction_id'],
|
@@ -1,172 +1,423 @@
|
|
1
|
-
require 'active_merchant/billing/gateways/sage/sage_bankcard'
|
2
|
-
require 'active_merchant/billing/gateways/sage/sage_virtual_check'
|
3
|
-
require 'active_merchant/billing/gateways/sage/sage_vault'
|
4
|
-
|
5
1
|
module ActiveMerchant #:nodoc:
|
6
2
|
module Billing #:nodoc:
|
7
3
|
class SageGateway < Gateway
|
8
|
-
self.
|
9
|
-
self.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
4
|
+
self.display_name = 'http://www.sagepayments.com'
|
5
|
+
self.homepage_url = 'Sage Payment Solutions'
|
6
|
+
self.live_url = 'https://www.sagepayments.net/cgi-bin'
|
7
|
+
|
8
|
+
self.supported_countries = ['US', 'CA']
|
9
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
|
10
|
+
|
11
|
+
TRANSACTIONS = {
|
12
|
+
:purchase => '01',
|
13
|
+
:authorization => '02',
|
14
|
+
:capture => '11',
|
15
|
+
:void => '04',
|
16
|
+
:credit => '06',
|
17
|
+
:refund => '10'
|
18
|
+
}
|
19
|
+
|
20
|
+
SOURCE_CARD = "bankcard"
|
21
|
+
SOURCE_ECHECK = "virtual_check"
|
22
|
+
|
22
23
|
def initialize(options = {})
|
23
24
|
requires!(options, :login, :password)
|
24
25
|
super
|
25
26
|
end
|
26
27
|
|
27
|
-
# Performs an authorization transaction
|
28
|
-
#
|
29
|
-
# ==== Parameters
|
30
|
-
# * <tt>money</tt> - The amount to be authorized as an integer value in cents.
|
31
|
-
# * <tt>credit_card</tt> - The CreditCard object to be used as the funding source for the transaction.
|
32
|
-
# * <tt>options</tt> - A hash of optional parameters.
|
33
|
-
# * <tt>:order_id</tt> - A unique reference for this order. (maximum of 20 characters).
|
34
|
-
# * <tt>:email</tt> - The customer's email address
|
35
|
-
# * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
|
36
|
-
# * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
|
37
|
-
# * <tt>:address1</tt> - The billing address street
|
38
|
-
# * <tt>:city</tt> - The billing address city
|
39
|
-
# * <tt>:state</tt> - The billing address state
|
40
|
-
# * <tt>:country</tt> - The 2 digit ISO billing address country code
|
41
|
-
# * <tt>:zip</tt> - The billing address zip code
|
42
|
-
# * <tt>:phone</tt> - The billing address phone number
|
43
|
-
# * <tt>:fax</tt> - The billing address fax number
|
44
|
-
# * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
|
45
|
-
# * <tt>:name</tt> - The name at the shipping address
|
46
|
-
# * <tt>:address1</tt> - The shipping address street
|
47
|
-
# * <tt>:city</tt> - The shipping address city
|
48
|
-
# * <tt>:state</tt> - The shipping address state code
|
49
|
-
# * <tt>:country</tt> - The 2 digit ISO shipping address country code
|
50
|
-
# * <tt>:zip</tt> - The shipping address zip code
|
51
|
-
# * <tt>:tax</tt> - The tax amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_tax</tt>.
|
52
|
-
# * <tt>:shipping</tt> - The shipping amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_shipping</tt>.
|
53
28
|
def authorize(money, credit_card, options = {})
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
# * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
|
67
|
-
# * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
|
68
|
-
# * <tt>:address1</tt> - The billing address street
|
69
|
-
# * <tt>:city</tt> - The billing address city
|
70
|
-
# * <tt>:state</tt> - The billing address state
|
71
|
-
# * <tt>:country</tt> - The 2 digit ISO billing address country code
|
72
|
-
# * <tt>:zip</tt> - The billing address zip code
|
73
|
-
# * <tt>:phone</tt> - The billing address phone number
|
74
|
-
# * <tt>:fax</tt> - The billing address fax number
|
75
|
-
# * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
|
76
|
-
# * <tt>:name</tt> - The name at the shipping address
|
77
|
-
# * <tt>:address1</tt> - The shipping address street
|
78
|
-
# * <tt>:city</tt> - The shipping address city
|
79
|
-
# * <tt>:state</tt> - The shipping address state code
|
80
|
-
# * <tt>:country</tt> - The 2 digit ISO shipping address country code
|
81
|
-
# * <tt>:zip</tt> - The shipping address zip code
|
82
|
-
# * <tt>:tax</tt> - The tax amount for the transaction as an integer value in cents. Maps to Sage <tt>T_tax</tt>.
|
83
|
-
# * <tt>:shipping</tt> - The shipping amount for the transaction as an integer value in cents. Maps to Sage <tt>T_shipping</tt>.
|
84
|
-
#
|
85
|
-
# ==== Additional options in the +options+ hash for when using a Check as the funding source
|
86
|
-
# * <tt>:originator_id</tt> - 10 digit originator. If not provided, Sage will use the default Originator ID for the specific customer type.
|
87
|
-
# * <tt>:addenda</tt> - Transaction addenda.
|
88
|
-
# * <tt>:ssn</tt> - The customer's Social Security Number.
|
89
|
-
# * <tt>:drivers_license_state</tt> - The customer's drivers license state code.
|
90
|
-
# * <tt>:drivers_license_number</tt> - The customer's drivers license number.
|
91
|
-
# * <tt>:date_of_birth</tt> - The customer's date of birth as a Time or Date object or a string in the format <tt>mm/dd/yyyy</tt>.
|
92
|
-
def purchase(money, source, options = {})
|
93
|
-
if card_brand(source) == "check"
|
94
|
-
virtual_check.purchase(money, source, options)
|
29
|
+
post = {}
|
30
|
+
add_credit_card(post, credit_card)
|
31
|
+
add_transaction_data(post, money, options)
|
32
|
+
commit(:authorization, post, SOURCE_CARD)
|
33
|
+
end
|
34
|
+
|
35
|
+
def purchase(money, payment_method, options = {})
|
36
|
+
post = {}
|
37
|
+
if card_brand(payment_method) == "check"
|
38
|
+
source = SOURCE_ECHECK
|
39
|
+
add_check(post, payment_method)
|
40
|
+
add_check_customer_data(post, options)
|
95
41
|
else
|
96
|
-
|
42
|
+
source = SOURCE_CARD
|
43
|
+
add_credit_card(post, payment_method)
|
97
44
|
end
|
45
|
+
add_transaction_data(post, money, options)
|
46
|
+
commit(:purchase, post, source)
|
98
47
|
end
|
99
48
|
|
100
|
-
#
|
101
|
-
#
|
102
|
-
# ==== Parameters
|
103
|
-
#
|
104
|
-
# * <tt>money</tt> - The amount to be authorized as an integer value in cents. Sage doesn't support changing the capture amount, so the full amount of the initial transaction will be captured.
|
105
|
-
# * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
|
49
|
+
# The +money+ amount is not used. The entire amount of the
|
50
|
+
# initial authorization will be captured.
|
106
51
|
def capture(money, reference, options = {})
|
107
|
-
|
52
|
+
post = {}
|
53
|
+
add_reference(post, reference)
|
54
|
+
commit(:capture, post, SOURCE_CARD)
|
108
55
|
end
|
109
56
|
|
110
|
-
# Voids a prior transaction. Works for both CreditCard and Check transactions.
|
111
|
-
#
|
112
|
-
# ==== Parameters
|
113
|
-
#
|
114
|
-
# * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
|
115
57
|
def void(reference, options = {})
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
58
|
+
post = {}
|
59
|
+
add_reference(post, reference)
|
60
|
+
source = reference.split(";").last
|
61
|
+
commit(:void, post, source)
|
121
62
|
end
|
122
63
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
if card_brand(source) == "check"
|
130
|
-
virtual_check.credit(money, source, options)
|
64
|
+
def credit(money, payment_method, options = {})
|
65
|
+
post = {}
|
66
|
+
if card_brand(payment_method) == "check"
|
67
|
+
source = SOURCE_ECHECK
|
68
|
+
add_check(post, payment_method)
|
69
|
+
add_check_customer_data(post, options)
|
131
70
|
else
|
132
|
-
|
71
|
+
source = SOURCE_CARD
|
72
|
+
add_credit_card(post, payment_method)
|
133
73
|
end
|
74
|
+
add_transaction_data(post, money, options)
|
75
|
+
commit(:credit, post, source)
|
134
76
|
end
|
135
77
|
|
136
78
|
def refund(money, reference, options={})
|
137
|
-
|
79
|
+
post = {}
|
80
|
+
add_reference(post, reference)
|
81
|
+
add_transaction_data(post, money, options)
|
82
|
+
commit(:refund, post, SOURCE_CARD)
|
138
83
|
end
|
139
84
|
|
140
|
-
# Stores a credit card in the Sage vault.
|
141
|
-
#
|
142
|
-
# ==== Parameters
|
143
|
-
#
|
144
|
-
# * <tt>credit_card</tt> - The CreditCard object to be stored.
|
145
85
|
def store(credit_card, options = {})
|
146
86
|
vault.store(credit_card, options)
|
147
87
|
end
|
148
88
|
|
149
|
-
# Deletes a stored card from the Sage vault.
|
150
|
-
#
|
151
|
-
# ==== Parameters
|
152
|
-
#
|
153
|
-
# * <tt>identification</tt> - The 'GUID' identifying the stored card.
|
154
89
|
def unstore(identification, options = {})
|
155
90
|
vault.unstore(identification, options)
|
156
91
|
end
|
157
92
|
|
158
93
|
private
|
159
94
|
|
160
|
-
def
|
161
|
-
|
95
|
+
def add_credit_card(post, credit_card)
|
96
|
+
post[:C_name] = credit_card.name
|
97
|
+
post[:C_cardnumber] = credit_card.number
|
98
|
+
post[:C_exp] = expdate(credit_card)
|
99
|
+
post[:C_cvv] = credit_card.verification_value if credit_card.verification_value?
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_check(post, check)
|
103
|
+
post[:C_first_name] = check.first_name
|
104
|
+
post[:C_last_name] = check.last_name
|
105
|
+
post[:C_rte] = check.routing_number
|
106
|
+
post[:C_acct] = check.account_number
|
107
|
+
post[:C_check_number] = check.number
|
108
|
+
post[:C_acct_type] = account_type(check)
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_check_customer_data(post, options)
|
112
|
+
# Required Customer Type – (NACHA Transaction Class)
|
113
|
+
# CCD for Commercial, Merchant Initiated
|
114
|
+
# PPD for Personal, Merchant Initiated
|
115
|
+
# WEB for Internet, Consumer Initiated
|
116
|
+
# RCK for Returned Checks
|
117
|
+
# ARC for Account Receivable Entry
|
118
|
+
# TEL for TelephoneInitiated
|
119
|
+
post[:C_customer_type] = "WEB"
|
120
|
+
|
121
|
+
# Optional 10 Digit Originator ID – Assigned By for each transaction class or business purpose. If not provided, the default Originator ID for the specific Customer Type will be applied.
|
122
|
+
post[:C_originator_id] = options[:originator_id]
|
123
|
+
|
124
|
+
# Optional Transaction Addenda
|
125
|
+
post[:T_addenda] = options[:addenda]
|
126
|
+
|
127
|
+
# Required Check Writer Social Security Number ( Numbers Only, No Dashes )
|
128
|
+
post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '')
|
129
|
+
|
130
|
+
post[:C_dl_state_code] = options[:drivers_license_state]
|
131
|
+
post[:C_dl_number] = options[:drivers_license_number]
|
132
|
+
post[:C_dob] = format_birth_date(options[:date_of_birth])
|
162
133
|
end
|
163
134
|
|
164
|
-
def
|
165
|
-
|
135
|
+
def format_birth_date(date)
|
136
|
+
date.respond_to?(:strftime) ? date.strftime("%m/%d/%Y") : date
|
137
|
+
end
|
138
|
+
|
139
|
+
# DDA for Checking
|
140
|
+
# SAV for Savings
|
141
|
+
def account_type(check)
|
142
|
+
case check.account_type
|
143
|
+
when 'checking' then 'DDA'
|
144
|
+
when 'savings' then 'SAV'
|
145
|
+
else raise ArgumentError, "Unknown account type #{check.account_type}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def parse(data, source)
|
150
|
+
source == SOURCE_ECHECK ? parse_check(data) : parse_credit_card(data)
|
151
|
+
end
|
152
|
+
|
153
|
+
def parse_check(data)
|
154
|
+
response = {}
|
155
|
+
response[:success] = data[1,1]
|
156
|
+
response[:code] = data[2,6].strip
|
157
|
+
response[:message] = data[8,32].strip
|
158
|
+
response[:risk] = data[40, 2]
|
159
|
+
response[:reference] = data[42, 10]
|
160
|
+
|
161
|
+
extra_data = data[53...-1].split("\034")
|
162
|
+
response[:order_number] = extra_data[0]
|
163
|
+
response[:authentication_indicator] = extra_data[1]
|
164
|
+
response[:authentication_disclosure] = extra_data[2]
|
165
|
+
response
|
166
|
+
end
|
167
|
+
|
168
|
+
def parse_credit_card(data)
|
169
|
+
response = {}
|
170
|
+
response[:success] = data[1,1]
|
171
|
+
response[:code] = data[2,6]
|
172
|
+
response[:message] = data[8,32].strip
|
173
|
+
response[:front_end] = data[40, 2]
|
174
|
+
response[:cvv_result] = data[42, 1]
|
175
|
+
response[:avs_result] = data[43, 1].strip
|
176
|
+
response[:risk] = data[44, 2]
|
177
|
+
response[:reference] = data[46, 10]
|
178
|
+
|
179
|
+
response[:order_number], response[:recurring] = data[57...-1].split("\034")
|
180
|
+
response
|
181
|
+
end
|
182
|
+
|
183
|
+
def add_invoice(post, options)
|
184
|
+
post[:T_ordernum] = (options[:order_id] || generate_unique_id).slice(0, 20)
|
185
|
+
post[:T_tax] = amount(options[:tax]) unless options[:tax].blank?
|
186
|
+
post[:T_shipping] = amount(options[:shipping]) unless options[:shipping].blank?
|
187
|
+
end
|
188
|
+
|
189
|
+
def add_reference(post, reference)
|
190
|
+
ref, _ = reference.to_s.split(";")
|
191
|
+
post[:T_reference] = ref
|
192
|
+
end
|
193
|
+
|
194
|
+
def add_amount(post, money)
|
195
|
+
post[:T_amt] = amount(money)
|
196
|
+
end
|
197
|
+
|
198
|
+
def add_customer_data(post, options)
|
199
|
+
post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def add_addresses(post, options)
|
203
|
+
billing_address = options[:billing_address] || options[:address] || {}
|
204
|
+
|
205
|
+
post[:C_address] = billing_address[:address1]
|
206
|
+
post[:C_city] = billing_address[:city]
|
207
|
+
|
208
|
+
if ['US', 'CA'].include?(billing_address[:country])
|
209
|
+
post[:C_state] = billing_address[:state]
|
210
|
+
else
|
211
|
+
post[:C_state] = "Outside of United States"
|
212
|
+
end
|
213
|
+
|
214
|
+
post[:C_zip] = billing_address[:zip]
|
215
|
+
post[:C_country] = billing_address[:country]
|
216
|
+
post[:C_telephone] = billing_address[:phone]
|
217
|
+
post[:C_fax] = billing_address[:fax]
|
218
|
+
post[:C_email] = options[:email]
|
219
|
+
|
220
|
+
if shipping_address = options[:shipping_address]
|
221
|
+
post[:C_ship_name] = shipping_address[:name]
|
222
|
+
post[:C_ship_address] = shipping_address[:address1]
|
223
|
+
post[:C_ship_city] = shipping_address[:city]
|
224
|
+
post[:C_ship_state] = shipping_address[:state]
|
225
|
+
post[:C_ship_zip] = shipping_address[:zip]
|
226
|
+
post[:C_ship_country] = shipping_address[:country]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def add_transaction_data(post, money, options)
|
231
|
+
add_amount(post, money)
|
232
|
+
add_invoice(post, options)
|
233
|
+
add_addresses(post, options)
|
234
|
+
add_customer_data(post, options)
|
235
|
+
end
|
236
|
+
|
237
|
+
def commit(action, params, source)
|
238
|
+
url = url(params, source)
|
239
|
+
response = parse(ssl_post(url, post_data(action, params)), source)
|
240
|
+
|
241
|
+
Response.new(success?(response), response[:message], response,
|
242
|
+
:test => test?,
|
243
|
+
:authorization => authorization_from(response, source),
|
244
|
+
:avs_result => { :code => response[:avs_result] },
|
245
|
+
:cvv_result => response[:cvv_result]
|
246
|
+
)
|
247
|
+
end
|
248
|
+
|
249
|
+
def url(params, source)
|
250
|
+
if source == SOURCE_ECHECK
|
251
|
+
"#{live_url}/eftVirtualCheck.dll?transaction"
|
252
|
+
else
|
253
|
+
"#{live_url}/eftBankcard.dll?transaction"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def authorization_from(response, source)
|
258
|
+
"#{response[:reference]};#{source}"
|
259
|
+
end
|
260
|
+
|
261
|
+
def success?(response)
|
262
|
+
response[:success] == 'A'
|
263
|
+
end
|
264
|
+
|
265
|
+
def post_data(action, params = {})
|
266
|
+
params[:M_id] = @options[:login]
|
267
|
+
params[:M_key] = @options[:password]
|
268
|
+
params[:T_code] = TRANSACTIONS[action]
|
269
|
+
|
270
|
+
params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
166
271
|
end
|
167
272
|
|
168
273
|
def vault
|
169
|
-
@vault ||=
|
274
|
+
@vault ||= SageVault.new(@options, self)
|
275
|
+
end
|
276
|
+
|
277
|
+
class SageVault
|
278
|
+
|
279
|
+
def initialize(options, gateway)
|
280
|
+
@live_url = 'https://www.sagepayments.net/web_services/wsVault/wsVault.asmx'
|
281
|
+
@options = options
|
282
|
+
@gateway = gateway
|
283
|
+
end
|
284
|
+
|
285
|
+
def store(credit_card, options = {})
|
286
|
+
request = build_store_request(credit_card, options)
|
287
|
+
commit(:store, request)
|
288
|
+
end
|
289
|
+
|
290
|
+
def unstore(identification, options = {})
|
291
|
+
request = build_unstore_request(identification, options)
|
292
|
+
commit(:unstore, request)
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
# A valid request example, since the Sage docs have none:
|
298
|
+
#
|
299
|
+
# <?xml version="1.0" encoding="UTF-8" ?>
|
300
|
+
# <SOAP-ENV:Envelope
|
301
|
+
# xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
302
|
+
# xmlns:ns1="https://www.sagepayments.net/web_services/wsVault/wsVault">
|
303
|
+
# <SOAP-ENV:Body>
|
304
|
+
# <ns1:INSERT_CREDIT_CARD_DATA>
|
305
|
+
# <ns1:M_ID>279277516172</ns1:M_ID>
|
306
|
+
# <ns1:M_KEY>O3I8G2H8V6A3</ns1:M_KEY>
|
307
|
+
# <ns1:CARDNUMBER>4111111111111111</ns1:CARDNUMBER>
|
308
|
+
# <ns1:EXPIRATION_DATE>0915</ns1:EXPIRATION_DATE>
|
309
|
+
# </ns1:INSERT_CREDIT_CARD_DATA>
|
310
|
+
# </SOAP-ENV:Body>
|
311
|
+
# </SOAP-ENV:Envelope>
|
312
|
+
def build_store_request(credit_card, options)
|
313
|
+
xml = Builder::XmlMarkup.new
|
314
|
+
add_credit_card(xml, credit_card, options)
|
315
|
+
xml.target!
|
316
|
+
end
|
317
|
+
|
318
|
+
def build_unstore_request(identification, options)
|
319
|
+
xml = Builder::XmlMarkup.new
|
320
|
+
add_identification(xml, identification, options)
|
321
|
+
xml.target!
|
322
|
+
end
|
323
|
+
|
324
|
+
def add_customer_data(xml)
|
325
|
+
xml.tag! 'ns1:M_ID', @options[:login]
|
326
|
+
xml.tag! 'ns1:M_KEY', @options[:password]
|
327
|
+
end
|
328
|
+
|
329
|
+
def add_credit_card(xml, credit_card, options)
|
330
|
+
xml.tag! 'ns1:CARDNUMBER', credit_card.number
|
331
|
+
xml.tag! 'ns1:EXPIRATION_DATE', exp_date(credit_card)
|
332
|
+
end
|
333
|
+
|
334
|
+
def add_identification(xml, identification, options)
|
335
|
+
xml.tag! 'ns1:GUID', identification
|
336
|
+
end
|
337
|
+
|
338
|
+
def exp_date(credit_card)
|
339
|
+
year = sprintf("%.4i", credit_card.year)
|
340
|
+
month = sprintf("%.2i", credit_card.month)
|
341
|
+
|
342
|
+
"#{month}#{year[-2..-1]}"
|
343
|
+
end
|
344
|
+
|
345
|
+
def commit(action, request)
|
346
|
+
response = parse(@gateway.ssl_post(@live_url,
|
347
|
+
build_soap_request(action, request),
|
348
|
+
build_headers(action))
|
349
|
+
)
|
350
|
+
|
351
|
+
case action
|
352
|
+
when :store
|
353
|
+
success = response[:success] == 'true'
|
354
|
+
message = response[:message].downcase.capitalize if response[:message]
|
355
|
+
when :unstore
|
356
|
+
success = response[:delete_data_result] == 'true'
|
357
|
+
message = success ? 'Succeeded' : 'Failed'
|
358
|
+
end
|
359
|
+
|
360
|
+
Response.new(success, message, response,
|
361
|
+
authorization: response[:guid]
|
362
|
+
)
|
363
|
+
end
|
364
|
+
|
365
|
+
ENVELOPE_NAMESPACES = {
|
366
|
+
'xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/",
|
367
|
+
'xmlns:ns1' => "https://www.sagepayments.net/web_services/wsVault/wsVault"
|
368
|
+
}
|
369
|
+
|
370
|
+
ACTION_ELEMENTS = {
|
371
|
+
store: 'INSERT_CREDIT_CARD_DATA',
|
372
|
+
unstore: 'DELETE_DATA'
|
373
|
+
}
|
374
|
+
|
375
|
+
def build_soap_request(action, body)
|
376
|
+
xml = Builder::XmlMarkup.new
|
377
|
+
|
378
|
+
xml.instruct!
|
379
|
+
xml.tag! 'SOAP-ENV:Envelope', ENVELOPE_NAMESPACES do
|
380
|
+
xml.tag! 'SOAP-ENV:Body' do
|
381
|
+
xml.tag! "ns1:#{ACTION_ELEMENTS[action]}" do
|
382
|
+
add_customer_data(xml)
|
383
|
+
xml << body
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
xml.target!
|
388
|
+
end
|
389
|
+
|
390
|
+
SOAP_ACTIONS = {
|
391
|
+
store: 'https://www.sagepayments.net/web_services/wsVault/wsVault/INSERT_CREDIT_CARD_DATA',
|
392
|
+
unstore: 'https://www.sagepayments.net/web_services/wsVault/wsVault/DELETE_DATA'
|
393
|
+
}
|
394
|
+
|
395
|
+
def build_headers(action)
|
396
|
+
{
|
397
|
+
"SOAPAction" => SOAP_ACTIONS[action],
|
398
|
+
"Content-Type" => "text/xml; charset=utf-8"
|
399
|
+
}
|
400
|
+
end
|
401
|
+
|
402
|
+
def parse(body)
|
403
|
+
response = {}
|
404
|
+
hashify_xml!(body, response)
|
405
|
+
response
|
406
|
+
end
|
407
|
+
|
408
|
+
def hashify_xml!(xml, response)
|
409
|
+
xml = REXML::Document.new(xml)
|
410
|
+
|
411
|
+
# Store
|
412
|
+
xml.elements.each("//Table1/*") do |node|
|
413
|
+
response[node.name.underscore.to_sym] = node.text
|
414
|
+
end
|
415
|
+
|
416
|
+
# Unstore
|
417
|
+
xml.elements.each("//DELETE_DATAResponse/*") do |node|
|
418
|
+
response[node.name.underscore.to_sym] = node.text
|
419
|
+
end
|
420
|
+
end
|
170
421
|
end
|
171
422
|
end
|
172
423
|
end
|