activemerchant 1.126.0 → 1.131.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +269 -0
  3. data/lib/active_merchant/billing/check.rb +40 -8
  4. data/lib/active_merchant/billing/credit_card.rb +28 -1
  5. data/lib/active_merchant/billing/credit_card_methods.rb +80 -24
  6. data/lib/active_merchant/billing/gateways/adyen.rb +69 -10
  7. data/lib/active_merchant/billing/gateways/airwallex.rb +40 -11
  8. data/lib/active_merchant/billing/gateways/alelo.rb +256 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +24 -6
  10. data/lib/active_merchant/billing/gateways/beanstream.rb +18 -0
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -1
  12. data/lib/active_merchant/billing/gateways/bogus.rb +4 -0
  13. data/lib/active_merchant/billing/gateways/borgun.rb +57 -16
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +72 -24
  15. data/lib/active_merchant/billing/gateways/card_connect.rb +27 -9
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +23 -0
  17. data/lib/active_merchant/billing/gateways/checkout_v2.rb +238 -57
  18. data/lib/active_merchant/billing/gateways/commerce_hub.rb +366 -0
  19. data/lib/active_merchant/billing/gateways/credorax.rb +47 -27
  20. data/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb +36 -0
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +119 -33
  22. data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +454 -0
  23. data/lib/active_merchant/billing/gateways/d_local.rb +45 -5
  24. data/lib/active_merchant/billing/gateways/decidir.rb +15 -4
  25. data/lib/active_merchant/billing/gateways/ebanx.rb +36 -24
  26. data/lib/active_merchant/billing/gateways/element.rb +21 -1
  27. data/lib/active_merchant/billing/gateways/global_collect.rb +113 -40
  28. data/lib/active_merchant/billing/gateways/ipg.rb +13 -8
  29. data/lib/active_merchant/billing/gateways/iveri.rb +39 -3
  30. data/lib/active_merchant/billing/gateways/kushki.rb +21 -1
  31. data/lib/active_merchant/billing/gateways/litle.rb +25 -5
  32. data/lib/active_merchant/billing/gateways/mastercard.rb +1 -8
  33. data/lib/active_merchant/billing/gateways/mercado_pago.rb +17 -0
  34. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +44 -10
  35. data/lib/active_merchant/billing/gateways/monei.rb +2 -0
  36. data/lib/active_merchant/billing/gateways/moneris.rb +20 -5
  37. data/lib/active_merchant/billing/gateways/mundipagg.rb +3 -0
  38. data/lib/active_merchant/billing/gateways/ogone.rb +35 -7
  39. data/lib/active_merchant/billing/gateways/openpay.rb +20 -3
  40. data/lib/active_merchant/billing/gateways/orbital.rb +43 -22
  41. data/lib/active_merchant/billing/gateways/pay_trace.rb +64 -18
  42. data/lib/active_merchant/billing/gateways/payeezy.rb +59 -4
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +18 -6
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
  45. data/lib/active_merchant/billing/gateways/paypal_express.rb +2 -0
  46. data/lib/active_merchant/billing/gateways/paysafe.rb +22 -14
  47. data/lib/active_merchant/billing/gateways/payu_latam.rb +4 -1
  48. data/lib/active_merchant/billing/gateways/plexo.rb +308 -0
  49. data/lib/active_merchant/billing/gateways/priority.rb +29 -6
  50. data/lib/active_merchant/billing/gateways/rapyd.rb +110 -49
  51. data/lib/active_merchant/billing/gateways/reach.rb +277 -0
  52. data/lib/active_merchant/billing/gateways/redsys.rb +11 -6
  53. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
  54. data/lib/active_merchant/billing/gateways/securion_pay.rb +40 -0
  55. data/lib/active_merchant/billing/gateways/shift4.rb +345 -0
  56. data/lib/active_merchant/billing/gateways/simetrik.rb +28 -22
  57. data/lib/active_merchant/billing/gateways/stripe.rb +30 -6
  58. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +62 -22
  59. data/lib/active_merchant/billing/gateways/tns.rb +2 -5
  60. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +1 -1
  61. data/lib/active_merchant/billing/gateways/trust_commerce.rb +14 -3
  62. data/lib/active_merchant/billing/gateways/vanco.rb +12 -3
  63. data/lib/active_merchant/billing/gateways/visanet_peru.rb +1 -1
  64. data/lib/active_merchant/billing/gateways/vpos.rb +7 -4
  65. data/lib/active_merchant/billing/gateways/wompi.rb +8 -4
  66. data/lib/active_merchant/billing/gateways/worldpay.rb +128 -13
  67. data/lib/active_merchant/billing/response.rb +15 -1
  68. data/lib/active_merchant/connection.rb +0 -2
  69. data/lib/active_merchant/country.rb +1 -0
  70. data/lib/active_merchant/errors.rb +4 -1
  71. data/lib/active_merchant/version.rb +1 -1
  72. metadata +24 -3
@@ -68,6 +68,8 @@ module ActiveMerchant
68
68
  'business_savings' => 'CORPORATE_SAVINGS'
69
69
  }
70
70
 
71
+ SHOPPER_INITIATOR = %w(CUSTOMER CARDHOLDER)
72
+
71
73
  STATE_CODE_COUNTRIES = %w(US CA)
72
74
 
73
75
  def initialize(options = {})
@@ -179,6 +181,7 @@ module ActiveMerchant
179
181
  doc.send('store-card', options[:store_card] || false)
180
182
  add_amount(doc, money, options)
181
183
  add_fraud_info(doc, payment_method, options)
184
+ add_stored_credentials(doc, options)
182
185
 
183
186
  if payment_method.is_a?(String)
184
187
  doc.send('vaulted-shopper-id', payment_method)
@@ -190,6 +193,19 @@ module ActiveMerchant
190
193
  end
191
194
  end
192
195
 
196
+ def add_stored_credentials(doc, options)
197
+ return unless stored_credential = options[:stored_credential]
198
+
199
+ initiator = stored_credential[:initiator]&.upcase
200
+ initiator = 'SHOPPER' if SHOPPER_INITIATOR.include?(initiator)
201
+ doc.send('transaction-initiator', initiator) if stored_credential[:initiator]
202
+ if stored_credential[:network_transaction_id]
203
+ doc.send('network-transaction-info') do
204
+ doc.send('original-network-transaction-id', stored_credential[:network_transaction_id])
205
+ end
206
+ end
207
+ end
208
+
193
209
  def add_amount(doc, money, options)
194
210
  currency = options[:currency] || currency(money)
195
211
  doc.amount(localized_amount(money, currency))
@@ -244,6 +260,7 @@ module ActiveMerchant
244
260
  def add_order(doc, options)
245
261
  doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
246
262
  doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
263
+ doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
247
264
  add_metadata(doc, options)
248
265
  add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure]
249
266
  add_level_3_data(doc, options)
@@ -350,6 +367,7 @@ module ActiveMerchant
350
367
  def add_alt_transaction_purchase(doc, money, payment_method_details, options)
351
368
  doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
352
369
  doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
370
+ doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
353
371
  add_amount(doc, money, options)
354
372
 
355
373
  vaulted_shopper_id = payment_method_details.vaulted_shopper_id
@@ -358,6 +376,7 @@ module ActiveMerchant
358
376
  add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?) if payment_method_details.check?
359
377
 
360
378
  add_fraud_info(doc, payment_method_details.payment_method, options)
379
+ add_stored_credentials(doc, options)
361
380
  add_metadata(doc, options)
362
381
  end
363
382
 
@@ -512,7 +531,9 @@ module ActiveMerchant
512
531
  end
513
532
 
514
533
  def authorization_from(action, parsed_response, payment_method_details)
515
- action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id']
534
+ return vaulted_shopper_id(parsed_response, payment_method_details) if action == :store
535
+
536
+ parsed_response['refund-transaction-id'] || parsed_response['transaction-id']
516
537
  end
517
538
 
518
539
  def vaulted_shopper_id(parsed_response, payment_method_details)
@@ -90,6 +90,10 @@ module ActiveMerchant #:nodoc:
90
90
  end
91
91
  end
92
92
 
93
+ def verify(credit_card, options = {})
94
+ authorize(0, credit_card, options)
95
+ end
96
+
93
97
  def store(paysource, options = {})
94
98
  case normalize(paysource)
95
99
  when /1$/
@@ -23,18 +23,34 @@ module ActiveMerchant #:nodoc:
23
23
 
24
24
  def purchase(money, payment, options = {})
25
25
  post = {}
26
- post[:TransType] = '1'
26
+ action = ''
27
+ if options[:apply_3d_secure] == '1'
28
+ add_3ds_preauth_fields(post, options)
29
+ action = '3ds_preauth'
30
+ else
31
+ post[:TransType] = '1'
32
+ add_3ds_fields(post, options)
33
+ action = 'sale'
34
+ end
27
35
  add_invoice(post, money, options)
28
36
  add_payment_method(post, payment)
29
- commit('sale', post)
37
+ commit(action, post, options)
30
38
  end
31
39
 
32
40
  def authorize(money, payment, options = {})
33
41
  post = {}
34
- post[:TransType] = '5'
42
+ action = ''
43
+ if options[:apply_3d_secure] == '1'
44
+ add_3ds_preauth_fields(post, options)
45
+ action = '3ds_preauth'
46
+ else
47
+ post[:TransType] = '5'
48
+ add_3ds_fields(post, options)
49
+ action = 'authonly'
50
+ end
35
51
  add_invoice(post, money, options)
36
52
  add_payment_method(post, payment)
37
- commit('authonly', post, options)
53
+ commit(action, post, options)
38
54
  end
39
55
 
40
56
  def capture(money, authorization, options = {})
@@ -80,10 +96,28 @@ module ActiveMerchant #:nodoc:
80
96
  CURRENCY_CODES['ISK'] = '352'
81
97
  CURRENCY_CODES['EUR'] = '978'
82
98
  CURRENCY_CODES['USD'] = '840'
99
+ CURRENCY_CODES['GBP'] = '826'
100
+
101
+ def add_3ds_fields(post, options)
102
+ post[:ThreeDSMessageId] = options[:three_ds_message_id] if options[:three_ds_message_id]
103
+ post[:ThreeDS_PARes] = options[:three_ds_pares] if options[:three_ds_pares]
104
+ post[:ThreeDS_CRes] = options[:three_ds_cres] if options[:three_ds_cres]
105
+ end
106
+
107
+ def add_3ds_preauth_fields(post, options)
108
+ post[:SaleDescription] = options[:sale_description] || ''
109
+ post[:MerchantReturnURL] = options[:redirect_url] if options[:redirect_url]
110
+ end
83
111
 
84
112
  def add_invoice(post, money, options)
85
113
  post[:TrAmount] = amount(money)
86
114
  post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)]
115
+ # The ISK currency must have a currency exponent of 2 on the 3DS request but not on the auth request
116
+ if post[:TrCurrency] == '352' && options[:apply_3d_secure] != '1'
117
+ post[:TrCurrencyExponent] = 0
118
+ else
119
+ post[:TrCurrencyExponent] = 2
120
+ end
87
121
  post[:TerminalID] = options[:terminal_id] || '1'
88
122
  end
89
123
 
@@ -103,11 +137,11 @@ module ActiveMerchant #:nodoc:
103
137
  post[:AuthCode] = authcode
104
138
  end
105
139
 
106
- def parse(xml)
140
+ def parse(xml, options = nil)
107
141
  response = {}
108
142
 
109
143
  doc = Nokogiri::XML(CGI.unescapeHTML(xml))
110
- body = doc.xpath('//getAuthorizationReply')
144
+ body = options[:apply_3d_secure] == '1' ? doc.xpath('//get3DSAuthenticationReply') : doc.xpath('//getAuthorizationReply')
111
145
  body = doc.xpath('//cancelAuthorizationReply') if body.length == 0
112
146
  body.children.each do |node|
113
147
  if node.text?
@@ -121,7 +155,6 @@ module ActiveMerchant #:nodoc:
121
155
  end
122
156
  end
123
157
  end
124
-
125
158
  response
126
159
  end
127
160
 
@@ -132,7 +165,7 @@ module ActiveMerchant #:nodoc:
132
165
 
133
166
  request = build_request(action, post, options)
134
167
  raw = ssl_post(url(action), request, headers)
135
- pairs = parse(raw)
168
+ pairs = parse(raw, options)
136
169
  success = success_from(pairs)
137
170
 
138
171
  Response.new(
@@ -145,7 +178,7 @@ module ActiveMerchant #:nodoc:
145
178
  end
146
179
 
147
180
  def success_from(response)
148
- (response[:actioncode] == '000')
181
+ (response[:actioncode] == '000') || (response[:status_resultcode] == '0')
149
182
  end
150
183
 
151
184
  def message_from(succeeded, response)
@@ -182,16 +215,17 @@ module ActiveMerchant #:nodoc:
182
215
 
183
216
  def build_request(action, post, options = {})
184
217
  mode = action == 'void' ? 'cancel' : 'get'
218
+ transaction_type = action == '3ds_preauth' ? '3DSAuthentication' : 'Authorization'
185
219
  xml = Builder::XmlMarkup.new indent: 18
186
220
  xml.instruct!(:xml, version: '1.0', encoding: 'utf-8')
187
- xml.tag!("#{mode}Authorization") do
221
+ xml.tag!("#{mode}#{transaction_type}") do
188
222
  post.each do |field, value|
189
223
  xml.tag!(field, value)
190
224
  end
191
225
  build_airline_xml(xml, options[:passenger_itinerary_data]) if options[:passenger_itinerary_data]
192
226
  end
193
227
  inner = CGI.escapeHTML(xml.target!)
194
- envelope(mode).sub(/{{ :body }}/, inner)
228
+ envelope(mode, action).sub(/{{ :body }}/, inner)
195
229
  end
196
230
 
197
231
  def build_airline_xml(xml, airline_data)
@@ -204,16 +238,23 @@ module ActiveMerchant #:nodoc:
204
238
  end
205
239
  end
206
240
 
207
- def envelope(mode)
241
+ def envelope(mode, action)
242
+ if action == '3ds_preauth'
243
+ transaction_action = "#{mode}3DSAuthentication"
244
+ request_action = "#{mode}Auth3DSReqXml"
245
+ else
246
+ transaction_action = "#{mode}AuthorizationInput"
247
+ request_action = "#{mode}AuthReqXml"
248
+ end
208
249
  <<-XML
209
250
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:aut="http://Borgun/Heimir/pub/ws/Authorization">
210
251
  <soapenv:Header/>
211
252
  <soapenv:Body>
212
- <aut:#{mode}AuthorizationInput>
213
- <#{mode}AuthReqXml>
253
+ <aut:#{transaction_action}>
254
+ <#{request_action}>
214
255
  {{ :body }}
215
- </#{mode}AuthReqXml>
216
- </aut:#{mode}AuthorizationInput>
256
+ </#{request_action}>
257
+ </aut:#{transaction_action}>
217
258
  </soapenv:Body>
218
259
  </soapenv:Envelope>
219
260
  XML
@@ -75,6 +75,12 @@ module ActiveMerchant #:nodoc:
75
75
  @braintree_gateway = Braintree::Gateway.new(@configuration)
76
76
  end
77
77
 
78
+ def setup_purchase
79
+ commit do
80
+ Response.new(true, 'Client token created', { client_token: @braintree_gateway.client_token.generate })
81
+ end
82
+ end
83
+
78
84
  def authorize(money, credit_card_or_vault_id, options = {})
79
85
  return Response.new(false, DIRECT_BANK_ERROR) if credit_card_or_vault_id.is_a? Check
80
86
 
@@ -82,9 +88,16 @@ module ActiveMerchant #:nodoc:
82
88
  end
83
89
 
84
90
  def capture(money, authorization, options = {})
85
- commit do
86
- result = @braintree_gateway.transaction.submit_for_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s)
87
- response_from_result(result)
91
+ if options[:partial_capture] == true
92
+ commit do
93
+ result = @braintree_gateway.transaction.submit_for_partial_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s)
94
+ response_from_result(result)
95
+ end
96
+ else
97
+ commit do
98
+ result = @braintree_gateway.transaction.submit_for_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s)
99
+ response_from_result(result)
100
+ end
88
101
  end
89
102
  end
90
103
 
@@ -184,8 +197,7 @@ module ActiveMerchant #:nodoc:
184
197
  first_name: creditcard.first_name,
185
198
  last_name: creditcard.last_name,
186
199
  email: scrub_email(options[:email]),
187
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
188
- options[:billing_address][:phone]),
200
+ phone: phone_from(options),
189
201
  credit_card: credit_card_params)
190
202
  Response.new(result.success?, message_from_result(result),
191
203
  braintree_customer: (customer_hash(@braintree_gateway.customer.find(vault_id), :include_credit_cards) if result.success?),
@@ -254,8 +266,7 @@ module ActiveMerchant #:nodoc:
254
266
  first_name: creditcard.first_name,
255
267
  last_name: creditcard.last_name,
256
268
  email: scrub_email(options[:email]),
257
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
258
- options[:billing_address][:phone]),
269
+ phone: phone_from(options),
259
270
  id: options[:customer],
260
271
  device_data: options[:device_data]
261
272
  }.merge credit_card_params
@@ -335,6 +346,10 @@ module ActiveMerchant #:nodoc:
335
346
  parameters
336
347
  end
337
348
 
349
+ def phone_from(options)
350
+ options[:phone] || options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
351
+ end
352
+
338
353
  def map_address(address)
339
354
  mapped = {
340
355
  street_address: address[:address1],
@@ -467,16 +482,28 @@ module ActiveMerchant #:nodoc:
467
482
  end
468
483
  end
469
484
 
485
+ def additional_processor_response_from_result(result)
486
+ result.transaction&.additional_processor_response
487
+ end
488
+
470
489
  def create_transaction(transaction_type, money, credit_card_or_vault_id, options)
471
490
  transaction_params = create_transaction_parameters(money, credit_card_or_vault_id, options)
472
491
  commit do
473
492
  result = @braintree_gateway.transaction.send(transaction_type, transaction_params)
493
+ make_default_payment_method_token(result) if options.dig(:paypal, :paypal_flow_type) == 'checkout_with_vault' && result.success?
474
494
  response = Response.new(result.success?, message_from_transaction_result(result), response_params(result), response_options(result))
475
495
  response.cvv_result['message'] = ''
476
496
  response
477
497
  end
478
498
  end
479
499
 
500
+ def make_default_payment_method_token(result)
501
+ @braintree_gateway.customer.update(
502
+ result.transaction.customer_details.id,
503
+ default_payment_method_token: result.transaction.paypal_details.implicitly_vaulted_payment_method_token
504
+ )
505
+ end
506
+
480
507
  def extract_refund_args(args)
481
508
  options = args.extract_options!
482
509
 
@@ -516,7 +543,10 @@ module ActiveMerchant #:nodoc:
516
543
  end
517
544
 
518
545
  def transaction_hash(result)
519
- return { 'processor_response_code' => response_code_from_result(result) } unless result.success?
546
+ unless result.success?
547
+ return { 'processor_response_code' => response_code_from_result(result),
548
+ 'additional_processor_response' => additional_processor_response_from_result(result) }
549
+ end
520
550
 
521
551
  transaction = result.transaction
522
552
  if transaction.vault_customer
@@ -576,19 +606,20 @@ module ActiveMerchant #:nodoc:
576
606
  end
577
607
 
578
608
  {
579
- 'order_id' => transaction.order_id,
580
- 'amount' => transaction.amount.to_s,
581
- 'status' => transaction.status,
582
- 'credit_card_details' => credit_card_details,
583
- 'customer_details' => customer_details,
584
- 'billing_details' => billing_details,
585
- 'shipping_details' => shipping_details,
586
- 'vault_customer' => vault_customer,
587
- 'merchant_account_id' => transaction.merchant_account_id,
588
- 'risk_data' => risk_data,
589
- 'network_transaction_id' => transaction.network_transaction_id || nil,
590
- 'processor_response_code' => response_code_from_result(result),
591
- 'recurring' => transaction.recurring
609
+ 'order_id' => transaction.order_id,
610
+ 'amount' => transaction.amount.to_s,
611
+ 'status' => transaction.status,
612
+ 'credit_card_details' => credit_card_details,
613
+ 'customer_details' => customer_details,
614
+ 'billing_details' => billing_details,
615
+ 'shipping_details' => shipping_details,
616
+ 'vault_customer' => vault_customer,
617
+ 'merchant_account_id' => transaction.merchant_account_id,
618
+ 'risk_data' => risk_data,
619
+ 'network_transaction_id' => transaction.network_transaction_id || nil,
620
+ 'processor_response_code' => response_code_from_result(result),
621
+ 'processor_authorization_code' => transaction.processor_authorization_code,
622
+ 'recurring' => transaction.recurring
592
623
  }
593
624
  end
594
625
 
@@ -599,8 +630,7 @@ module ActiveMerchant #:nodoc:
599
630
  customer: {
600
631
  id: options[:store] == true ? '' : options[:store],
601
632
  email: scrub_email(options[:email]),
602
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
603
- options[:billing_address][:phone])
633
+ phone: phone_from(options)
604
634
  },
605
635
  options: {
606
636
  store_in_vault: options[:store] ? true : false,
@@ -616,6 +646,7 @@ module ActiveMerchant #:nodoc:
616
646
  add_account_type(parameters, options) if options[:account_type]
617
647
  add_skip_options(parameters, options)
618
648
  add_merchant_account_id(parameters, options)
649
+ add_profile_id(parameters, options)
619
650
 
620
651
  add_payment_method(parameters, credit_card_or_vault_id, options)
621
652
  add_stored_credential_data(parameters, credit_card_or_vault_id, options)
@@ -623,6 +654,7 @@ module ActiveMerchant #:nodoc:
623
654
 
624
655
  add_descriptor(parameters, options)
625
656
  add_risk_data(parameters, options)
657
+ add_paypal_options(parameters, options)
626
658
  add_travel_data(parameters, options) if options[:travel_data]
627
659
  add_lodging_data(parameters, options) if options[:lodging_data]
628
660
  add_channel(parameters, options)
@@ -658,6 +690,13 @@ module ActiveMerchant #:nodoc:
658
690
  parameters[:merchant_account_id] = merchant_account_id
659
691
  end
660
692
 
693
+ def add_profile_id(parameters, options)
694
+ return unless profile_id = options[:venmo_profile_id]
695
+
696
+ parameters[:options][:venmo] = {}
697
+ parameters[:options][:venmo][:profile_id] = profile_id
698
+ end
699
+
661
700
  def add_transaction_source(parameters, options)
662
701
  parameters[:transaction_source] = options[:transaction_source] if options[:transaction_source]
663
702
  parameters[:transaction_source] = 'recurring' if options[:recurring]
@@ -692,6 +731,15 @@ module ActiveMerchant #:nodoc:
692
731
  }
693
732
  end
694
733
 
734
+ def add_paypal_options(parameters, options)
735
+ return unless options[:paypal_custom_field] || options[:paypal_description]
736
+
737
+ parameters[:options][:paypal] = {
738
+ custom_field: options[:paypal_custom_field],
739
+ description: options[:paypal_description]
740
+ }
741
+ end
742
+
695
743
  def add_level_2_data(parameters, options)
696
744
  parameters[:tax_amount] = options[:tax_amount] if options[:tax_amount]
697
745
  parameters[:tax_exempt] = options[:tax_exempt] if options[:tax_exempt]
@@ -885,7 +933,7 @@ module ActiveMerchant #:nodoc:
885
933
  first_name: payment_method.first_name,
886
934
  last_name: payment_method.last_name,
887
935
  email: scrub_email(options[:email]),
888
- phone: options[:phone] || options.dig(:billing_address, :phone),
936
+ phone: phone_from(options),
889
937
  device_data: options[:device_data]
890
938
  }.compact
891
939
 
@@ -1,8 +1,8 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class CardConnectGateway < Gateway
4
- self.test_url = 'https://fts.cardconnect.com:6443/cardconnect/rest/'
5
- self.live_url = 'https://fts.cardconnect.com:8443/cardconnect/rest/'
4
+ self.test_url = 'https://fts-uat.cardconnect.com/cardconnect/rest/'
5
+ self.live_url = 'https://fts.cardconnect.com/cardconnect/rest/'
6
6
 
7
7
  self.supported_countries = ['US']
8
8
  self.default_currency = 'USD'
@@ -61,6 +61,8 @@ module ActiveMerchant #:nodoc:
61
61
  '60' => STANDARD_ERROR_CODE[:pickup_card]
62
62
  }
63
63
 
64
+ SCHEDULED_PAYMENT_TYPES = %w(recurring installment)
65
+
64
66
  def initialize(options = {})
65
67
  requires!(options, :merchant_id, :username, :password)
66
68
  require_valid_domain!(options, :domain)
@@ -87,8 +89,9 @@ module ActiveMerchant #:nodoc:
87
89
  add_currency(post, money, options)
88
90
  add_address(post, options)
89
91
  add_customer_data(post, options)
90
- add_3DS(post, options)
92
+ add_three_ds_mpi_data(post, options)
91
93
  add_additional_data(post, options)
94
+ add_stored_credential(post, options)
92
95
  post[:capture] = 'Y'
93
96
  commit('auth', post)
94
97
  end
@@ -102,8 +105,9 @@ module ActiveMerchant #:nodoc:
102
105
  add_payment(post, payment)
103
106
  add_address(post, options)
104
107
  add_customer_data(post, options)
105
- add_3DS(post, options)
108
+ add_three_ds_mpi_data(post, options)
106
109
  add_additional_data(post, options)
110
+ add_stored_credential(post, options)
107
111
  commit('auth', post)
108
112
  end
109
113
 
@@ -188,7 +192,11 @@ module ActiveMerchant #:nodoc:
188
192
 
189
193
  def add_invoice(post, options)
190
194
  post[:orderid] = options[:order_id]
191
- post[:ecomind] = (options[:recurring] ? 'R' : 'E')
195
+ post[:ecomind] = if options[:ecomind]
196
+ options[:ecomind].capitalize
197
+ else
198
+ (options[:recurring] ? 'R' : 'E')
199
+ end
192
200
  end
193
201
 
194
202
  def add_payment(post, payment)
@@ -241,10 +249,20 @@ module ActiveMerchant #:nodoc:
241
249
  post[:userfields] = options[:user_fields] if options[:user_fields]
242
250
  end
243
251
 
244
- def add_3DS(post, options)
245
- post[:secureflag] = options[:secure_flag] if options[:secure_flag]
246
- post[:securevalue] = options[:secure_value] if options[:secure_value]
247
- post[:securexid] = options[:secure_xid] if options[:secure_xid]
252
+ def add_three_ds_mpi_data(post, options)
253
+ return unless three_d_secure = options[:three_d_secure]
254
+
255
+ post[:secureflag] = three_d_secure[:eci]
256
+ post[:securevalue] = three_d_secure[:cavv]
257
+ post[:securedstid] = three_d_secure[:ds_transaction_id]
258
+ end
259
+
260
+ def add_stored_credential(post, options)
261
+ return unless stored_credential = options[:stored_credential]
262
+
263
+ post[:cof] = stored_credential[:initiator] == 'merchant' ? 'M' : 'C'
264
+ post[:cofscheduled] = SCHEDULED_PAYMENT_TYPES.include?(stored_credential[:reason_type]) ? 'Y' : 'N'
265
+ post[:cofpermission] = stored_credential[:initial_transaction] ? 'Y' : 'N'
248
266
  end
249
267
 
250
268
  def headers
@@ -222,6 +222,7 @@ module ActiveMerchant #:nodoc:
222
222
  add_customer_data(post, options)
223
223
  add_remote_address(post, options)
224
224
  add_country_code(post, options)
225
+ add_threeds_fields(post, options)
225
226
  end
226
227
 
227
228
  def add_amount(post, money, options)
@@ -283,6 +284,20 @@ module ActiveMerchant #:nodoc:
283
284
  add_pair(post, :threeDSRequired, options[:threeds_required] || @threeds_required ? 'Y' : 'N')
284
285
  end
285
286
 
287
+ def add_threeds_fields(post, options)
288
+ return unless three_d_secure = options[:three_d_secure]
289
+
290
+ add_pair(post, :threeDSEnrolled, formatted_enrollment(three_d_secure[:enrolled]))
291
+ if three_d_secure[:enrolled] == 'true'
292
+ add_pair(post, :threeDSAuthenticated, three_d_secure[:authentication_response_status])
293
+ if three_d_secure[:authentication_response_status] == 'Y'
294
+ post[:threeDSECI] = three_d_secure[:eci]
295
+ post[:threeDSCAVV] = three_d_secure[:cavv]
296
+ post[:threeDSXID] = three_d_secure[:xid] || three_d_secure[:ds_transaction_id]
297
+ end
298
+ end
299
+ end
300
+
286
301
  def add_remote_address(post, options = {})
287
302
  add_pair(post, :remoteAddress, options[:ip] || '1.1.1.1')
288
303
  end
@@ -366,6 +381,14 @@ module ActiveMerchant #:nodoc:
366
381
  def add_pair(post, key, value, options = {})
367
382
  post[key] = value if !value.blank? || options[:required]
368
383
  end
384
+
385
+ def formatted_enrollment(val)
386
+ case val
387
+ when 'Y', 'N', 'U' then val
388
+ when true, 'true' then 'Y'
389
+ when false, 'false' then 'N'
390
+ end
391
+ end
369
392
  end
370
393
  end
371
394
  end