require 'active_merchant/billing/gateways/beanstream/beanstream_core'

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    # This class implements the Canadian {Beanstream}[http://www.beanstream.com] payment gateway.
    # It is also named TD Canada Trust Online Mart payment gateway.
    # To learn more about the specification of Beanstream gateway, please read the OM_Direct_Interface_API.pdf,
    # which you can get from your Beanstream account or get from me by email.
    #
    # == Supported transaction types by Beanstream:
    # * +P+ - Purchase
    # * +PA+ - Pre Authorization
    # * +PAC+ - Pre Authorization Completion
    #
    # == Secure Payment Profiles:
    # BeanStream supports payment profiles (vaults). This allows you to store cc information with BeanStream and process subsequent transactions with a customer id.
    # Secure Payment Profiles must be enabled on your account (must be done over the phone).
    # Your API Access Passcode must be set in Administration => account settings => order settings.
    # To learn more about storing credit cards with the Beanstream gateway, documentation can be found at http://developer.beanstream.com/documentation/classic-apis
    #
    # To store a credit card using Beanstream's Legato Javascript Library (http://developer.beanstream.com/documentation/legato) you must pass the singleUseToken in
    # the store method's option parameter. Example: @gateway.store("gt6-0c78c25b-3637-4ba0-90e2-26105287f198")
    #
    # == Notes
    # * Adding of order products information is not implemented.
    # * Ensure that country and province data is provided as a code such as "CA", "US", "QC".
    # * login is the Beanstream merchant ID, username and password should be enabled in your Beanstream account and passed in using the <tt>:user</tt> and <tt>:password</tt> options.
    # * Test your app with your true merchant id and test credit card information provided in the api pdf document.
    # * Beanstream does not allow Payment Profiles to be deleted with their API. The accounts are 'closed', but have to be deleted manually.
    #
    #  Example authorization (Beanstream PA transaction type):
    #
    #   twenty = 2000
    #   gateway = BeanstreamGateway.new(
    #     :login => '100200000',
    #     :user => 'xiaobozz',
    #     :password => 'password'
    #   )
    #
    #   credit_card = CreditCard.new(
    #     :number => '4030000010001234',
    #     :month => 8,
    #     :year => 2011,
    #     :first_name => 'xiaobo',
    #     :last_name => 'zzz',
    #     :verification_value => 137
    #   )
    #   response = gateway.authorize(twenty, credit_card,
    #     :order_id => '1234',
    #     :billing_address => {
    #       :name => 'xiaobo zzz',
    #       :phone => '555-555-5555',
    #       :address1 => '1234 Levesque St.',
    #       :address2 => 'Apt B',
    #       :city => 'Montreal',
    #       :state => 'QC',
    #       :country => 'CA',
    #       :zip => 'H2C1X8'
    #     },
    #     :email => 'xiaobozzz@example.com',
    #     :subtotal => 800,
    #     :shipping => 100,
    #     :tax1 => 100,
    #     :tax2 => 100,
    #     :custom => 'reference one'
    #   )
    class BeanstreamGateway < Gateway
      include BeanstreamCore

      def authorize(money, source, options = {})
        post = {}
        add_amount(post, money)
        add_invoice(post, options)
        add_source(post, source)
        add_address(post, options)
        add_transaction_type(post, :authorization)
        add_customer_ip(post, options)
        add_recurring_payment(post, options)
        add_three_ds(post, options)
        commit(post)
      end

      def purchase(money, source, options = {})
        post = {}
        add_amount(post, money)
        add_invoice(post, options)
        add_source(post, source)
        add_address(post, options)
        add_transaction_type(post, purchase_action(source))
        add_customer_ip(post, options)
        add_recurring_payment(post, options)
        add_three_ds(post, options)
        commit(post)
      end

      def void(authorization, options = {})
        reference, amount, type = split_auth(authorization)
        if type == TRANSACTIONS[:authorization]
          capture(0, authorization, options)
        else
          post = {}
          add_reference(post, reference)
          add_original_amount(post, amount)
          add_transaction_type(post, void_action(type))
          commit(post)
        end
      end

      def verify(source, options = {})
        MultiResponse.run(:use_first_response) do |r|
          r.process { authorize(100, source, options) }
          r.process(:ignore_result) { void(r.authorization, options) }
        end
      end

      def success?(response)
        response[:trnApproved] == '1' || response[:responseCode] == '1'
      end

      def recurring(money, source, options = {})
        ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE

        post = {}
        add_amount(post, money)
        add_invoice(post, options)
        add_credit_card(post, source)
        add_address(post, options)
        add_transaction_type(post, purchase_action(source))
        add_recurring_type(post, options)
        commit(post)
      end

      def update_recurring(amount, source, options = {})
        ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE

        post = {}
        add_recurring_amount(post, amount)
        add_recurring_invoice(post, options)
        add_credit_card(post, source)
        add_address(post, options)
        add_recurring_operation_type(post, :update)
        add_recurring_service(post, options)
        recurring_commit(post)
      end

      def cancel_recurring(options = {})
        ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE

        post = {}
        add_recurring_operation_type(post, :cancel)
        add_recurring_service(post, options)
        recurring_commit(post)
      end

      def interac
        @interac ||= BeanstreamInteracGateway.new(@options)
      end

      # To match the other stored-value gateways, like TrustCommerce,
      # store and unstore need to be defined
      #
      # When passing a single-use token the :name option is required
      def store(payment_method, options = {})
        post = {}
        add_address(post, options)

        if payment_method.respond_to?(:number)
          add_credit_card(post, payment_method)
        else
          post[:singleUseToken] = payment_method
        end
        add_secure_profile_variables(post, options)

        commit(post, true)
      end

      # can't actually delete a secure profile with the supplicated API. This function sets the status of the profile to closed (C).
      # Closed profiles will have to removed manually.
      def delete(vault_id)
        update(vault_id, false, { status: 'C' })
      end

      alias unstore delete

      # Update the values (such as CC expiration) stored at
      # the gateway.  The CC number must be supplied in the
      # CreditCard object.
      def update(vault_id, payment_method, options = {})
        post = {}
        add_address(post, options)
        if payment_method.respond_to?(:number)
          add_credit_card(post, payment_method)
        else
          post[:singleUseToken] = payment_method
        end
        options[:vault_id] = vault_id
        options[:operation] = secure_profile_action(:modify)
        add_secure_profile_variables(post, options)
        commit(post, true)
      end

      def supports_scrubbing?
        true
      end

      def scrub(transcript)
        transcript.
          gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
          gsub(/(&?password=)[^&\s]*(&?)/, '\1[FILTERED]\2').
          gsub(/(&?passcode=)[^&\s]*(&?)/, '\1[FILTERED]\2').
          gsub(/(&?trnCardCvd=)\d*(&?)/, '\1[FILTERED]\2').
          gsub(/(&?trnCardNumber=)\d*(&?)/, '\1[FILTERED]\2')
      end

      private

      def build_response(*args)
        Response.new(*args)
      end

      def add_three_ds(post, options)
        return unless three_d_secure = options[:three_d_secure]

        post[:SecureXID] = (three_d_secure[:ds_transaction_id] || three_d_secure[:xid]) if three_d_secure.slice(:ds_transaction_id, :xid).values.any?
        post[:SecureECI] = formatted_three_ds_eci(three_d_secure[:eci]) if three_d_secure[:eci].present?
        post[:SecureCAVV] = three_d_secure[:cavv] if three_d_secure[:cavv].present?
      end

      def formatted_three_ds_eci(val)
        case val
        when '05', '02' then 5
        when '06', '01' then 6
        else val.to_i
        end
      end
    end
  end
end
