require 'active_merchant/billing/gateways/viaklix'
require 'nokogiri'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class ElavonGateway < Gateway
include Empty
class_attribute :test_url, :live_url, :delimiter, :actions
self.test_url = 'https://api.demo.convergepay.com/VirtualMerchantDemo/processxml.do'
self.live_url = 'https://api.convergepay.com/VirtualMerchant/processxml.do'
self.display_name = 'Elavon MyVirtualMerchant'
self.supported_countries = %w(US CA PR DE IE NO PL LU BE NL MX)
self.supported_cardtypes = %i[visa master american_express discover]
self.homepage_url = 'http://www.elavon.com/'
self.money_format = :dollars
self.default_currency = 'USD'
self.delimiter = "\n"
self.actions = {
purchase: 'CCSALE',
credit: 'CCCREDIT',
refund: 'CCRETURN',
authorize: 'CCAUTHONLY',
capture: 'CCFORCE',
capture_complete: 'CCCOMPLETE',
void: 'CCDELETE',
store: 'CCGETTOKEN',
update: 'CCUPDATETOKEN',
verify: 'CCVERIFY'
}
def initialize(options = {})
requires!(options, :login, :password)
super
end
def purchase(money, payment_method, options = {})
request = build_xml_request do |xml|
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_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_level_3_fields(xml, options) if options[:level_3_data]
end
commit(request)
end
def authorize(money, creditcard, options = {})
request = build_xml_request do |xml|
xml.ssl_transaction_type self.actions[:authorize]
xml.ssl_amount amount(money)
add_salestax(xml, options)
add_invoice(xml, options)
add_creditcard(xml, creditcard)
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_level_3_fields(xml, options) if options[:level_3_data]
end
commit(request)
end
def capture(money, authorization, options = {})
request = build_xml_request do |xml|
if options[:credit_card]
xml.ssl_transaction_type self.actions[:capture]
xml.ssl_amount amount(money)
add_salestax(xml, options)
add_approval_code(xml, authorization)
add_invoice(xml, options)
add_creditcard(xml, options[:credit_card])
add_currency(xml, money, options)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
else
xml.ssl_transaction_type self.actions[:capture_complete]
xml.ssl_amount amount(money)
add_currency(xml, money, options)
add_txn_id(xml, authorization)
add_partial_shipment_flag(xml, options)
add_test_mode(xml, options)
end
end
commit(request)
end
def refund(money, identification, options = {})
request = build_xml_request do |xml|
xml.ssl_transaction_type self.actions[:refund]
xml.ssl_amount amount(money)
add_txn_id(xml, identification)
add_test_mode(xml, options)
end
commit(request)
end
def void(identification, options = {})
request = build_xml_request do |xml|
xml.ssl_transaction_type self.actions[:void]
add_txn_id(xml, identification)
add_test_mode(xml, options)
end
commit(request)
end
def credit(money, creditcard, options = {})
raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' if creditcard.is_a?(String)
request = build_xml_request do |xml|
xml.ssl_transaction_type self.actions[:credit]
xml.ssl_amount amount(money)
add_invoice(xml, options)
add_creditcard(xml, creditcard)
add_currency(xml, money, options)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
end
commit(request)
end
def verify(credit_card, options = {})
request = build_xml_request do |xml|
xml.ssl_transaction_type self.actions[:verify]
add_creditcard(xml, credit_card)
add_address(xml, options)
add_test_mode(xml, options)
add_ip(xml, options)
end
commit(request)
end
def store(creditcard, options = {})
request = build_xml_request do |xml|
xml.ssl_transaction_type self.actions[:store]
xml.ssl_add_token 'Y'
add_creditcard(xml, creditcard)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
add_verification(xml, options)
end
commit(request)
end
def update(token, creditcard, options = {})
request = build_xml_request do |xml|
xml.ssl_transaction_type self.actions[:update]
add_token(xml, token)
add_creditcard(xml, creditcard)
add_address(xml, options)
add_customer_email(xml, options)
add_test_mode(xml, options)
end
commit(request)
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r(()(.*)()), '\1[FILTERED]\3').
gsub(%r(()(.*)()), '\1[FILTERED]\3').
gsub(%r(()(.*)()), '\1[FILTERED]\3')
end
private
def add_invoice(xml, options)
xml.ssl_invoice_number truncate((options[:order_id] || options[:invoice]), 25)
xml.ssl_description truncate(options[:description], 255)
end
def add_approval_code(xml, authorization)
xml.ssl_approval_code authorization.split(';').first
end
def add_txn_id(xml, authorization)
xml.ssl_txn_id authorization.split(';').last
end
def add_creditcard(xml, creditcard)
xml.ssl_card_number creditcard.number
xml.ssl_exp_date expdate(creditcard)
add_verification_value(xml, creditcard) if creditcard.verification_value?
xml.ssl_first_name truncate(creditcard.first_name, 20)
xml.ssl_last_name truncate(creditcard.last_name, 30)
end
def add_currency(xml, money, options)
currency = options[:currency] || currency(money)
return unless currency && (@options[:multi_currency] || options[:multi_currency])
xml.ssl_transaction_currency currency
end
def add_token(xml, token)
xml.ssl_token token
end
def add_verification_value(xml, creditcard)
xml.ssl_cvv2cvc2 creditcard.verification_value
xml.ssl_cvv2cvc2_indicator 1
end
def add_customer_email(xml, options)
xml.ssl_email truncate(options[:email], 100) unless empty?(options[:email])
end
def add_salestax(xml, options)
return unless options[:tax].present?
xml.ssl_salestax options[:tax]
end
def add_address(xml, options)
billing_address = options[:billing_address] || options[:address]
if billing_address
xml.ssl_avs_address truncate(billing_address[:address1], 30)
xml.ssl_address2 truncate(billing_address[:address2], 30)
xml.ssl_avs_zip truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9)
xml.ssl_city truncate(billing_address[:city], 30)
xml.ssl_state truncate(billing_address[:state], 10)
xml.ssl_company truncate(billing_address[:company], 50)
xml.ssl_phone truncate(billing_address[:phone], 20)
xml.ssl_country truncate(billing_address[:country], 50)
end
if shipping_address = options[:shipping_address]
xml.ssl_ship_to_address1 truncate(shipping_address[:address1], 30)
xml.ssl_ship_to_address2 truncate(shipping_address[:address2], 30)
xml.ssl_ship_to_city truncate(shipping_address[:city], 30)
xml.ssl_ship_to_company truncate(shipping_address[:company], 50)
xml.ssl_ship_to_country truncate(shipping_address[:country], 50)
xml.ssl_ship_to_first_name truncate(shipping_address[:first_name], 20)
xml.ssl_ship_to_last_name truncate(shipping_address[:last_name], 30)
xml.ssl_ship_to_phone truncate(shipping_address[:phone], 10)
xml.ssl_ship_to_state truncate(shipping_address[:state], 2)
xml.ssl_ship_to_zip truncate(shipping_address[:zip], 10)
end
end
def add_verification(xml, options)
xml.ssl_verify 'Y' if options[:verify]
end
def add_test_mode(xml, options)
xml.ssl_test_mode 'TRUE' if options[:test_mode]
end
def add_partial_shipment_flag(xml, options)
xml.ssl_partial_shipment_flag 'Y' if options[:partial_shipment_flag]
end
def add_ip(xml, options)
xml.ssl_cardholder_ip options[:ip] if options.has_key?(:ip)
end
def add_auth_purchase_params(xml, 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_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)
add_custom_fields(xml, options) if options[:custom_fields]
add_stored_credential(xml, options) if options[:stored_credential]
end
def add_custom_fields(xml, options)
options[:custom_fields]&.each do |key, value|
xml.send(key.to_sym, value)
end
end
def add_level_3_fields(xml, options)
level_3_data = options[:level_3_data]
xml.ssl_customer_code level_3_data[:customer_code] if level_3_data[:customer_code]
xml.ssl_salestax level_3_data[:salestax] if level_3_data[:salestax]
xml.ssl_salestax_indicator level_3_data[:salestax_indicator] if level_3_data[:salestax_indicator]
xml.ssl_level3_indicator level_3_data[:level3_indicator] if level_3_data[:level3_indicator]
xml.ssl_ship_to_zip level_3_data[:ship_to_zip] if level_3_data[:ship_to_zip]
xml.ssl_ship_to_country level_3_data[:ship_to_country] if level_3_data[:ship_to_country]
xml.ssl_shipping_amount level_3_data[:shipping_amount] if level_3_data[:shipping_amount]
xml.ssl_ship_from_postal_code level_3_data[:ship_from_postal_code] if level_3_data[:ship_from_postal_code]
xml.ssl_discount_amount level_3_data[:discount_amount] if level_3_data[:discount_amount]
xml.ssl_duty_amount level_3_data[:duty_amount] if level_3_data[:duty_amount]
xml.ssl_national_tax_indicator level_3_data[:national_tax_indicator] if level_3_data[:national_tax_indicator]
xml.ssl_national_tax_amount level_3_data[:national_tax_amount] if level_3_data[:national_tax_amount]
xml.ssl_order_date level_3_data[:order_date] if level_3_data[:order_date]
xml.ssl_other_tax level_3_data[:other_tax] if level_3_data[:other_tax]
xml.ssl_summary_commodity_code level_3_data[:summary_commodity_code] if level_3_data[:summary_commodity_code]
xml.ssl_merchant_vat_number level_3_data[:merchant_vat_number] if level_3_data[:merchant_vat_number]
xml.ssl_customer_vat_number level_3_data[:customer_vat_number] if level_3_data[:customer_vat_number]
xml.ssl_freight_tax_amount level_3_data[:freight_tax_amount] if level_3_data[:freight_tax_amount]
xml.ssl_vat_invoice_number level_3_data[:vat_invoice_number] if level_3_data[:vat_invoice_number]
xml.ssl_tracking_number level_3_data[:tracking_number] if level_3_data[:tracking_number]
xml.ssl_shipping_company level_3_data[:shipping_company] if level_3_data[:shipping_company]
xml.ssl_other_fees level_3_data[:other_fees] if level_3_data[:other_fees]
add_line_items(xml, level_3_data) if level_3_data[:line_items]
end
def add_line_items(xml, level_3_data)
xml.LineItemProducts {
level_3_data[:line_items].each do |line_item|
xml.product {
line_item.each do |key, value|
prefixed_key = "ssl_line_Item_#{key}"
xml.send(prefixed_key, value)
end
}
end
}
end
def add_stored_credential(xml, options)
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)
end
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'
end
def entry_mode(options)
return options[:entry_mode] if options[:entry_mode]
return 12 if options[:stored_credential]
end
def build_xml_request
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
xml.txn do
xml.ssl_merchant_id @options[:login]
xml.ssl_user_id @options[:user]
xml.ssl_pin @options[:password]
yield(xml)
end
end
builder.to_xml.gsub("\n", '')
end
def commit(request)
request = "xmldata=#{request}".delete('&')
response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
Response.new(
response[:result] == '0',
response[:result_message] || response[:errorMessage],
response,
test: @options[:test] || test?,
authorization: authorization_from(response),
error_code: response[:errorCode],
avs_result: { code: response[:avs_response] },
cvv_result: response[:cvv2_response],
network_transaction_id: build_network_transaction_id(response)
)
end
def build_network_transaction_id(response)
"#{response[:oar_data]}|#{response[:ps2000_data]}"
end
def headers
{
'Accept' => 'application/xml',
'Content-type' => 'application/x-www-form-urlencoded'
}
end
def parse(body)
xml = Nokogiri::XML(body)
response = Hash.from_xml(xml.to_s)['txn']
response.deep_transform_keys { |key| key.gsub('ssl_', '').to_sym }
end
def authorization_from(response)
[response[:approval_code], response[:txn_id]].join(';')
end
end
end
end