module ActiveMerchant #:nodoc:
  module Billing #:nodoc:

    class UsaEpayTransactionGateway < Gateway
      self.test_url = self.live_url = 'https://www.usaepay.com/gate.php'

      self.supported_cardtypes = [:visa, :master, :american_express]
      self.supported_countries = ['US']
      self.homepage_url = 'http://www.usaepay.com/'
      self.display_name = 'USA ePay'

      TRANSACTIONS = {
        :authorization => 'authonly',
        :purchase => 'sale',
        :capture => 'capture',
        :refund => 'refund',
        :void => 'void'
      }

      def initialize(options = {})
        requires!(options, :login)
        super
      end

      def authorize(money, credit_card, options = {})
        post = {}

        add_amount(post, money)
        add_invoice(post, options)
        add_credit_card(post, credit_card)
        add_address(post, credit_card, options)
        add_customer_data(post, options)

        commit(:authorization, post)
      end

      def purchase(money, credit_card, options = {})
        post = {}

        add_amount(post, money)
        add_invoice(post, options)
        add_credit_card(post, credit_card)
        add_address(post, credit_card, options)
        add_customer_data(post, options)

        commit(:purchase, post)
      end

      def capture(money, authorization, options = {})
        post = { :refNum => authorization }

        add_amount(post, money)
        commit(:capture, post)
      end

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

        add_amount(post, money)
        commit(:refund, post)
      end

      def void(authorization, options = {})
        post = { :refNum => authorization }
        commit(:void, post)
      end

      private

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

      def expdate(credit_card)
        year  = format(credit_card.year, :two_digits)
        month = format(credit_card.month, :two_digits)

        "#{month}#{year}"
      end

      def add_customer_data(post, options)
        address = options[:billing_address] || options[:address] || {}
        post[:street] = address[:address1]
        post[:zip] = address[:zip]

        if options.has_key? :email
          post[:custemail] = options[:email]
          post[:custreceipt] = 'No'
        end

        if options.has_key? :customer
          post[:custid] = options[:customer]
        end

        if options.has_key? :ip
          post[:ip] = options[:ip]
        end
      end

      def add_address(post, credit_card, options)
        billing_address = options[:billing_address] || options[:address]

        add_address_for_type(:billing, post, credit_card, billing_address) if billing_address
        add_address_for_type(:shipping, post, credit_card, options[:shipping_address]) if options[:shipping_address]
      end

      def add_address_for_type(type, post, credit_card, address)
        prefix = address_key_prefix(type)

        post[address_key(prefix, 'fname')] = credit_card.first_name
        post[address_key(prefix, 'lname')] = credit_card.last_name
        post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank?
        post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank?
        post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank?
        post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank?
        post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank?
        post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank?
        post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank?
        post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank?
      end

      def address_key_prefix(type)
        case type
        when :shipping then 'ship'
        when :billing then 'bill'
        end
      end

      def address_key(prefix, key)
        "#{prefix}#{key}".to_sym
      end

      def add_invoice(post, options)
        post[:invoice] = options[:order_id]
        post[:description] = options[:description]
      end

      def add_credit_card(post, credit_card)
        post[:card]  = credit_card.number
        post[:cvv2] = credit_card.verification_value if credit_card.verification_value?
        post[:expir]  = expdate(credit_card)
        post[:name] = credit_card.name
      end

      def parse(body)
        fields = {}
        for line in body.split('&')
          key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
          fields[key] = CGI.unescape(value.to_s)
        end

        {
          :status => fields['UMstatus'],
          :auth_code => fields['UMauthCode'],
          :ref_num => fields['UMrefNum'],
          :batch => fields['UMbatch'],
          :avs_result => fields['UMavsResult'],
          :avs_result_code => fields['UMavsResultCode'],
          :cvv2_result => fields['UMcvv2Result'],
          :cvv2_result_code => fields['UMcvv2ResultCode'],
          :vpas_result_code => fields['UMvpasResultCode'],
          :result => fields['UMresult'],
          :error => fields['UMerror'],
          :error_code => fields['UMerrorcode'],
          :acs_url => fields['UMacsurl'],
          :payload => fields['UMpayload']
        }.delete_if{|k, v| v.nil?}
      end

      def commit(action, parameters)
        response = parse( ssl_post(self.live_url, post_data(action, parameters)) )

        Response.new(response[:status] == 'Approved', message_from(response), response,
          :test => test?,
          :authorization => response[:ref_num],
          :cvv_result => response[:cvv2_result_code],
          :avs_result => { :code => response[:avs_result_code] }
        )
      end

      def message_from(response)
        if response[:status] == "Approved"
          return 'Success'
        else
          return 'Unspecified error' if response[:error].blank?
          return response[:error]
        end
      end

      def post_data(action, parameters = {})
        parameters[:command]  = TRANSACTIONS[action]
        parameters[:key] = @options[:login]
        parameters[:software] = 'Active Merchant'
        parameters[:testmode] = (@options[:test] ? 1 : 0)

        parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join("&")
      end
    end
  end
end

