Skip to content

Commit

Permalink
Merge branch 'master' into ECS-3430_Adyen_Format_Error_Fix
Browse files Browse the repository at this point in the history
  • Loading branch information
JesseBowling committed Aug 5, 2024
2 parents dea3ee6 + f52d344 commit 180d6e6
Show file tree
Hide file tree
Showing 26 changed files with 727 additions and 153 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
= ActiveMerchant CHANGELOG

== HEAD

= Version 1.137.0 (August 2, 2024)

* Unlock dependency on `rexml` to allow fixing a CVE (#5181).
* Bump Ruby version to 3.1 [dustinhaefele] #5104
* FlexCharge: Update inquire method to use the new orders end-point
* Worldpay: Prefer options for network_transaction_id [aenand] #5129
Expand All @@ -26,6 +30,10 @@
* Braintree Blue: Pass overridden mid into client token for GS 3DS [sinourain] #5166
* Moneris: Update crypt_type for 3DS [almalee24] #5162
* CheckoutV2: Update 3DS message & error code [almalee24] #5177
* DecicirPlus: Update error_message to add safety navigator [almalee24] #5187
* Elavon: Add updated stored credential version [almalee24] #5170
* Adyen: Add header fields to response body [yunnydang] #5184
* Stripe and Stripe PI: Add header fields to response body [yunnydang] #5185

== Version 1.136.0 (June 3, 2024)
* Shift4V2: Add new gateway based on SecurionPay adapter [heavyblade] #4860
Expand Down
2 changes: 1 addition & 1 deletion activemerchant.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Gem::Specification.new do |s|
s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
s.add_dependency('i18n', '>= 0.6.9')
s.add_dependency('nokogiri', '~> 1.4')
s.add_dependency('rexml', '~> 3.2.5')
s.add_dependency('rexml', '~> 3.3', '>= 3.3.4')

s.add_development_dependency('mocha', '~> 1')
s.add_development_dependency('pry')
Expand Down
26 changes: 25 additions & 1 deletion lib/active_merchant/billing/gateways/adyen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -766,10 +766,34 @@ def add_metadata(post, options = {})
post[:metadata].merge!(options[:metadata]) if options[:metadata]
end

def add_header_fields(response)
return unless @response_headers.present?

headers = {}
headers['response_headers'] = {}
headers['response_headers']['transient_error'] = @response_headers['transient-error'] if @response_headers['transient-error']

response.merge!(headers)
end

def parse(body)
return {} if body.blank?

JSON.parse(body)
response = JSON.parse(body)
add_header_fields(response)
response
end

# Override the regular handle response so we can access the headers
# set header fields and values so we can add them to the response body
def handle_response(response)
@response_headers = response.each_header.to_h if response.respond_to?(:header)
case response.code.to_i
when 200...300
response.body
else
raise ResponseError.new(response)
end
end

def commit(action, parameters, options)
Expand Down
8 changes: 4 additions & 4 deletions lib/active_merchant/billing/gateways/datatrans.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def store(payment_method, options = {})
end

def unstore(authorization, options = {})
data_alias = authorization.split('|')[2].split('-')[0]
data_alias = authorization.split('|')[2]
commit('delete_alias', {}, { alias_id: data_alias }, :delete)
end

Expand All @@ -110,7 +110,7 @@ def scrub(transcript)
def add_payment_method(post, payment_method)
case payment_method
when String
token, exp_month, exp_year = payment_method.split('|')[2].split('-')
token, exp_month, exp_year = payment_method.split('|')[2..4]
card = {
type: 'ALIAS',
alias: token,
Expand Down Expand Up @@ -250,9 +250,9 @@ def success_from(action, response)
end

def authorization_from(response, action, options)
string = [response.dig('responses', 0, 'alias'), options[:expiry_month], options[:expiry_year]].join('-') if action == 'tokenize'
token_array = [response.dig('responses', 0, 'alias'), options[:expiry_month], options[:expiry_year]].join('|') if action == 'tokenize'

auth = [response['transactionId'], response['acquirerAuthorizationCode'], string].join('|')
auth = [response['transactionId'], response['acquirerAuthorizationCode'], token_array].join('|')
return auth unless auth == '||'
end

Expand Down
5 changes: 4 additions & 1 deletion lib/active_merchant/billing/gateways/decidir_plus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,11 @@ def error_message(response)
return error_code_from(response) unless validation_errors = response.dig('validation_errors')

validation_errors = validation_errors[0]
message = "#{validation_errors&.dig('code')}: #{validation_errors&.dig('param')}"
return message unless message == ': '

"#{validation_errors.dig('code')}: #{validation_errors.dig('param')}"
errors = response['validation_errors'].map { |k, v| "#{k}: #{v}" }.join(', ')
"#{response['error_type']} - #{errors}"
end

def rejected?(response)
Expand Down
102 changes: 77 additions & 25 deletions lib/active_merchant/billing/gateways/elavon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def purchase(money, payment_method, options = {})
add_customer_email(xml, options)
add_test_mode(xml, options)
add_ip(xml, options)
add_auth_purchase_params(xml, options)
add_auth_purchase_params(xml, payment_method, options)
add_level_3_fields(xml, options) if options[:level_3_data]
end
commit(request)
Expand All @@ -70,7 +70,7 @@ def authorize(money, payment_method, options = {})
add_customer_email(xml, options)
add_test_mode(xml, options)
add_ip(xml, options)
add_auth_purchase_params(xml, options)
add_auth_purchase_params(xml, payment_method, options)
add_level_3_fields(xml, options) if options[:level_3_data]
end
commit(request)
Expand All @@ -86,7 +86,7 @@ def capture(money, authorization, options = {})
add_salestax(xml, options)
add_approval_code(xml, authorization)
add_invoice(xml, options)
add_creditcard(xml, options[:credit_card])
add_creditcard(xml, options[:credit_card], options)
add_currency(xml, money, options)
add_address(xml, options)
add_customer_email(xml, options)
Expand Down Expand Up @@ -133,7 +133,7 @@ def credit(money, creditcard, options = {})
xml.ssl_transaction_type self.actions[:credit]
xml.ssl_amount amount(money)
add_invoice(xml, options)
add_creditcard(xml, creditcard)
add_creditcard(xml, creditcard, options)
add_currency(xml, money, options)
add_address(xml, options)
add_customer_email(xml, options)
Expand All @@ -146,7 +146,7 @@ def verify(credit_card, options = {})
request = build_xml_request do |xml|
xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
xml.ssl_transaction_type self.actions[:verify]
add_creditcard(xml, credit_card)
add_creditcard(xml, credit_card, options)
add_address(xml, options)
add_test_mode(xml, options)
add_ip(xml, options)
Expand All @@ -159,7 +159,7 @@ def store(creditcard, options = {})
xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
xml.ssl_transaction_type self.actions[:store]
xml.ssl_add_token 'Y'
add_creditcard(xml, creditcard)
add_creditcard(xml, creditcard, options)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
Expand All @@ -172,8 +172,8 @@ def update(token, creditcard, options = {})
request = build_xml_request do |xml|
xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
xml.ssl_transaction_type self.actions[:update]
add_token(xml, token)
add_creditcard(xml, creditcard)
xml.ssl_token token
add_creditcard(xml, creditcard, options)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
Expand All @@ -195,12 +195,12 @@ def scrub(transcript)
private

def add_payment(xml, payment, options)
if payment.is_a?(String)
xml.ssl_token payment
if payment.is_a?(String) || options[:ssl_token]
xml.ssl_token options[:ssl_token] || payment
elsif payment.is_a?(NetworkTokenizationCreditCard)
add_network_token(xml, payment)
else
add_creditcard(xml, payment)
add_creditcard(xml, payment, options)
end
end

Expand All @@ -227,11 +227,11 @@ def add_network_token(xml, payment_method)
end
end

def add_creditcard(xml, creditcard)
def add_creditcard(xml, creditcard, options)
xml.ssl_card_number creditcard.number
xml.ssl_exp_date expdate(creditcard)

add_verification_value(xml, creditcard) if creditcard.verification_value?
add_verification_value(xml, creditcard, options)

xml.ssl_first_name url_encode_truncate(creditcard.first_name, 20)
xml.ssl_last_name url_encode_truncate(creditcard.last_name, 30)
Expand All @@ -244,12 +244,12 @@ def add_currency(xml, money, options)
xml.ssl_transaction_currency currency
end

def add_token(xml, token)
xml.ssl_token token
end
def add_verification_value(xml, credit_card, options)
return unless credit_card.verification_value?
# Don't add cvv if this is a non-initial stored credential transaction
return if options[:stored_credential] && !options.dig(:stored_credential, :initial_transaction) && options[:stored_cred_v2]

def add_verification_value(xml, creditcard)
xml.ssl_cvv2cvc2 creditcard.verification_value
xml.ssl_cvv2cvc2 credit_card.verification_value
xml.ssl_cvv2cvc2_indicator 1
end

Expand Down Expand Up @@ -308,16 +308,20 @@ def add_ip(xml, options)
end

# add_recurring_token is a field that can be sent in to obtain a token from Elavon for use with their tokenization program
def add_auth_purchase_params(xml, options)
def add_auth_purchase_params(xml, payment_method, options)
xml.ssl_dynamic_dba options[:dba] if options.has_key?(:dba)
xml.ssl_merchant_initiated_unscheduled merchant_initiated_unscheduled(options) if merchant_initiated_unscheduled(options)
xml.ssl_add_token options[:add_recurring_token] if options.has_key?(:add_recurring_token)
xml.ssl_token options[:ssl_token] if options[:ssl_token]
xml.ssl_customer_code options[:customer] if options.has_key?(:customer)
xml.ssl_customer_number options[:customer_number] if options.has_key?(:customer_number)
xml.ssl_entry_mode entry_mode(options) if entry_mode(options)
xml.ssl_entry_mode entry_mode(payment_method, options) if entry_mode(payment_method, options)
add_custom_fields(xml, options) if options[:custom_fields]
add_stored_credential(xml, options) if options[:stored_credential]
if options[:stored_cred_v2]
add_stored_credential_v2(xml, payment_method, options)
add_installment_fields(xml, options)
else
add_stored_credential(xml, options)
end
end

def add_custom_fields(xml, options)
Expand Down Expand Up @@ -367,6 +371,8 @@ def add_line_items(xml, level_3_data)
end

def add_stored_credential(xml, options)
return unless options[:stored_credential]

network_transaction_id = options.dig(:stored_credential, :network_transaction_id)
case
when network_transaction_id.nil?
Expand All @@ -382,14 +388,60 @@ def add_stored_credential(xml, options)
end
end

def add_stored_credential_v2(xml, payment_method, options)
return unless options[:stored_credential]

network_transaction_id = options.dig(:stored_credential, :network_transaction_id)
xml.ssl_recurring_flag recurring_flag(options) if recurring_flag(options)
xml.ssl_par_value options[:par_value] if options[:par_value]
xml.ssl_association_token_data options[:association_token_data] if options[:association_token_data]

unless payment_method.is_a?(String) || options[:ssl_token].present?
xml.ssl_approval_code options[:approval_code] if options[:approval_code]
if network_transaction_id.to_s.include?('|')
oar_data, ps2000_data = network_transaction_id.split('|')
xml.ssl_oar_data oar_data unless oar_data.blank?
xml.ssl_ps2000_data ps2000_data unless ps2000_data.blank?
elsif network_transaction_id.to_s.length > 22
xml.ssl_oar_data network_transaction_id
elsif network_transaction_id.present?
xml.ssl_ps2000_data network_transaction_id
end
end
end

def recurring_flag(options)
return unless reason = options.dig(:stored_credential, :reason_type)
return 1 if reason == 'recurring'
return 2 if reason == 'installment'
end

def merchant_initiated_unscheduled(options)
return options[:merchant_initiated_unscheduled] if options[:merchant_initiated_unscheduled]
return 'Y' if options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled' || options.dig(:stored_credential, :reason_type) == 'recurring'
return 'Y' if options.dig(:stored_credential, :initiator) == 'merchant' && merchant_reason_type(options)
end

def merchant_reason_type(options)
if options[:stored_cred_v2]
options.dig(:stored_credential, :reason_type) == 'unscheduled'
else
options.dig(:stored_credential, :reason_type) == 'unscheduled' || options.dig(:stored_credential, :reason_type) == 'recurring'
end
end

def entry_mode(options)
def add_installment_fields(xml, options)
return unless options.dig(:stored_credential, :reason_type) == 'installment'

xml.ssl_payment_number options[:payment_number]
xml.ssl_payment_count options[:installments]
end

def entry_mode(payment_method, options)
return options[:entry_mode] if options[:entry_mode]
return 12 if options[:stored_credential]
return 12 if options[:stored_credential] && options[:stored_cred_v2] != true

return if payment_method.is_a?(String) || options[:ssl_token]
return 12 if options.dig(:stored_credential, :reason_type) == 'unscheduled'
end

def build_xml_request
Expand Down
2 changes: 1 addition & 1 deletion lib/active_merchant/billing/gateways/fat_zebra.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def add_three_ds(post, options)
par: three_d_secure[:authentication_response_status],
ver: formatted_enrollment(three_d_secure[:enrolled]),
threeds_version: three_d_secure[:version],
ds_transaction_id: three_d_secure[:ds_transaction_id]
directory_server_txn_id: three_d_secure[:ds_transaction_id]
}.compact
end

Expand Down
1 change: 1 addition & 0 deletions lib/active_merchant/billing/gateways/rapyd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ def add_ewallet(post, options)
def add_payment_fields(post, options)
post[:description] = options[:description] if options[:description]
post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
post[:save_payment_method] = options[:save_payment_method] if options[:save_payment_method]
end

def add_payment_urls(post, options, action = '')
Expand Down
27 changes: 26 additions & 1 deletion lib/active_merchant/billing/gateways/stripe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,21 @@ def add_radar_data(post, options = {})
post[:radar_options] = radar_options unless radar_options.empty?
end

def add_header_fields(response)
return unless @response_headers.present?

headers = {}
headers['response_headers'] = {}
headers['response_headers']['idempotent_replayed'] = @response_headers['idempotent-replayed'] if @response_headers['idempotent-replayed']
headers['response_headers']['stripe_should_retry'] = @response_headers['stripe-should-retry'] if @response_headers['stripe-should-retry']

response.merge!(headers)
end

def parse(body)
JSON.parse(body)
response = JSON.parse(body)
add_header_fields(response)
response
end

def post_data(params)
Expand Down Expand Up @@ -752,6 +765,18 @@ def success_from(response, options)
!response.key?('error') && response['status'] != 'failed'
end

# Override the regular handle response so we can access the headers
# set header fields and values so we can add them to the response body
def handle_response(response)
@response_headers = response.each_header.to_h if response.respond_to?(:header)
case response.code.to_i
when 200...300
response.body
else
raise ResponseError.new(response)
end
end

def response_error(raw_response)
parse(raw_response)
rescue JSON::ParserError
Expand Down
2 changes: 1 addition & 1 deletion lib/active_merchant/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ActiveMerchant
VERSION = '1.136.0'
VERSION = '1.137.0'
end
Loading

0 comments on commit 180d6e6

Please sign in to comment.