activemerchant/active_merchant

View on GitHub
lib/active_merchant/billing/gateways/clearhaus.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class ClearhausGateway < Gateway
      self.test_url = 'https://gateway.test.clearhaus.com'
      self.live_url = 'https://gateway.clearhaus.com'

      self.supported_countries = %w[DK NO SE FI DE CH NL AD AT BE BG HR CY CZ FO GL EE FR GR
                                    HU IS IE IT LV LI LT LU MT PL PT RO SK SI ES GB]

      self.default_currency    = 'EUR'
      self.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
      self.supported_cardtypes = %i[visa master]

      self.homepage_url = 'https://www.clearhaus.com'
      self.display_name = 'Clearhaus'
      self.money_format = :cents

      ACTION_CODE_MESSAGES = {
        20000 => 'Approved',
        40000 => 'General input error',
        40110 => 'Invalid card number',
        40120 => 'Invalid CSC',
        40130 => 'Invalid expire date',
        40135 => 'Card expired',
        40140 => 'Invalid currency',
        40200 => 'Clearhaus rule violation',
        40300 => '3-D Secure problem',
        40310 => '3-D Secure authentication failure',
        40400 => 'Backend problem',
        40410 => 'Declined by issuer or card scheme',
        40411 => 'Card restricted',
        40412 => 'Card lost or stolen',
        40413 => 'Insufficient funds',
        40414 => 'Suspected fraud',
        40415 => 'Amount limit exceeded',
        50000 => 'Clearhaus error'
      }

      def initialize(options = {})
        requires!(options, :api_key)
        options[:private_key] = options[:private_key].strip if options[:private_key]
        super
      end

      def purchase(amount, payment, options = {})
        MultiResponse.run(:use_first_response) do |r|
          r.process { authorize(amount, payment, options) }
          r.process { capture(amount, r.authorization, options) }
        end
      end

      def authorize(amount, payment, options = {})
        post = {}
        add_invoice(post, amount, options)

        action =
          if payment.respond_to?(:number)
            add_payment(post, payment)
            '/authorizations'
          elsif payment.kind_of?(String)
            "/cards/#{payment}/authorizations"
          else
            raise ArgumentError.new("Unknown payment type #{payment.inspect}")
          end

        post[:recurring] = options[:recurring] if options[:recurring]
        post[:card][:pares] = options[:pares] if options[:pares]

        commit(action, post)
      end

      def capture(amount, authorization, options = {})
        post = {}
        add_invoice(post, amount, options)

        commit("/authorizations/#{authorization}/captures", post)
      end

      def refund(amount, authorization, options = {})
        post = {}
        add_amount(post, amount, options)

        commit("/authorizations/#{authorization}/refunds", post)
      end

      def void(authorization, options = {})
        commit("/authorizations/#{authorization}/voids", options)
      end

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

      def store(credit_card, options = {})
        post = {}
        add_payment(post, credit_card)

        commit('/cards', post)
      end

      def supports_scrubbing?
        true
      end

      def scrub(transcript)
        transcript.
          gsub(%r((Authorization: Basic )[\w=]+), '\1[FILTERED]').
          gsub(%r((&?card(?:\[|%5B)csc(?:\]|%5D)=)[^&]*)i, '\1[FILTERED]').
          gsub(%r((&?card(?:\[|%5B)pan(?:\]|%5D)=)[^&]*)i, '\1[FILTERED]')
      end

      private

      def add_invoice(post, money, options)
        add_amount(post, money, options)
        post[:reference] = options[:order_id] if options[:order_id]
        post[:text_on_statement] = options[:text_on_statement] if options[:text_on_statement]
      end

      def add_amount(post, amount, options)
        post[:amount]   = localized_amount(amount, options[:currency] || default_currency)
        post[:currency] = (options[:currency] || default_currency)
      end

      def add_payment(post, payment)
        card = {}
        card[:pan]          = payment.number
        card[:expire_month] = '%02d' % payment.month
        card[:expire_year]  = payment.year

        card[:csc] = payment.verification_value if payment.verification_value?

        post[:card] = card if card.any?
      end

      def headers(api_key)
        {
          'Authorization' => 'Basic ' + Base64.strict_encode64("#{api_key}:"),
          'User-Agent'    => "Clearhaus ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
        }
      end

      def parse(body)
        JSON.parse(body) rescue body
      end

      def commit(action, parameters)
        url = (test? ? test_url : live_url) + action
        headers = headers(@options[:api_key])
        body = parameters.to_query

        if @options[:signing_key] && @options[:private_key]
          begin
            headers['Signature'] = generate_signature(body)
          rescue OpenSSL::PKey::RSAError => e
            return Response.new(false, e.message)
          end
        end

        response =
          begin
            parse(ssl_post(url, body, headers))
          rescue ResponseError => e
            raise unless e.response.code.to_s =~ /400/

            parse(e.response.body)
          end

        Response.new(
          success_from(response),
          message_from(response),
          response,
          authorization: authorization_from(action, response),
          test: test?,
          error_code: error_code_from(response)
        )
      end

      def success_from(response)
        (response && (response['status']['code'] == 20000))
      end

      def message_from(response)
        default_message = ACTION_CODE_MESSAGES[response['status']['code']]

        if success_from(response)
          default_message
        else
          (response['status']['message'] || default_message)
        end
      end

      def authorization_from(action, response)
        id_of_auth_for_capture(action) || response['id']
      end

      def id_of_auth_for_capture(action)
        match = action.match(/authorizations\/(.+)\/captures/)
        return nil unless match

        match.captures.first
      end

      def generate_signature(body)
        key = OpenSSL::PKey::RSA.new(@options[:private_key])
        hex = key.sign(OpenSSL::Digest.new('sha256'), body).unpack1('H*')

        "#{@options[:signing_key]} RS256-hex #{hex}"
      end

      def error_code_from(response)
        response['status']['code'] unless success_from(response)
      end
    end
  end
end