Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elavon: Update Stored Credential behavior #5031

Merged
merged 1 commit into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 65 additions & 46 deletions lib/active_merchant/billing/gateways/elavon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,41 +42,34 @@ def purchase(money, payment_method, options = {})
xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
xml.ssl_transaction_type self.actions[:purchase]
xml.ssl_amount amount(money)

if payment_method.is_a?(String)
add_token(xml, payment_method)
else
add_creditcard(xml, payment_method)
end

add_token_or_card(xml, payment_method, options)
add_invoice(xml, options)
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)
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)
add_token_or_card(xml, payment_method, options)
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 +85,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 +132,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 +145,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 +158,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 @@ -174,12 +167,12 @@ def store(creditcard, options = {})
commit(request)
end

def update(token, creditcard, options = {})
def update(token, 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[:update]
add_token(xml, token)
add_creditcard(xml, creditcard)
xml.ssl_token token
add_creditcard(xml, payment_method, options)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
Expand All @@ -200,6 +193,14 @@ def scrub(transcript)

private

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

def add_invoice(xml, options)
xml.ssl_invoice_number url_encode_truncate((options[:order_id] || options[:invoice]), 25)
xml.ssl_description url_encode_truncate(options[:description], 255)
Expand All @@ -213,11 +214,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 @@ -230,12 +231,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[:stored_credential][:initial_transaction]

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 @@ -294,16 +295,16 @@ 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 +353,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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not necessary since you already did if options[:stored_credential] when calling the method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, though I'd lean toward overeager guard clauses than less; if it gets used in the future it's already there.


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]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two new values that may be present on an initial transaction that should be passed in subsequent COF transactions.


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'
Copy link
Contributor Author

@curiousepic curiousepic Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the primary new requirement for indicating recurring txns.

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'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should only be present for reason type unscheduled, not recurring.

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'
Copy link
Contributor Author

@curiousepic curiousepic Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elavon indicated that this should only be 12 for unscheduled reason types. It's also only needed for pass-in cards, not tokens. But we retain the ability to override it with an option param.

end

def build_xml_request
Expand Down
Loading
Loading