module ActiveMerchant #:nodoc: module Billing #:nodoc: class SecureNetGateway < Gateway API_VERSION = "4.0" TRANSACTIONS = { :auth_only => "0000", # :partial_auth_only => "0001", :auth_capture => "0100", # :partial_auth_capture => "0101", :prior_auth_capture => "0200", :capture_only => "0300", # :void => "0400", # :partial_void => "0401", :credit => "0500", # :credit_authonly => "0501", :credit_priorauthcapture => "0502", :force_credit => "0600", :force_credit_authonly => "0601", :force_credit_priorauthcapture => "0602", :verification => "0700", :auth_increment => "0800", :issue => "0900", :activate => "0901", :redeem => "0902", :redeem_partial => "0903", :deactivate => "0904", :reactivate => "0905", :inquiry_balance => "0906" } XML_ATTRIBUTES = { 'xmlns' => "http://gateway.securenet.com/API/Contracts", 'xmlns:i' => "http://www.w3.org/2001/XMLSchema-instance" } NIL_ATTRIBUTE = { 'i:nil' => "true" } # SUCCESS = "true" # SENSITIVE_FIELDS = [ :verification_str2, :expiry_date, :card_number ] self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.homepage_url = 'http://www.securenet.com/' self.display_name = 'SecureNet' # self.wiredump_device = STDOUT # self.test_url = 'https://certify.securenet.com/api/Gateway.svc' self.test_url = 'https://certify.securenet.com/API/gateway.svc/webHttp/ProcessTransaction' self.live_url = 'https://gateway.securenet.com/api/Gateway.svc' APPROVED, DECLINED, ERROR = 1, 2, 3 RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT = 0, 2, 3 AVS_RESULT_CODE, CARD_CODE_RESPONSE_CODE, TRANSACTION_ID = 5, 6, 8 CARD_CODE_ERRORS = %w( N S ) AVS_ERRORS = %w( A E N R W Z ) def initialize(options = {}) requires!(options, :login, :password) @options = options super end def authorize(money, creditcard, options = {}) commit(build_sale_or_authorization_request(creditcard, options, :auth_only), money) end def purchase(money, creditcard, options = {}) commit(build_sale_or_authorization_request(creditcard, options, :auth_capture), money) end def capture(money, creditcard, authorization, options = {}) commit(build_capture_request(authorization, creditcard, options, :prior_auth_capture), money) end def void(money, creditcard, authorization, options = {}) commit(build_void_request(authorization, creditcard, options, :void), money) end def credit(money, creditcard, authorization, options = {}) commit(build_credit_request(authorization, creditcard, options, :credit), money) end private def commit(request, money) xml = build_request(request, money) data = ssl_post(self.test_url, xml, "Content-Type" => "text/xml") response = parse(data) test_mode = test? Response.new(success?(response), message_from(response), response, :test => test_mode, :authorization => response[:transactionid], :avs_result => { :code => response[:avs_result_code] }, :cvv_result => response[:card_code_response_code] ) end def build_request(request, money) xml = Builder::XmlMarkup.new xml.instruct! xml.tag!("TRANSACTION", XML_ATTRIBUTES) do xml.tag! 'AMOUNT', amount(money) xml << request end xml.target! end def build_sale_or_authorization_request(creditcard, options, action) xml = Builder::XmlMarkup.new add_credit_card(xml, creditcard) xml.tag! 'CODE', TRANSACTIONS[action] add_customer_data(xml, options) add_address(xml, creditcard, options) xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 add_invoice(xml, options) add_merchant_key(xml, options) xml.tag! 'METHOD', 'CC' xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? xml.tag! 'TEST', 'TRUE' xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 xml.tag! 'TRANSACTION_SERVICE', 0 xml.target! end def build_capture_or_credit_request(identification, options) xml = Builder::XmlMarkup.new add_identification(xml, identification) add_customer_data(xml, options) xml.target! end def build_capture_request(authorization, creditcard, options, action) xml = Builder::XmlMarkup.new add_credit_card(xml, creditcard) xml.tag! 'CODE', TRANSACTIONS[action] add_customer_data(xml, options) xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 add_merchant_key(xml, options) xml.tag! 'METHOD', 'CC' xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it xml.tag! 'REF_TRANSID', authorization xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? xml.tag! 'TEST', 'TRUE' xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 xml.tag! 'TRANSACTION_SERVICE', 0 xml.target! end def build_credit_request(authorization, creditcard, options, action) # requires!(options, :card_number) xml = Builder::XmlMarkup.new add_credit_card(xml, creditcard) xml.tag! 'CODE', TRANSACTIONS[action] add_customer_data(xml, options) xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 add_merchant_key(xml, options) xml.tag! 'METHOD', 'CC' xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it xml.tag! 'REF_TRANSID', authorization xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? xml.tag! 'TEST', 'TRUE' xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 xml.tag! 'TRANSACTION_SERVICE', 0 xml.target! end def build_void_request(authorization, creditcard, options, action) xml = Builder::XmlMarkup.new add_credit_card(xml, creditcard) xml.tag! 'CODE', TRANSACTIONS[action] add_customer_data(xml, options) xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 add_merchant_key(xml, options) xml.tag! 'METHOD', 'CC' xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it xml.tag! 'REF_TRANSID', authorization xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? xml.tag! 'TEST', 'TRUE' xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 xml.tag! 'TRANSACTION_SERVICE', 0 xml.target! end ######################################################################### # FUNCTIONS RELATED TO BUILDING THE XML ######################################################################### def add_credit_card(xml, creditcard) xml.tag!("CARD") do xml.tag! 'CARDCODE', creditcard.verification_value if creditcard.verification_value? xml.tag! 'CARDNUMBER', creditcard.number xml.tag! 'EXPDATE', expdate(creditcard) end end def expdate(creditcard) year = sprintf("%.4i", creditcard.year) month = sprintf("%.2i", creditcard.month) "#{month}#{year[-2..-1]}" end def add_customer_data(xml, options) if options.has_key? :customer xml.tag! 'CUSTOMERID', options[:customer] end if options.has_key? :ip xml.tag! 'CUSTOMERIP', options[:ip] end end def add_address(xml, creditcard, options) if address = options[:billing_address] || options[:address] xml.tag!("CUSTOMER_BILL") do xml.tag! 'ADDRESS', address[:address1].to_s xml.tag! 'CITY', address[:city].to_s xml.tag! 'COMPANY', address[:company].to_s xml.tag! 'COUNTRY', address[:country].to_s if options.has_key? :email xml.tag! 'EMAIL', options[:email] # xml.tag! 'EMAIL', 'myemail@yahoo.com' xml.tag! 'EMAILRECEIPT', 'FALSE' end xml.tag! 'FIRSTNAME', creditcard.first_name xml.tag! 'LASTNAME', creditcard.last_name xml.tag! 'PHONE', address[:phone].to_s xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] xml.tag! 'ZIP', address[:zip].to_s end end if address = options[:shipping_address] xml.tag!("CUSTOMER_SHIP") do xml.tag! 'ADDRESS', address[:address1].to_s xml.tag! 'CITY', address[:city].to_s xml.tag! 'COMPANY', address[:company].to_s xml.tag! 'COUNTRY', address[:country].to_s xml.tag! 'FIRSTNAME', address[:first_name].to_s xml.tag! 'LASTNAME', address[:last_name].to_s xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] xml.tag! 'ZIP', address[:zip].to_s end else xml.tag!('CUSTOMER_SHIP', NIL_ATTRIBUTE) do end end end def add_invoice(xml, options) xml.tag! 'INVOICEDESC', options[:description] xml.tag! 'INVOICENUM', 'inv-8' end def add_merchant_key(xml, options) xml.tag!("MERCHANT_KEY") do xml.tag! 'GROUPID', 0 xml.tag! 'SECUREKEY', @options[:password] xml.tag! 'SECURENETID', @options[:login] end end ######################################################################### # FUNCTIONS RELATED TO THE RESPONSE ######################################################################### def success?(response) response[:response_code].to_i == APPROVED end def message_from(response) if response[:response_code].to_i == DECLINED return CVVResult.messages[ response[:card_code_response_code] ] if CARD_CODE_ERRORS.include?(response[:card_code_response_code]) return AVSResult.messages[ response[:avs_result_code] ] if AVS_ERRORS.include?(response[:avs_result_code]) end return response[:response_reason_text].nil? ? '' : response[:response_reason_text][0..-1] end def parse(xml) response = {} xml = REXML::Document.new(xml) root = REXML::XPath.first(xml, "//GATEWAYRESPONSE")# || # root = REXML::XPath.first(xml, "//ProcessTransactionResponse")# || # REXML::XPath.first(xml, "//ErrorResponse") if root root.elements.to_a.each do |node| recurring_parse_element(response, node) end end response end def recurring_parse_element(response, node) if node.has_elements? node.elements.each{|e| recurring_parse_element(response, e) } else response[node.name.underscore.to_sym] = node.text end end end end end