require 'rexml/document'

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    # Public: For more information on the Eway Gateway please visit their
    # {Developers Area}[http://www.eway.com.au/developers/api/direct-payments]
    class EwayGateway < Gateway
      self.live_url = 'https://www.eway.com.au'

      self.money_format = :cents
      self.supported_countries = ['AU']
      self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
      self.homepage_url = 'http://www.eway.com.au/'
      self.display_name = 'eWAY'

      # Public: Create a new Eway Gateway.
      # options - A hash of options:
      #           :login     - Your Customer ID.
      #           :password  - Your XML Refund Password that you
      #                        specified on the Eway site. (optional)
      def initialize(options = {})
        requires!(options, :login)
        super
      end

      def purchase(money, creditcard, options = {})
        requires_address!(options)

        post = {}
        add_creditcard(post, creditcard)
        add_address(post, options)
        add_customer_id(post)
        add_invoice_data(post, options)
        add_non_optional_data(post)
        add_amount(post, money)
        post[:CustomerEmail] = options[:email]

        commit(purchase_url(post[:CVN]), money, post)
      end

      def refund(money, authorization, options={})
        post = {}

        add_customer_id(post)
        add_amount(post, money)
        add_non_optional_data(post)
        post[:OriginalTrxnNumber] = authorization
        post[:RefundPassword] = @options[:password]
        post[:CardExpiryMonth] = nil
        post[:CardExpiryYear] = nil

        commit(refund_url, money, post)
      end

      def supports_scrubbing
        true
      end

      def scrub(transcript)
        transcript.
          gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
          gsub(%r((<ewayCardNumber>)\d+(</ewayCardNumber>))i, '\1[FILTERED]\2').
          gsub(%r((<ewayCVN>)\d+(</ewayCVN>))i, '\1[FILTERED]\2')
      end

      private
      def requires_address!(options)
        raise ArgumentError.new('Missing eWay required parameters: address or billing_address') unless (options.has_key?(:address) or options.has_key?(:billing_address))
      end

      def add_creditcard(post, creditcard)
        post[:CardNumber]  = creditcard.number
        post[:CardExpiryMonth]  = sprintf('%.2i', creditcard.month)
        post[:CardExpiryYear] = sprintf('%.4i', creditcard.year)[-2..-1]
        post[:CustomerFirstName] = creditcard.first_name
        post[:CustomerLastName]  = creditcard.last_name
        post[:CardHoldersName] = creditcard.name

        post[:CVN] = creditcard.verification_value if creditcard.verification_value?
      end

      def add_address(post, options)
        if address = options[:billing_address] || options[:address]
          post[:CustomerAddress]    = [ address[:address1], address[:address2], address[:city], address[:state], address[:country] ].compact.join(', ')
          post[:CustomerPostcode]   = address[:zip]
        end
      end

      def add_customer_id(post)
        post[:CustomerID] = @options[:login]
      end

      def add_invoice_data(post, options)
        post[:CustomerInvoiceRef] = options[:order_id]
        post[:CustomerInvoiceDescription] = options[:description]
      end

      def add_amount(post, money)
        post[:TotalAmount] = amount(money)
      end

      def add_non_optional_data(post)
        post[:Option1] = nil
        post[:Option2] = nil
        post[:Option3] = nil
        post[:TrxnNumber] = nil
      end

      def commit(url, money, parameters)
        raw_response = ssl_post(url, post_data(parameters))
        response = parse(raw_response)

        Response.new(success?(response),
          message_from(response[:ewaytrxnerror]),
          response,
          :authorization => response[:ewaytrxnnumber],
          :test => test?
        )
      end

      def success?(response)
        response[:ewaytrxnstatus] == 'True'
      end

      def parse(xml)
        response = {}
        xml = REXML::Document.new(xml)
        xml.elements.each('//ewayResponse/*') do |node|
          response[node.name.downcase.to_sym] = normalize(node.text)
        end unless xml.root.nil?

        response
      end

      def post_data(parameters = {})
        xml   = REXML::Document.new
        root  = xml.add_element('ewaygateway')

        parameters.each do |key, value|
          root.add_element("eway#{key}").text = value
        end
        xml.to_s
      end

      def message_from(message)
        return '' if message.blank?
        MESSAGES[message[0,2]] || message
      end

      def purchase_url(cvn)
        suffix = test? ? 'xmltest/testpage.asp' : 'xmlpayment.asp'
        gateway_part = cvn ? 'gateway_cvn' : 'gateway'
        "#{live_url}/#{gateway_part}/#{suffix}"
      end

      def refund_url
        suffix = test? ? 'xmltest/refund_test.asp' : 'xmlpaymentrefund.asp'
        "#{live_url}/gateway/#{suffix}"
      end

      MESSAGES = {
        '00' => 'Transaction Approved',
        '01' => 'Refer to Issuer',
        '02' => 'Refer to Issuer, special',
        '03' => 'No Merchant',
        '04' => 'Pick Up Card',
        '05' => 'Do Not Honour',
        '06' => 'Error',
        '07' => 'Pick Up Card, Special',
        '08' => 'Honour With Identification',
        '09' => 'Request In Progress',
        '10' => 'Approved For Partial Amount',
        '11' => 'Approved, VIP',
        '12' => 'Invalid Transaction',
        '13' => 'Invalid Amount',
        '14' => 'Invalid Card Number',
        '15' => 'No Issuer',
        '16' => 'Approved, Update Track 3',
        '19' => 'Re-enter Last Transaction',
        '21' => 'No Action Taken',
        '22' => 'Suspected Malfunction',
        '23' => 'Unacceptable Transaction Fee',
        '25' => 'Unable to Locate Record On File',
        '30' => 'Format Error',
        '31' => 'Bank Not Supported By Switch',
        '33' => 'Expired Card, Capture',
        '34' => 'Suspected Fraud, Retain Card',
        '35' => 'Card Acceptor, Contact Acquirer, Retain Card',
        '36' => 'Restricted Card, Retain Card',
        '37' => 'Contact Acquirer Security Department, Retain Card',
        '38' => 'PIN Tries Exceeded, Capture',
        '39' => 'No Credit Account',
        '40' => 'Function Not Supported',
        '41' => 'Lost Card',
        '42' => 'No Universal Account',
        '43' => 'Stolen Card',
        '44' => 'No Investment Account',
        '51' => 'Insufficient Funds',
        '52' => 'No Cheque Account',
        '53' => 'No Savings Account',
        '54' => 'Expired Card',
        '55' => 'Incorrect PIN',
        '56' => 'No Card Record',
        '57' => 'Function Not Permitted to Cardholder',
        '58' => 'Function Not Permitted to Terminal',
        '59' => 'Suspected Fraud',
        '60' => 'Acceptor Contact Acquirer',
        '61' => 'Exceeds Withdrawal Limit',
        '62' => 'Restricted Card',
        '63' => 'Security Violation',
        '64' => 'Original Amount Incorrect',
        '66' => 'Acceptor Contact Acquirer, Security',
        '67' => 'Capture Card',
        '75' => 'PIN Tries Exceeded',
        '82' => 'CVV Validation Error',
        '90' => 'Cutoff In Progress',
        '91' => 'Card Issuer Unavailable',
        '92' => 'Unable To Route Transaction',
        '93' => 'Cannot Complete, Violation Of The Law',
        '94' => 'Duplicate Transaction',
        '96' => 'System Error'
      }
    end
  end
end
