Skip to content

Commit

Permalink
Elavon: Update Stored Credential behavior
Browse files Browse the repository at this point in the history
To satisfy new Elavon API requirements, including recurring_flag,
approval_code for non-tokenized PMs, installment_count and _number, and
situational par_value and association_token_data fields.

This also now allows Authorize transactions with a token/stored card.

Remote (Same 5 Failures on Master):
39 tests, 162 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
87.1795% passed

Unit:
53 tests, 298 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
  • Loading branch information
curiousepic committed Feb 9, 2024
1 parent 0932b65 commit 645d5b3
Show file tree
Hide file tree
Showing 3 changed files with 525 additions and 129 deletions.
95 changes: 61 additions & 34 deletions lib/active_merchant/billing/gateways/elavon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ def purchase(money, payment_method, options = {})
xml.ssl_transaction_type self.actions[:purchase]
xml.ssl_amount amount(money)

if payment_method.is_a?(String)
if payment_method.is_a?(String) || options[:ssl_token]
add_token(xml, payment_method)
else
add_creditcard(xml, payment_method)
add_creditcard(xml, payment_method, options)
end

add_invoice(xml, options)
Expand All @@ -56,27 +56,32 @@ 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)
end

def authorize(money, creditcard, options = {})
def authorize(money, payment_method, 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[:authorize]
xml.ssl_amount amount(money)

add_salestax(xml, options)
if payment_method.is_a?(String) || options[:ssl_token]
add_token(xml, payment_method)
else
add_creditcard(xml, payment_method, options)
end

add_invoice(xml, options)
add_creditcard(xml, creditcard)
add_salestax(xml, options)
add_currency(xml, money, options)
add_address(xml, 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 @@ -92,7 +97,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 @@ -139,7 +144,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 @@ -152,7 +157,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 @@ -165,7 +170,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 @@ -179,7 +184,7 @@ def update(token, creditcard, options = {})
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)
add_creditcard(xml, creditcard, options)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
Expand Down Expand Up @@ -213,11 +218,11 @@ def add_txn_id(xml, authorization)
xml.ssl_txn_id authorization.split(';').last
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 @@ -234,8 +239,11 @@ def add_token(xml, token)
xml.ssl_token token
end

def add_verification_value(xml, creditcard)
xml.ssl_cvv2cvc2 creditcard.verification_value
def add_verification_value(xml, credit_card, options)
return unless credit_card.verification_value?
return if options[:stored_credential] && options[:stored_credential][:initial_transaction] != 'Y'

xml.ssl_cvv2cvc2 credit_card.verification_value
xml.ssl_cvv2cvc2_indicator 1
end

Expand Down Expand Up @@ -294,16 +302,17 @@ 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 add_entry_mode(payment_method, options) if add_entry_mode(payment_method, options)
add_custom_fields(xml, options) if options[:custom_fields]
add_stored_credential(xml, options) if options[:stored_credential]
add_stored_credential(xml, payment_method, options) if options[:stored_credential]
add_installment_fields(xml, options) if options.dig(:stored_credential, :reason_type) == 'installment'
end

def add_custom_fields(xml, options)
Expand Down Expand Up @@ -352,30 +361,48 @@ def add_line_items(xml, level_3_data)
}
end

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

network_transaction_id = options.dig(:stored_credential, :network_transaction_id)
case
when network_transaction_id.nil?
return
when network_transaction_id.to_s.include?('|')
oar_data, ps2000_data = options[:stored_credential][:network_transaction_id].split('|')
xml.ssl_oar_data oar_data unless oar_data.nil? || oar_data.empty?
xml.ssl_ps2000_data ps2000_data unless ps2000_data.nil? || ps2000_data.empty?
when network_transaction_id.to_s.length > 22
xml.ssl_oar_data options.dig(:stored_credential, :network_transaction_id)
else
xml.ssl_ps2000_data 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 = options[:stored_credential][: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' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
end

def add_installment_fields(xml, options)
xml.ssl_payment_number options[:payment_number]
xml.ssl_payment_count options[:installments]
end

def entry_mode(options)
def add_entry_mode(payment_method, options)
return options[:entry_mode] if options[:entry_mode]
return 12 if options[:stored_credential]
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
Loading

0 comments on commit 645d5b3

Please sign in to comment.