activemerchant 1.121.0 → 1.125.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +217 -0
  3. data/README.md +1 -1
  4. data/lib/active_merchant/billing/check.rb +13 -19
  5. data/lib/active_merchant/billing/credit_card.rb +13 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +24 -12
  8. data/lib/active_merchant/billing/gateway.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/adyen.rb +75 -27
  10. data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -4
  17. data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +41 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +12 -6
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/decidir_plus.rb +173 -0
  22. data/lib/active_merchant/billing/gateways/ebanx.rb +16 -1
  23. data/lib/active_merchant/billing/gateways/elavon.rb +65 -30
  24. data/lib/active_merchant/billing/gateways/element.rb +22 -2
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +130 -26
  26. data/lib/active_merchant/billing/gateways/ipg.rb +416 -0
  27. data/lib/active_merchant/billing/gateways/kushki.rb +30 -0
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +6 -3
  29. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  30. data/lib/active_merchant/billing/gateways/mit.rb +260 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +290 -0
  32. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  33. data/lib/active_merchant/billing/gateways/mundipagg.rb +22 -11
  34. data/lib/active_merchant/billing/gateways/nmi.rb +29 -10
  35. data/lib/active_merchant/billing/gateways/orbital.rb +46 -8
  36. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  37. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +14 -2
  44. data/lib/active_merchant/billing/gateways/paysafe.rb +412 -0
  45. data/lib/active_merchant/billing/gateways/payu_latam.rb +9 -4
  46. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  47. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  48. data/lib/active_merchant/billing/gateways/priority.rb +347 -0
  49. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  50. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  51. data/lib/active_merchant/billing/gateways/safe_charge.rb +8 -2
  52. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  53. data/lib/active_merchant/billing/gateways/stripe.rb +27 -7
  54. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +115 -39
  55. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  56. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +21 -7
  58. data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
  59. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +226 -62
  61. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  62. data/lib/active_merchant/billing/response.rb +4 -0
  63. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  64. data/lib/active_merchant/billing.rb +1 -0
  65. data/lib/active_merchant/version.rb +1 -1
  66. metadata +13 -3
@@ -0,0 +1,193 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class WompiGateway < Gateway
4
+ self.test_url = 'https://sync.sandbox.wompi.co/v1'
5
+ self.live_url = 'https://sync.production.wompi.co/v1'
6
+
7
+ self.supported_countries = ['CO']
8
+ self.default_currency = 'COP'
9
+ self.supported_cardtypes = %i[visa master american_express]
10
+
11
+ self.homepage_url = 'https://wompi.co/'
12
+ self.display_name = 'Wompi'
13
+
14
+ self.money_format = :cents
15
+
16
+ def initialize(options = {})
17
+ ## Sandbox keys have prefix pub_test_ and prv_test_
18
+ ## Production keys have prefix pub_prod_ and prv_prod_
19
+ begin
20
+ requires!(options, :prod_private_key, :prod_public_key)
21
+ rescue ArgumentError
22
+ begin
23
+ requires!(options, :test_private_key, :test_public_key)
24
+ rescue ArgumentError
25
+ raise ArgumentError, 'Gateway requires both test_private_key and test_public_key, or both prod_private_key and prod_public_key'
26
+ end
27
+ end
28
+ super
29
+ end
30
+
31
+ def purchase(money, payment, options = {})
32
+ post = {
33
+ reference: options[:reference] || generate_reference,
34
+ public_key: public_key
35
+ }
36
+ add_invoice(post, money, options)
37
+ add_card(post, payment, options)
38
+
39
+ commit('sale', post, '/transactions_sync')
40
+ end
41
+
42
+ def authorize(money, payment, options = {})
43
+ post = {
44
+ public_key: public_key,
45
+ type: 'CARD',
46
+ financial_operation: 'PREAUTHORIZATION'
47
+ }
48
+ add_auth_params(post, money, payment, options)
49
+
50
+ commit('authorize', post, '/payment_sources_sync')
51
+ end
52
+
53
+ def capture(money, authorization, options = {})
54
+ post = {
55
+ reference: options[:reference] || generate_reference,
56
+ public_key: public_key,
57
+ payment_source_id: authorization.to_i
58
+ }
59
+ add_invoice(post, money, options)
60
+ commit('capture', post, '/transactions_sync')
61
+ end
62
+
63
+ def refund(money, authorization, options = {})
64
+ post = { amount_in_cents: amount(money).to_i, transaction_id: authorization.to_s }
65
+ commit('refund', post, '/refunds_sync')
66
+ end
67
+
68
+ def void(authorization, options = {})
69
+ commit('void', {}, "/transactions/#{authorization}/void_sync")
70
+ end
71
+
72
+ def supports_scrubbing?
73
+ true
74
+ end
75
+
76
+ def scrub(transcript)
77
+ transcript.gsub(/(Bearer )\w+/, '\1[REDACTED]').
78
+ gsub(/(\\\"number\\\":\\\")\d+/, '\1[REDACTED]').
79
+ gsub(/(\\\"cvc\\\":\\\")\d+/, '\1[REDACTED]').
80
+ gsub(/(\\\"phone_number\\\":\\\")\+?\d+/, '\1[REDACTED]').
81
+ gsub(/(\\\"email\\\":\\\")\S+\\\",/, '\1[REDACTED]\",').
82
+ gsub(/(\\\"legal_id\\\":\\\")\d+/, '\1[REDACTED]')
83
+ end
84
+
85
+ private
86
+
87
+ def headers
88
+ {
89
+ 'Authorization' => "Bearer #{private_key}",
90
+ 'Content-Type' => 'application/json'
91
+ }
92
+ end
93
+
94
+ def generate_reference
95
+ SecureRandom.alphanumeric(12)
96
+ end
97
+
98
+ def private_key
99
+ test? ? options[:test_private_key] : options[:prod_private_key]
100
+ end
101
+
102
+ def public_key
103
+ test? ? options[:test_public_key] : options[:prod_public_key]
104
+ end
105
+
106
+ def add_invoice(post, money, options)
107
+ post[:amount_in_cents] = amount(money).to_i
108
+ post[:currency] = (options[:currency] || currency(money))
109
+ end
110
+
111
+ def add_card(post, card, options)
112
+ payment_method = {
113
+ type: 'CARD'
114
+ }
115
+ add_basic_card_info(payment_method, card, options)
116
+ post[:payment_method] = payment_method
117
+ end
118
+
119
+ def add_auth_params(post, money, card, options)
120
+ data = {
121
+ amount_in_cents: amount(money).to_i,
122
+ currency: (options[:currency] || currency(money))
123
+ }
124
+ add_basic_card_info(data, card, options)
125
+ post[:data] = data
126
+ end
127
+
128
+ def add_basic_card_info(post, card, options)
129
+ installments = options[:installments] ? options[:installments].to_i : 1
130
+ cvc = card.verification_value || nil
131
+
132
+ post[:number] = card.number
133
+ post[:exp_month] = card.month.to_s.rjust(2, '0')
134
+ post[:exp_year] = card.year.to_s[2..3]
135
+ post[:installments] = installments
136
+ post[:card_holder] = card.name
137
+ post[:cvc] = cvc if cvc && !cvc.empty?
138
+ end
139
+
140
+ def parse(body)
141
+ JSON.parse(body)
142
+ end
143
+
144
+ def commit(action, parameters, endpoint)
145
+ url = (test? ? test_url : live_url) + endpoint
146
+ response = parse(ssl_post(url, post_data(action, parameters), headers))
147
+ Response.new(
148
+ success_from(response),
149
+ message_from(response),
150
+ response,
151
+ authorization: authorization_from(response),
152
+ avs_result: nil,
153
+ cvv_result: nil,
154
+ test: test?,
155
+ error_code: error_code_from(response)
156
+ )
157
+ end
158
+
159
+ def handle_response(response)
160
+ case response.code.to_i
161
+ when 200...300, 401, 404, 422
162
+ response.body
163
+ else
164
+ raise ResponseError.new(response)
165
+ end
166
+ end
167
+
168
+ def success_from(response)
169
+ success_statuses.include? response.dig('data', 'status')
170
+ end
171
+
172
+ def success_statuses
173
+ %w(APPROVED AVAILABLE)
174
+ end
175
+
176
+ def message_from(response)
177
+ response.dig('data', 'status_message') || response.dig('error', 'reason') || response.dig('error', 'messages').to_json
178
+ end
179
+
180
+ def authorization_from(response)
181
+ response.dig('data', 'transaction_id') || response.dig('data', 'id') || response.dig('data', 'transaction', 'id')
182
+ end
183
+
184
+ def post_data(action, parameters = {})
185
+ parameters.to_json
186
+ end
187
+
188
+ def error_code_from(response)
189
+ response.dig('error', 'type') unless success_from(response)
190
+ end
191
+ end
192
+ end
193
+ end
@@ -8,13 +8,26 @@ module ActiveMerchant #:nodoc:
8
8
 
9
9
  self.default_currency = 'GBP'
10
10
  self.money_format = :cents
11
- 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)
11
+ self.supported_countries = %w(AD AE AG AI AL AM AO AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BM BN BO BR BS BT BW
12
+ BY BZ CA CC CF CH CK CL CM CN CO CR CV CX CY CZ DE DJ DK DO DZ EC EE EG EH ES ET FI FJ FK
13
+ FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GT GU GW GY HK HM HN HR HT HU ID IE IL
14
+ IM IN IO IS IT JE JM JO JP KE KG KH KI KM KN KR KW KY KZ LA LC LI LK LS LT LU LV MA MC MD
15
+ ME MG MH MK ML MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ
16
+ OM PA PE PF PH PK PL PN PR PT PW PY QA RE RO RS RU RW SA SB SC SE SG SI SK SL SM SN ST SV
17
+ SZ TC TD TF TG TH TJ TK TM TO TR TT TV TW TZ UA UG US UY UZ VA VC VE VI VN VU WF WS YE YT
18
+ ZA ZM)
12
19
  self.supported_cardtypes = %i[visa master american_express discover jcb maestro elo naranja cabal unionpay]
13
- self.currencies_without_fractions = %w(HUF IDR ISK JPY KRW)
14
- self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND)
20
+ self.currencies_without_fractions = %w(HUF IDR JPY KRW BEF XOF XAF XPF GRD GNF ITL LUF MGA MGF PYG PTE RWF ESP TRL VND KMF)
21
+ self.currencies_with_three_decimal_places = %w(BHD KWD OMR TND LYD JOD IQD)
15
22
  self.homepage_url = 'http://www.worldpay.com/'
16
23
  self.display_name = 'Worldpay Global'
17
24
 
25
+ NETWORK_TOKEN_TYPE = {
26
+ apple_pay: 'APPLEPAY',
27
+ google_pay: 'GOOGLEPAY',
28
+ network_token: 'NETWORKTOKEN'
29
+ }
30
+
18
31
  CARD_CODES = {
19
32
  'visa' => 'VISA-SSL',
20
33
  'master' => 'ECMC-SSL',
@@ -64,7 +77,7 @@ module ActiveMerchant #:nodoc:
64
77
 
65
78
  def authorize(money, payment_method, options = {})
66
79
  requires!(options, :order_id)
67
- payment_details = payment_details_from(payment_method)
80
+ payment_details = payment_details(payment_method)
68
81
  authorize_request(money, payment_method, payment_details.merge(options))
69
82
  end
70
83
 
@@ -110,13 +123,18 @@ module ActiveMerchant #:nodoc:
110
123
  # and other transactions should be performed on a normal eCom-flagged
111
124
  # merchant ID.
112
125
  def credit(money, payment_method, options = {})
113
- payment_details = payment_details_from(payment_method)
114
- credit_request(money, payment_method, payment_details.merge(credit: true, **options))
126
+ payment_details = payment_details(payment_method)
127
+ if options[:fast_fund_credit]
128
+ fast_fund_credit_request(money, payment_method, payment_details.merge(credit: true, **options))
129
+ else
130
+ credit_request(money, payment_method, payment_details.merge(credit: true, **options))
131
+ end
115
132
  end
116
133
 
117
134
  def verify(payment_method, options = {})
135
+ amount = (eligible_for_0_auth?(payment_method, options) ? 0 : 100)
118
136
  MultiResponse.run(:use_first_response) do |r|
119
- r.process { authorize(100, payment_method, options) }
137
+ r.process { authorize(amount, payment_method, options) }
120
138
  r.process(:ignore_result) { void(r.authorization, options.merge(authorization_validated: true)) }
121
139
  end
122
140
  end
@@ -130,17 +148,23 @@ module ActiveMerchant #:nodoc:
130
148
  true
131
149
  end
132
150
 
151
+ def supports_network_tokenization?
152
+ true
153
+ end
154
+
133
155
  def scrub(transcript)
134
156
  transcript.
135
157
  gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
136
158
  gsub(%r((<cardNumber>)\d+(</cardNumber>)), '\1[FILTERED]\2').
137
- gsub(%r((<cvc>)[^<]+(</cvc>)), '\1[FILTERED]\2')
159
+ gsub(%r((<cvc>)[^<]+(</cvc>)), '\1[FILTERED]\2').
160
+ gsub(%r((<tokenNumber>)\d+(</tokenNumber>)), '\1[FILTERED]\2').
161
+ gsub(%r((<cryptogram>)[^<]+(</cryptogram>)), '\1[FILTERED]\2')
138
162
  end
139
163
 
140
164
  private
141
165
 
142
166
  def authorize_request(money, payment_method, options)
143
- commit('authorize', build_authorization_request(money, payment_method, options), 'AUTHORISED', options)
167
+ commit('authorize', build_authorization_request(money, payment_method, options), 'AUTHORISED', 'CAPTURED', options)
144
168
  end
145
169
 
146
170
  def capture_request(money, authorization, options)
@@ -163,6 +187,10 @@ module ActiveMerchant #:nodoc:
163
187
  commit('credit', build_authorization_request(money, payment_method, options), :ok, 'SENT_FOR_REFUND', options)
164
188
  end
165
189
 
190
+ def fast_fund_credit_request(money, payment_method, options)
191
+ commit('fast_credit', build_fast_fund_credit_request(money, payment_method, options), :ok, 'PUSH_APPROVED', options)
192
+ end
193
+
166
194
  def store_request(credit_card, options)
167
195
  commit('store', build_store_request(credit_card, options), options)
168
196
  end
@@ -201,15 +229,12 @@ module ActiveMerchant #:nodoc:
201
229
  xml.order order_tag_attributes(options) do
202
230
  xml.description(options[:description].blank? ? 'Purchase' : options[:description])
203
231
  add_amount(xml, money, options)
204
- if options[:order_content]
205
- xml.orderContent do
206
- xml.cdata! options[:order_content]
207
- end
208
- end
232
+ add_order_content(xml, options)
209
233
  add_payment_method(xml, money, payment_method, options)
210
234
  add_shopper(xml, options)
211
235
  add_statement_narrative(xml, options)
212
236
  add_risk_data(xml, options[:risk_data]) if options[:risk_data]
237
+ add_sub_merchant_data(xml, options[:sub_merchant_data]) if options[:sub_merchant_data]
213
238
  add_hcg_additional_data(xml, options) if options[:hcg_additional_data]
214
239
  add_instalments_data(xml, options) if options[:instalments]
215
240
  add_moto_flag(xml, options) if options.dig(:metadata, :manual_entry)
@@ -221,7 +246,19 @@ module ActiveMerchant #:nodoc:
221
246
  end
222
247
 
223
248
  def order_tag_attributes(options)
224
- { 'orderCode' => options[:order_id], 'installationId' => options[:inst_id] || @options[:inst_id] }.reject { |_, v| !v.present? }
249
+ { 'orderCode' => clean_order_id(options[:order_id]), 'installationId' => options[:inst_id] || @options[:inst_id] }.reject { |_, v| !v.present? }
250
+ end
251
+
252
+ def clean_order_id(order_id)
253
+ order_id.to_s.gsub(/(\s|\||<|>|'|")/, '')[0..64]
254
+ end
255
+
256
+ def add_order_content(xml, options)
257
+ return unless options[:order_content]
258
+
259
+ xml.orderContent do
260
+ xml.cdata! options[:order_content]
261
+ end
225
262
  end
226
263
 
227
264
  def build_capture_request(money, authorization, options)
@@ -235,7 +272,11 @@ module ActiveMerchant #:nodoc:
235
272
  end
236
273
 
237
274
  def build_void_request(authorization, options)
238
- build_order_modify_request(authorization, &:cancel)
275
+ if options[:cancel_or_refund]
276
+ build_order_modify_request(authorization, &:cancelOrRefund)
277
+ else
278
+ build_order_modify_request(authorization, &:cancel)
279
+ end
239
280
  end
240
281
 
241
282
  def build_refund_request(money, authorization, options)
@@ -262,11 +303,69 @@ module ActiveMerchant #:nodoc:
262
303
  add_card(xml, credit_card, options)
263
304
  end
264
305
  end
306
+ add_transaction_identifier(xml, options) if network_transaction_id(options)
307
+ end
308
+ end
309
+ end
310
+ end
311
+
312
+ def network_transaction_id(options)
313
+ options[:stored_credential_transaction_id] || options.dig(:stored_credential, :network_transaction_id)
314
+ end
315
+
316
+ def add_transaction_identifier(xml, options)
317
+ xml.storedCredentials 'usage' => 'FIRST' do
318
+ xml.schemeTransactionIdentifier network_transaction_id(options)
319
+ end
320
+ end
321
+
322
+ def build_fast_fund_credit_request(money, payment_method, options)
323
+ build_request do |xml|
324
+ xml.submit do
325
+ xml.order order_tag_attributes(options) do
326
+ xml.description(options[:description].blank? ? 'Fast Fund Credit' : options[:description])
327
+ add_amount(xml, money, options)
328
+ add_order_content(xml, options)
329
+ add_payment_details_for_ff_credit(xml, payment_method, options)
330
+ add_shopper_id(xml, options)
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ def add_payment_details_for_ff_credit(xml, payment_method, options)
337
+ xml.paymentDetails do
338
+ xml.tag! 'FF_DISBURSE-SSL' do
339
+ if payment_method.is_a?(CreditCard)
340
+ add_card_for_ff_credit(xml, payment_method, options)
341
+ else
342
+ add_token_for_ff_credit(xml, payment_method, options)
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ def add_card_for_ff_credit(xml, payment_method, options)
349
+ xml.recipient do
350
+ xml.paymentInstrument do
351
+ xml.cardDetails do
352
+ add_card(xml, payment_method, options)
265
353
  end
266
354
  end
267
355
  end
268
356
  end
269
357
 
358
+ def add_token_for_ff_credit(xml, payment_method, options)
359
+ return unless payment_method.is_a?(String)
360
+
361
+ token_details = token_details_from_authorization(payment_method)
362
+
363
+ xml.tag! 'recipient', 'tokenScope' => token_details[:token_scope] do
364
+ xml.paymentTokenID token_details[:token_id]
365
+ add_authenticated_shopper_id(xml, token_details)
366
+ end
367
+ end
368
+
270
369
  def add_additional_3ds_data(xml, options)
271
370
  additional_data = { 'dfReferenceId' => options[:session_id] }
272
371
  additional_data['challengeWindowSize'] = options[:browser_size] if options[:browser_size]
@@ -305,6 +404,20 @@ module ActiveMerchant #:nodoc:
305
404
  end
306
405
  end
307
406
 
407
+ def add_sub_merchant_data(xml, options)
408
+ xml.subMerchantData do
409
+ xml.pfId options[:pf_id] if options[:pf_id]
410
+ xml.subName options[:sub_name] if options[:sub_name]
411
+ xml.subId options[:sub_id] if options[:sub_id]
412
+ xml.subStreet options[:sub_street] if options[:sub_street]
413
+ xml.subCity options[:sub_city] if options[:sub_city]
414
+ xml.subState options[:sub_state] if options[:sub_state]
415
+ xml.subCountryCode options[:sub_country_code] if options[:sub_country_code]
416
+ xml.subPostalCode options[:sub_postal_code] if options[:sub_postal_code]
417
+ xml.subTaxId options[:sub_tax_id] if options[:sub_tax_id]
418
+ end
419
+ end
420
+
308
421
  def add_shopper_account_risk_data(xml, shopper_account_risk_data)
309
422
  return unless shopper_account_risk_data
310
423
 
@@ -379,46 +492,89 @@ module ActiveMerchant #:nodoc:
379
492
  end
380
493
 
381
494
  def add_payment_method(xml, amount, payment_method, options)
382
- if options[:payment_type] == :pay_as_order
383
- if options[:merchant_code]
384
- xml.payAsOrder 'orderCode' => payment_method, 'merchantCode' => options[:merchant_code] do
385
- add_amount(xml, amount, options)
386
- end
387
- else
388
- xml.payAsOrder 'orderCode' => payment_method do
389
- add_amount(xml, amount, options)
390
- end
495
+ case options[:payment_type]
496
+ when :pay_as_order
497
+ add_amount_for_pay_as_order(xml, amount, payment_method, options)
498
+ when :network_token
499
+ add_network_tokenization_card(xml, payment_method)
500
+ else
501
+ add_card_or_token(xml, payment_method, options)
502
+ end
503
+ end
504
+
505
+ def add_amount_for_pay_as_order(xml, amount, payment_method, options)
506
+ if options[:merchant_code]
507
+ xml.payAsOrder 'orderCode' => payment_method, 'merchantCode' => options[:merchant_code] do
508
+ add_amount(xml, amount, options)
391
509
  end
392
510
  else
393
- xml.paymentDetails credit_fund_transfer_attribute(options) do
394
- if options[:payment_type] == :token
395
- xml.tag! 'TOKEN-SSL', 'tokenScope' => options[:token_scope] do
396
- xml.paymentTokenID options[:token_id]
397
- end
398
- else
399
- xml.tag! card_code_for(payment_method) do
400
- add_card(xml, payment_method, options)
401
- end
402
- end
403
- add_stored_credential_options(xml, options)
404
- if options[:ip] && options[:session_id]
405
- xml.session 'shopperIPAddress' => options[:ip], 'id' => options[:session_id]
406
- else
407
- xml.session 'shopperIPAddress' => options[:ip] if options[:ip]
408
- xml.session 'id' => options[:session_id] if options[:session_id]
409
- end
511
+ xml.payAsOrder 'orderCode' => payment_method do
512
+ add_amount(xml, amount, options)
513
+ end
514
+ end
515
+ end
516
+
517
+ def add_network_tokenization_card(xml, payment_method)
518
+ token_type = NETWORK_TOKEN_TYPE.fetch(payment_method.source, 'NETWORKTOKEN')
410
519
 
411
- if three_d_secure = options[:three_d_secure]
412
- add_three_d_secure(three_d_secure, xml)
520
+ xml.paymentDetails do
521
+ xml.tag! 'EMVCO_TOKEN-SSL', 'type' => token_type do
522
+ xml.tokenNumber payment_method.number
523
+ xml.expiryDate do
524
+ xml.date(
525
+ 'month' => format(payment_method.month, :two_digits),
526
+ 'year' => format(payment_method.year, :four_digits_year)
527
+ )
413
528
  end
529
+ name = card_holder_name(payment_method, options)
530
+ eci = format(payment_method.eci, :two_digits)
531
+ xml.cardHolderName name if name.present?
532
+ xml.cryptogram payment_method.payment_cryptogram
533
+ xml.eciIndicator eci.empty? ? '07' : eci
534
+ end
535
+ end
536
+ end
537
+
538
+ def add_card_or_token(xml, payment_method, options)
539
+ xml.paymentDetails credit_fund_transfer_attribute(options) do
540
+ if options[:payment_type] == :token
541
+ add_token_details(xml, options)
542
+ else
543
+ add_card_details(xml, payment_method, options)
414
544
  end
545
+ add_stored_credential_options(xml, options)
546
+ add_shopper_id(xml, options)
547
+ add_three_d_secure(xml, options)
548
+ end
549
+ end
550
+
551
+ def add_token_details(xml, options)
552
+ xml.tag! 'TOKEN-SSL', 'tokenScope' => options[:token_scope] do
553
+ xml.paymentTokenID options[:token_id]
554
+ end
555
+ end
556
+
557
+ def add_card_details(xml, payment_method, options)
558
+ xml.tag! card_code_for(payment_method) do
559
+ add_card(xml, payment_method, options)
560
+ end
561
+ end
562
+
563
+ def add_shopper_id(xml, options)
564
+ if options[:ip] && options[:session_id]
565
+ xml.session 'shopperIPAddress' => options[:ip], 'id' => options[:session_id]
566
+ else
567
+ xml.session 'shopperIPAddress' => options[:ip] if options[:ip]
568
+ xml.session 'id' => options[:session_id] if options[:session_id]
415
569
  end
416
570
  end
417
571
 
418
- def add_three_d_secure(three_d_secure, xml)
572
+ def add_three_d_secure(xml, options)
573
+ return unless three_d_secure = options[:three_d_secure]
574
+
419
575
  xml.info3DSecure do
420
576
  xml.threeDSVersion three_d_secure[:version]
421
- if /^2/.match?(three_d_secure[:version])
577
+ if three_d_secure[:version] && three_d_secure[:ds_transaction_id]
422
578
  xml.dsTransactionId three_d_secure[:ds_transaction_id]
423
579
  else
424
580
  xml.xid three_d_secure[:xid]
@@ -433,12 +589,10 @@ module ActiveMerchant #:nodoc:
433
589
  xml.expiryDate do
434
590
  xml.date(
435
591
  'month' => format(payment_method.month, :two_digits),
436
- 'year' => format(payment_method.year, :four_digits)
592
+ 'year' => format(payment_method.year, :four_digits_year)
437
593
  )
438
594
  end
439
-
440
- card_holder_name = test? && options[:execute_threed] && !options[:three_ds_version]&.start_with?('2') ? '3D' : payment_method.name
441
- xml.cardHolderName card_holder_name
595
+ xml.cardHolderName card_holder_name(payment_method, options)
442
596
  xml.cvc payment_method.verification_value
443
597
 
444
598
  add_address(xml, (options[:billing_address] || options[:address]), options)
@@ -600,6 +754,7 @@ module ActiveMerchant #:nodoc:
600
754
  def commit(action, request, *success_criteria, options)
601
755
  xml = ssl_post(url, request, headers(options))
602
756
  raw = parse(action, xml)
757
+
603
758
  if options[:execute_threed]
604
759
  raw[:cookie] = @cookie if defined?(@cookie)
605
760
  raw[:session_id] = options[:session_id]
@@ -728,21 +883,22 @@ module ActiveMerchant #:nodoc:
728
883
  token_details
729
884
  end
730
885
 
731
- def payment_details_from(payment_method)
732
- payment_details = {}
733
- if payment_method.respond_to?(:number)
734
- payment_details[:payment_type] = :credit
886
+ def payment_details(payment_method)
887
+ case payment_method
888
+ when String
889
+ token_type_and_details(payment_method)
890
+ when NetworkTokenizationCreditCard
891
+ { payment_type: :network_token }
735
892
  else
736
- token_details = token_details_from_authorization(payment_method)
737
- payment_details.merge!(token_details)
738
- if token_details.has_key?(:token_id)
739
- payment_details[:payment_type] = :token
740
- else
741
- payment_details[:payment_type] = :pay_as_order
742
- end
893
+ { payment_type: :credit }
743
894
  end
895
+ end
744
896
 
745
- payment_details
897
+ def token_type_and_details(token)
898
+ token_details = token_details_from_authorization(token)
899
+ token_details[:payment_type] = token_details.has_key?(:token_id) ? :token : :pay_as_order
900
+
901
+ token_details
746
902
  end
747
903
 
748
904
  def credit_fund_transfer_attribute(options)
@@ -766,6 +922,14 @@ module ActiveMerchant #:nodoc:
766
922
  def card_code_for(payment_method)
767
923
  CARD_CODES[card_brand(payment_method)] || CARD_CODES['unknown']
768
924
  end
925
+
926
+ def eligible_for_0_auth?(payment_method, options = {})
927
+ payment_method.is_a?(CreditCard) && %w(visa master).include?(payment_method.brand) && options[:zero_dollar_auth]
928
+ end
929
+
930
+ def card_holder_name(payment_method, options)
931
+ test? && options[:execute_threed] && !options[:three_ds_version]&.start_with?('2') ? '3D' : payment_method.name
932
+ end
769
933
  end
770
934
  end
771
935
  end
@@ -14,7 +14,7 @@ module ActiveMerchant #:nodoc:
14
14
  self.require_verification_value = false
15
15
  self.require_name = false
16
16
 
17
- attr_accessor :payment_cryptogram, :eci, :transaction_id
17
+ attr_accessor :payment_cryptogram, :eci, :transaction_id, :metadata
18
18
  attr_writer :source
19
19
 
20
20
  SOURCES = %i(apple_pay android_pay google_pay network_token)
@@ -10,6 +10,10 @@ module ActiveMerchant #:nodoc:
10
10
  @success
11
11
  end
12
12
 
13
+ def failure?
14
+ !success?
15
+ end
16
+
13
17
  def test?
14
18
  @test
15
19
  end
@@ -0,0 +1,27 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+ module ThreeDSecureEciMapper
4
+ NON_THREE_D_SECURE_TRANSACTION = :non_three_d_secure_transaction
5
+ ATTEMPTED_AUTHENTICATION_TRANSACTION = :attempted_authentication_transaction
6
+ FULLY_AUTHENTICATED_TRANSACTION = :fully_authenticated_transaction
7
+
8
+ ECI_00_01_02_MAP = { '00' => NON_THREE_D_SECURE_TRANSACTION, '01' => ATTEMPTED_AUTHENTICATION_TRANSACTION, '02' => FULLY_AUTHENTICATED_TRANSACTION }.freeze
9
+ ECI_05_06_07_MAP = { '05' => FULLY_AUTHENTICATED_TRANSACTION, '06' => ATTEMPTED_AUTHENTICATION_TRANSACTION, '07' => NON_THREE_D_SECURE_TRANSACTION }.freeze
10
+ BRAND_TO_ECI_MAP = {
11
+ american_express: ECI_05_06_07_MAP,
12
+ dankort: ECI_05_06_07_MAP,
13
+ diners_club: ECI_05_06_07_MAP,
14
+ discover: ECI_05_06_07_MAP,
15
+ elo: ECI_05_06_07_MAP,
16
+ jcb: ECI_05_06_07_MAP,
17
+ maestro: ECI_00_01_02_MAP,
18
+ master: ECI_00_01_02_MAP,
19
+ visa: ECI_05_06_07_MAP
20
+ }.freeze
21
+
22
+ def self.map(brand, eci)
23
+ BRAND_TO_ECI_MAP.dig(brand, eci)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -13,3 +13,4 @@ require 'active_merchant/billing/apple_pay_payment_token'
13
13
  require 'active_merchant/billing/response'
14
14
  require 'active_merchant/billing/gateways'
15
15
  require 'active_merchant/billing/gateway'
16
+ require 'active_merchant/billing/three_d_secure_eci_mapper'
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.121.0'
2
+ VERSION = '1.125.0'
3
3
  end