Class: Paymos::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/paymos/client.rb,
sig/paymos.rbs

Constant Summary collapse

SAFE_METHODS =
%w[GET HEAD OPTIONS].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, api_secret:, base_url: 'https://api.paymos.io', timeout: 30, max_retries: 2, base_delay: 0.150, transport: nil, clock: -> { Time.now.to_i }, sleeper: ->(seconds) { sleep(seconds) }, random: Random.new) ⇒ Client

Returns a new instance of Client.

Parameters:

  • api_key: (String)
  • api_secret: (String)
  • base_url: (String) (defaults to: 'https://api.paymos.io')
  • timeout: (Numeric) (defaults to: 30)
  • max_retries: (Integer) (defaults to: 2)
  • base_delay: (Numeric) (defaults to: 0.150)
  • transport: (Object) (defaults to: nil)
  • clock: (Object) (defaults to: -> { Time.now.to_i })
  • sleeper: (Object) (defaults to: ->(seconds) { sleep(seconds) })
  • random: (Random) (defaults to: Random.new)

Raises:



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/paymos/client.rb', line 24

def initialize(api_key:, api_secret:, base_url: 'https://api.paymos.io', timeout: 30,
               max_retries: 2, base_delay: 0.150, transport: nil,
               clock: -> { Time.now.to_i }, sleeper: ->(seconds) { sleep(seconds) }, random: Random.new)
  raise ConfigurationError, 'api_key and api_secret are required' if api_key.to_s.empty? || api_secret.to_s.empty?
  unless timeout.positive? && max_retries >= 0 && base_delay >= 0
    raise ConfigurationError,
          'timeout, max_retries, and base_delay are invalid'
  end
  raise ConfigurationError, 'base_url is required' if base_url.to_s.strip.empty?

  @api_key = api_key
  @api_secret = api_secret
  origin = URI.parse(base_url)
  unless origin.absolute? && origin.host && [nil, '',
                                             '/'].include?(origin.path) && origin.query.nil? && origin.fragment.nil?
    raise ConfigurationError, 'base_url must be an absolute origin without a path'
  end

  @base_url = base_url.delete_suffix('/')
  @timeout = timeout
  @max_retries = max_retries
  @base_delay = base_delay
  @transport = transport || method(:default_transport)
  @clock = clock
  @sleeper = sleeper
  @random = random
  @invoices = Invoices.new(self)
  @withdrawals = Withdrawals.new(self)
  @balances = Balances.new(self)
  @system = System.new(self)
end

Instance Attribute Details

#balancesBalances (readonly)

Returns the value of attribute balances.

Returns:



22
23
24
# File 'lib/paymos/client.rb', line 22

def balances
  @balances
end

#invoicesInvoices (readonly)

Returns the value of attribute invoices.

Returns:



22
23
24
# File 'lib/paymos/client.rb', line 22

def invoices
  @invoices
end

#systemSystem (readonly)

Returns the value of attribute system.

Returns:



22
23
24
# File 'lib/paymos/client.rb', line 22

def system
  @system
end

#withdrawalsWithdrawals (readonly)

Returns the value of attribute withdrawals.

Returns:



22
23
24
# File 'lib/paymos/client.rb', line 22

def withdrawals
  @withdrawals
end

Instance Method Details

#request(method, path, payload: nil, query: '') ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/paymos/client.rb', line 56

def request(method, path, payload: nil, query: '')
  method = method.to_s.upcase
  body = payload.nil? ? '' : JSON.generate(payload)
  attempts = 0

  loop do
    begin
      timestamp = @clock.call.to_i.to_s
      headers = {
        'Authorization' => Signing.authorization(@api_key, @api_secret, timestamp, method, path, query, body),
        'X-Request-Timestamp' => timestamp,
        'Content-Type' => 'application/json',
        'Accept' => 'application/json',
        'User-Agent' => "paymos-ruby/#{VERSION}"
      }
      response = @transport.call(method, "#{@base_url}#{path}#{query}", headers, body.empty? ? nil : body, @timeout)
    rescue IOError, SystemCallError, Timeout::Error, SocketError => e
      if attempts < @max_retries && SAFE_METHODS.include?(method)
        wait(attempts, nil)
        attempts += 1
        next
      end
      raise Error, "Paymos request failed: #{e.message}", cause: e
    end

    unless response.respond_to?(:status) && response.respond_to?(:body) && response.respond_to?(:headers)
      raise Error, 'Paymos transport returned an invalid response'
    end

    if attempts < @max_retries && retryable?(method, response.status)
      wait(attempts, header(response.headers, 'retry-after'))
      attempts += 1
      next
    end

    unless (200..299).cover?(response.status)
      raise Paymos.api_error(response.status, response.body,
                             response.headers)
    end
    raise Error, 'Paymos API returned an empty response' if response.body.to_s.empty?

    begin
      return JSON.parse(response.body)
    rescue JSON::ParserError => e
      raise Error, "Paymos API returned invalid JSON: #{e.message}"
    end
  end
end