activemerchant 1.47.0 → 1.48.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG +34 -0
  5. data/CONTRIBUTORS +16 -0
  6. data/README.md +8 -2
  7. data/lib/active_merchant/billing/credit_card.rb +16 -4
  8. data/lib/active_merchant/billing/gateway.rb +0 -1
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +48 -1
  10. data/lib/active_merchant/billing/gateways/axcessms.rb +181 -0
  11. data/lib/active_merchant/billing/gateways/braintree_blue.rb +16 -6
  12. data/lib/active_merchant/billing/gateways/cenpos.rb +272 -0
  13. data/lib/active_merchant/billing/gateways/epay.rb +8 -9
  14. data/lib/active_merchant/billing/gateways/exact.rb +9 -0
  15. data/lib/active_merchant/billing/gateways/fat_zebra.rb +33 -0
  16. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +49 -3
  17. data/lib/active_merchant/billing/gateways/inspire.rb +7 -1
  18. data/lib/active_merchant/billing/gateways/iridium.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -2
  20. data/lib/active_merchant/billing/gateways/monei.rb +307 -0
  21. data/lib/active_merchant/billing/gateways/nab_transact.rb +47 -37
  22. data/lib/active_merchant/billing/gateways/netbilling.rb +40 -7
  23. data/lib/active_merchant/billing/gateways/orbital.rb +45 -21
  24. data/lib/active_merchant/billing/gateways/pay_conex.rb +246 -0
  25. data/lib/active_merchant/billing/gateways/pay_hub.rb +213 -0
  26. data/lib/active_merchant/billing/gateways/paymill.rb +3 -2
  27. data/lib/active_merchant/billing/gateways/pin.rb +2 -2
  28. data/lib/active_merchant/billing/gateways/quickbooks.rb +6 -4
  29. data/lib/active_merchant/billing/gateways/qvalent.rb +179 -0
  30. data/lib/active_merchant/billing/gateways/redsys.rb +29 -15
  31. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
  32. data/lib/active_merchant/billing/gateways/stripe.rb +12 -3
  33. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +8 -3
  34. data/lib/active_merchant/billing/gateways/worldpay.rb +2 -2
  35. data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +205 -0
  36. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +12 -4
  37. data/lib/active_merchant/billing/response.rb +1 -1
  38. data/lib/active_merchant/posts_data.rb +6 -0
  39. data/lib/active_merchant/version.rb +1 -1
  40. data/lib/support/outbound_hosts.rb +13 -10
  41. metadata +9 -2
  42. 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, url, parameters=nil)
67
+ def commit(method, action, parameters=nil)
67
68
  begin
68
- raw_response = ssl_request(method, "https://api.paymill.com/v2/#{url}", post_data(parameters), headers)
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 a certin amount of the original
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((\"card\":{\"number\":\")\d+), '\1[FILTERED]').
108
- gsub(%r((\"cvc\":\")\d+), '\1[FILTERED]')
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
- response['errors'].present? ? FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) : true
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(" ") : "Transaction Approved"
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.sermepa.es:25443/sis/operaciones"
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, creditcard, options = {})
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
- add_creditcard(data, creditcard)
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, creditcard, options = {})
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
- add_creditcard(data, creditcard)
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 add_creditcard(data, card)
248
- name = [card.first_name, card.last_name].join(' ').slice(0, 60)
249
- year = sprintf("%.4i", card.year)
250
- month = sprintf("%.2i", card.month)
251
- data[:card] = {
252
- :name => name,
253
- :pan => card.number,
254
- :date => "#{year[2..3]}#{month}",
255
- :cvv => card.verification_value
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 => "3.00"
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
- post = { :refNum => authorization }
98
- commit(:void, post)
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 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)
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