activemerchant 1.91.0 → 1.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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