activemerchant 1.129.0 → 1.133.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +51 -1
  3. data/lib/active_merchant/billing/credit_card.rb +2 -0
  4. data/lib/active_merchant/billing/credit_card_methods.rb +7 -3
  5. data/lib/active_merchant/billing/gateways/adyen.rb +82 -2
  6. data/lib/active_merchant/billing/gateways/authorize_net.rb +3 -2
  7. data/lib/active_merchant/billing/gateways/borgun.rb +11 -8
  8. data/lib/active_merchant/billing/gateways/braintree_blue.rb +8 -7
  9. data/lib/active_merchant/billing/gateways/checkout_v2.rb +15 -8
  10. data/lib/active_merchant/billing/gateways/commerce_hub.rb +13 -4
  11. data/lib/active_merchant/billing/gateways/cyber_source.rb +32 -7
  12. data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +6 -8
  13. data/lib/active_merchant/billing/gateways/d_local.rb +1 -0
  14. data/lib/active_merchant/billing/gateways/global_collect.rb +41 -19
  15. data/lib/active_merchant/billing/gateways/ipg.rb +1 -1
  16. data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
  17. data/lib/active_merchant/billing/gateways/mit.rb +18 -18
  18. data/lib/active_merchant/billing/gateways/nmi.rb +5 -0
  19. data/lib/active_merchant/billing/gateways/paypal_express.rb +2 -0
  20. data/lib/active_merchant/billing/gateways/payu_latam.rb +1 -1
  21. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +1 -1
  22. data/lib/active_merchant/billing/gateways/redsys.rb +2 -1
  23. data/lib/active_merchant/billing/gateways/safe_charge.rb +2 -1
  24. data/lib/active_merchant/billing/gateways/shift4.rb +5 -2
  25. data/lib/active_merchant/billing/gateways/stripe.rb +21 -5
  26. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +111 -68
  27. data/lib/active_merchant/billing/gateways/vpos.rb +1 -1
  28. data/lib/active_merchant/billing/gateways/worldpay.rb +12 -24
  29. data/lib/active_merchant/version.rb +1 -1
  30. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 415c09ab9b38e3503d10b38cd42959b50c131f8b47237f39b36a8da7bc044d82
4
- data.tar.gz: 2b2cdd2955bd0ed6e2b808cad4fa31888749ee6cbe91b2a75c563f1cc09429d6
3
+ metadata.gz: 5d705df588bf311375b4dfc46da6912709e0aaecc7bd1800e69af7b61fcfa23b
4
+ data.tar.gz: 80ae4869bcbf875c2ef0bd7b5b257641c1810bc5110958a66d1cda956e6a994a
5
5
  SHA512:
6
- metadata.gz: 104444c88b66340a16502ae980ae4c2795f25cd8f9d3a78a05d636d55730a462d9ead8a565291774096dd3fa75149961f64ec472d2a74c7499ce6b472ec6de95
7
- data.tar.gz: bc738912b2788963721d232e43cf02675cc6a672c7aca913c6bd876fd73b72fd0cd0b7d43e734dca7f5494957e9092e8054d75b788b59ef3a2ca25a917d3d949
6
+ metadata.gz: ba783e41a2872b825b73e5b20c6084e2d4b7200ab899a91ee6aa31977c6e4b3da651f96cd3c895417a8e3e70fdd84cd082a7450eb8a2d35c48b4fc2410ea2c02
7
+ data.tar.gz: b6c1718e9160fdf16745eff1758f5ffc25cbdd6a4bb219c4e13510e3a40b998f9f01b1426985fcb8b987cf73c1c5e8a760a4ffb8b506459ce4501ddb242bedb4
data/CHANGELOG CHANGED
@@ -3,7 +3,57 @@
3
3
 
4
4
  == HEAD
5
5
 
6
- == Version 1.129.0 (April 24th, 2023)
6
+ == Version 1.133.0 (July 20, 2023)
7
+ * CyberSource: remove credentials from tests [bbraschi] #4836
8
+
9
+ == Version 1.132.0 (July 20, 2023)
10
+ * Stripe Payment Intents: Add support for new card on file field [aenand] #4807
11
+ * Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
12
+ * Nuvei (formerly SafeCharge): Add customer details to credit action [yunnydang] #4820
13
+ * IPG: Update live url to correct endpoint [curiousepic] #4121
14
+ * VPos: Adding Panal Credit Card type [jherreraa] #4814
15
+ * Stripe PI: Update parameters for creation of customer [almalee24] #4782
16
+ * WorldPay: Update xml tag for Credit Cards [almalee24] #4797
17
+ * PaywayDotCom: update `live_url` [jcreiff] #4824
18
+ * Stripe & Stripe PI: Update login key validation [almalee24] #4816
19
+ * CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
20
+ * NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields [jcreiff] #4825
21
+ * Borgun: Update authorization_from & message_from [almalee24] #4826
22
+ * Kushki: Add Brazil as supported country [almalee24] #4829
23
+ * Adyen: Add additional data for airline and lodging [javierpedrozaing] #4815
24
+ * MIT: Changed how the payload was sent to the gateway [alejandrofloresm] #4655
25
+ * SafeCharge: Add unreferenced_refund field [yunnydang] #4831
26
+ * CyberSource: include `paymentSolution` for ApplePay and GooglePay [bbraschi] #4835
27
+
28
+ == Version 1.131.0 (June 21, 2023)
29
+ * Redsys: Add supported countries [jcreiff] #4811
30
+ * Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
31
+ * CheckoutV2: Add support for several customer data fields [rachelkirk] #4800
32
+ * Worldpay: check payment_method responds to payment_cryptogram and eci [bbraschi] #4812
33
+
34
+ == Version 1.130.0 (June 13th, 2023)
35
+ * Payu Latam - Update error code method to surface network code [yunnydang] #4773
36
+ * CyberSource: Handling Canadian bank accounts [heavyblade] #4764
37
+ * CyberSource Rest: Fixing currency detection [heavyblade] #4777
38
+ * CyberSource: Allow business rules for requests with network tokens [aenand] #4764
39
+ * Adyen: Update Mastercard error messaging [kylene-spreedly] #4770
40
+ * Authorize.net: Update mapping for billing address phone number [jcreiff] #4778
41
+ * Braintree: Update mapping for billing address phone number [jcreiff] #4779
42
+ * CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
43
+ * Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
44
+ * Worldpay: Fix Google Pay [almalee24] #4774
45
+ * Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4788
46
+ * Borgun: support for GBP currency [naashton] #4789
47
+ * CyberSource: Enable auto void on r230 [aenand] #4794
48
+ * Redsys: Set appropriate request fields for stored credentials with CITs and MITs [BritneyS] #4784
49
+ * Stripe & Stripe PI: Validate API Key [almalee24] #4801
50
+ * Add BIN for Maestro [jcreiff] #4799
51
+ * D_Local: Add save field on card object [yunnydang] #4805
52
+ * PayPal Express: Adds support for MsgSubID property on DoReferenceTransaction and DoExpressCheckoutPayment [wikiti] #4798
53
+ * Checkout_v2: use credit_card?, not case equality with CreditCard [bbraschi] #4803
54
+ * Shift4: Enable general credit feature [jherreraa] #4790
55
+
56
+ == Version 1.129.0 (May 3rd, 2023)
7
57
  * Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
8
58
  * Shift4: Add vendorReference field [jcreiff] #4762
9
59
  * Shift4: Add OAuth error [aenand] #4760
@@ -38,6 +38,7 @@ module ActiveMerchant #:nodoc:
38
38
  # * Edenred
39
39
  # * Anda
40
40
  # * Creditos directos (Tarjeta D)
41
+ # * Panal
41
42
  #
42
43
  # For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of
43
44
  # validations, allowing you to focus on your core concerns until you're ready to be more concerned
@@ -130,6 +131,7 @@ module ActiveMerchant #:nodoc:
130
131
  # * +'edenred'+
131
132
  # * +'anda'+
132
133
  # * +'tarjeta-d'+
134
+ # * +'panal'+
133
135
  #
134
136
  # Or, if you wish to test your implementation, +'bogus'+.
135
137
  #
@@ -46,7 +46,8 @@ module ActiveMerchant #:nodoc:
46
46
  'edenred' => ->(num) { num =~ /^637483\d{10}$/ },
47
47
  'anda' => ->(num) { num =~ /^603199\d{10}$/ },
48
48
  'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ },
49
- 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) }
49
+ 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) },
50
+ 'panal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), PANAL_RANGES) }
50
51
  }
51
52
 
52
53
  SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ }
@@ -109,7 +110,7 @@ module ActiveMerchant #:nodoc:
109
110
  MAESTRO_BINS = Set.new(
110
111
  %w[ 500057
111
112
  501018 501043 501045 501047 501049 501051 501072 501075 501083 501087 501089 501095
112
- 501500
113
+ 501500 501623
113
114
  501879 502113 502120 502121 502301
114
115
  503175 503337 503645 503670
115
116
  504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910
@@ -182,7 +183,8 @@ module ActiveMerchant #:nodoc:
182
183
  (601256..601276),
183
184
  (601640..601652),
184
185
  (601689..601700),
185
- (602011..602050),
186
+ (602011..602048),
187
+ [602050],
186
188
  (630400..630499),
187
189
  (639000..639099),
188
190
  (670000..679999),
@@ -247,6 +249,8 @@ module ActiveMerchant #:nodoc:
247
249
  637568..637568, 637599..637599, 637609..637609, 637612..637612
248
250
  ]
249
251
 
252
+ PANAL_RANGES = [[602049]]
253
+
250
254
  def self.included(base)
251
255
  base.extend(ClassMethods)
252
256
  end
@@ -68,6 +68,8 @@ module ActiveMerchant #:nodoc:
68
68
  add_application_info(post, options)
69
69
  add_level_2_data(post, options)
70
70
  add_level_3_data(post, options)
71
+ add_data_airline(post, options)
72
+ add_data_lodging(post, options)
71
73
  commit('authorise', post, options)
72
74
  end
73
75
 
@@ -291,6 +293,84 @@ module ActiveMerchant #:nodoc:
291
293
  post[:additionalData].compact!
292
294
  end
293
295
 
296
+ def add_data_airline(post, options)
297
+ return unless options[:additional_data_airline]
298
+
299
+ mapper = %w[
300
+ agency_invoice_number
301
+ agency_plan_name
302
+ airline_code
303
+ airline_designator_code
304
+ boarding_fee
305
+ computerized_reservation_system
306
+ customer_reference_number
307
+ document_type
308
+ flight_date
309
+ ticket_issue_address
310
+ ticket_number
311
+ travel_agency_code
312
+ travel_agency_name
313
+ passenger_name
314
+ ].each_with_object({}) { |value, hash| hash["airline.#{value}"] = value }
315
+
316
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_airline]))
317
+
318
+ if options[:additional_data_airline][:leg].present?
319
+ leg_data = %w[
320
+ carrier_code
321
+ class_of_travel
322
+ date_of_travel
323
+ depart_airport
324
+ depart_tax
325
+ destination_code
326
+ fare_base_code
327
+ flight_number
328
+ stop_over_code
329
+ ].each_with_object({}) { |value, hash| hash["airline.leg.#{value}"] = value }
330
+
331
+ post[:additionalData].merge!(extract_and_transform(leg_data, options[:additional_data_airline][:leg]))
332
+ end
333
+
334
+ if options[:additional_data_airline][:passenger].present?
335
+ passenger_data = %w[
336
+ date_of_birth
337
+ first_name
338
+ last_name
339
+ telephone_number
340
+ traveller_type
341
+ ].each_with_object({}) { |value, hash| hash["airline.passenger.#{value}"] = value }
342
+
343
+ post[:additionalData].merge!(extract_and_transform(passenger_data, options[:additional_data_airline][:passenger]))
344
+ end
345
+ post[:additionalData].compact!
346
+ end
347
+
348
+ def add_data_lodging(post, options)
349
+ return unless options[:additional_data_lodging]
350
+
351
+ mapper = {
352
+ 'lodging.checkInDate': 'check_in_date',
353
+ 'lodging.checkOutDate': 'check_out_date',
354
+ 'lodging.customerServiceTollFreeNumber': 'customer_service_toll_free_number',
355
+ 'lodging.fireSafetyActIndicator': 'fire_safety_act_indicator',
356
+ 'lodging.folioCashAdvances': 'folio_cash_advances',
357
+ 'lodging.folioNumber': 'folio_number',
358
+ 'lodging.foodBeverageCharges': 'food_beverage_charges',
359
+ 'lodging.noShowIndicator': 'no_show_indicator',
360
+ 'lodging.prepaidExpenses': 'prepaid_expenses',
361
+ 'lodging.propertyPhoneNumber': 'property_phone_number',
362
+ 'lodging.room1.numberOfNights': 'number_of_nights',
363
+ 'lodging.room1.rate': 'rate',
364
+ 'lodging.totalRoomTax': 'total_room_tax',
365
+ 'lodging.totalTax': 'totalTax',
366
+ 'travelEntertainmentAuthData.duration': 'duration',
367
+ 'travelEntertainmentAuthData.market': 'market'
368
+ }
369
+
370
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_lodging]))
371
+ post[:additionalData].compact!
372
+ end
373
+
294
374
  def add_shopper_data(post, options)
295
375
  post[:shopperEmail] = options[:email] if options[:email]
296
376
  post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
@@ -703,8 +783,8 @@ module ActiveMerchant #:nodoc:
703
783
  end
704
784
 
705
785
  def authorize_message_from(response)
706
- if response['refusalReason'] && response['additionalData'] && response['additionalData']['refusalReasonRaw']
707
- "#{response['refusalReason']} | #{response['additionalData']['refusalReasonRaw']}"
786
+ if response['refusalReason'] && response['additionalData'] && (response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw'])
787
+ "#{response['refusalReason']} | #{response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw']}"
708
788
  else
709
789
  response['refusalReason'] || response['resultCode'] || response['message'] || response['result']
710
790
  end
@@ -362,7 +362,7 @@ module ActiveMerchant
362
362
  xml.accountType(options[:account_type])
363
363
  xml.routingNumber(options[:routing_number])
364
364
  xml.accountNumber(options[:account_number])
365
- xml.nameOnAccount("#{options[:first_name]} #{options[:last_name]}")
365
+ xml.nameOnAccount(truncate("#{options[:first_name]} #{options[:last_name]}", 22))
366
366
  end
367
367
  else
368
368
  xml.creditCard do
@@ -604,6 +604,7 @@ module ActiveMerchant
604
604
  first_name, last_name = names_from(payment_source, address, options)
605
605
  state = state_from(address, options)
606
606
  full_address = "#{address[:address1]} #{address[:address2]}".strip
607
+ phone = address[:phone] || address[:phone_number] || ''
607
608
 
608
609
  xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
609
610
  xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
@@ -613,7 +614,7 @@ module ActiveMerchant
613
614
  xml.state(truncate(state, 40))
614
615
  xml.zip(truncate((address[:zip] || options[:zip]), 20))
615
616
  xml.country(truncate(address[:country], 60))
616
- xml.phoneNumber(truncate(address[:phone], 25)) unless empty?(address[:phone])
617
+ xml.phoneNumber(truncate(phone, 25)) unless empty?(phone)
617
618
  xml.faxNumber(truncate(address[:fax], 25)) unless empty?(address[:fax])
618
619
  end
619
620
  end
@@ -96,6 +96,7 @@ module ActiveMerchant #:nodoc:
96
96
  CURRENCY_CODES['ISK'] = '352'
97
97
  CURRENCY_CODES['EUR'] = '978'
98
98
  CURRENCY_CODES['USD'] = '840'
99
+ CURRENCY_CODES['GBP'] = '826'
99
100
 
100
101
  def add_3ds_fields(post, options)
101
102
  post[:ThreeDSMessageId] = options[:three_ds_message_id] if options[:three_ds_message_id]
@@ -105,17 +106,17 @@ module ActiveMerchant #:nodoc:
105
106
 
106
107
  def add_3ds_preauth_fields(post, options)
107
108
  post[:SaleDescription] = options[:sale_description] || ''
108
- post[:MerchantReturnURL] = options[:merchant_return_url] if options[:merchant_return_url]
109
+ post[:MerchantReturnURL] = options[:redirect_url] if options[:redirect_url]
109
110
  end
110
111
 
111
112
  def add_invoice(post, money, options)
112
113
  post[:TrAmount] = amount(money)
113
114
  post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)]
114
115
  # The ISK currency must have a currency exponent of 2 on the 3DS request but not on the auth request
115
- if post[:TrCurrency] == '352' && options[:apply_3d_secure] == '1'
116
- post[:TrCurrencyExponent] = 2
117
- else
116
+ if post[:TrCurrency] == '352' && options[:apply_3d_secure] != '1'
118
117
  post[:TrCurrencyExponent] = 0
118
+ else
119
+ post[:TrCurrencyExponent] = 2
119
120
  end
120
121
  post[:TerminalID] = options[:terminal_id] || '1'
121
122
  end
@@ -171,7 +172,7 @@ module ActiveMerchant #:nodoc:
171
172
  success,
172
173
  message_from(success, pairs),
173
174
  pairs,
174
- authorization: authorization_from(pairs),
175
+ authorization: authorization_from(pairs, options),
175
176
  test: test?
176
177
  )
177
178
  end
@@ -184,12 +185,12 @@ module ActiveMerchant #:nodoc:
184
185
  if succeeded
185
186
  'Succeeded'
186
187
  else
187
- response[:message] || "Error with ActionCode=#{response[:actioncode]}"
188
+ response[:message] || response[:status_errormessage] || "Error with ActionCode=#{response[:actioncode]}"
188
189
  end
189
190
  end
190
191
 
191
- def authorization_from(response)
192
- [
192
+ def authorization_from(response, options)
193
+ authorization = [
193
194
  response[:dateandtime],
194
195
  response[:batch],
195
196
  response[:transaction],
@@ -199,6 +200,8 @@ module ActiveMerchant #:nodoc:
199
200
  response[:tramount],
200
201
  response[:trcurrency]
201
202
  ].join('|')
203
+
204
+ authorization == '|||||||' ? nil : authorization
202
205
  end
203
206
 
204
207
  def split_authorization(authorization)
@@ -197,8 +197,7 @@ module ActiveMerchant #:nodoc:
197
197
  first_name: creditcard.first_name,
198
198
  last_name: creditcard.last_name,
199
199
  email: scrub_email(options[:email]),
200
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
201
- options[:billing_address][:phone]),
200
+ phone: phone_from(options),
202
201
  credit_card: credit_card_params)
203
202
  Response.new(result.success?, message_from_result(result),
204
203
  braintree_customer: (customer_hash(@braintree_gateway.customer.find(vault_id), :include_credit_cards) if result.success?),
@@ -267,8 +266,7 @@ module ActiveMerchant #:nodoc:
267
266
  first_name: creditcard.first_name,
268
267
  last_name: creditcard.last_name,
269
268
  email: scrub_email(options[:email]),
270
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
271
- options[:billing_address][:phone]),
269
+ phone: phone_from(options),
272
270
  id: options[:customer],
273
271
  device_data: options[:device_data]
274
272
  }.merge credit_card_params
@@ -348,6 +346,10 @@ module ActiveMerchant #:nodoc:
348
346
  parameters
349
347
  end
350
348
 
349
+ def phone_from(options)
350
+ options[:phone] || options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
351
+ end
352
+
351
353
  def map_address(address)
352
354
  mapped = {
353
355
  street_address: address[:address1],
@@ -628,8 +630,7 @@ module ActiveMerchant #:nodoc:
628
630
  customer: {
629
631
  id: options[:store] == true ? '' : options[:store],
630
632
  email: scrub_email(options[:email]),
631
- phone: options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
632
- options[:billing_address][:phone])
633
+ phone: phone_from(options)
633
634
  },
634
635
  options: {
635
636
  store_in_vault: options[:store] ? true : false,
@@ -932,7 +933,7 @@ module ActiveMerchant #:nodoc:
932
933
  first_name: payment_method.first_name,
933
934
  last_name: payment_method.last_name,
934
935
  email: scrub_email(options[:email]),
935
- phone: options[:phone] || options.dig(:billing_address, :phone),
936
+ phone: phone_from(options),
936
937
  device_data: options[:device_data]
937
938
  }.compact
938
939
 
@@ -139,6 +139,7 @@ module ActiveMerchant #:nodoc:
139
139
  add_authorization_type(post, options)
140
140
  add_payment_method(post, payment_method, options)
141
141
  add_customer_data(post, options)
142
+ add_extra_customer_data(post, payment_method, options)
142
143
  add_shipping_address(post, options)
143
144
  add_stored_credential_options(post, options)
144
145
  add_transaction_data(post, options)
@@ -185,7 +186,7 @@ module ActiveMerchant #:nodoc:
185
186
  post[key][:token_type] = token_type
186
187
  post[key][:cryptogram] = cryptogram if cryptogram
187
188
  post[key][:eci] = eci if eci
188
- when CreditCard
189
+ when ->(pm) { pm.try(:credit_card?) }
189
190
  post[key][:type] = 'card'
190
191
  post[key][:name] = payment_method.name
191
192
  post[key][:number] = payment_method.number
@@ -239,6 +240,15 @@ module ActiveMerchant #:nodoc:
239
240
  end
240
241
  end
241
242
 
243
+ # created a separate method for these fields because they should not be included
244
+ # in all transaction types that include methods with source and customer fields
245
+ def add_extra_customer_data(post, payment_method, options)
246
+ post[:source][:phone] = {}
247
+ post[:source][:phone][:number] = options[:phone] || options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
248
+ post[:source][:phone][:country_code] = options[:phone_country_code] if options[:phone_country_code]
249
+ post[:customer][:name] = payment_method.name if payment_method.respond_to?(:name)
250
+ end
251
+
242
252
  def add_shipping_address(post, options)
243
253
  if address = options[:shipping_address]
244
254
  post[:shipping] = {}
@@ -353,9 +363,6 @@ module ActiveMerchant #:nodoc:
353
363
  end
354
364
 
355
365
  def response(action, succeeded, response, source_id = nil)
356
- successful_response = succeeded && action == :purchase || action == :authorize
357
- avs_result = successful_response ? avs_result(response) : nil
358
- cvv_result = successful_response ? cvv_result(response) : nil
359
366
  authorization = authorization_from(response) unless action == :unstore
360
367
  body = action == :unstore ? { response_code: response.to_s } : response
361
368
  Response.new(
@@ -365,8 +372,8 @@ module ActiveMerchant #:nodoc:
365
372
  authorization: authorization,
366
373
  error_code: error_code_from(succeeded, body),
367
374
  test: test?,
368
- avs_result: avs_result,
369
- cvv_result: cvv_result
375
+ avs_result: avs_result(response),
376
+ cvv_result: cvv_result(response)
370
377
  )
371
378
  end
372
379
 
@@ -417,11 +424,11 @@ module ActiveMerchant #:nodoc:
417
424
  end
418
425
 
419
426
  def avs_result(response)
420
- response['source'] && response['source']['avs_check'] ? AVSResult.new(code: response['source']['avs_check']) : nil
427
+ response.respond_to?(:dig) && response.dig('source', 'avs_check') ? AVSResult.new(code: response['source']['avs_check']) : nil
421
428
  end
422
429
 
423
430
  def cvv_result(response)
424
- response['source'] && response['source']['cvv_check'] ? CVVResult.new(response['source']['cvv_check']) : nil
431
+ response.respond_to?(:dig) && response.dig('source', 'cvv_check') ? CVVResult.new(response['source']['cvv_check']) : nil
425
432
  end
426
433
 
427
434
  def parse(body, error: nil)
@@ -114,13 +114,17 @@ module ActiveMerchant #:nodoc:
114
114
  post[:transactionInteraction][:origin] = options[:origin] || 'ECOM'
115
115
  post[:transactionInteraction][:eciIndicator] = options[:eci_indicator] || 'CHANNEL_ENCRYPTED'
116
116
  post[:transactionInteraction][:posConditionCode] = options[:pos_condition_code] || 'CARD_NOT_PRESENT_ECOM'
117
- post[:transactionInteraction][:posEntryMode] = options[:pos_entry_mode] || 'MANUAL'
117
+ post[:transactionInteraction][:posEntryMode] = (options[:pos_entry_mode] || 'MANUAL') unless options[:encryption_data].present?
118
118
  post[:transactionInteraction][:additionalPosInformation] = {}
119
119
  post[:transactionInteraction][:additionalPosInformation][:dataEntrySource] = options[:data_entry_source] || 'UNSPECIFIED'
120
120
  end
121
121
 
122
122
  def add_transaction_details(post, options, action = nil)
123
- details = { captureFlag: options[:capture_flag], createToken: options[:create_token] }
123
+ details = {
124
+ captureFlag: options[:capture_flag],
125
+ createToken: options[:create_token],
126
+ physicalGoodsIndicator: [true, 'true'].include?(options[:physical_goods_indicator])
127
+ }
124
128
 
125
129
  if options[:order_id].present? && action == 'sale'
126
130
  details[:merchantOrderId] = options[:order_id]
@@ -214,7 +218,7 @@ module ActiveMerchant #:nodoc:
214
218
  post[:storedCredentials][:sequence] = stored_credential[:initial_transaction] ? 'FIRST' : 'SUBSEQUENT'
215
219
  post[:storedCredentials][:initiator] = stored_credential[:initiator] == 'merchant' ? 'MERCHANT' : 'CARD_HOLDER'
216
220
  post[:storedCredentials][:scheduled] = SCHEDULED_REASON_TYPES.include?(stored_credential[:reason_type])
217
- post[:storedCredentials][:schemeReferenceTransactionId] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
221
+ post[:storedCredentials][:schemeReferenceTransactionId] = options[:scheme_reference_transaction_id] || stored_credential[:network_transaction_id]
218
222
  end
219
223
 
220
224
  def add_credit_card(source, payment, options)
@@ -256,7 +260,12 @@ module ActiveMerchant #:nodoc:
256
260
  when NetworkTokenizationCreditCard
257
261
  add_decrypted_wallet(source, payment, options)
258
262
  when CreditCard
259
- add_credit_card(source, payment, options)
263
+ if options[:encryption_data].present?
264
+ source[:sourceType] = 'PaymentCard'
265
+ source[:encryptionData] = options[:encryption_data]
266
+ else
267
+ add_credit_card(source, payment, options)
268
+ end
260
269
  when String
261
270
  add_payment_token(source, payment, options)
262
271
  end
@@ -132,6 +132,11 @@ module ActiveMerchant #:nodoc:
132
132
  r703: 'Export hostname_country/ip_country match'
133
133
  }
134
134
 
135
+ @@payment_solution = {
136
+ apple_pay: '001',
137
+ google_pay: '012'
138
+ }
139
+
135
140
  # These are the options that can be used when creating a new CyberSource
136
141
  # Gateway object.
137
142
  #
@@ -322,6 +327,7 @@ module ActiveMerchant #:nodoc:
322
327
  add_airline_data(xml, options)
323
328
  add_sales_slip_number(xml, options)
324
329
  add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
330
+ add_payment_solution(xml, creditcard_or_reference.source) if network_tokenization?(creditcard_or_reference)
325
331
  add_tax_management_indicator(xml, options)
326
332
  add_stored_credential_subsequent_auth(xml, options)
327
333
  add_issuer_additional_data(xml, options)
@@ -393,6 +399,7 @@ module ActiveMerchant #:nodoc:
393
399
  add_airline_data(xml, options)
394
400
  add_sales_slip_number(xml, options)
395
401
  add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
402
+ add_payment_solution(xml, payment_method_or_reference.source) if network_tokenization?(payment_method_or_reference)
396
403
  add_tax_management_indicator(xml, options)
397
404
  add_stored_credential_subsequent_auth(xml, options)
398
405
  add_issuer_additional_data(xml, options)
@@ -519,11 +526,9 @@ module ActiveMerchant #:nodoc:
519
526
  def add_business_rules_data(xml, payment_method, options)
520
527
  prioritized_options = [options, @options]
521
528
 
522
- unless network_tokenization?(payment_method)
523
- xml.tag! 'businessRules' do
524
- xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs).to_s == 'true'
525
- xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
526
- end
529
+ xml.tag! 'businessRules' do
530
+ xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs).to_s == 'true'
531
+ xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
527
532
  end
528
533
  end
529
534
 
@@ -672,6 +677,12 @@ module ActiveMerchant #:nodoc:
672
677
  end
673
678
  end
674
679
 
680
+ def add_payment_solution(xml, source)
681
+ return unless (payment_solution = @@payment_solution[source])
682
+
683
+ xml.tag! 'paymentSolution', payment_solution
684
+ end
685
+
675
686
  def add_issuer_additional_data(xml, options)
676
687
  return unless options[:issuer_additional_data]
677
688
 
@@ -705,8 +716,8 @@ module ActiveMerchant #:nodoc:
705
716
  def add_check(xml, check, options)
706
717
  xml.tag! 'check' do
707
718
  xml.tag! 'accountNumber', check.account_number
708
- xml.tag! 'accountType', check.account_type[0]
709
- xml.tag! 'bankTransitNumber', check.routing_number
719
+ xml.tag! 'accountType', check.account_type == 'checking' ? 'C' : 'S'
720
+ xml.tag! 'bankTransitNumber', format_routing_number(check.routing_number, options)
710
721
  xml.tag! 'secCode', options[:sec_code] if options[:sec_code]
711
722
  end
712
723
  end
@@ -1055,6 +1066,8 @@ module ActiveMerchant #:nodoc:
1055
1066
  message = message_from(response)
1056
1067
  authorization = success || in_fraud_review?(response) ? authorization_from(response, action, amount, options) : nil
1057
1068
 
1069
+ message = auto_void?(authorization_from(response, action, amount, options), response, message, options)
1070
+
1058
1071
  Response.new(success, message, response,
1059
1072
  test: test?,
1060
1073
  authorization: authorization,
@@ -1063,6 +1076,14 @@ module ActiveMerchant #:nodoc:
1063
1076
  cvv_result: response[:cvCode])
1064
1077
  end
1065
1078
 
1079
+ def auto_void?(authorization, response, message, options = {})
1080
+ return message unless response[:reasonCode] == '230' && options[:auto_void_230]
1081
+
1082
+ response = void(authorization, options)
1083
+ response&.success? ? message += ' - transaction has been auto-voided.' : message += ' - transaction could not be auto-voided.'
1084
+ message
1085
+ end
1086
+
1066
1087
  # Parse the SOAP response
1067
1088
  # Technique inspired by the Paypal Gateway
1068
1089
  def parse(xml)
@@ -1131,6 +1152,10 @@ module ActiveMerchant #:nodoc:
1131
1152
  def eligible_for_zero_auth?(payment_method, options = {})
1132
1153
  payment_method.is_a?(CreditCard) && options[:zero_amount_auth]
1133
1154
  end
1155
+
1156
+ def format_routing_number(routing_number, options)
1157
+ options[:currency] == 'CAD' && routing_number.length > 8 ? routing_number[-8..-1] : routing_number
1158
+ end
1134
1159
  end
1135
1160
  end
1136
1161
  end
@@ -112,7 +112,7 @@ module ActiveMerchant #:nodoc:
112
112
  add_code(post, options)
113
113
  add_payment(post, payment, options)
114
114
  add_mdd_fields(post, options)
115
- add_amount(post, amount)
115
+ add_amount(post, amount, options)
116
116
  add_address(post, payment, options[:billing_address], options, :billTo)
117
117
  add_address(post, payment, options[:shipping_address], options, :shipTo)
118
118
  add_business_rules_data(post, payment, options)
@@ -125,7 +125,7 @@ module ActiveMerchant #:nodoc:
125
125
  { clientReferenceInformation: {}, orderInformation: {} }.tap do |post|
126
126
  add_code(post, options)
127
127
  add_mdd_fields(post, options)
128
- add_amount(post, amount)
128
+ add_amount(post, amount, options)
129
129
  add_partner_solution_id(post)
130
130
  end.compact
131
131
  end
@@ -135,7 +135,7 @@ module ActiveMerchant #:nodoc:
135
135
  add_code(post, options)
136
136
  add_credit_card(post, payment)
137
137
  add_mdd_fields(post, options)
138
- add_amount(post, amount)
138
+ add_amount(post, amount, options)
139
139
  add_address(post, payment, options[:billing_address], options, :billTo)
140
140
  add_merchant_description(post, options)
141
141
  end.compact
@@ -161,7 +161,7 @@ module ActiveMerchant #:nodoc:
161
161
  }
162
162
  end
163
163
 
164
- def add_amount(post, amount)
164
+ def add_amount(post, amount, options)
165
165
  currency = options[:currency] || currency(amount)
166
166
  post[:orderInformation][:amountDetails] = {
167
167
  totalAmount: localized_amount(amount, currency),
@@ -413,10 +413,8 @@ module ActiveMerchant #:nodoc:
413
413
 
414
414
  def add_business_rules_data(post, payment, options)
415
415
  post[:processingInformation][:authorizationOptions] = {}
416
- unless payment.is_a?(NetworkTokenizationCreditCard)
417
- post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true'
418
- post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true'
419
- end
416
+ post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true'
417
+ post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true'
420
418
  end
421
419
 
422
420
  def add_mdd_fields(post, options)
@@ -188,6 +188,7 @@ module ActiveMerchant #:nodoc:
188
188
  post[:card][:installments] = options[:installments] if options[:installments]
189
189
  post[:card][:installments_id] = options[:installments_id] if options[:installments_id]
190
190
  post[:card][:force_type] = options[:force_type].to_s.upcase if options[:force_type]
191
+ post[:card][:save] = options[:save] if options[:save]
191
192
  end
192
193
 
193
194
  def parse(body)