mathie/sage_pay

View on GitHub
lib/sage_pay/server/command.rb

Summary

Maintainability
A
35 mins
Test Coverage
module SagePay
  module Server
    class Command
      include ActiveModel::Validations

      class_attribute :tx_type, :vps_protocol

      self.vps_protocol = "2.23"

      attr_accessor :mode, :vendor, :vendor_tx_code, :http_proxy

      validates_presence_of :vps_protocol, :mode, :tx_type, :vendor,
        :vendor_tx_code

      validates_length_of :vps_protocol,     :is      => 4
      validates_length_of :vendor,           :maximum => 15
      validates_length_of :vendor_tx_code,   :maximum => 40

      validates_inclusion_of :mode, :allow_blank => true, :in => [ :showpost, :simulator, :test, :live ]

      def self.decimal_accessor(*attrs)
        attrs.each do |attr|
          attr_reader attr
          define_method("#{attr}=") do |value|
            instance_variable_set("@#{attr}", value.blank? ? nil : BigDecimal.new(value.to_s))
          end
        end
      end

      def initialize(attributes = {})
        attributes.each do |k, v|
          send("#{k}=", v)
        end
      end

      def run!
        @response ||= handle_response(post)
      end

      def live_service
        raise NotImplementedError, "Subclass of command implement live_service with tail of the URL used for that command in the test & live systems."
      end

      def simulator_service
        raise NotImplementedError, "Subclass of command implement simulator_service with tail of the URL used for that command in the simulator."
      end

      def url
        case mode
        when :showpost
          "https://test.sagepay.com/showpost/showpost.asp?Service=#{simulator_service}"
        when :simulator
          "https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=#{simulator_service}"
        when :test
          "https://test.sagepay.com/gateway/service/#{live_service}.vsp"
        when :live
          "https://live.sagepay.com/gateway/service/#{live_service}.vsp"
        else
          raise ArgumentError, "Invalid transaction mode"
        end
      end

      def post_params
        raise ArgumentError, "Invalid transaction registration options (see errors hash for details)" unless valid?

        {
          "VPSProtocol"        => vps_protocol,
          "TxType"             => tx_type.to_s.upcase,
          "Vendor"             => vendor,
          "VendorTxCode"       => vendor_tx_code,
        }
      end

      def response_from_response_body(response_body)
        Response.from_response_body(response_body)
      end

      private
      def post
        parsed_uri = URI.parse(url)
        request = Net::HTTP::Post.new(parsed_uri.request_uri)
        request.form_data = post_params

        if http_proxy
          uri = URI.parse(http_proxy)
          proxy_user, proxy_pass = uri.userinfo.split(/:/) if uri.userinfo
          http_client = Net::HTTP::Proxy(uri.host, uri.port, proxy_user, proxy_pass)
        else
          http_client = Net::HTTP
        end

        http = http_client.new(parsed_uri.host, parsed_uri.port)

        if parsed_uri.scheme == "https"
          http.use_ssl = true
          http.verify_mode = OpenSSL::SSL::VERIFY_PEER
          http.ca_file = '/etc/ssl/certs/ca-certificates.crt' if File.exists?('/etc/ssl/certs/ca-certificates.crt')
        end

        http.start { |http|
          http.request(request)
        }
      end

      def handle_response(response)
        case response.code.to_i
        when 200
          response_from_response_body(response.body)
        else
          # FIXME: custom error response would be nice.
          raise RuntimeError, "I guess SagePay doesn't like us today: #{response.inspect}"
        end
      end
    end
  end
end