activemerchant 1.129.0 → 1.133.0

Sign up to get free protection for your applications and to get access to all the features.
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)