module ActiveMerchant
  module Billing
    # Gateway for netregistry.com.au.
    #
    # Note that NetRegistry itself uses gateway service providers.  At the
    # time of this writing, there are at least two (Quest and Ingenico).
    # This module has only been tested with Quest.
    #
    # Also note that NetRegistry does not offer a test mode, nor does it
    # have support for the authorize/capture/void functionality by default
    # (you may arrange for this as described in "Programming for
    # NetRegistry's E-commerce Gateway." [http://rubyurl.com/hNG]), and no
    # #void functionality is documented.  As a result, the #authorize and
    # #capture have not yet been tested through a live gateway, and #void
    # will raise an error.
    #
    # If you have this functionality enabled, please consider contributing
    # to ActiveMerchant by writing tests/code for these methods, and
    # submitting a patch.
    #
    # In addition to the standard ActiveMerchant functionality, the
    # response will contain a 'receipt' parameter
    # (response.params['receipt']) if a receipt was issued by the gateway.
    class NetRegistryGateway < Gateway
      self.live_url = self.test_url = 'https://paygate.ssllock.net/external2.pl'

      FILTERED_PARAMS = %w[card_no card_expiry receipt_array]

      self.supported_countries = ['AU']

      # Note that support for Diners, Amex, and JCB require extra
      # steps in setting up your account, as detailed in
      # "Programming for NetRegistry's E-commerce Gateway."
      # [http://rubyurl.com/hNG]
      self.supported_cardtypes = %i[visa master diners_club american_express jcb]
      self.display_name = 'NetRegistry'
      self.homepage_url = 'http://www.netregistry.com.au'

      TRANSACTIONS = {
        authorization: 'preauth',
        purchase: 'purchase',
        capture: 'completion',
        status: 'status',
        refund: 'refund'
      }

      # Create a new NetRegistry gateway.
      #
      # Options :login and :password must be given.
      def initialize(options = {})
        requires!(options, :login, :password)
        super
      end

      # Note that #authorize and #capture only work if your account
      # vendor is St George, and if your account has been setup as
      # described in "Programming for NetRegistry's E-commerce
      # Gateway." [http://rubyurl.com/hNG]
      def authorize(money, credit_card, options = {})
        params = {
          'AMOUNT'  => amount(money),
          'CCNUM'   => credit_card.number,
          'CCEXP'   => expiry(credit_card)
        }
        add_request_details(params, options)
        commit(:authorization, params)
      end

      # Note that #authorize and #capture only work if your account
      # vendor is St George, and if your account has been setup as
      # described in "Programming for NetRegistry's E-commerce
      # Gateway." [http://rubyurl.com/hNG]
      def capture(money, authorization, options = {})
        requires!(options, :credit_card)
        credit_card = options[:credit_card]

        params = {
          'PREAUTHNUM' => authorization,
          'AMOUNT'     => amount(money),
          'CCNUM'      => credit_card.number,
          'CCEXP'      => expiry(credit_card)
        }
        add_request_details(params, options)
        commit(:capture, params)
      end

      def purchase(money, credit_card, options = {})
        params = {
          'AMOUNT'  => amount(money),
          'CCNUM'   => credit_card.number,
          'CCEXP'   => expiry(credit_card)
        }
        add_request_details(params, options)
        commit(:purchase, params)
      end

      def refund(money, identification, options = {})
        params = {
          'AMOUNT'  => amount(money),
          'TXNREF'  => identification
        }
        add_request_details(params, options)
        commit(:refund, params)
      end

      def credit(money, identification, options = {})
        ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
        refund(money, identification, options)
      end

      # Specific to NetRegistry.
      #
      # Run a 'status' command.  This lets you view the status of a
      # completed transaction.
      #
      def status(identification)
        params = {
          'TXNREF'  => identification
        }

        commit(:status, params)
      end

      private

      def add_request_details(params, options)
        params['COMMENT'] = options[:description] unless options[:description].blank?
      end

      # Return the expiry for the given creditcard in the required
      # format for a command.
      def expiry(credit_card)
        month = format(credit_card.month, :two_digits)
        year  = format(credit_card.year,  :two_digits)
        "#{month}/#{year}"
      end

      # Post the a request with the given parameters and return the
      # response object.
      #
      # Login and password are added automatically, and the comment is
      # omitted if nil.
      def commit(action, params)
        # get gateway response
        response = parse(ssl_post(self.live_url, post_data(action, params)))

        Response.new(
          response['status'] == 'approved',
          message_from(response),
          response,
          authorization: authorization_from(response, action)
        )
      end

      def post_data(action, params)
        params['COMMAND'] = TRANSACTIONS[action]
        params['LOGIN'] = "#{@options[:login]}/#{@options[:password]}"
        escape_uri(params.map { |k, v| "#{k}=#{v}" }.join('&'))
      end

      # The upstream is picky and so we can't use CGI.escape like we want to
      def escape_uri(uri)
        URI::DEFAULT_PARSER.escape(uri)
      end

      def parse(response)
        params = {}

        lines = response.split("\n")

        # Just incase there is no real response returned
        params['status'] = lines[0]
        params['response_text'] = lines[1]

        started = false
        lines.each do |line|
          if started
            key, val = line.chomp.split(/=/, 2)
            params[key] = val unless FILTERED_PARAMS.include?(key)
          end

          started = line.chomp =~ /^\.$/ unless started
        end

        params
      end

      def message_from(response)
        response['response_text']
      end

      def authorization_from(response, command)
        case command
        when :purchase
          response['txn_ref']
        when :authorization
          response['transaction_no']
        end
      end
    end
  end
end
