activemerchant 1.120.0 → 1.125.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +227 -1
  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 +81 -26
  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 +9 -5
  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 +43 -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 +55 -9
  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 +1 -1
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +14 -2
  44. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  45. data/lib/active_merchant/billing/gateways/paysafe.rb +412 -0
  46. data/lib/active_merchant/billing/gateways/payu_latam.rb +9 -4
  47. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  48. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  49. data/lib/active_merchant/billing/gateways/priority.rb +347 -0
  50. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  51. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  52. data/lib/active_merchant/billing/gateways/safe_charge.rb +8 -2
  53. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  54. data/lib/active_merchant/billing/gateways/stripe.rb +27 -7
  55. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +115 -39
  56. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  58. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +21 -7
  59. data/lib/active_merchant/billing/gateways/vpos.rb +58 -10
  60. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  61. data/lib/active_merchant/billing/gateways/worldpay.rb +226 -62
  62. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  63. data/lib/active_merchant/billing/response.rb +4 -0
  64. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  65. data/lib/active_merchant/billing.rb +1 -0
  66. data/lib/active_merchant/version.rb +1 -1
  67. 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.120.0'
2
+ VERSION = '1.125.0'
3
3
  end