module ActiveMerchant #:nodoc: module Billing #:nodoc: class MerchantWareVersionFourGateway < Gateway self.live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' self.test_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' self.supported_countries = ['US'] self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://merchantwarehouse.com/merchantware' self.display_name = 'MerchantWARE' ENV_NAMESPACES = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } TX_NAMESPACE = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/' ACTIONS = { purchase: 'SaleKeyed', reference_purchase: 'RepeatSale', authorize: 'PreAuthorizationKeyed', capture: 'PostAuthorization', void: 'Void', refund: 'Refund' } # Creates a new MerchantWareVersionFourGateway # # The gateway requires that a valid login, password, and name be passed # in the +options+ hash. # # ==== Options # # * :login - The MerchantWARE SiteID. # * :password - The MerchantWARE Key. # * :name - The MerchantWARE Name. def initialize(options = {}) requires!(options, :login, :password, :name) super end # Authorize a credit card for a given amount. # # ==== Parameters # * money - The amount to be authorized as an Integer value in cents. # * credit_card - The CreditCard details for the transaction. # * options # * :order_id - A unique reference for this order (required). # * :billing_address - The billing address for the cardholder. def authorize(money, credit_card, options = {}) request = build_purchase_request(:authorize, money, credit_card, options) commit(:authorize, request) end # Authorize and immediately capture funds from a credit card. # # ==== Parameters # * money - The amount to be authorized as anInteger value in cents. # * payment_source - The CreditCard details or 'token' from prior transaction # * options # * :order_id - A unique reference for this order (required). # * :billing_address - The billing address for the cardholder. def purchase(money, payment_source, options = {}) action = payment_source.is_a?(String) ? :reference_purchase : :purchase request = build_purchase_request(action, money, payment_source, options) commit(action, request) end # Capture authorized funds from a credit card. # # ==== Parameters # * money - The amount to be captured as anInteger value in cents. # * authorization - The authorization string returned from the initial authorization. def capture(money, authorization, options = {}) request = build_capture_request(:capture, money, authorization, options) commit(:capture, request) end # Void a transaction. # # ==== Parameters # * authorization - The authorization string returned from the initial authorization or purchase. def void(authorization, options = {}) reference, options[:order_id] = split_reference(authorization) request = soap_request(:void) do |xml| add_reference_token(xml, reference) end commit(:void, request) end # Refund an amount back a cardholder # # ==== Parameters # # * money - The amount to be refunded as an Integer value in cents. # * identification - The credit card you want to refund or the authorization for the existing transaction you are refunding. # * options # * :order_id - A unique reference for this order (required when performing a non-referenced credit) def refund(money, identification, options = {}) reference, options[:order_id] = split_reference(identification) request = soap_request(:refund) do |xml| add_reference_token(xml, reference) add_invoice(xml, options) add_amount(xml, money, 'overrideAmount') end commit(:refund, request) end def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } end end def supports_scrubbing? true end def scrub(transcript) transcript. gsub(%r(().+?())i, '\1[FILTERED]\2'). gsub(%r(().+?())i, '\1[FILTERED]\2'). gsub(%r(().+?())i, '\1[FILTERED]\2') end private def soap_request(action) xml = Builder::XmlMarkup.new indent: 2 xml.instruct! xml.tag! 'soap:Envelope', ENV_NAMESPACES do xml.tag! 'soap:Body' do xml.tag! ACTIONS[action], 'xmlns' => TX_NAMESPACE do xml.tag! 'merchantName', @options[:name] xml.tag! 'merchantSiteId', @options[:login] xml.tag! 'merchantKey', @options[:password] yield xml end end end xml.target! end def build_purchase_request(action, money, payment_source, options) requires!(options, :order_id) soap_request(action) do |xml| add_invoice(xml, options) add_amount(xml, money) add_payment_source(xml, payment_source) add_address(xml, options) end end def build_capture_request(action, money, identification, options) reference, options[:order_id] = split_reference(identification) soap_request(action) do |xml| add_reference_token(xml, reference) add_invoice(xml, options) add_amount(xml, money) end end def add_invoice(xml, options) xml.tag! 'invoiceNumber', truncate(options[:order_id].to_s.gsub(/[^\w]/, ''), 8) end def add_amount(xml, money, tag = 'amount') xml.tag! tag, amount(money) end def add_reference_token(xml, reference) xml.tag! 'token', reference end def add_address(xml, options) address = options[:billing_address] || options[:address] || {} xml.tag! 'avsStreetAddress', address[:address1] xml.tag! 'avsStreetZipCode', address[:zip] end def add_payment_source(xml, source) if source.is_a?(String) add_reference_token(xml, source) else add_credit_card(xml, source) end end def add_credit_card(xml, credit_card) xml.tag! 'cardNumber', credit_card.number xml.tag! 'expirationDate', expdate(credit_card) xml.tag! 'cardholder', credit_card.name xml.tag! 'cardSecurityCode', credit_card.verification_value if credit_card.verification_value? end def split_reference(reference) reference.to_s.split(';') end def parse(action, data) response = {} xml = REXML::Document.new(data) root = REXML::XPath.first(xml, "//#{ACTIONS[action]}Response/#{ACTIONS[action]}Result") root.elements.each do |element| response[element.name] = element.text end if response['ErrorMessage'].present? response[:message] = response['ErrorMessage'] response[:success] = false else status, code, message = response['ApprovalStatus'].split(';') response[:status] = status if response[:success] = status == 'APPROVED' response[:message] = status else response[:message] = message response[:failure_code] = code end end response end def parse_error(http_response, action) response = {} response[:http_code] = http_response.code response[:http_message] = http_response.message response[:success] = false document = REXML::Document.new(http_response.body) node = REXML::XPath.first(document, "//#{ACTIONS[action]}Response/#{ACTIONS[action]}Result") node.elements.each do |element| response[element.name] = element.text end response[:message] = response['ErrorMessage'].to_s.tr("\n", ' ') response rescue REXML::ParseException response[:http_body] = http_response.body response[:message] = 'Failed to parse the failed response' response end def soap_action(action) "#{TX_NAMESPACE}#{ACTIONS[action]}" end def url test? ? test_url : live_url end def commit(action, request) begin data = ssl_post(url, request, 'Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => soap_action(action)) response = parse(action, data) rescue ActiveMerchant::ResponseError => e response = parse_error(e.response, action) end Response.new(response[:success], response[:message], response, test: test?, authorization: authorization_from(response), avs_result: { code: response['AvsResponse'] }, cvv_result: response['CvResponse']) end def authorization_from(response) response['Token'] end end end end