require 'rexml/document' module ActiveMerchant #:nodoc: module Billing #:nodoc: class EfsnetGateway < Gateway self.supported_countries = ['US'] self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.concordefsnet.com/' self.display_name = 'Efsnet' self.test_url = 'https://testefsnet.concordebiz.com/efsnet.dll' self.live_url = 'https://efsnet.concordebiz.com/efsnet.dll' # login is your Store ID # password is your Store Key def initialize(options = {}) requires!(options, :login, :password) super end def authorize(money, creditcard, options = {}) request = build_credit_card_request(money, creditcard, options) commit(:credit_card_authorize, request) end def purchase(money, creditcard, options = {}) request = build_credit_card_request(money, creditcard, options) commit(:credit_card_charge, request) end def capture(money, identification, options = {}) request = build_refund_or_settle_request(money, identification, options) commit(:credit_card_settle, request) end def credit(money, identification_or_credit_card, options = {}) if identification_or_credit_card.is_a?(String) ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE # Perform authorization reversal refund(money, identification_or_credit_card, options) else # Perform credit request = build_credit_card_request(money, identification_or_credit_card, options) commit(:credit_card_credit, request) end end def refund(money, reference, options = {}) # Perform authorization reversal request = build_refund_or_settle_request(money, reference, options) commit(:credit_card_refund, request) end def void(identification, options = {}) requires!(options, :order_id) original_transaction_id, = identification.split(';') commit(:void_transaction, {reference_number: format_reference_number(options[:order_id]), transaction_id: original_transaction_id}) end def voice_authorize(money, authorization_code, creditcard, options = {}) options[:authorization_number] = authorization_code request = build_credit_card_request(money, creditcard, options) commit(:credit_card_voice_authorize, request) end def force(money, authorization_code, creditcard, options = {}) options[:authorization_number] = authorization_code request = build_credit_card_request(money, creditcard, options) commit(:credit_card_capture, request) end def system_check commit(:system_check, {}) end private def build_refund_or_settle_request(money, identification, options = {}) original_transaction_id, original_transaction_amount = identification.split(';') requires!(options, :order_id) { reference_number: format_reference_number(options[:order_id]), transaction_amount: amount(money), original_transaction_amount: original_transaction_amount, original_transaction_id: original_transaction_id, client_ip_address: options[:ip] } end def build_credit_card_request(money, creditcard, options = {}) requires!(options, :order_id) post = { reference_number: format_reference_number(options[:order_id]), authorization_number: options[:authorization_number], transaction_amount: amount(money), client_ip_address: options[:ip] } add_creditcard(post, creditcard) add_address(post, options) post end def format_reference_number(number) number.to_s.slice(0, 12) end def add_address(post, options) if address = options[:billing_address] || options[:address] if address[:address2] post[:billing_address] = address[:address1].to_s << ' ' << address[:address2].to_s else post[:billing_address] = address[:address1].to_s end post[:billing_city] = address[:city].to_s post[:billing_state] = address[:state].blank? ? 'n/a' : address[:state] post[:billing_postal_code] = address[:zip].to_s post[:billing_country] = address[:country].to_s end if address = options[:shipping_address] if address[:address2] post[:shipping_address] = address[:address1].to_s << ' ' << address[:address2].to_s else post[:shipping_address] = address[:address1].to_s end post[:shipping_city] = address[:city].to_s post[:shipping_state] = address[:state].blank? ? 'n/a' : address[:state] post[:shipping_postal_code] = address[:zip].to_s post[:shipping_country] = address[:country].to_s end end def add_creditcard(post, creditcard) post[:billing_name] = creditcard.name if creditcard.name post[:account_number] = creditcard.number post[:card_verification_value] = creditcard.verification_value if creditcard.verification_value? post[:expiration_month] = sprintf('%.2i', creditcard.month) post[:expiration_year] = sprintf('%.4i', creditcard.year)[-2..-1] end def commit(action, parameters) response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters), 'Content-Type' => 'text/xml')) Response.new(success?(response), message_from(response[:result_message]), response, test: test?, authorization: authorization_from(response, parameters), avs_result: { code: response[:avs_response_code] }, cvv_result: response[:cvv_response_code] ) end def success?(response) response[:response_code] == '0' end def authorization_from(response, params) [response[:transaction_id], params[:transaction_amount]].compact.join(';') end def parse(xml) response = {} xml = REXML::Document.new(xml) xml.elements.each('//Reply//TransactionReply/*') do |node| response[node.name.underscore.to_sym] = normalize(node.text) end unless xml.root.nil? response end def post_data(action, parameters = {}) xml = REXML::Document.new("") root = xml.add_element('Request') root.attributes['StoreID'] = options[:login] root.attributes['StoreKey'] = options[:password] root.attributes['ApplicationID'] = 'ot 1.0' transaction = root.add_element(action.to_s.camelize) actions[action].each do |key| transaction.add_element(key).text = parameters[key.underscore.to_sym] unless parameters[key.underscore.to_sym].blank? end xml.to_s end def message_from(message) return 'Unspecified error' if message.blank? message.gsub(/[^\w]/, ' ').split.join(' ').capitalize end def actions ACTIONS end CREDIT_CARD_FIELDS = %w(AuthorizationNumber ClientIpAddress BillingAddress BillingCity BillingState BillingPostalCode BillingCountry BillingName CardVerificationValue ExpirationMonth ExpirationYear ReferenceNumber TransactionAmount AccountNumber) ACTIONS = { credit_card_authorize: CREDIT_CARD_FIELDS, credit_card_charge: CREDIT_CARD_FIELDS, credit_card_voice_authorize: CREDIT_CARD_FIELDS, credit_card_capture: CREDIT_CARD_FIELDS, credit_card_credit: CREDIT_CARD_FIELDS + ['OriginalTransactionAmount'], credit_card_refund: %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), void_transaction: %w(ReferenceNumber TransactionID), credit_card_settle: %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), system_check: %w(SystemCheck), } end end end