activemerchant 1.47.0 → 1.48.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 +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG +34 -0
- data/CONTRIBUTORS +16 -0
- data/README.md +8 -2
- data/lib/active_merchant/billing/credit_card.rb +16 -4
- data/lib/active_merchant/billing/gateway.rb +0 -1
- data/lib/active_merchant/billing/gateways/authorize_net.rb +48 -1
- data/lib/active_merchant/billing/gateways/axcessms.rb +181 -0
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +16 -6
- data/lib/active_merchant/billing/gateways/cenpos.rb +272 -0
- data/lib/active_merchant/billing/gateways/epay.rb +8 -9
- data/lib/active_merchant/billing/gateways/exact.rb +9 -0
- data/lib/active_merchant/billing/gateways/fat_zebra.rb +33 -0
- data/lib/active_merchant/billing/gateways/firstdata_e4.rb +49 -3
- data/lib/active_merchant/billing/gateways/inspire.rb +7 -1
- data/lib/active_merchant/billing/gateways/iridium.rb +1 -1
- data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -2
- data/lib/active_merchant/billing/gateways/monei.rb +307 -0
- data/lib/active_merchant/billing/gateways/nab_transact.rb +47 -37
- data/lib/active_merchant/billing/gateways/netbilling.rb +40 -7
- data/lib/active_merchant/billing/gateways/orbital.rb +45 -21
- data/lib/active_merchant/billing/gateways/pay_conex.rb +246 -0
- data/lib/active_merchant/billing/gateways/pay_hub.rb +213 -0
- data/lib/active_merchant/billing/gateways/paymill.rb +3 -2
- data/lib/active_merchant/billing/gateways/pin.rb +2 -2
- data/lib/active_merchant/billing/gateways/quickbooks.rb +6 -4
- data/lib/active_merchant/billing/gateways/qvalent.rb +179 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +29 -15
- data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/stripe.rb +12 -3
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +8 -3
- data/lib/active_merchant/billing/gateways/worldpay.rb +2 -2
- data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +205 -0
- data/lib/active_merchant/billing/network_tokenization_credit_card.rb +12 -4
- data/lib/active_merchant/billing/response.rb +1 -1
- data/lib/active_merchant/posts_data.rb +6 -0
- data/lib/active_merchant/version.rb +1 -1
- data/lib/support/outbound_hosts.rb +13 -10
- metadata +9 -2
- metadata.gz.sig +0 -0
@@ -10,6 +10,7 @@ module ActiveMerchant #:nodoc:
|
|
10
10
|
self.display_name = 'PAYMILL'
|
11
11
|
self.money_format = :cents
|
12
12
|
self.default_currency = 'EUR'
|
13
|
+
self.live_url = "https://api.paymill.com/v2/"
|
13
14
|
|
14
15
|
def initialize(options = {})
|
15
16
|
requires!(options, :public_key, :private_key)
|
@@ -63,9 +64,9 @@ module ActiveMerchant #:nodoc:
|
|
63
64
|
{ 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:private_key]}:X").chomp) }
|
64
65
|
end
|
65
66
|
|
66
|
-
def commit(method,
|
67
|
+
def commit(method, action, parameters=nil)
|
67
68
|
begin
|
68
|
-
raw_response = ssl_request(method,
|
69
|
+
raw_response = ssl_request(method, live_url + action, post_data(parameters), headers)
|
69
70
|
rescue ResponseError => e
|
70
71
|
begin
|
71
72
|
parsed = JSON.parse(e.response.body)
|
@@ -59,10 +59,10 @@ module ActiveMerchant #:nodoc:
|
|
59
59
|
purchase(money, creditcard, options)
|
60
60
|
end
|
61
61
|
|
62
|
-
# Captures a previously authorized charge. Capturing
|
62
|
+
# Captures a previously authorized charge. Capturing only part of the original
|
63
63
|
# authorization is currently not supported.
|
64
64
|
def capture(money, token, options = {})
|
65
|
-
commit(:put, "charges/#{CGI.escape(token)}/capture", {}, options)
|
65
|
+
commit(:put, "charges/#{CGI.escape(token)}/capture", { :amount => amount(money) }, options)
|
66
66
|
end
|
67
67
|
|
68
68
|
# Updates the credit card for the customer.
|
@@ -104,8 +104,8 @@ module ActiveMerchant #:nodoc:
|
|
104
104
|
gsub(%r((oauth_nonce=\")\w+), '\1[FILTERED]').
|
105
105
|
gsub(%r((oauth_signature=\")[a-zA-Z%0-9]+), '\1[FILTERED]').
|
106
106
|
gsub(%r((oauth_token=\")\w+), '\1[FILTERED]').
|
107
|
-
gsub(%r((
|
108
|
-
gsub(%r((
|
107
|
+
gsub(%r((number\D+)\d{16}), '\1[FILTERED]').
|
108
|
+
gsub(%r((cvc\D+)\d{3}), '\1[FILTERED]')
|
109
109
|
end
|
110
110
|
|
111
111
|
private
|
@@ -242,11 +242,13 @@ module ActiveMerchant #:nodoc:
|
|
242
242
|
end
|
243
243
|
|
244
244
|
def success?(response)
|
245
|
-
|
245
|
+
return FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) if response['errors']
|
246
|
+
|
247
|
+
!['DECLINED', 'CANCELLED'].include?(response['status'])
|
246
248
|
end
|
247
249
|
|
248
250
|
def message_from(response)
|
249
|
-
response['errors'].present? ? response["errors"].map {|error_hash| error_hash["message"] }.join(" ") :
|
251
|
+
response['errors'].present? ? response["errors"].map {|error_hash| error_hash["message"] }.join(" ") : response['status']
|
250
252
|
end
|
251
253
|
|
252
254
|
def errors_from(response)
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
class QvalentGateway < Gateway
|
4
|
+
self.display_name = "Qvalent"
|
5
|
+
self.homepage_url = "https://www.qvalent.com/"
|
6
|
+
|
7
|
+
self.test_url = "https://ccapi.client.support.qvalent.com/post/CreditCardAPIReceiver"
|
8
|
+
self.live_url = "https://ccapi.client.qvalent.com/post/CreditCardAPIReceiver"
|
9
|
+
|
10
|
+
self.supported_countries = ["AU"]
|
11
|
+
self.default_currency = "AUD"
|
12
|
+
self.money_format = :cents
|
13
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners]
|
14
|
+
|
15
|
+
def initialize(options={})
|
16
|
+
requires!(options, :username, :password, :merchant)
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def purchase(amount, payment_method, options={})
|
21
|
+
post = {}
|
22
|
+
add_invoice(post, amount, options)
|
23
|
+
add_order_number(post, options)
|
24
|
+
add_payment_method(post, payment_method)
|
25
|
+
add_verification_value(post, payment_method)
|
26
|
+
add_customer_data(post, options)
|
27
|
+
|
28
|
+
commit("capture", post)
|
29
|
+
end
|
30
|
+
|
31
|
+
def refund(amount, authorization, options={})
|
32
|
+
post = {}
|
33
|
+
add_invoice(post, amount, options)
|
34
|
+
add_reference(post, authorization, options)
|
35
|
+
add_customer_data(post, options)
|
36
|
+
|
37
|
+
commit("refund", post)
|
38
|
+
end
|
39
|
+
|
40
|
+
def store(payment_method, options = {})
|
41
|
+
post = {}
|
42
|
+
add_payment_method(post, payment_method)
|
43
|
+
add_card_reference(post)
|
44
|
+
|
45
|
+
commit("registerAccount", post)
|
46
|
+
end
|
47
|
+
|
48
|
+
def supports_scrubbing?
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def scrub(transcript)
|
53
|
+
transcript.
|
54
|
+
gsub(%r((&?customer.password=)[^&]*), '\1[FILTERED]').
|
55
|
+
gsub(%r((&?card.PAN=)[^&]*), '\1[FILTERED]').
|
56
|
+
gsub(%r((&?card.CVN=)[^&]*), '\1[FILTERED]')
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
CURRENCY_CODES = Hash.new{|h,k| raise ArgumentError.new("Unsupported currency: #{k}")}
|
62
|
+
CURRENCY_CODES["AUD"] = "AUD"
|
63
|
+
CURRENCY_CODES["INR"] = "INR"
|
64
|
+
|
65
|
+
def add_invoice(post, money, options)
|
66
|
+
post["order.amount"] = amount(money)
|
67
|
+
post["card.currency"] = CURRENCY_CODES[options[:currency] || currency(money)]
|
68
|
+
post["order.ECI"] = "SSL"
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_payment_method(post, payment_method)
|
72
|
+
post["card.cardHolderName"] = payment_method.name
|
73
|
+
post["card.PAN"] = payment_method.number
|
74
|
+
post["card.expiryYear"] = format(payment_method.year, :two_digits)
|
75
|
+
post["card.expiryMonth"] = format(payment_method.month, :two_digits)
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_verification_value(post, payment_method)
|
79
|
+
post["card.CVN"] = payment_method.verification_value
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_card_reference(post)
|
83
|
+
post["customer.customerReferenceNumber"] = options[:order_id]
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_reference(post, authorization, options)
|
87
|
+
post["customer.originalOrderNumber"] = authorization
|
88
|
+
add_order_number(post, options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def add_order_number(post, options)
|
92
|
+
post["customer.orderNumber"] = options[:order_id] || SecureRandom.uuid
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_customer_data(post, options)
|
96
|
+
post["order.ipAddress"] = options[:ip]
|
97
|
+
end
|
98
|
+
|
99
|
+
def commit(action, post)
|
100
|
+
post["customer.username"] = @options[:username]
|
101
|
+
post["customer.password"] = @options[:password]
|
102
|
+
post["customer.merchant"] = @options[:merchant]
|
103
|
+
post["order.type"] = action
|
104
|
+
|
105
|
+
data = build_request(post)
|
106
|
+
raw = parse(ssl_post(url(action), data, headers))
|
107
|
+
|
108
|
+
succeeded = success_from(raw["response.responseCode"])
|
109
|
+
Response.new(
|
110
|
+
succeeded,
|
111
|
+
message_from(succeeded, raw),
|
112
|
+
raw,
|
113
|
+
authorization: raw["response.orderNumber"] || raw["response.customerReferenceNumber"],
|
114
|
+
error_code: error_code_from(succeeded, raw),
|
115
|
+
test: test?
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
def headers
|
120
|
+
{
|
121
|
+
"Content-Type" => "application/x-www-form-urlencoded"
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_request(post)
|
126
|
+
post.to_query + "&message.end"
|
127
|
+
end
|
128
|
+
|
129
|
+
def url(action)
|
130
|
+
(test? ? test_url : live_url)
|
131
|
+
end
|
132
|
+
|
133
|
+
def parse(body)
|
134
|
+
result = {}
|
135
|
+
body.to_s.each_line do |pair|
|
136
|
+
result[$1] = $2 if pair.strip =~ /\A([^=]+)=(.+)\Z/im
|
137
|
+
end
|
138
|
+
result
|
139
|
+
end
|
140
|
+
|
141
|
+
def parse_element(response, node)
|
142
|
+
if node.has_elements?
|
143
|
+
node.elements.each{|element| parse_element(response, element) }
|
144
|
+
else
|
145
|
+
response[node.name.underscore.to_sym] = node.text
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
SUCCESS_CODES = %w(00 08 10 11 16 QS QZ)
|
150
|
+
|
151
|
+
def success_from(response)
|
152
|
+
SUCCESS_CODES.include?(response)
|
153
|
+
end
|
154
|
+
|
155
|
+
def message_from(succeeded, response)
|
156
|
+
if succeeded
|
157
|
+
"Succeeded"
|
158
|
+
else
|
159
|
+
response["response.text"] || "Unable to read error message"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
STANDARD_ERROR_CODE_MAPPING = {
|
164
|
+
"14" => STANDARD_ERROR_CODE[:invalid_number],
|
165
|
+
"QQ" => STANDARD_ERROR_CODE[:invalid_cvc],
|
166
|
+
"33" => STANDARD_ERROR_CODE[:expired_card],
|
167
|
+
"NT" => STANDARD_ERROR_CODE[:incorrect_address],
|
168
|
+
"12" => STANDARD_ERROR_CODE[:card_declined],
|
169
|
+
"06" => STANDARD_ERROR_CODE[:processing_error],
|
170
|
+
"01" => STANDARD_ERROR_CODE[:call_issuer],
|
171
|
+
"04" => STANDARD_ERROR_CODE[:pickup_card],
|
172
|
+
}
|
173
|
+
|
174
|
+
def error_code_from(succeeded, response)
|
175
|
+
succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response["response.responseCode"]]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -26,7 +26,7 @@ module ActiveMerchant #:nodoc:
|
|
26
26
|
# test access details please get in touch: sam@cabify.com.
|
27
27
|
class RedsysGateway < Gateway
|
28
28
|
self.live_url = "https://sis.sermepa.es/sis/operaciones"
|
29
|
-
self.test_url = "https://sis-t.
|
29
|
+
self.test_url = "https://sis-t.redsys.es:25443/sis/operaciones"
|
30
30
|
|
31
31
|
self.supported_countries = ['ES']
|
32
32
|
self.default_currency = 'EUR'
|
@@ -159,28 +159,30 @@ module ActiveMerchant #:nodoc:
|
|
159
159
|
super
|
160
160
|
end
|
161
161
|
|
162
|
-
def purchase(money,
|
162
|
+
def purchase(money, payment, options = {})
|
163
163
|
requires!(options, :order_id)
|
164
164
|
|
165
165
|
data = {}
|
166
166
|
add_action(data, :purchase)
|
167
167
|
add_amount(data, money, options)
|
168
168
|
add_order(data, options[:order_id])
|
169
|
-
|
169
|
+
add_payment(data, payment)
|
170
170
|
data[:description] = options[:description]
|
171
|
+
data[:store_in_vault] = options[:store]
|
171
172
|
|
172
173
|
commit data
|
173
174
|
end
|
174
175
|
|
175
|
-
def authorize(money,
|
176
|
+
def authorize(money, payment, options = {})
|
176
177
|
requires!(options, :order_id)
|
177
178
|
|
178
179
|
data = {}
|
179
180
|
add_action(data, :authorize)
|
180
181
|
add_amount(data, money, options)
|
181
182
|
add_order(data, options[:order_id])
|
182
|
-
|
183
|
+
add_payment(data, payment)
|
183
184
|
data[:description] = options[:description]
|
185
|
+
data[:store_in_vault] = options[:store]
|
184
186
|
|
185
187
|
commit data
|
186
188
|
end
|
@@ -244,16 +246,20 @@ module ActiveMerchant #:nodoc:
|
|
244
246
|
test? ? test_url : live_url
|
245
247
|
end
|
246
248
|
|
247
|
-
def
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
:
|
255
|
-
|
256
|
-
|
249
|
+
def add_payment(data, card)
|
250
|
+
if card.is_a?(String)
|
251
|
+
data[:credit_card_token] = card
|
252
|
+
else
|
253
|
+
name = [card.first_name, card.last_name].join(' ').slice(0, 60)
|
254
|
+
year = sprintf("%.4i", card.year)
|
255
|
+
month = sprintf("%.2i", card.month)
|
256
|
+
data[:card] = {
|
257
|
+
:name => name,
|
258
|
+
:pan => card.number,
|
259
|
+
:date => "#{year[2..3]}#{month}",
|
260
|
+
:cvv => card.verification_value
|
261
|
+
}
|
262
|
+
end
|
257
263
|
end
|
258
264
|
|
259
265
|
def commit(data)
|
@@ -276,6 +282,11 @@ module ActiveMerchant #:nodoc:
|
|
276
282
|
end
|
277
283
|
|
278
284
|
str << data[:action]
|
285
|
+
if data[:store_in_vault]
|
286
|
+
str << 'REQUIRED'
|
287
|
+
elsif data[:credit_card_token]
|
288
|
+
str << data[:credit_card_token]
|
289
|
+
end
|
279
290
|
str << @options[:secret_key]
|
280
291
|
|
281
292
|
Digest::SHA1.hexdigest(str)
|
@@ -301,6 +312,9 @@ module ActiveMerchant #:nodoc:
|
|
301
312
|
xml.DS_MERCHANT_PAN data[:card][:pan]
|
302
313
|
xml.DS_MERCHANT_EXPIRYDATE data[:card][:date]
|
303
314
|
xml.DS_MERCHANT_CVV2 data[:card][:cvv]
|
315
|
+
xml.DS_MERCHANT_IDENTIFIER 'REQUIRED' if data[:store_in_vault]
|
316
|
+
elsif data[:credit_card_token]
|
317
|
+
xml.DS_MERCHANT_IDENTIFIER data[:credit_card_token]
|
304
318
|
end
|
305
319
|
end
|
306
320
|
xml.target!
|
@@ -354,7 +354,7 @@ module ActiveMerchant #:nodoc:
|
|
354
354
|
parameters.update(
|
355
355
|
:Vendor => @options[:login],
|
356
356
|
:TxType => TRANSACTIONS[action],
|
357
|
-
:VPSProtocol =>
|
357
|
+
:VPSProtocol => @options.fetch(:protocol_version, '3.00')
|
358
358
|
)
|
359
359
|
|
360
360
|
if(application_id && (application_id != Gateway.application_id))
|
@@ -24,7 +24,7 @@ module ActiveMerchant #:nodoc:
|
|
24
24
|
# Source: https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
|
25
25
|
CURRENCIES_WITHOUT_FRACTIONS = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VUV', 'XAF', 'XOF', 'XPF']
|
26
26
|
|
27
|
-
self.supported_countries = %w(AU BE CA CH DE ES FI FR GB IE IT LU NL US)
|
27
|
+
self.supported_countries = %w(AT AU BE CA CH DE DK ES FI FR GB IE IT LU NL NO SE US)
|
28
28
|
self.default_currency = 'USD'
|
29
29
|
self.money_format = :cents
|
30
30
|
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
|
@@ -212,7 +212,8 @@ module ActiveMerchant #:nodoc:
|
|
212
212
|
transcript.
|
213
213
|
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
|
214
214
|
gsub(%r((card\[number\]=)\d+), '\1[FILTERED]').
|
215
|
-
gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]')
|
215
|
+
gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]').
|
216
|
+
gsub(%r((&?three_d_secure\[cryptogram\]=)[\w=]*(&?)), '\1[FILTERED]\2')
|
216
217
|
end
|
217
218
|
|
218
219
|
private
|
@@ -236,7 +237,7 @@ module ActiveMerchant #:nodoc:
|
|
236
237
|
post[:description] = options[:description]
|
237
238
|
post[:statement_description] = options[:statement_description]
|
238
239
|
|
239
|
-
post[:metadata] = {}
|
240
|
+
post[:metadata] = options[:metadata] || {}
|
240
241
|
post[:metadata][:email] = options[:email] if options[:email]
|
241
242
|
post[:metadata][:order_id] = options[:order_id] if options[:order_id]
|
242
243
|
post.delete(:metadata) if post[:metadata].empty?
|
@@ -294,6 +295,14 @@ module ActiveMerchant #:nodoc:
|
|
294
295
|
end
|
295
296
|
|
296
297
|
post[:card] = card
|
298
|
+
|
299
|
+
if creditcard.is_a?(NetworkTokenizationCreditCard)
|
300
|
+
post[:three_d_secure] = {
|
301
|
+
apple_pay: true,
|
302
|
+
cryptogram: creditcard.payment_cryptogram
|
303
|
+
}
|
304
|
+
end
|
305
|
+
|
297
306
|
add_address(post, options)
|
298
307
|
elsif creditcard.kind_of?(String)
|
299
308
|
if options[:track_data]
|
@@ -15,7 +15,8 @@ module ActiveMerchant #:nodoc:
|
|
15
15
|
:purchase => 'cc:sale',
|
16
16
|
:capture => 'cc:capture',
|
17
17
|
:refund => 'cc:refund',
|
18
|
-
:void => 'cc:void'
|
18
|
+
:void => 'cc:void',
|
19
|
+
:void_release => 'cc:void:release'
|
19
20
|
}
|
20
21
|
|
21
22
|
STANDARD_ERROR_CODE_MAPPING = {
|
@@ -93,9 +94,10 @@ module ActiveMerchant #:nodoc:
|
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
97
|
+
# Pass `no_release: true` to keep the void from immediately settling
|
96
98
|
def void(authorization, options = {})
|
97
|
-
|
98
|
-
commit(
|
99
|
+
command = (options[:no_release] ? :void : :void_release)
|
100
|
+
commit(command, refNum: authorization)
|
99
101
|
end
|
100
102
|
|
101
103
|
private
|
@@ -245,6 +247,9 @@ module ActiveMerchant #:nodoc:
|
|
245
247
|
parameters[:key] = @options[:login]
|
246
248
|
parameters[:software] = 'Active Merchant'
|
247
249
|
parameters[:testmode] = (@options[:test] ? 1 : 0)
|
250
|
+
seed = SecureRandom.hex(32).upcase
|
251
|
+
hash = Digest::SHA1.hexdigest("#{parameters[:command]}:#{@options[:password]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}")
|
252
|
+
parameters[:hash] = "s/#{seed}/#{hash}/n"
|
248
253
|
|
249
254
|
parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
250
255
|
end
|
@@ -6,10 +6,10 @@ module ActiveMerchant #:nodoc:
|
|
6
6
|
|
7
7
|
self.default_currency = 'GBP'
|
8
8
|
self.money_format = :cents
|
9
|
-
self.supported_countries = %w(HK
|
9
|
+
self.supported_countries = %w(HK GB AU AD BE CH CY CZ DE DK ES FI FR GI GR HU IE IL IT LI LU MC MT NL NO NZ PL PT SE SG SI SM TR UM VA)
|
10
10
|
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :laser, :switch]
|
11
11
|
self.homepage_url = 'http://www.worldpay.com/'
|
12
|
-
self.display_name = 'Worldpay'
|
12
|
+
self.display_name = 'Worldpay Global'
|
13
13
|
|
14
14
|
CARD_CODES = {
|
15
15
|
'visa' => 'VISA-SSL',
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
class WorldpayOnlinePaymentsGateway < Gateway
|
4
|
+
self.live_url = 'https://api.worldpay.com/v1/'
|
5
|
+
|
6
|
+
self.default_currency = 'GBP'
|
7
|
+
|
8
|
+
self.money_format = :cents
|
9
|
+
|
10
|
+
self.supported_countries = %w(HK US GB AU AD BE CH CY CZ DE DK ES FI FR GI GR HU IE IL IT LI LU MC MT NL NO NZ PL PT SE SG SI SM TR UM VA)
|
11
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :laser, :switch]
|
12
|
+
|
13
|
+
self.homepage_url = 'http://online.worldpay.com'
|
14
|
+
self.display_name = 'Worldpay Online Payments'
|
15
|
+
|
16
|
+
def initialize(options={})
|
17
|
+
requires!(options, :client_key, :service_key)
|
18
|
+
@client_key = options[:client_key]
|
19
|
+
@service_key = options[:service_key]
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def authorize(money, credit_card, options={})
|
24
|
+
response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value)
|
25
|
+
|
26
|
+
if response.success?
|
27
|
+
options[:authorizeOnly] = true
|
28
|
+
post = create_post_for_auth_or_purchase(response.authorization, money, options)
|
29
|
+
response = commit(:post, 'orders', post)
|
30
|
+
end
|
31
|
+
response
|
32
|
+
end
|
33
|
+
|
34
|
+
def capture(money, authorization, options={})
|
35
|
+
if authorization
|
36
|
+
commit(:post, "orders/#{CGI.escape(authorization)}/capture", {"captureAmount"=>money}, options)
|
37
|
+
else
|
38
|
+
Response.new(false,
|
39
|
+
'FAILED',
|
40
|
+
'FAILED',
|
41
|
+
:test => test?,
|
42
|
+
:authorization => false,
|
43
|
+
:avs_result => {},
|
44
|
+
:cvv_result => {},
|
45
|
+
:error_code => false
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def purchase(money, credit_card, options={})
|
51
|
+
response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value)
|
52
|
+
if response.success?
|
53
|
+
post = create_post_for_auth_or_purchase(response.authorization, money, options)
|
54
|
+
response = commit(:post, 'orders', post, options)
|
55
|
+
end
|
56
|
+
response
|
57
|
+
end
|
58
|
+
|
59
|
+
def refund(money, orderCode, options={})
|
60
|
+
obj = money ? {"refundAmount" => money} : {}
|
61
|
+
commit(:post, "orders/#{CGI.escape(orderCode)}/refund", obj, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def void(orderCode, options={})
|
65
|
+
response = commit(:delete, "orders/#{CGI.escape(orderCode)}", nil, options)
|
66
|
+
if !response.success? && (response.params && response.params['customCode'] != 'ORDER_NOT_FOUND')
|
67
|
+
response = refund(nil, orderCode)
|
68
|
+
end
|
69
|
+
response
|
70
|
+
end
|
71
|
+
|
72
|
+
def verify(credit_card, options={})
|
73
|
+
authorize(0, credit_card, options)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def create_token(reusable, name, exp_month, exp_year, number, cvc)
|
79
|
+
obj = {
|
80
|
+
"reusable"=> reusable,
|
81
|
+
"paymentMethod"=> {
|
82
|
+
"type"=> "Card",
|
83
|
+
"name"=> name,
|
84
|
+
"expiryMonth"=> exp_month,
|
85
|
+
"expiryYear"=> exp_year,
|
86
|
+
"cardNumber"=> number,
|
87
|
+
"cvc"=> cvc
|
88
|
+
},
|
89
|
+
"clientKey"=> @client_key
|
90
|
+
}
|
91
|
+
token_response = commit(:post, 'tokens', obj, {'Authorization' => @service_key})
|
92
|
+
token_response
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_post_for_auth_or_purchase(token, money, options)
|
96
|
+
{
|
97
|
+
"token" => token,
|
98
|
+
"orderDescription" => options[:description],
|
99
|
+
"amount" => money,
|
100
|
+
"currencyCode" => options[:currency] || default_currency,
|
101
|
+
"name" => options[:billing_address]&&options[:billing_address][:name] ? options[:billing_address][:name] : '',
|
102
|
+
"billingAddress" => {
|
103
|
+
"address1"=>options[:billing_address]&&options[:billing_address][:address1] ? options[:billing_address][:address1] : '',
|
104
|
+
"address2"=>options[:billing_address]&&options[:billing_address][:address2] ? options[:billing_address][:address2] : '',
|
105
|
+
"address3"=>"",
|
106
|
+
"postalCode"=>options[:billing_address]&&options[:billing_address][:zip] ? options[:billing_address][:zip] : '',
|
107
|
+
"city"=>options[:billing_address]&&options[:billing_address][:city] ? options[:billing_address][:city] : '',
|
108
|
+
"state"=>options[:billing_address]&&options[:billing_address][:state] ? options[:billing_address][:state] : '',
|
109
|
+
"countryCode"=>options[:billing_address]&&options[:billing_address][:country] ? options[:billing_address][:country] : ''
|
110
|
+
},
|
111
|
+
"customerOrderCode" => options[:order_id],
|
112
|
+
"orderType" => "ECOM",
|
113
|
+
"authorizeOnly" => options[:authorizeOnly] ? true : false
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse(body)
|
118
|
+
body ? JSON.parse(body) : {}
|
119
|
+
end
|
120
|
+
|
121
|
+
def headers(options = {})
|
122
|
+
headers = {
|
123
|
+
"Authorization" => @service_key,
|
124
|
+
"Content-Type" => 'application/json',
|
125
|
+
"User-Agent" => "Worldpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
|
126
|
+
"X-Worldpay-Client-User-Agent" => user_agent,
|
127
|
+
"X-Worldpay-Client-User-Metadata" => {:ip => options[:ip]}.to_json
|
128
|
+
}
|
129
|
+
if options['Authorization']
|
130
|
+
headers['Authorization'] = options['Authorization']
|
131
|
+
end
|
132
|
+
headers
|
133
|
+
end
|
134
|
+
|
135
|
+
def commit(method, url, parameters=nil, options = {})
|
136
|
+
raw_response = response = nil
|
137
|
+
success = false
|
138
|
+
begin
|
139
|
+
json = parameters ? parameters.to_json : nil
|
140
|
+
|
141
|
+
raw_response = ssl_request(method, self.live_url + url, json, headers(options))
|
142
|
+
|
143
|
+
if (raw_response != '')
|
144
|
+
response = parse(raw_response)
|
145
|
+
success = !response.key?("httpStatusCode")
|
146
|
+
else
|
147
|
+
success = true
|
148
|
+
response = {}
|
149
|
+
end
|
150
|
+
|
151
|
+
rescue ResponseError => e
|
152
|
+
raw_response = e.response.body
|
153
|
+
response = response_error(raw_response)
|
154
|
+
rescue JSON::ParserError => e
|
155
|
+
response = json_error(raw_response)
|
156
|
+
end
|
157
|
+
|
158
|
+
if response["orderCode"]
|
159
|
+
authorization = response["orderCode"]
|
160
|
+
elsif response["token"]
|
161
|
+
authorization = response["token"]
|
162
|
+
else
|
163
|
+
authorization = response["message"]
|
164
|
+
end
|
165
|
+
|
166
|
+
Response.new(success,
|
167
|
+
success ? "SUCCESS" : response["message"],
|
168
|
+
response,
|
169
|
+
:test => test?,
|
170
|
+
:authorization => authorization,
|
171
|
+
:avs_result => {},
|
172
|
+
:cvv_result => {},
|
173
|
+
:error_code => success ? nil : response["customCode"]
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
def test?
|
178
|
+
@service_key[0]=="T" ? true : false
|
179
|
+
end
|
180
|
+
|
181
|
+
def response_error(raw_response)
|
182
|
+
begin
|
183
|
+
parse(raw_response)
|
184
|
+
rescue JSON::ParserError
|
185
|
+
json_error(raw_response)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def json_error(raw_response)
|
190
|
+
msg = 'Invalid response received from the Worldpay Online Payments API. Please contact techsupport.online@worldpay.com if you continue to receive this message.'
|
191
|
+
msg += " (The raw response returned by the API was #{raw_response.inspect})"
|
192
|
+
{
|
193
|
+
"error" => {
|
194
|
+
"message" => msg
|
195
|
+
}
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
def handle_response(response)
|
200
|
+
response.body
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|