nebolsin/sailplay

View on GitHub
lib/sailplay/client.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'rest-client'
require 'multi_json'
require 'sailplay/configuration'
require 'sailplay/error'
require 'sailplay/response'

module Sailplay
  class Client
    attr_reader :protocol,
                :host,
                :port,
                :endpoint,
                :secure,
                :connection_options,
                :store_id,
                :store_key,
                :store_pin

    alias_method :secure?, :secure

    def initialize(options = {})
      [ :protocol,
        :host,
        :port,
        :endpoint,
        :secure,
        :connection_options,
        :store_id,
        :store_key,
        :store_pin
      ].each do |option|
        instance_variable_set("@#{option}", options[option])
      end
    end

    # @param [String] phone
    # @param [Hash]   options
    #
    # @option options [true|false] :auth — authenticate user
    #
    # @return [Sailplay::User]
    def create_user(phone, options = {})
      params = {:user_phone => phone}
      params[:extra_fields] = 'auth_hash' if options[:auth]

      response = request(:get, '/v1/users/reg', :user_phone => phone)
      if response.success?
        User.parse(response.data)
      else
        raise APIError, "Cannot create user '#{phone}': #{response.error_message}"
      end
    end

    # @param [String] user_id
    # @param [Hash]   options
    #
    # @option options [true|false] :auth — authenticate user
    #
    # @return [Sailplay::User]
    def find_user(user_id, options = {})
      params = {:origin_user_id => user_id}
      params[:user_phone] = options[:phone] if options[:phone]
      params[:extra_fields] = 'auth_hash' if options[:auth]

      response = request(:get, '/v1/users/points-info', params)
      if response.success?
        User.parse(response.data)
      else
        raise APIError, "Cannot find a user '#{phone}': #{response.error_message}"
      end
    end

    # options[:points_rate]    —  коэффициент конвертации рублей в баллы. Может принимать значение из полуинтервала (0,1].
    #                             При отсутствии данного параметра, используется значение, указанное в настройках.
    #                             Формат points_rate=0.45
    # options[:force_complete] —  если true, транзакция считается подтвержденной несмотря на флаг в настройках.
    #                             Данный аттрибут может быть использован, например, в случае когда часть оплат
    #                             необходимо подтверждать, а про остальные известно что они уже подтверждены.
    # options[:order_id]       —  ID заказа
    #
    # @return [Sailplay::Purchase]
    def create_purchase(user_id, price, options = {})
      params = {:price => price, :origin_user_id => user_id}

      params[:user_phone] = options[:phone] if options[:phone]
      params[:points_rate] = options[:points_rate] if options[:points_rate]
      params[:force_complete] = options[:force_complete] if options[:force_complete]
      params[:order_num] = options[:order_id] if options[:order_id]
      params[:l_date] = options[:date].to_i if options[:date]

      params[:fields] = [:public_key, options[:order_id] && :order_num].compact.join(',')

      response = request(:get, '/v1/purchases/new', params)

      if response.success?
        Purchase.parse(response.data)
      else
        raise APIError, "Cannot create a purchase: #{response.error_message}"
      end
    end

    # @param [Integer] order_id
    # @param [Hash]    options
    #
    # @option options [BigDecimal] :price
    def confirm_purchase(order_id, options = {})
      params = {:order_num => order_id}
      params[:new_price] = options[:price] if options[:price]

      response = request(:get, '/v1/purchases/confirm', params)

      if response.success?
        Purchase.parse(response.data)
      else
        raise APIError, "Cannot confirm a purchase: #{response.error_message}"
      end
    end

    # @param [String] gift_public_key
    def confirm_gift(gift_public_key)
      params = {:gift_public_key => gift_public_key}
      response = request(:get, '/v1/ecommerce/gifts/commit-transaction', params)

      if response.success?
      else
        raise APIError, "Cannot confirm a gift: #{response.error_message}"
      end
    end
    
    # @param [String]  user_id    origin user id
    # @param [Integer] points     amount of points to deposit to the user's account
    # @param [String]  comment    
    def add_points(user_id, points, comment = nil)
      params = {:origin_user_id => user_id, :points => points, :comment => comment}
      response = request(:get, '/v2/points/add', params)
      
      if response.success?
        response.data[:public_key]
      else
        raise APIError, "Cannot add points: #{response.error_message}"
      end
    end

    def request(method, url, params = {})
      execute_request(method, url, auth_params.merge(params))
    end

    def logger
      Sailplay.logger
    end

    private

    def auth_params
      {:store_department_id => store_id, :token => api_token}
    end

    def api_url(url = '')
      URI.parse("#{protocol}://#{host}:#{port}").merge("#{endpoint}/#{url}").to_s
    end

    def api_token
      @api_token ||= login
    end

    def login
      raise ConfigurationError, 'Missing client configuration: ' +
          'please check that store_id, store_key and pin_code are ' +
          'configured' unless credentials?

      response = execute_request(:get, '/v1/login', credentials)

      if response.success?
        response.data[:token]
      else
        raise AuthenticationError.new("Cannot authenticate on Sailplay. Check your config. (Response: #{response})")
      end
    end

    def credentials
      {
          :store_department_id => @store_id,
          :store_department_key => @store_key,
          :pin_code => @store_pin
      }
    end

    def credentials?
      credentials.values.all?
    end

    def execute_request(method, url, params = {})
      logger.debug(self.class) {"Starting #{method} request to #{url} with #{params.inspect}"}
      url = api_url(url)
      headers = @connection_options[:headers].merge(:params => params)

      request_opts = {:method => method, :url => url, :headers => headers}

      begin
        response = RestClient::Request.execute(request_opts)
      rescue RestClient::ExceptionWithResponse => e
        if e.http_code && e.http_body
          handle_api_error(e.http_code, e.http_body)
        else
          handle_restclient_error(e)
        end
      rescue RestClient::Exception, SocketError, Errno::ECONNREFUSED => e
        handle_restclient_error(e)
      end


      http_code, http_body = response.code, response.body

      logger.debug(self.class) {"\t HTTP Code -> #{http_code}"}
      #logger.debug(self.class) {"\t HTTP Body -> #{http_body}"}

      begin
        json_body = MultiJson.load(http_body, :symbolize_keys => true)
      rescue MultiJson::DecodeError
        raise APIError.new("Invalid response object from API: #{http_body.inspect} (HTTP response code was #{http_code})", http_code, http_body)
      end

      #logger.debug(self.class) {"\t JSON Body -> #{json_body}"}

      response = Response.new(json_body)

      logger.debug(self.class) {"\t Valid     -> #{response.success?}"}
      logger.debug(self.class) {"\t Payload   -> #{response.data}"}

      response
    end

    def handle_restclient_error(e)
      case e
        when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
          message = "Could not connect to Sailplay (#{endpoint}). " +
              "Please check your internet connection and try again. " +
              "If this problem persists, let us know at support@sailplay.ru."
        when SocketError
          message = "Unexpected error communicating when trying to connect to Sailplay. " +
              "HINT: You may be seeing this message because your DNS is not working. " +
              "To check, try running 'host sailplay.ru' from the command line."
        else
          message = "Unexpected error communicating with Sailplay. " +
              "If this problem persists, let us know at support@sailplay.ru."
      end
      message += "\n\n(Network error: #{e.message})"
      raise APIConnectionError, message
    end

    def handle_api_error(http_code, http_body)
      begin
        error_obj = MultiJson.load(http_body, :symbolize_keys => true)
        message = error_obj[:message]
      rescue MultiJson::DecodeError
        message = "Invalid response object from API: #{http_body.inspect} (HTTP response code was #{http_code})"
        raise APIError.new(message, http_code, http_body)
      end

      case http_code
        when 400, 404 then
          raise InvalidRequestError.new(message, http_code, http_body, error_obj)
        when 401
          raise AuthenticationError.new(message, http_code, http_body, error_obj)
        else
          raise APIError.new(message, http_code, http_body, error_obj)
      end
    end

  end
end