require 'digest/md5'

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    # This gateway accepts the following arguments:
    #   :login    => your PayJunction username
    #   :password => your PayJunction pass
    # Example use:
    #
    #   gateway = ActiveMerchant::Billing::Base.gateway(:pay_gate_xml).new(
    #               :login => "my_account",
    #               :password => "my_pass"
    #            )
    #
    #   # set up credit card obj as in main ActiveMerchant example
    #   creditcard = ActiveMerchant::Billing::CreditCard.new(
    #     :type       => 'visa',
    #     :number     => '4242424242424242',
    #     :month      => 8,
    #     :year       => 2009,
    #     :first_name => 'Bob',
    #     :last_name  => 'Bobsen'
    #   )
    #
    #   # run request
    #   response = gateway.purchase(1000, creditcard) # charge 10 dollars
    #
    # 1) Check whether the transaction was successful
    #
    #   response.success?
    #
    # 2) Retrieve the message returned by PayJunction
    #
    #   response.message
    #
    # 3) Retrieve the unique transaction ID returned by PayGateXML
    #
    #   response.authorization
    #
    # This gateway has many other features which are not implemented here yet
    # The basic setup here only supports auth/capture transactions.
    #
    # Test Transactions
    #
    # PayGateXML has a global test user/pass, but you can also sign up for your own.
    # The class and the test come equipped with the global test creds
    #
    # Usage Details
    #
    # Below is a map of only SOME of the values accepted by PayGateXML and how you should submit
    # each to ActiveMerchant
    #
    # PayGateXML Field        ActiveMerchant Use
    #
    # pgid                    use :login value to gateway instantation
    # pwd                     use :password value to gateway instantiation
    #
    # cname                   credit_card.name
    # cc                      credit_card.number
    # exp                     credit_card values formatted to YYYYMMDD
    # budp                    South Africa only - set to 0 if purchase is not on budget
    # amt                     include as argument to method for your transaction type
    # ver                     do nothing, always set to current API version
    #
    # cref                    provide as :invoice in options, varchar(80)
    # cur                     3 char field, currently only ZAR
    # cvv                     credit_card.verification
    # bno                     batch processing number, i.e. you supply this
    #
    # others -- not used in this implementation
    # nurl, rurl - must remain blank or absent or they will use an alternative authentication mechanism
    # email, ip  - must remain blank or absent or they will use a PayGate extra service call PayProtector
    # threed     - must remain blank unless you are using your own 3D Secure server
    #
    class PayGateXmlGateway < Gateway
      self.live_url = 'https://www.paygate.co.za/payxml/process.trans'

      # The countries the gateway supports merchants from as 2 digit ISO country codes
      self.supported_countries = ['US', 'ZA']

      # The card types supported by the payment gateway
      self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]

      # The homepage URL of the gateway
      self.homepage_url = 'http://paygate.co.za/'

      # The name of the gateway
      self.display_name = 'PayGate PayXML'

      # PayGate only supports Rands
      self.default_currency = 'ZAR'

      # PayGate accepts only lowest denomination
      self.money_format = :cents

      # PayGateXML public test account - you can get a private one too
      TEST_ID_3DSECURE  = '10011013800'
      TEST_ID           = '10011021600'
      TEST_PWD          = 'test'

      API_VERSION = '4.0'

      DECLINE_CODES = {
        # Credit Card Errors - These RESULT_CODEs are returned if the transaction cannot be authorized due to a problem with the card.  The TRANSACTION_STATUS will be 2
        900001  => "Call for Approval",
        900002  => "Card Expired",
        900003  => "Insufficient Funds",
        900004  => "Invalid Card Number",
        900005  => "Bank Interface Timeout",  # indicates a communications failure between the banks systems
        900006  => "Invalid Card",
        900007  => "Declined",
        900009  => "Lost Card",
        900010  => "Invalid Card Length",
        900011  => "Suspected Fraud",
        900012  => "Card Reported As Stolen",
        900013  => "Restricted Card",
        900014  => "Excessive Card Usage",
        900015  => "Card Blacklisted",

        900207  => "Declined; authentication failed",  # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly

        990020  => "Auth Declined",

        991001  => "Invalid expiry date",
        991002  => "Invalid amount",

        # Communication Errors - These RESULT_CODEs are returned if the transaction cannot be completed due to an unexpected error.  TRANSACTION_STATUS will be 0.
        900205  => "Unexpected authentication result (phase 1)",
        900206  => "Unexpected authentication result (phase 1)",

        990001  => "Could not insert into Database",

        990022  => "Bank not available",

        990053  => "Error processing transaction",

        # Miscellaneous - Unless otherwise noted, the TRANSACTION_STATUS will be 0.
        900209  => "Transaction verification failed (phase 2)",  # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered
        900210  => "Authentication complete; transaction must be restarted",  # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed.  Most likely caused by the customer clicking the refresh button

        990024  => "Duplicate Transaction Detected.  Please check before submitting",

        990028  => "Transaction cancelled"  # Customer clicks the 'Cancel' button on the payment page
      }

      SUCCESS_CODES = %w( 990004 990005 990017 990012 990018 990031 )

      TRANSACTION_CODES = {
        0 => 'Not Done',
        1 => 'Approved',
        2 => 'Declined',
        3 => 'Paid',
        4 => 'Refunded',
        5 => 'Received by PayGate',
        6 => 'Replied to Client'
      }

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

      def purchase(money, creditcard, options = {})
        MultiResponse.run do |r|
          r.process{authorize(money, creditcard, options)}
          r.process{capture(money, r.authorization, options)}
        end
      end

      def authorize(money, creditcard, options = {})
        action = 'authtx'

        options.merge!(:money => money, :creditcard => creditcard)
        commit(action, build_request(action, options))
      end

      def capture(money, authorization, options = {})
        action = 'settletx'

        options.merge!(:money => money, :authorization => authorization)
        commit(action, build_request(action, options))
      end

      private

      def successful?(response)
        SUCCESS_CODES.include?(response[:res])
      end

      def build_request(action, options={})
        xml = Builder::XmlMarkup.new
        xml.instruct!

        xml.tag! 'protocol', :ver => API_VERSION, :pgid => (test? ? TEST_ID : @options[:login]), :pwd => @options[:password] do |protocol|
          case action
            when 'authtx'
              money       = options.delete(:money)
              creditcard  = options.delete(:creditcard)
              build_authorization(protocol, money, creditcard, options)
            when 'settletx'
              money           = options.delete(:money)
              authorization   = options.delete(:authorization)
              build_capture(protocol, money, authorization, options)
          else
            raise "no action specified for build_request"
          end
        end

        xml.target!
      end

      def build_authorization(xml, money, creditcard, options={})
        xml.tag! 'authtx', {
          :cref  => options[:order_id],
          :cname => creditcard.name,
          :cc    => creditcard.number,
          :exp   => "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :four_digits)}",
          :budp  => 0,
          :amt   => amount(money),
          :cur   => (options[:currency] || currency(money)),
          :cvv   => creditcard.verification_value
        }
      end

      def build_capture(xml, money, authorization, options={})
        xml.tag! 'settletx', {
          :tid => authorization
        }
      end

      def parse(action, body)
        hash  = {}
        xml   = REXML::Document.new(body)

        response_action = action.gsub(/tx/, 'rx')
        root  = REXML::XPath.first(xml.root, response_action)
        # we might have gotten an error
        if root.nil?
          root  = REXML::XPath.first(xml.root, 'errorrx')
        end
        root.attributes.each do |name, value|
          hash[name.to_sym] = value
        end
        hash
      end

      def commit(action, request)
        response = parse(action, ssl_post(self.live_url, request))
        Response.new(successful?(response), message_from(response), response,
          :test           => test?,
          :authorization  => response[:tid]
        )
      end

      def message_from(response)
        (response[:rdesc] || response[:edesc])
      end
    end
  end
end

