activemerchant 1.91.0 → 1.92.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c313ba80bb5c2c04320b78d8b7248c86cb569869f9b9b25b450de62bec97dfde
4
- data.tar.gz: 8fb0d7c5d581d5407cb68d7a545e8c8a52d377a232cb919804e9218fd4363d48
3
+ metadata.gz: 7269c204ea4aba074e22b8d23828116e472e5537bee163e0e60159192609cc27
4
+ data.tar.gz: 4b42091ca8a875d56f2bd1cc53d8440e63733930143ee662d9ca5ef87ff3cd00
5
5
  SHA512:
6
- metadata.gz: d583c8da5c326c3c3e12589f4fa37ed10243d3be91098436e085bf1ccba255a6ae8e27cbe657d022d5740bbb03b0097803ae10ec8c85c327b436f7c3e33fccb4
7
- data.tar.gz: cac4ea1716084ca36cd0e2a66f3940e34728c0e4731c85b5c569bf7b0aa0e56097c2b71b56e3c0fef5c1a004e8a014abdc1ed448e0ab22201c3aaca80c39cec1
6
+ metadata.gz: 8a4cf32bc3055e7f8f6663a024f26424a498ec66f746a703f504723275f969f67fbfbd1cb4b7824245deef9dc7769dd94451a0d7a182491a19439a9df258f624
7
+ data.tar.gz: 847b1508be32ccd72185dc2e6d58075e4e6fecf693e069a068ab34dee1c8fd848e6dc760bf44708a9e4d97ca00fbcec9955b6612311555194d3bb5ee9e6351a2
data/CHANGELOG CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  == HEAD
4
4
 
5
+ == Version 1.91.0 (April 8, 2019)
6
+ * BluePay: Send customer IP address when provided [jknipp] #3149
7
+ * PaymentExpress: Use ip field for client_info field [jknipp] #3150
8
+ * Bambora Asia-Pacific: Adds Store [molbrown] #3147
9
+ * Orbital: Pass normalized stored credential fields [curiousepic] #3148
10
+ * Adds Elo card type in general and specifically to Adyen [deedeelavinder] #3153
11
+ * Mercado Pago: Adds Elo card type [deedeelavinder] #3156
12
+ * Litle: Add support for stored credentials [bayprogrammer] #3155
13
+ * Adyen: Correctly process risk_data option [bayprogrammer] #3161
14
+ * Paymentez: Adds Elo card type [deedeelavinder] #3162
15
+ * WorldPay: Adds Elo card type [deedeelavinder] #3163
16
+ * Adyen: Idempotency for non-purchase requests [molbrown] #3164
17
+ * FirstData e4 v27: Support v28 url and stored creds [curiousepic] #3165
18
+ * WorldPay: Fix element order for 3DS + stored cred [bayprogrammer] #3172
19
+ * Braintree: Add risk data to returned response [jknipp] #3169
20
+ * Adyen: Support idempotency on purchase [molbrown] #3168
21
+ * Adyen: Pass phone, statement, device_fingerprint [curiousepic] #3178
22
+ * Adyen: Fix adding phone from billing address [curiousepic] #3179
23
+ * Fix partial or missing address exceptions [molbrown] #3180
24
+ * Adyen: Update to support normalized stored credential fields [molbrown] #3182
25
+ * VisaNet Peru: Always include DSC_COD_ACCION [bayprogrammer] #3174
26
+ * Adyen: Support adjust action [curiousepic] #3190
27
+ * CyberSource: Add support for stored credentials [therufs] #3185
28
+
5
29
  == Version 1.91.0 (February 22, 2019)
6
30
  * WorldPay: Pull CVC and AVS Result from Response [nfarve] #3106
7
31
  * Worldpay: Add AVS and CVC Mapping [nfarve] #3107
@@ -18,6 +18,7 @@ module ActiveMerchant #:nodoc:
18
18
  # * Dankort
19
19
  # * Maestro
20
20
  # * Forbrugsforeningen
21
+ # * Elo
21
22
  #
22
23
  # For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of
23
24
  # validations, allowing you to focus on your core concerns until you're ready to be more concerned
@@ -88,6 +89,7 @@ module ActiveMerchant #:nodoc:
88
89
  # * +'dankort'+
89
90
  # * +'maestro'+
90
91
  # * +'forbrugsforeningen'+
92
+ # * +'elo'+
91
93
  #
92
94
  # Or, if you wish to test your implementation, +'bogus'+.
93
95
  #
@@ -5,6 +5,7 @@ module ActiveMerchant #:nodoc:
5
5
  CARD_COMPANY_DETECTORS = {
6
6
  'visa' => ->(num) { num =~ /^4\d{12}(\d{3})?(\d{3})?$/ },
7
7
  'master' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MASTERCARD_RANGES) },
8
+ 'elo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ELO_RANGES) },
8
9
  'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}|(62\d{14,17})$/ },
9
10
  'american_express' => ->(num) { num =~ /^3[47]\d{13}$/ },
10
11
  'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11}$/ },
@@ -66,6 +67,18 @@ module ActiveMerchant #:nodoc:
66
67
  (670000..679999),
67
68
  ]
68
69
 
70
+ # https://dev.elo.com.br/apis/tabela-de-bins, download csv from left sidebar
71
+ ELO_RANGES = [
72
+ 506707..506708, 506715..506715, 506718..506722, 506724..506724, 506726..506736, 506739..506739, 506741..506743,
73
+ 506745..506747, 506753..506753, 506774..506776, 506778..506778, 509000..509001, 509003..509003, 509007..509007,
74
+ 509020..509022, 509035..509035, 509039..509042, 509045..509045, 509048..509048, 509051..509071, 509073..509074,
75
+ 509077..509080, 509084..509084, 509091..509094, 509098..509098, 509100..509100, 509104..509104, 509106..509109,
76
+ 627780..627780, 636368..636368, 650031..650033, 650035..650045, 650047..650047, 650406..650410, 650434..650436,
77
+ 650439..650439, 650485..650504, 650506..650530, 650577..650580, 650582..650591, 650721..650727, 650901..650922,
78
+ 650928..650928, 650938..650939, 650946..650948, 650954..650955, 650962..650963, 650967..650967, 650971..650971,
79
+ 651652..651667, 651675..651678, 655000..655010, 655012..655015, 655051..655052, 655056..655057
80
+ ]
81
+
69
82
  def self.included(base)
70
83
  base.extend(ClassMethods)
71
84
  end
@@ -9,7 +9,7 @@ module ActiveMerchant #:nodoc:
9
9
 
10
10
  self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US']
11
11
  self.default_currency = 'USD'
12
- self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover]
12
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo]
13
13
 
14
14
  self.money_format = :cents
15
15
 
@@ -38,7 +38,7 @@ module ActiveMerchant #:nodoc:
38
38
  else
39
39
  MultiResponse.run do |r|
40
40
  r.process { authorize(money, payment, options) }
41
- r.process { capture(money, r.authorization, options) }
41
+ r.process { capture(money, r.authorization, capture_options(options)) }
42
42
  end
43
43
  end
44
44
  end
@@ -49,31 +49,38 @@ module ActiveMerchant #:nodoc:
49
49
  add_invoice(post, money, options)
50
50
  add_payment(post, payment)
51
51
  add_extra_data(post, payment, options)
52
- add_shopper_interaction(post, payment, options)
52
+ add_stored_credentials(post, payment, options)
53
53
  add_address(post, options)
54
54
  add_installments(post, options) if options[:installments]
55
55
  add_3ds(post, options)
56
- commit('authorise', post)
56
+ commit('authorise', post, options)
57
57
  end
58
58
 
59
59
  def capture(money, authorization, options={})
60
60
  post = init_post(options)
61
61
  add_invoice_for_modification(post, money, options)
62
62
  add_reference(post, authorization, options)
63
- commit('capture', post)
63
+ commit('capture', post, options)
64
64
  end
65
65
 
66
66
  def refund(money, authorization, options={})
67
67
  post = init_post(options)
68
68
  add_invoice_for_modification(post, money, options)
69
69
  add_original_reference(post, authorization, options)
70
- commit('refund', post)
70
+ commit('refund', post, options)
71
71
  end
72
72
 
73
73
  def void(authorization, options={})
74
74
  post = init_post(options)
75
75
  add_reference(post, authorization, options)
76
- commit('cancel', post)
76
+ commit('cancel', post, options)
77
+ end
78
+
79
+ def adjust(money, authorization, options={})
80
+ post = init_post(options)
81
+ add_invoice_for_modification(post, money, options)
82
+ add_reference(post, authorization, options)
83
+ commit('adjustAuthorisation', post, options)
77
84
  end
78
85
 
79
86
  def store(credit_card, options={})
@@ -82,14 +89,16 @@ module ActiveMerchant #:nodoc:
82
89
  add_invoice(post, 0, options)
83
90
  add_payment(post, credit_card)
84
91
  add_extra_data(post, credit_card, options)
92
+ add_stored_credentials(post, credit_card, options)
85
93
  add_recurring_contract(post, options)
86
94
  add_address(post, options)
87
- commit('authorise', post)
95
+ commit('authorise', post, options)
88
96
  end
89
97
 
90
98
  def verify(credit_card, options={})
91
99
  MultiResponse.run(:use_first_response) do |r|
92
100
  r.process { authorize(0, credit_card, options) }
101
+ options[:idempotency_key] = nil
93
102
  r.process(:ignore_result) { void(r.authorization, options) }
94
103
  end
95
104
  end
@@ -151,9 +160,11 @@ module ActiveMerchant #:nodoc:
151
160
  }
152
161
 
153
162
  def add_extra_data(post, payment, options)
163
+ post[:telephoneNumber] = options[:billing_address][:phone] if options.dig(:billing_address, :phone)
154
164
  post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
155
165
  post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
156
166
  post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
167
+ post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
157
168
  post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
158
169
  post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
159
170
  post[:selectedBrand] ||= NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
@@ -163,18 +174,24 @@ module ActiveMerchant #:nodoc:
163
174
  post[:additionalData][:overwriteBrand] = normalize(options[:overwrite_brand]) if options[:overwrite_brand]
164
175
  post[:additionalData][:customRoutingFlag] = options[:custom_routing_flag] if options[:custom_routing_flag]
165
176
  post[:additionalData]['paymentdatasource.type'] = NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
177
+ post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
166
178
  add_risk_data(post, options)
167
179
  end
168
180
 
169
181
  def add_risk_data(post, options)
170
- risk_data = {}
171
- risk_data.merge!(options[:risk_data]) if options[:risk_data]
182
+ if (risk_data = options[:risk_data])
183
+ risk_data = Hash[risk_data.map { |k, v| ["riskdata.#{k}", v] }]
184
+ post[:additionalData].merge!(risk_data)
185
+ end
186
+ end
172
187
 
173
- post[:additionalData][:riskData] = risk_data unless risk_data.empty?
188
+ def add_stored_credentials(post, payment, options)
189
+ add_shopper_interaction(post, payment, options)
190
+ add_recurring_processing_model(post, options)
174
191
  end
175
192
 
176
193
  def add_shopper_interaction(post, payment, options={})
177
- if (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard)
194
+ if options.dig(:stored_credential, :initial_transaction) || (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard)
178
195
  shopper_interaction = 'Ecommerce'
179
196
  else
180
197
  shopper_interaction = 'ContAuth'
@@ -183,6 +200,17 @@ module ActiveMerchant #:nodoc:
183
200
  post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
184
201
  end
185
202
 
203
+ def add_recurring_processing_model(post, options)
204
+ return unless options.dig(:stored_credential, :reason_type) || options[:recurring_processing_model]
205
+ if options.dig(:stored_credential, :reason_type) && options[:stored_credential][:reason_type] == 'unscheduled'
206
+ recurring_processing_model = 'CardOnFile'
207
+ else
208
+ recurring_processing_model = 'Subscription'
209
+ end
210
+
211
+ post[:recurringProcessingModel] = options[:recurring_processing_model] || recurring_processing_model
212
+ end
213
+
186
214
  def add_address(post, options)
187
215
  return unless post[:card]&.kind_of?(Hash)
188
216
  if (address = options[:billing_address] || options[:address]) && address[:country]
@@ -202,7 +230,6 @@ module ActiveMerchant #:nodoc:
202
230
  currency: options[:currency] || currency(money)
203
231
  }
204
232
  post[:amount] = amount
205
- post[:recurringProcessingModel] = options[:recurring_processing_model] if options[:recurring_processing_model]
206
233
  end
207
234
 
208
235
  def add_invoice_for_modification(post, money, options)
@@ -239,6 +266,11 @@ module ActiveMerchant #:nodoc:
239
266
  post[:card] = card
240
267
  end
241
268
 
269
+ def capture_options(options)
270
+ return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key]
271
+ options
272
+ end
273
+
242
274
  def add_reference(post, authorization, options = {})
243
275
  _, psp_reference, _ = authorization.split('#')
244
276
  post[:originalReference] = single_reference(authorization) || psp_reference
@@ -286,9 +318,9 @@ module ActiveMerchant #:nodoc:
286
318
  JSON.parse(body)
287
319
  end
288
320
 
289
- def commit(action, parameters)
321
+ def commit(action, parameters, options)
290
322
  begin
291
- raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers)
323
+ raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers(options))
292
324
  response = parse(raw_response)
293
325
  rescue ResponseError => e
294
326
  raw_response = e.response.body
@@ -329,18 +361,20 @@ module ActiveMerchant #:nodoc:
329
361
  Base64.strict_encode64("#{@username}:#{@password}")
330
362
  end
331
363
 
332
- def request_headers
333
- {
364
+ def request_headers(options)
365
+ headers = {
334
366
  'Content-Type' => 'application/json',
335
367
  'Authorization' => "Basic #{basic_auth}"
336
368
  }
369
+ headers['Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
370
+ headers
337
371
  end
338
372
 
339
373
  def success_from(action, response)
340
374
  case action.to_s
341
375
  when 'authorise', 'authorise3d'
342
376
  ['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode'])
343
- when 'capture', 'refund', 'cancel'
377
+ when 'capture', 'refund', 'cancel', 'adjustAuthorisation'
344
378
  response['response'] == "[#{action}-received]"
345
379
  else
346
380
  false
@@ -3,8 +3,8 @@ require 'nokogiri'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class BamboraApacGateway < Gateway
6
- self.live_url = 'https://www.bambora.co.nz/interface/api/dts.asmx'
7
- self.test_url = 'https://demo.bambora.co.nz/interface/api/dts.asmx'
6
+ self.live_url = 'https://www.bambora.co.nz/interface/api'
7
+ self.test_url = 'https://demo.bambora.co.nz/interface/api'
8
8
 
9
9
  self.supported_countries = ['AU', 'NZ']
10
10
  self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
@@ -32,7 +32,7 @@ module ActiveMerchant #:nodoc:
32
32
  xml.CustRef options[:order_id]
33
33
  add_amount(xml, money)
34
34
  xml.TrnType '1'
35
- add_credit_card(xml, payment)
35
+ add_payment(xml, payment)
36
36
  add_credentials(xml, options)
37
37
  xml.TrnSource options[:ip]
38
38
  end
@@ -45,7 +45,7 @@ module ActiveMerchant #:nodoc:
45
45
  xml.CustRef options[:order_id]
46
46
  add_amount(xml, money)
47
47
  xml.TrnType '2'
48
- add_credit_card(xml, payment)
48
+ add_payment(xml, payment)
49
49
  add_credentials(xml, options)
50
50
  xml.TrnSource options[:ip]
51
51
  end
@@ -82,6 +82,19 @@ module ActiveMerchant #:nodoc:
82
82
  end
83
83
  end
84
84
 
85
+ def store(payment, options={})
86
+ commit('TokeniseCreditCard') do |xml|
87
+ xml.TokeniseCreditCard do
88
+ xml.CardNumber payment.number
89
+ xml.ExpM format(payment.month, :two_digits)
90
+ xml.ExpY format(payment.year, :four_digits)
91
+ xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
92
+ xml.UserName @options[:username]
93
+ xml.Password @options[:password]
94
+ end
95
+ end
96
+ end
97
+
85
98
  def supports_scrubbing?
86
99
  true
87
100
  end
@@ -107,6 +120,21 @@ module ActiveMerchant #:nodoc:
107
120
  xml.Amount amount(money)
108
121
  end
109
122
 
123
+ def add_payment(xml, payment)
124
+ if payment.is_a?(String)
125
+ add_token(xml, payment)
126
+ else
127
+ add_credit_card(xml, payment)
128
+ end
129
+ end
130
+
131
+ def add_token(xml, payment)
132
+ xml.CreditCard do
133
+ xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
134
+ xml.CardNumber payment
135
+ end
136
+ end
137
+
110
138
  def add_credit_card(xml, payment)
111
139
  xml.CreditCard :Registered => 'False' do
112
140
  xml.CardNumber payment.number
@@ -131,9 +159,9 @@ module ActiveMerchant #:nodoc:
131
159
  def commit(action, &block)
132
160
  headers = {
133
161
  'Content-Type' => 'text/xml; charset=utf-8',
134
- 'SOAPAction' => "http://www.ippayments.com.au/interface/api/dts/#{action}",
162
+ 'SOAPAction' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}/#{action}"
135
163
  }
136
- response = parse(ssl_post(commit_url, new_submit_xml(action, &block), headers))
164
+ response = parse(ssl_post("#{commit_url}/#{endpoint(action)}.asmx", new_submit_xml(action, &block), headers))
137
165
 
138
166
  Response.new(
139
167
  success_from(response),
@@ -150,11 +178,19 @@ module ActiveMerchant #:nodoc:
150
178
  xml.instruct!
151
179
  xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
152
180
  xml.soap :Body do
153
- xml.__send__(action, 'xmlns' => 'http://www.ippayments.com.au/interface/api/dts') do
154
- xml.trnXML do
155
- inner_xml = Builder::XmlMarkup.new(indent: 2)
156
- yield(inner_xml)
157
- xml.cdata!(inner_xml.target!)
181
+ xml.__send__(action, 'xmlns' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}") do
182
+ if action == 'TokeniseCreditCard'
183
+ xml.tokeniseCreditCardXML do
184
+ inner_xml = Builder::XmlMarkup.new(indent: 2)
185
+ yield(inner_xml)
186
+ xml.cdata!(inner_xml.target!)
187
+ end
188
+ else
189
+ xml.trnXML do
190
+ inner_xml = Builder::XmlMarkup.new(indent: 2)
191
+ yield(inner_xml)
192
+ xml.cdata!(inner_xml.target!)
193
+ end
158
194
  end
159
195
  end
160
196
  end
@@ -162,12 +198,16 @@ module ActiveMerchant #:nodoc:
162
198
  xml.target!
163
199
  end
164
200
 
201
+ def endpoint(action)
202
+ action == 'TokeniseCreditCard' ? 'sipp' : 'dts'
203
+ end
204
+
165
205
  def commit_url
166
206
  test? ? test_url : live_url
167
207
  end
168
208
 
169
209
  def success_from(response)
170
- response[:response_code] == '0'
210
+ response[:response_code] == '0' || response[:return_value] == '0'
171
211
  end
172
212
 
173
213
  def error_code_from(response)
@@ -175,11 +215,11 @@ module ActiveMerchant #:nodoc:
175
215
  end
176
216
 
177
217
  def message_from(response)
178
- response[:declined_message]
218
+ success_from(response) ? 'Succeeded' : response[:declined_message]
179
219
  end
180
220
 
181
221
  def authorization_from(response)
182
- response[:receipt]
222
+ response[:receipt] || response[:token]
183
223
  end
184
224
  end
185
225
  end
@@ -24,7 +24,7 @@ module ActiveMerchant #:nodoc:
24
24
  'REBID' => :rebid,
25
25
  'TRANS_TYPE' => :trans_type,
26
26
  'PAYMENT_ACCOUNT_MASK' => :acct_mask,
27
- 'CARD_TYPE' => :card_type,
27
+ 'CARD_TYPE' => :card_type
28
28
  }
29
29
 
30
30
  REBILL_FIELD_MAP = {
@@ -41,6 +41,7 @@ module ActiveMerchant #:nodoc:
41
41
  'REB_AMOUNT' => :rebill_amount,
42
42
  'NEXT_AMOUNT' => :next_amount,
43
43
  'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc.
44
+ 'CUST_TOKEN' => :cust_token
44
45
  }
45
46
 
46
47
  self.supported_countries = ['US', 'CA']
@@ -84,7 +85,7 @@ module ActiveMerchant #:nodoc:
84
85
  add_rebill(post, options) if options[:rebill]
85
86
  add_duplicate_override(post, options)
86
87
  post[:TRANS_TYPE] = 'AUTH'
87
- commit('AUTH_ONLY', money, post)
88
+ commit('AUTH_ONLY', money, post, options)
88
89
  end
89
90
 
90
91
  # Perform a purchase, which is essentially an authorization and capture in a single operation.
@@ -107,7 +108,7 @@ module ActiveMerchant #:nodoc:
107
108
  add_rebill(post, options) if options[:rebill]
108
109
  add_duplicate_override(post, options)
109
110
  post[:TRANS_TYPE] = 'SALE'
110
- commit('AUTH_CAPTURE', money, post)
111
+ commit('AUTH_CAPTURE', money, post, options)
111
112
  end
112
113
 
113
114
  # Captures the funds from an authorize transaction.
@@ -123,7 +124,7 @@ module ActiveMerchant #:nodoc:
123
124
  add_customer_data(post, options)
124
125
  post[:MASTER_ID] = identification
125
126
  post[:TRANS_TYPE] = 'CAPTURE'
126
- commit('PRIOR_AUTH_CAPTURE', money, post)
127
+ commit('PRIOR_AUTH_CAPTURE', money, post, options)
127
128
  end
128
129
 
129
130
  # Void a previous transaction
@@ -136,7 +137,7 @@ module ActiveMerchant #:nodoc:
136
137
  post = {}
137
138
  post[:MASTER_ID] = identification
138
139
  post[:TRANS_TYPE] = 'VOID'
139
- commit('VOID', nil, post)
140
+ commit('VOID', nil, post, options)
140
141
  end
141
142
 
142
143
  # Performs a credit.
@@ -169,7 +170,7 @@ module ActiveMerchant #:nodoc:
169
170
  add_invoice(post, options)
170
171
  add_address(post, options)
171
172
  add_customer_data(post, options)
172
- commit('CREDIT', money, post)
173
+ commit('CREDIT', money, post, options)
173
174
  end
174
175
 
175
176
  def credit(money, payment_object, options = {})
@@ -189,7 +190,7 @@ module ActiveMerchant #:nodoc:
189
190
  add_invoice(post, options)
190
191
  add_address(post, options)
191
192
  add_customer_data(post, options)
192
- commit('CREDIT', money, post)
193
+ commit('CREDIT', money, post, options)
193
194
  end
194
195
 
195
196
  # Create a new recurring payment.
@@ -313,10 +314,11 @@ module ActiveMerchant #:nodoc:
313
314
 
314
315
  private
315
316
 
316
- def commit(action, money, fields)
317
+ def commit(action, money, fields, options = {})
317
318
  fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill')
318
319
  fields[:MODE] = (test? ? 'TEST' : 'LIVE')
319
320
  fields[:ACCOUNT_ID] = @options[:login]
321
+ fields[:CUSTOMER_IP] = options[:ip] if options[:ip]
320
322
 
321
323
  if action == 'rebill'
322
324
  url = rebilling_url
@@ -543,6 +543,17 @@ module ActiveMerchant #:nodoc:
543
543
  'token' => transaction.credit_card_details.token
544
544
  }
545
545
 
546
+ if transaction.risk_data
547
+ risk_data = {
548
+ 'id' => transaction.risk_data.id,
549
+ 'decision' => transaction.risk_data.decision,
550
+ 'device_data_captured' => transaction.risk_data.device_data_captured,
551
+ 'fraud_service_provider' => transaction.risk_data.fraud_service_provider
552
+ }
553
+ else
554
+ risk_data = nil
555
+ end
556
+
546
557
  {
547
558
  'order_id' => transaction.order_id,
548
559
  'amount' => transaction.amount.to_s,
@@ -553,6 +564,7 @@ module ActiveMerchant #:nodoc:
553
564
  'shipping_details' => shipping_details,
554
565
  'vault_customer' => vault_customer,
555
566
  'merchant_account_id' => transaction.merchant_account_id,
567
+ 'risk_data' => risk_data,
556
568
  'processor_response_code' => response_code_from_result(result)
557
569
  }
558
570
  end
@@ -24,7 +24,7 @@ module ActiveMerchant #:nodoc:
24
24
  self.test_url = 'https://ics2wstesta.ic3.com/commerce/1.x/transactionProcessor'
25
25
  self.live_url = 'https://ics2wsa.ic3.com/commerce/1.x/transactionProcessor'
26
26
 
27
- XSD_VERSION = '1.121'
27
+ XSD_VERSION = '1.153'
28
28
 
29
29
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro]
30
30
  self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB)
@@ -260,6 +260,7 @@ module ActiveMerchant #:nodoc:
260
260
  add_threeds_services(xml, options)
261
261
  add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
262
262
  add_business_rules_data(xml, creditcard_or_reference, options)
263
+ add_stored_credential_options(xml, options)
263
264
  xml.target!
264
265
  end
265
266
 
@@ -513,7 +514,24 @@ module ActiveMerchant #:nodoc:
513
514
  if network_tokenization?(payment_method)
514
515
  add_auth_network_tokenization(xml, payment_method, options)
515
516
  else
516
- xml.tag! 'ccAuthService', {'run' => 'true'}
517
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
518
+ check_for_stored_cred_commerce_indicator(xml, options)
519
+ end
520
+ end
521
+ end
522
+
523
+ def check_for_stored_cred_commerce_indicator(xml, options)
524
+ return unless options[:stored_credential]
525
+ if commerce_indicator(options)
526
+ xml.tag!('commerceIndicator', commerce_indicator(options))
527
+ end
528
+ end
529
+
530
+ def commerce_indicator(options)
531
+ return if options[:stored_credential][:initial_transaction]
532
+ case options[:stored_credential][:reason_type]
533
+ when 'installment' then 'install'
534
+ when 'recurring' then 'recurring'
517
535
  end
518
536
  end
519
537
 
@@ -680,6 +698,18 @@ module ActiveMerchant #:nodoc:
680
698
  country_code&.code(:alpha2)
681
699
  end
682
700
 
701
+ def add_stored_credential_options(xml, options={})
702
+ return unless options[:stored_credential]
703
+ if options[:stored_credential][:initial_transaction]
704
+ xml.tag! 'subsequentAuthFirst', 'true'
705
+ elsif options[:stored_credential][:reason_type] == 'unscheduled'
706
+ xml.tag! 'subsequentAuth', 'true'
707
+ xml.tag! 'subsequentAuthTransactionID', options[:stored_credential][:network_transaction_id]
708
+ else
709
+ xml.tag! 'subsequentAuthTransactionID', options[:stored_credential][:network_transaction_id]
710
+ end
711
+ end
712
+
683
713
  # Where we actually build the full SOAP request using builder
684
714
  def build_request(body, options)
685
715
  xml = Builder::XmlMarkup.new :indent => 2
@@ -93,7 +93,7 @@ module ActiveMerchant #:nodoc:
93
93
  end
94
94
 
95
95
  def lookup_country_code(country)
96
- Country.find(country).code(:alpha2)
96
+ Country.find(country).code(:alpha2).value
97
97
  end
98
98
 
99
99
  def add_payer(post, card, options)
@@ -102,7 +102,7 @@ module ActiveMerchant #:nodoc:
102
102
  post[:payer][:name] = card.name
103
103
  post[:payer][:email] = options[:email] if options[:email]
104
104
  post[:payer][:birth_date] = options[:birth_date] if options[:birth_date]
105
- post[:payer][:phone] = address[:phone] if address[:phone]
105
+ post[:payer][:phone] = address[:phone] if address && address[:phone]
106
106
  post[:payer][:document] = options[:document] if options[:document]
107
107
  post[:payer][:document2] = options[:document2] if options[:document2]
108
108
  post[:payer][:user_reference] = options[:user_reference] if options[:user_reference]
@@ -1,8 +1,8 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class FirstdataE4V27Gateway < Gateway
4
- self.test_url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v27'
5
- self.live_url = 'https://api.globalgatewaye4.firstdata.com/transaction/v27'
4
+ self.test_url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v28'
5
+ self.live_url = 'https://api.globalgatewaye4.firstdata.com/transaction/v28'
6
6
 
7
7
  TRANSACTIONS = {
8
8
  sale: '00',
@@ -148,6 +148,7 @@ module ActiveMerchant #:nodoc:
148
148
  add_credit_card_token(xml, credit_card_or_store_authorization, options)
149
149
  else
150
150
  add_credit_card(xml, credit_card_or_store_authorization, options)
151
+ add_stored_credentials(xml, credit_card_or_store_authorization, options)
151
152
  end
152
153
 
153
154
  add_address(xml, options)
@@ -312,6 +313,35 @@ module ActiveMerchant #:nodoc:
312
313
  xml.tag!('Level3') { |x| x << options[:level_3] } if options[:level_3]
313
314
  end
314
315
 
316
+ def add_stored_credentials(xml, card, options)
317
+ return unless options[:stored_credential]
318
+ xml.tag! 'StoredCredentials' do
319
+ xml.tag! 'Indicator', stored_credential_indicator(xml, card, options)
320
+ if initiator = options.dig(:stored_credential, :initiator)
321
+ xml.tag! initiator == 'merchant' ? 'M' : 'C'
322
+ end
323
+ if reason_type = options.dig(:stored_credential, :reason_type)
324
+ xml.tag! 'Schedule', reason_type == 'unscheduled' ? 'U' : 'S'
325
+ end
326
+ xml.tag! 'AuthorizationTypeOverride', options[:authorization_type_override] if options[:authorization_type_override]
327
+ if network_transaction_id = options[:stored_credential][:network_transaction_id]
328
+ xml.tag! 'TransactionId', network_transaction_id
329
+ else
330
+ xml.tag! 'TransactionId', 'new'
331
+ end
332
+ xml.tag! 'OriginalAmount', options[:original_amount] if options[:original_amount]
333
+ xml.tag! 'ProtectbuyIndicator', options[:protectbuy_indicator] if options[:protectbuy_indicator]
334
+ end
335
+ end
336
+
337
+ def stored_credential_indicator(xml, card, options)
338
+ if card.brand == 'master' || options.dig(:stored_credential, :initial_transaction) == false
339
+ 'S'
340
+ else
341
+ '1'
342
+ end
343
+ end
344
+
315
345
  def expdate(credit_card)
316
346
  "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
317
347
  end
@@ -438,9 +468,18 @@ module ActiveMerchant #:nodoc:
438
468
 
439
469
  def parse_elements(response, root)
440
470
  root.elements.to_a.each do |node|
441
- response[node.name.gsub(/EXact/, 'Exact').underscore.to_sym] = (node.text || '').strip
471
+ if node.has_elements?
472
+ parse_elements(response, node)
473
+ else
474
+ response[name_node(root, node)] = (node.text || '').strip
475
+ end
442
476
  end
443
477
  end
478
+
479
+ def name_node(root, node)
480
+ parent = root.name unless root.name == 'TransactionResult'
481
+ "#{parent}#{node.name}".gsub(/EXact/, 'Exact').underscore.to_sym
482
+ end
444
483
  end
445
484
  end
446
485
  end
@@ -3,7 +3,7 @@ require 'nokogiri'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class LitleGateway < Gateway
6
- SCHEMA_VERSION = '9.12'
6
+ SCHEMA_VERSION = '9.14'
7
7
 
8
8
  self.test_url = 'https://www.testvantivcnp.com/sandbox/communicator/online'
9
9
  self.live_url = 'https://payments.vantivcnp.com/vap/communicator/online'
@@ -223,6 +223,7 @@ module ActiveMerchant #:nodoc:
223
223
  add_descriptor(doc, options)
224
224
  add_merchant_data(doc, options)
225
225
  add_debt_repayment(doc, options)
226
+ add_stored_credential_params(doc, options)
226
227
  end
227
228
 
228
229
  def add_merchant_data(doc, options={})
@@ -293,6 +294,38 @@ module ActiveMerchant #:nodoc:
293
294
  end
294
295
  end
295
296
 
297
+ def add_stored_credential_params(doc, options={})
298
+ return unless options[:stored_credential]
299
+
300
+ if options[:stored_credential][:initial_transaction]
301
+ add_stored_credential_params_initial(doc, options)
302
+ else
303
+ add_stored_credential_params_used(doc, options)
304
+ end
305
+ end
306
+
307
+ def add_stored_credential_params_initial(doc, options)
308
+ case options[:stored_credential][:reason_type]
309
+ when 'unscheduled'
310
+ doc.processingType('initialCOF')
311
+ when 'installment'
312
+ doc.processingType('initialInstallment')
313
+ when 'recurring'
314
+ doc.processingType('initialRecurring')
315
+ end
316
+ end
317
+
318
+ def add_stored_credential_params_used(doc, options)
319
+ if options[:stored_credential][:reason_type] == 'unscheduled'
320
+ if options[:stored_credential][:initiator] == 'merchant'
321
+ doc.processingType('merchantInitiatedCOF')
322
+ else
323
+ doc.processingType('cardholderInitiatedCOF')
324
+ end
325
+ end
326
+ doc.originalNetworkTransactionId(options[:stored_credential][:network_transaction_id])
327
+ end
328
+
296
329
  def add_billing_address(doc, payment_method, options)
297
330
  return if payment_method.is_a?(String)
298
331
 
@@ -332,8 +365,9 @@ module ActiveMerchant #:nodoc:
332
365
  end
333
366
 
334
367
  def add_order_source(doc, payment_method, options)
335
- if options[:order_source]
336
- doc.orderSource(options[:order_source])
368
+ order_source = order_source(options)
369
+ if order_source
370
+ doc.orderSource(order_source)
337
371
  elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay
338
372
  doc.orderSource('applepay')
339
373
  elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :android_pay
@@ -345,6 +379,30 @@ module ActiveMerchant #:nodoc:
345
379
  end
346
380
  end
347
381
 
382
+ def order_source(options={})
383
+ return options[:order_source] unless options[:stored_credential]
384
+ order_source = nil
385
+
386
+ case options[:stored_credential][:reason_type]
387
+ when 'unscheduled'
388
+ if options[:stored_credential][:initiator] == 'merchant'
389
+ # For merchant-initiated, we should always set order source to
390
+ # 'ecommerce'
391
+ order_source = 'ecommerce'
392
+ else
393
+ # For cardholder-initiated, we rely on #add_order_source's
394
+ # default logic to set orderSource appropriately
395
+ order_source = options[:order_source]
396
+ end
397
+ when 'installment'
398
+ order_source = 'installment'
399
+ when 'recurring'
400
+ order_source = 'recurring'
401
+ end
402
+
403
+ order_source
404
+ end
405
+
348
406
  def add_pos(doc, payment_method)
349
407
  return unless payment_method.respond_to?(:track_data) && payment_method.track_data.present?
350
408
 
@@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc:
4
4
  self.live_url = self.test_url = 'https://api.mercadopago.com/v1'
5
5
 
6
6
  self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY']
7
- self.supported_cardtypes = [:visa, :master, :american_express]
7
+ self.supported_cardtypes = [:visa, :master, :american_express, :elo]
8
8
 
9
9
  self.homepage_url = 'https://www.mercadopago.com/'
10
10
  self.display_name = 'Mercado Pago'
@@ -128,8 +128,8 @@ module ActiveMerchant #:nodoc:
128
128
  def add_shipping_address(post, options)
129
129
  if address = options[:shipping_address]
130
130
  post[:address] = {}
131
- post[:address][:street] = address[:address1].match(/\D+/)[0].strip if address[:address1]
132
- post[:address][:number] = address[:address1].match(/\d+/)[0] if address[:address1]
131
+ post[:address][:street] = address[:address1].match(/\D+/)[0].strip if address[:address1]&.match(/\D+/)
132
+ post[:address][:number] = address[:address1].match(/\d+/)[0] if address[:address1]&.match(/\d+/)
133
133
  post[:address][:compliment] = address[:address2] if address[:address2]
134
134
  post[:address][:city] = address[:city] if address[:city]
135
135
  post[:address][:state] = address[:state] if address[:state]
@@ -507,9 +507,33 @@ module ActiveMerchant #:nodoc:
507
507
  end
508
508
 
509
509
  def add_stored_credentials(xml, parameters)
510
- xml.tag! :MITMsgType, parameters[:mit_msg_type] if parameters[:mit_msg_type]
511
- xml.tag! :MITStoredCredentialInd, parameters[:mit_stored_credential_ind] if parameters[:mit_stored_credential_ind]
512
- xml.tag! :MITSubmittedTransactionID, parameters[:mit_submitted_transaction_id] if parameters[:mit_submitted_transaction_id]
510
+ return unless parameters[:mit_stored_credential_ind] == 'Y' || parameters[:stored_credential] && !parameters[:stored_credential].values.all?(&:nil?)
511
+ if msg_type = get_msg_type(parameters)
512
+ xml.tag! :MITMsgType, msg_type
513
+ end
514
+ xml.tag! :MITStoredCredentialInd, 'Y'
515
+ if parameters[:mit_submitted_transaction_id]
516
+ xml.tag! :MITSubmittedTransactionID, parameters[:mit_submitted_transaction_id]
517
+ elsif parameters.dig(:stored_credential, :network_transaction_id) && parameters.dig(:stored_credential, :initiator) == 'merchant'
518
+ xml.tag! :MITSubmittedTransactionID, parameters[:stored_credential][:network_transaction_id]
519
+ end
520
+ end
521
+
522
+ def get_msg_type(parameters)
523
+ return parameters[:mit_msg_type] if parameters[:mit_msg_type]
524
+ return 'CSTO' if parameters[:stored_credential][:initial_transaction]
525
+ return unless parameters[:stored_credential][:initiator] && parameters[:stored_credential][:reason_type]
526
+ initiator = case parameters[:stored_credential][:initiator]
527
+ when 'customer' then 'C'
528
+ when 'merchant' then 'M'
529
+ end
530
+ reason = case parameters[:stored_credential][:reason_type]
531
+ when 'recurring' then 'REC'
532
+ when 'installment' then 'INS'
533
+ when 'unscheduled' then 'USE'
534
+ end
535
+
536
+ "#{initiator}#{reason}"
513
537
  end
514
538
 
515
539
  def parse(body)
@@ -153,6 +153,7 @@ module ActiveMerchant #:nodoc:
153
153
  add_invoice(result, options)
154
154
  add_address_verification_data(result, options)
155
155
  add_optional_elements(result, options)
156
+ add_ip(result, options)
156
157
  result
157
158
  end
158
159
 
@@ -163,6 +164,7 @@ module ActiveMerchant #:nodoc:
163
164
  add_invoice(result, options)
164
165
  add_reference(result, identification)
165
166
  add_optional_elements(result, options)
167
+ add_ip(result, options)
166
168
  result
167
169
  end
168
170
 
@@ -172,6 +174,7 @@ module ActiveMerchant #:nodoc:
172
174
  add_amount(result, 100, options) # need to make an auth request for $1
173
175
  add_token_request(result, options)
174
176
  add_optional_elements(result, options)
177
+ add_ip(result, options)
175
178
  result
176
179
  end
177
180
 
@@ -233,6 +236,10 @@ module ActiveMerchant #:nodoc:
233
236
  xml.add_element('AvsPostCode').text = address[:zip]
234
237
  end
235
238
 
239
+ def add_ip(xml, options)
240
+ xml.add_element('ClientInfo').text = options[:ip] if options[:ip]
241
+ end
242
+
236
243
  # The options hash may contain optional data which will be passed
237
244
  # through the specialized optional fields at PaymentExpress
238
245
  # as follows:
@@ -241,8 +248,7 @@ module ActiveMerchant #:nodoc:
241
248
  # :client_type => :web, # Possible values are: :web, :ivr, :moto, :unattended, :internet, or :recurring
242
249
  # :txn_data1 => "String up to 255 characters",
243
250
  # :txn_data2 => "String up to 255 characters",
244
- # :txn_data3 => "String up to 255 characters",
245
- # :client_info => "String up to 15 characters. The IP address of the user who processed the transaction."
251
+ # :txn_data3 => "String up to 255 characters"
246
252
  # }
247
253
  #
248
254
  # +:client_type+, while not documented for PxPost, will be sent as
@@ -278,8 +284,6 @@ module ActiveMerchant #:nodoc:
278
284
  xml.add_element('TxnData1').text = options[:txn_data1].to_s.slice(0, 255) unless options[:txn_data1].blank?
279
285
  xml.add_element('TxnData2').text = options[:txn_data2].to_s.slice(0, 255) unless options[:txn_data2].blank?
280
286
  xml.add_element('TxnData3').text = options[:txn_data3].to_s.slice(0, 255) unless options[:txn_data3].blank?
281
-
282
- xml.add_element('ClientInfo').text = options[:client_info] if options[:client_info]
283
287
  end
284
288
 
285
289
  def new_transaction
@@ -9,7 +9,7 @@ module ActiveMerchant #:nodoc:
9
9
 
10
10
  self.supported_countries = %w[MX EC VE CO BR CL]
11
11
  self.default_currency = 'USD'
12
- self.supported_cardtypes = %i[visa master american_express diners_club]
12
+ self.supported_cardtypes = %i[visa master american_express diners_club elo]
13
13
 
14
14
  self.homepage_url = 'https://secure.paymentez.com/'
15
15
  self.display_name = 'Paymentez'
@@ -38,7 +38,8 @@ module ActiveMerchant #:nodoc:
38
38
  'visa' => 'vi',
39
39
  'master' => 'mc',
40
40
  'american_express' => 'ax',
41
- 'diners_club' => 'di'
41
+ 'diners_club' => 'di',
42
+ 'elo' => 'el'
42
43
  }.freeze
43
44
 
44
45
  def initialize(options = {})
@@ -146,7 +146,9 @@ module ActiveMerchant #:nodoc:
146
146
  xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank?
147
147
  if options[:allow_guest_checkout]
148
148
  xml.tag! 'n2:SolutionType', 'Sole'
149
- xml.tag! 'n2:LandingPage', options[:landing_page] || 'Billing'
149
+ unless options[:paypal_chooses_landing_page]
150
+ xml.tag! 'n2:LandingPage', options[:landing_page] || 'Billing'
151
+ end
150
152
  end
151
153
  xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank?
152
154
 
@@ -103,7 +103,7 @@ module ActiveMerchant
103
103
  post = {}
104
104
 
105
105
  add_amount(post, money, options)
106
- add_credit_card_or_reference(post, credit_card_or_reference)
106
+ add_credit_card_or_reference(post, credit_card_or_reference, options)
107
107
  add_additional_params(:authorize, post, options)
108
108
 
109
109
  post
@@ -217,6 +217,12 @@ module ActiveMerchant
217
217
  post[:card][:expiration] = expdate(credit_card_or_reference)
218
218
  post[:card][:issued_to] = credit_card_or_reference.name
219
219
  end
220
+
221
+ if options[:three_d_secure]
222
+ post[:card][:cavv]= options.dig(:three_d_secure, :cavv)
223
+ post[:card][:eci] = options.dig(:three_d_secure, :eci)
224
+ post[:card][:xav] = options.dig(:three_d_secure, :xid)
225
+ end
220
226
  end
221
227
 
222
228
  def parse(body)
@@ -145,7 +145,11 @@ module ActiveMerchant
145
145
  add_card(xml, credit_card)
146
146
  xml.tag! 'autosettle', 'flag' => auto_settle_flag(action)
147
147
  add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number)
148
- add_network_tokenization_card(xml, credit_card) if credit_card.is_a?(NetworkTokenizationCreditCard)
148
+ if credit_card.is_a?(NetworkTokenizationCreditCard)
149
+ add_network_tokenization_card(xml, credit_card)
150
+ else
151
+ add_three_d_secure(xml, options)
152
+ end
149
153
  add_comments(xml, options)
150
154
  add_address_and_customer_info(xml, options)
151
155
  end
@@ -284,6 +288,16 @@ module ActiveMerchant
284
288
  end
285
289
  end
286
290
 
291
+ def add_three_d_secure(xml, options)
292
+ if options[:three_d_secure]
293
+ xml.tag! 'mpi' do
294
+ xml.tag! 'cavv', options[:three_d_secure][:cavv]
295
+ xml.tag! 'eci', options[:three_d_secure][:eci]
296
+ xml.tag! 'xid', options[:three_d_secure][:xid]
297
+ end
298
+ end
299
+ end
300
+
287
301
  def format_address_code(address)
288
302
  code = [address[:zip].to_s, address[:address1].to_s + address[:address2].to_s]
289
303
  code.collect { |e| e.gsub(/\D/, '') }.reject(&:empty?).join('|')
@@ -57,7 +57,10 @@ module ActiveMerchant #:nodoc:
57
57
  response = commit('cancelDeposit', params, options)
58
58
  return response if response.success? || split_authorization(authorization).length == 1 || !options[:force_full_refund_if_unsettled]
59
59
 
60
- # Attempt RefundSingleTransaction if unsettled
60
+ # Attempt RefundSingleTransaction if unsettled (and stash the original
61
+ # response message so it will be included it in the follow-up response
62
+ # message)
63
+ options[:error_message] = response.message
61
64
  prepare_refund_data(params, authorization, options)
62
65
  commit('refund', params, options)
63
66
  end
@@ -197,15 +200,24 @@ module ActiveMerchant #:nodoc:
197
200
  end
198
201
 
199
202
  def message_from(response, options, action)
200
- if empty?(response['errorMessage']) || response['errorMessage'] == '[ ]'
201
- action == 'refund' ? "#{response['data']['DSC_COD_ACCION']}, #{options[:error_message]}" : response['data']['DSC_COD_ACCION']
202
- elsif action == 'refund'
203
- message = "#{response['errorMessage']}, #{options[:error_message]}"
204
- options[:error_message] = response['errorMessage']
205
- message
206
- else
207
- response['errorMessage']
208
- end
203
+ message_from_messages(
204
+ response['errorMessage'],
205
+ action_code_description(response),
206
+ options[:error_message]
207
+ )
208
+ end
209
+
210
+ def message_from_messages(*args)
211
+ args.reject { |m| error_message_empty?(m) }.join(' | ')
212
+ end
213
+
214
+ def action_code_description(response)
215
+ return nil unless response['data']
216
+ response['data']['DSC_COD_ACCION']
217
+ end
218
+
219
+ def error_message_empty?(error_message)
220
+ empty?(error_message) || error_message == '[ ]'
209
221
  end
210
222
 
211
223
  def response_error(raw_response, options, action)
@@ -7,7 +7,7 @@ module ActiveMerchant #:nodoc:
7
7
  self.default_currency = 'GBP'
8
8
  self.money_format = :cents
9
9
  self.supported_countries = %w(HK GB AU AD AR BE BR CA CH CN CO CR CY CZ DE DK ES FI FR GI GR HU IE IN IT JP LI LU MC MT MY MX NL NO NZ PA PE PL PT SE SG SI SM TR UM VA)
10
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro]
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :elo]
11
11
  self.currencies_without_fractions = %w(HUF IDR ISK JPY KRW)
12
12
  self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND)
13
13
  self.homepage_url = 'http://www.worldpay.com/'
@@ -21,6 +21,7 @@ module ActiveMerchant #:nodoc:
21
21
  'jcb' => 'JCB-SSL',
22
22
  'maestro' => 'MAESTRO-SSL',
23
23
  'diners_club' => 'DINERS-SSL',
24
+ 'elo' => 'ELO-SSL'
24
25
  }
25
26
 
26
27
  AVS_CODE_MAP = {
@@ -262,13 +263,23 @@ module ActiveMerchant #:nodoc:
262
263
 
263
264
  add_address(xml, (options[:billing_address] || options[:address]))
264
265
  end
266
+ add_stored_credential_options(xml, options)
265
267
  if options[:ip] && options[:session_id]
266
268
  xml.tag! 'session', 'shopperIPAddress' => options[:ip], 'id' => options[:session_id]
267
269
  else
268
270
  xml.tag! 'session', 'shopperIPAddress' => options[:ip] if options[:ip]
269
271
  xml.tag! 'session', 'id' => options[:session_id] if options[:session_id]
270
272
  end
271
- add_stored_credential_options(xml, options)
273
+
274
+ if three_d_secure = options[:three_d_secure]
275
+ xml.tag! 'info3DSecure' do
276
+ xml.tag! 'threeDSVersion', three_d_secure[:version]
277
+ xid_tag = three_d_secure[:version] =~ /^2/ ? 'dsTransactionId' : 'xid'
278
+ xml.tag! xid_tag, three_d_secure[:xid]
279
+ xml.tag! 'cavv', three_d_secure[:cavv]
280
+ xml.tag! 'eci', three_d_secure[:eci]
281
+ end
282
+ end
272
283
  end
273
284
  end
274
285
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.91.0'
2
+ VERSION = '1.92.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.91.0
4
+ version: 1.92.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-22 00:00:00.000000000 Z
11
+ date: 2019-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport