Skip to content

Commit

Permalink
Update from original activemerchant 2024 10 07 (#13)
Browse files Browse the repository at this point in the history
* update active support version (activemerchant#5287)

* Adyen: remove unused ecommerce flag for NetworkTokenization cards (activemerchant#5280)

* CybersourceRest: Update message and error_code

Add dig to message_from and error_code_from to prevent
NoMethod errors.

Remote
53 tests, 176 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

* Redsys: update decoding to catch Json errors and try to urlsafe_decode (activemerchant#5290)

* Paysafe: Add `external_initial_transaction_id`

This allows a network transaction id obtained from a third party transaction
to be passed in and maintain MIT transactions without interruptions.

Remote Tests:
35 tests, 86 assertions, 9 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
74.2857% passed
*I fixed one remote test that was failing due to changed error message, but the rest are failing on master as well

Unit Tests:
20 tests, 101 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

* Worldpay: Add customStringFields
These fields are included as part of FraudSightData in auth/purchase requests
https://docs.worldpay.com/apis/wpg/fraudsightglobal/fraudsightdirect

CER-1766

LOCAL
6040 tests, 80501 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

801 files inspected, no offenses detected

UNIT
125 tests, 705 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

REMOTE
112 tests, 473 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
97.3214% passed

* Rubocop failures

---------

Co-authored-by: Dustin A Haefele <[email protected]>
Co-authored-by: Alma Malambo <[email protected]>
Co-authored-by: Rachel Kirk <[email protected]>
Co-authored-by: Joe Reiff <[email protected]>
Co-authored-by: Abel Pacheco <[email protected]>
  • Loading branch information
6 people authored Oct 8, 2024
1 parent 62c8039 commit 9fb4b1c
Show file tree
Hide file tree
Showing 21 changed files with 264 additions and 114 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
* StripePI: Update authorization for failed Payment Intents [almalee24] #5260
* Worldpay: Add support for recurring Apple Pay [kenderbolivart] #5628
* Cybersource Rest: Add support for recurring Apple Pay [bdcano] #5270
* Cybersource Rest: Update message and error_code [almalee24] #5276
* Paysafe: Add support for `external_initial_transaction_id` [rachelkirk] #5291
* Worldpay: Add customStringFields [jcreiff] #5284

== Version 1.137.0 (August 2, 2024)
* Unlock dependency on `rexml` to allow fixing a CVE (#5181).
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/Gemfile.rails_master
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
eval_gemfile '../Gemfile'

gem 'activesupport', github: 'rails/rails'
gem 'activesupport', '~>7.2.1'
2 changes: 1 addition & 1 deletion lib/active_merchant/billing/gateways/adyen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ def skip_mpi_data?(options = {})
end

def ecommerce_shopper_interaction?(payment, options)
return true if payment.is_a?(NetworkTokenizationCreditCard) && !options[:switch_cryptogram_mapping_nt]
return true if payment.is_a?(NetworkTokenizationCreditCard)
return true unless (stored_credential = options[:stored_credential])

(stored_credential[:initial_transaction] && stored_credential[:initiator] == 'cardholder') ||
Expand Down
84 changes: 46 additions & 38 deletions lib/active_merchant/billing/gateways/authorize_net.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,10 @@ class AuthorizeNetGateway < Gateway
AVS_REASON_CODES = %w(27 45)

TRACKS = {
1 => /^%(?<format_code>.)(?<pan>[\d]{1,19}+)\^(?<name>.{2,26})\^(?<expiration>[\d]{0,4}|\^)(?<service_code>[\d]{0,3}|\^)(?<discretionary_data>.*)\?\Z/,
2 => /\A;(?<pan>[\d]{1,19}+)=(?<expiration>[\d]{0,4}|=)(?<service_code>[\d]{0,3}|=)(?<discretionary_data>.*)\?\Z/
1 => /^%(?<format_code>.)(?<pan>\d{1,19}+)\^(?<name>.{2,26})\^(?<expiration>\d{0,4}|\^)(?<service_code>\d{0,3}|\^)(?<discretionary_data>.*)\?\Z/,
2 => /\A;(?<pan>\d{1,19}+)=(?<expiration>\d{0,4}|=)(?<service_code>\d{0,3}|=)(?<discretionary_data>.*)\?\Z/
}.freeze

APPLE_PAY_DATA_DESCRIPTOR = 'COMMON.APPLE.INAPP.PAYMENT'
PAYMENT_METHOD_NOT_SUPPORTED_ERROR = '155'
INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = '54'

Expand Down Expand Up @@ -165,7 +164,7 @@ def credit(amount, payment, options = {})
xml.transactionType('refundTransaction')
xml.amount(amount(amount))

add_payment_source(xml, payment, options, :credit)
add_payment_method(xml, payment, options, :credit)
xml.refTransId(transaction_id_from(options[:transaction_id])) if options[:transaction_id]
add_invoice(xml, 'refundTransaction', options)
add_customer_data(xml, payment, options)
Expand Down Expand Up @@ -262,7 +261,7 @@ def add_auth_purchase(xml, transaction_type, amount, payment, options)
xml.transactionRequest do
xml.transactionType(transaction_type)
xml.amount(amount(amount))
add_payment_source(xml, payment, options)
add_payment_method(xml, payment, options)
add_invoice(xml, transaction_type, options)
add_tax_fields(xml, options)
add_duty_fields(xml, options)
Expand All @@ -273,6 +272,7 @@ def add_auth_purchase(xml, transaction_type, amount, payment, options)
add_market_type_device_type(xml, payment, options)
add_settings(xml, payment, options)
add_user_fields(xml, amount, options)
add_surcharge_fields(xml, options)
add_ship_from_address(xml, options)
add_processing_options(xml, options)
add_subsequent_auth_information(xml, options)
Expand All @@ -287,8 +287,9 @@ def add_cim_auth_purchase(xml, transaction_type, amount, payment, options)
add_tax_fields(xml, options)
add_shipping_fields(xml, options)
add_duty_fields(xml, options)
add_payment_source(xml, payment, options)
add_payment_method(xml, payment, options)
add_invoice(xml, transaction_type, options)
add_surcharge_fields(xml, options)
add_tax_exempt_status(xml, options)
end
end
Expand Down Expand Up @@ -407,22 +408,29 @@ def normal_void(authorization, options)
end
end

def add_payment_source(xml, source, options, action = nil)
return unless source
def add_payment_method(xml, payment_method, options, action = nil)
return unless payment_method

if source.is_a?(String)
add_token_payment_method(xml, source, options)
elsif card_brand(source) == 'check'
add_check(xml, source)
elsif card_brand(source) == 'apple_pay'
add_apple_pay_payment_token(xml, source)
elsif card_brand(source) == 'opaque_data'
add_opaque_data_payment_method(xml, source)
case payment_method
when String
add_token_payment_method(xml, payment_method, options)
when Check
add_check(xml, payment_method)
when OpaqueDataToken
add_opaque_data_payment_method(xml, payment_method)
else
add_credit_card(xml, source, action)
if network_token?(payment_method, options, action)
add_network_token(xml, payment_method)
else
add_credit_card(xml, payment_method, action)
end
end
end

def network_token?(payment_method, options, action)
payment_method.instance_of?(NetworkTokenizationCreditCard) && action != :credit
end

def camel_case_lower(key)
String(key).split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
end
Expand Down Expand Up @@ -501,7 +509,6 @@ def add_credit_card(xml, credit_card, action)
xml.cardNumber(truncate(credit_card.number, 16))
xml.expirationDate(format(credit_card.month, :two_digits) + '/' + format(credit_card.year, :four_digits))
xml.cardCode(credit_card.verification_value) if credit_card.valid_card_verification_value?(credit_card.verification_value, credit_card.brand)
xml.cryptogram(credit_card.payment_cryptogram) if credit_card.is_a?(NetworkTokenizationCreditCard) && action != :credit
end
end
end
Expand All @@ -528,11 +535,13 @@ def add_token_payment_method(xml, token, options)
xml.customerPaymentProfileId(customer_payment_profile_id)
end

def add_apple_pay_payment_token(xml, apple_pay_payment_token)
def add_network_token(xml, payment_method)
xml.payment do
xml.opaqueData do
xml.dataDescriptor(APPLE_PAY_DATA_DESCRIPTOR)
xml.dataValue(Base64.strict_encode64(apple_pay_payment_token.payment_data.to_json))
xml.creditCard do
xml.cardNumber(truncate(payment_method.number, 16))
xml.expirationDate(format(payment_method.month, :two_digits) + '/' + format(payment_method.year, :four_digits))
xml.isPaymentToken(true)
xml.cryptogram(payment_method.payment_cryptogram)
end
end
end
Expand All @@ -547,7 +556,8 @@ def add_opaque_data_payment_method(xml, opaque_data_payment_token)
end

def add_market_type_device_type(xml, payment, options)
return if payment.is_a?(String) || card_brand(payment) == 'check' || card_brand(payment) == 'apple_pay' || card_brand(payment) == 'opaque_data'
return unless payment.is_a?(CreditCard)
return if payment.is_a?(NetworkTokenizationCreditCard)

if valid_track_data
xml.retail do
Expand Down Expand Up @@ -712,6 +722,16 @@ def add_duty_fields(xml, options)
end
end

def add_surcharge_fields(xml, options)
surcharge = options[:surcharge] if options[:surcharge]
if surcharge.is_a?(Hash)
xml.surcharge do
xml.amount(amount(surcharge[:amount].to_i)) if surcharge[:amount]
xml.description(surcharge[:description]) if surcharge[:description]
end
end
end

def add_shipping_fields(xml, options)
shipping = options[:shipping]
if shipping.is_a?(Hash)
Expand Down Expand Up @@ -765,13 +785,7 @@ def create_customer_payment_profile(credit_card, options)
xml.customerProfileId options[:customer_profile_id]
xml.paymentProfile do
add_billing_address(xml, credit_card, options)
xml.payment do
xml.creditCard do
xml.cardNumber(truncate(credit_card.number, 16))
xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
xml.cardCode(credit_card.verification_value) if credit_card.verification_value
end
end
add_credit_card(xml, credit_card, :cim_store_update)
end
end
end
Expand All @@ -787,13 +801,7 @@ def create_customer_profile(credit_card, options)
xml.customerType('individual')
add_billing_address(xml, credit_card, options)
add_shipping_address(xml, options, 'shipToList')
xml.payment do
xml.creditCard do
xml.cardNumber(truncate(credit_card.number, 16))
xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
xml.cardCode(credit_card.verification_value) if credit_card.verification_value
end
end
add_credit_card(xml, credit_card, :cim_store)
end
end
end
Expand Down Expand Up @@ -957,7 +965,7 @@ def parse_normal(action, body)
end

response[:card_type] =
if(element = doc.at_xpath('//accountType'))
if (element = doc.at_xpath('//accountType'))
(empty?(element.content) ? nil : element.content)
end

Expand Down
22 changes: 0 additions & 22 deletions lib/active_merchant/billing/gateways/checkout_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,6 @@ def perform_request(action, post, options, authorization = nil, method = :post)
raw_response = ssl_request(method, url(action, authorization), post.nil? || post.empty? ? nil : post.to_json, headers(action, options))
response = parse(raw_response)
response['id'] = response['_links']['payment']['href'].split('/')[-1] if action == :capture && response.key?('_links')
source_id = authorization if action == :unstore
rescue ResponseError => e
@options[:access_token] = '' if e.response.code == '401' && !@options[:secret_key]

Expand Down Expand Up @@ -722,27 +721,6 @@ def handle_response(response)
raise ResponseError.new(response)
end
end

def token_type_from(payment_method)
case payment_method.source
when :network_token
payment_method.brand == 'visa' ? 'vts' : 'mdes'
when :google_pay, :android_pay
'googlepay'
when :apple_pay
'applepay'
end
end

def handle_response(response)
case response.code.to_i
# to get the response code after unstore(delete instrument), because the body is nil
when 200...300
response.body || response.code
else
raise ResponseError.new(response)
end
end
end
end
end
11 changes: 4 additions & 7 deletions lib/active_merchant/billing/gateways/cyber_source_rest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -415,21 +415,18 @@ def success_from(response)
def message_from(response)
return response['status'] if success_from(response)

response['errorInformation']['message'] || response['message']
response.dig('errorInformation', 'message') || response['message']
end

def authorization_from(response)
id = response['id']
has_amount = response['orderInformation'] && response['orderInformation']['amountDetails'] && response['orderInformation']['amountDetails']['authorizedAmount']
amount = response['orderInformation']['amountDetails']['authorizedAmount'].delete('.') if has_amount
amount = response.dig('orderInformation', 'amountDetails', 'authorizedAmount')&.delete('.')

return id if amount.blank?

[id, amount].join('|')
amount.present? ? [id, amount].join('|') : id
end

def error_code_from(response)
response['errorInformation']['reason'] unless success_from(response)
response.dig('errorInformation', 'reason') unless success_from(response)
end

# This implementation follows the Cybersource guide on how create the request signature, see:
Expand Down
9 changes: 0 additions & 9 deletions lib/active_merchant/billing/gateways/mercado_pago.rb
Original file line number Diff line number Diff line change
Expand Up @@ -323,15 +323,6 @@ def inquire_path(authorization, options)
end
end

def inquire_path(authorization, options)
if authorization
authorization, = authorization.split('|')
"payments/#{authorization}"
else
"payments/search?external_reference=#{options[:order_id] || options[:external_reference]}"
end
end

def error_code_from(action, response)
unless success_from(action, response)
if cause = response['cause']
Expand Down
3 changes: 1 addition & 2 deletions lib/active_merchant/billing/gateways/nmi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,7 @@ def split_authorization(authorization)
end

def headers
headers = { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' }
headers
{ 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' }
end

def post_data(action, params)
Expand Down
1 change: 1 addition & 0 deletions lib/active_merchant/billing/gateways/paysafe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ def add_stored_credential(post, options)
end

post[:storedCredential][:initialTransactionId] = options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id]
post[:storedCredential][:externalInitialTransactionId] = options[:external_initial_transaction_id] if options[:external_initial_transaction_id]
end

def mastercard?(payment)
Expand Down
7 changes: 6 additions & 1 deletion lib/active_merchant/billing/gateways/redsys_rest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,12 @@ def commit(post, options)
payload = raw_response['Ds_MerchantParameters']
return Response.new(false, "#{raw_response['errorCode']} ERROR") unless payload

response = JSON.parse(Base64.decode64(payload)).transform_keys!(&:downcase).with_indifferent_access
begin
response = JSON.parse(Base64.decode64(payload)).transform_keys!(&:downcase).with_indifferent_access
rescue JSON::ParserError
response = JSON.parse(Base64.urlsafe_decode64(payload)).transform_keys!(&:downcase).with_indifferent_access
end

return Response.new(false, 'Unable to verify response') unless validate_signature(payload, raw_response['Ds_Signature'], response[:ds_order])

succeeded = success_from(response, options)
Expand Down
10 changes: 5 additions & 5 deletions lib/active_merchant/billing/gateways/square.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,21 @@ class SquareGateway < Gateway
'BAD_REQUEST' => STANDARD_ERROR_CODE[:processing_error]
}.freeze

def initialize(options={})
def initialize(options = {})
requires!(options, :access_token)
@access_token = options[:access_token]
@fee_currency = options[:fee_currency] || default_currency
super
end

def authorize(money, payment, options={})
def authorize(money, payment, options = {})
post = create_post_for_auth_or_purchase(money, payment, options)
post[:autocomplete] = false

commit(:post, 'payments', post, options)
end

def purchase(money, payment, options={})
def purchase(money, payment, options = {})
post = create_post_for_auth_or_purchase(money, payment, options)
post[:autocomplete] = true

Expand All @@ -88,7 +88,7 @@ def void(authorization, options = {})
commit(:post, "payments/#{authorization}/cancel", post, {})
end

def refund(money, identification, options={})
def refund(money, identification, options = {})
post = { payment_id: identification }

add_idempotency_key(post, options)
Expand Down Expand Up @@ -297,7 +297,7 @@ def headers(options = {})
{
'Content-Type' => 'application/json',
'Authorization' => "Bearer #{key}",
'Square-Version' => api_version(options),
'Square-Version' => api_version(options)
}
end

Expand Down
24 changes: 0 additions & 24 deletions lib/active_merchant/billing/gateways/stripe_payment_intents.rb
Original file line number Diff line number Diff line change
Expand Up @@ -484,30 +484,6 @@ def format_eci(payment_method, options)
end
end

def add_network_token_data(post_data, payment_method, options)
return unless adding_network_token_card_data?(payment_method)

post_data[:card] ||= {}
post_data[:card][:last4] = options[:last_4]
post_data[:card][:network_token] = {}
post_data[:card][:network_token][:number] = payment_method.number
post_data[:card][:network_token][:exp_month] = payment_method.month
post_data[:card][:network_token][:exp_year] = payment_method.year
post_data[:card][:network_token][:payment_account_reference] = options[:payment_account_reference] if options[:payment_account_reference]

post_data
end

def add_network_token_cryptogram_and_eci(post, payment_method)
return unless adding_network_token_card_data?(payment_method)

post[:payment_method_options] ||= {}
post[:payment_method_options][:card] ||= {}
post[:payment_method_options][:card][:network_token] ||= {}
post[:payment_method_options][:card][:network_token][:cryptogram] = payment_method.payment_cryptogram if payment_method.payment_cryptogram
post[:payment_method_options][:card][:network_token][:electronic_commerce_indicator] = payment_method.eci if payment_method.eci
end

def extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
if payment_method.include?('|')
customer_id, payment_method = payment_method.split('|')
Expand Down
Loading

0 comments on commit 9fb4b1c

Please sign in to comment.