require 'active_support/core_ext/float/rounding.rb' # Float#round(precision)

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    module Integrations #:nodoc:
      module AuthorizeNetSim
        # An example. Note the username as a parameter and transaction key you
        # will want to use later. The amount that you pass in will be *rounded*,
        # so preferably pass in X.2 decimal so that no rounding occurs. It is
        # rounded because if it looks like 00.000 Authorize.Net fails the
        # transaction as incorrectly formatted.
        # 
        #  payment_service_for('order_id', 'authorize_net_account', :service => :authorize_net_sim,  :amount => 157.0) do |service|
        # 
        #    # You must call setup_hash and invoice
        #    
        #    service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY',
        #        :order_timestamp => 1206836763
        #    service.customer_id 8
        #    service.customer :first_name => 'g',
        #                       :last_name => 'g',
        #                       :email => 'g@g.com',
        #                       :phone => '3'
        #   service.billing_address :zip => 'g',
        #                   :country => 'United States of America',
        #                   :address => 'g'
        # 
        #   service.ship_to_address :first_name => 'g',
        #                            :last_name => 'g',
        #                            :city => '',
        #                            :address => 'g',
        #                            :address2 => '',
        #                            :state => address.state,
        #                            :country => 'United States of America',
        #                            :zip => 'g'
        # 
        #   service.invoice "516428355" # your invoice number
        #   # The end-user is presented with the HTML produced by the notify_url.
        #   service.notify_url "http://t/authorize_net_sim/payment_received_notification_sub_step"
        #   service.payment_header 'My store name'
        #   service.add_line_item :name => 'item name', :quantity => 1, :unit_price => 0
        #   service.test_request 'true' # only if it's just a test
        #   service.shipping '25.0'
        #   # Tell it to display a "0" line item for shipping, with the price in
        #   # the name, otherwise it isn't shown at all, leaving the end user to
        #   # wonder why the total is different than the sum of the line items.
        #   service.add_shipping_as_line_item
        #   server.add_tax_as_line_item # same with tax
        #   # See the helper.rb file for various custom fields
        # end
         
        class Helper < ActiveMerchant::Billing::Integrations::Helper           
          mapping :order, 'x_fp_sequence'
          mapping :account, 'x_login'
          
          mapping :customer, :first_name => 'x_first_name',
                             :last_name  => 'x_last_name',
                             :email      => 'x_email',
                             :phone      => 'x_phone'
                            
          mapping :notify_url, 'x_relay_url'
          mapping :return_url, '' # unused
          mapping :cancel_return_url, '' # unused
          
          # Custom fields for Authorize.net SIM.
          # See http://www.Authorize.Net/support/SIM_guide.pdf for more descriptions.
          mapping :fax, 'x_fax'
          mapping :customer_id, 'x_cust_id'
          mapping :description, 'x_description'
          mapping :tax, 'x_tax'
          mapping :shipping, 'x_freight'
          
          # True or false, or 0 or 1 same effect [not required to send one,
          # defaults to false].
          mapping :test_request, 'x_test_request'
          
          # This one is necessary for the notify url to be able to parse its
          # information later! They also pass back customer id, if that's
          # useful.
          def invoice(number)
            add_field 'x_invoice_num', number
          end
          
          # Set the billing address. Call like service.billing_address {:city =>
          # 'provo, :state => 'UT'}...
          def billing_address(options)
            for setting in [:city, :state, :zip, :country, :po_num] do
              add_field 'x_' + setting.to_s, options[setting]
            end
            raise 'must use address1 and address2' if options[:address]
            add_field 'x_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip
          end
          
          # Adds a custom field which you submit to Authorize.Net. These fields
          # are all passed back to you verbatim when it does its relay
          # (callback) to you note that if you call it twice with the same name,
          # this function only uses keeps the second value you called it with.          
          def add_custom_field(name, value)
            add_field name, value
          end
          
          # Displays tax as a line item, so they can see it. Otherwise it isn't
          # displayed.
          def add_tax_as_line_item
            raise unless @fields['x_tax']
            add_line_item :name => 'Total Tax', :quantity => 1, :unit_price => @fields['x_tax'], :tax => 0, :line_title => 'Tax'
          end
          
          # Displays shipping as a line item, so they can see it. Otherwise it
          # isn't displayed.
          def add_shipping_as_line_item(extra_options = {})
            raise 'must set shipping/freight before calling this' unless @fields['x_freight']
            add_line_item extra_options.merge({:name => 'Shipping and Handling Cost', :quantity => 1, :unit_price => @fields['x_freight'], :line_title => 'Shipping'})
          end
          
          # Add ship_to_address in the same format as the normal address is
          # added.
          def ship_to_address(options)
            for setting in [:first_name, :last_name, :company, :city, :state, :zip, :country] do
              if options[setting] then
                add_field 'x_ship_to_' + setting.to_s, options[setting]
              end
            end
            raise 'must use :address1 and/or :address2' if options[:address]
            add_field 'x_ship_to_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip
          end
          
          # These control the look of the SIM payment page. Note that you can
          # include a CSS header in descriptors, etc.
          mapping :color_link, 'x_color_link'
          mapping :color_text, 'x_color_text'
          mapping :logo_url, 'x_logo_url'
          mapping :background_url, 'x_background_url' # background image url for the page
          mapping :payment_header, 'x_header_html_payment_form'
          mapping :payment_footer, 'x_footer_html_payment_form'
          
          # For this to work you must have also passed in an email for the
          # purchaser.
          def yes_email_customer_from_authorizes_side
            add_field 'x_email_customer', 'TRUE'
          end

          # Add a line item to Authorize.Net.
          # Call line add_line_item {:name => 'orange', :unit_price => 30, :tax_value => 'Y', :quantity => 3, }
          # Note you can't pass in a negative unit price, and you can add an
          # optional :line_title => 'special name' if you don't want it to say
          # 'Item 1' or what not, the default coded here.
          # Cannot have a negative price, nor a name with "'s or $
          # You can use the :line_title for the product name and then :name for description, if desired
          def add_line_item(options)
            raise 'needs name' unless options[:name]
            
            if @line_item_count == 30
              # Add a note that we are not showing at least one -- AN doesn't
              # display more than 30 or so.
              description_of_last = @raw_html_fields[-1][1]
              # Pull off the second to last section, which is the description.
              description_of_last =~ />([^>]*)<\|>[YN]$/
              # Create a new description, which can't be too big, so truncate here.
              @raw_html_fields[-1][1] = description_of_last.gsub($1, $1[0..200] + ' + more unshown items after this one.')
            end
            
            name = options[:name]
            quantity = options[:quantity] || 1
            line_title = options[:line_title] || ('Item ' + (@line_item_count + 1).to_s) # left most field
            unit_price = options[:unit_price] || 0
            unit_price = unit_price.to_f.round(2)
            tax_value = options[:tax_value] || 'N'
            
            # Sanitization, in case they include a reserved word here, following
            # their guidelines; unfortunately, they require 'raw' fields here,
            # not CGI escaped, using their own delimiters.
            # 
            # Authorize.net ignores the second field (sanitized_short_name)
            raise 'illegal char for line item <|>' if name.include? '<|>'
            raise 'illegal char for line item "' if name.include? '"'
            raise 'cannot pass in dollar sign' if unit_price.to_s.include? '$'
            raise 'must have positive or 0 unit price' if unit_price.to_f < 0
            # Using CGI::escape causes the output to be formated incorrectly in
            # the HTML presented to the end-user's browser (e.g., spaces turn
            # into +'s).
            sanitized_short_name = name[0..30]
            name = name[0..255]            

            add_raw_html_field "x_line_item", "#{line_title}<|>#{sanitized_short_name}<|>#{name}<|>#{quantity}<|>#{unit_price}<|>#{tax_value}"

            @line_item_count += 1
          end
            
          # If you call this it will e-mail to this address a copy of a receipt
          # after successful, from Authorize.Net.
          def email_merchant_from_authorizes_side(to_this_email)
            add_field 'x_email_merchant', to_this_email
          end
          
          # You MUST call this at some point for it to actually work. Options
          # must include :transaction_key and :order_timestamp
          def setup_hash(options)
            raise unless options[:transaction_key]
            raise unless options[:order_timestamp]
            amount = @fields['x_amount']
            data = "#{@fields['x_login']}^#{@fields['x_fp_sequence']}^#{options[:order_timestamp].to_i}^#{amount}^#{@fields['x_currency_code']}"
            hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('md5'), options[:transaction_key], data)
            add_field 'x_fp_hash', hmac
            add_field 'x_fp_timestamp', options[:order_timestamp].to_i
          end
          
          # Note that you should call #invoice and #setup_hash as well, for the
          # response_url to actually work.
          def initialize(order, account, options = {})
            super
            raise 'missing parameter' unless order and account and options[:amount]
            raise 'error -- amount with no digits!' unless options[:amount].to_s =~ /\d/
            add_field('x_type', 'AUTH_CAPTURE') # the only one we deal with, for now.  Not refunds or anything else, currently.
            add_field 'x_show_form', 'PAYMENT_FORM'
            add_field 'x_relay_response', 'TRUE'
            add_field 'x_duplicate_window', '28800' # large default duplicate window.
            add_field 'x_currency_code', currency_code
          	add_field 'x_version' , '3.1' # version from doc
            add_field 'x_amount', options[:amount].to_f.round(2)
          	@line_item_count = 0
          end

        end
      end
    end
  end
end
