Class: FluvPay::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/fluvpay/client.rb

Overview

Cliente principal da FluvPay.

Estilo Stripe: um objeto FluvPay::Client configurado com a API key, expondo recursos (charges, transactions, withdrawals, internal_transfers e sandbox). O transporte usa net/http da biblioteca padrão, com retries (apenas em GET e POSTs idempotentes), geração automática de Idempotency-Key (UUIDv4) e mapeamento de erro tipado.

Examples:

client = FluvPay::Client.new(api_key: "fluv_test_sua_chave")
charge = client.charges.create(amount_cents: 4990, description: "Pedido #1042")
puts charge["pix_copy_paste"]

Constant Summary collapse

DEFAULT_BASE_URL =
"https://api.fluvpay.com/api/v1"
DEFAULT_TIMEOUT =
30
DEFAULT_OPEN_TIMEOUT =
10
DEFAULT_MAX_RETRIES =
2
DEFAULT_BACKOFF_FACTOR =
0.5
DEFAULT_MAX_BACKOFF =
8.0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, open_timeout: DEFAULT_OPEN_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES, backoff_factor: DEFAULT_BACKOFF_FACTOR) ⇒ Client

Returns a new instance of Client.

Parameters:

  • api_key (String)

    chave da API (fluv_live_... ou fluv_test_...).

  • base_url (String) (defaults to: DEFAULT_BASE_URL)

    URL base (padrão: produção e sandbox unificados).

  • timeout (Numeric) (defaults to: DEFAULT_TIMEOUT)

    timeout de leitura por requisição, em segundos.

  • open_timeout (Numeric) (defaults to: DEFAULT_OPEN_TIMEOUT)

    timeout de abertura de conexão, em segundos.

  • max_retries (Integer) (defaults to: DEFAULT_MAX_RETRIES)

    tentativas extras (só GET e POSTs idempotentes).

  • backoff_factor (Float) (defaults to: DEFAULT_BACKOFF_FACTOR)

    fator do backoff exponencial com jitter.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/fluvpay/client.rb', line 54

def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT,
               open_timeout: DEFAULT_OPEN_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES,
               backoff_factor: DEFAULT_BACKOFF_FACTOR)
  if !api_key.is_a?(String) || api_key.empty?
    raise ArgumentError, "api_key é obrigatória e deve ser uma string não vazia."
  end

  @api_key = api_key
  @base_url = base_url.to_s.sub(%r{/+\z}, "")
  @timeout = timeout
  @open_timeout = open_timeout
  @max_retries = max_retries
  @backoff_factor = backoff_factor

  @charges = Resources::Charges.new(self)
  @transactions = Resources::Transactions.new(self)
  @withdrawals = Resources::Withdrawals.new(self)
  @internal_transfers = Resources::InternalTransfers.new(self)
  @sandbox = Resources::Sandbox.new(self)
end

Instance Attribute Details

#api_keyString (readonly)

Returns a API key configurada.

Returns:

  • (String)

    a API key configurada.



39
40
41
# File 'lib/fluvpay/client.rb', line 39

def api_key
  @api_key
end

#base_urlString (readonly)

Returns URL base sem barra final.

Returns:

  • (String)

    URL base sem barra final.



41
42
43
# File 'lib/fluvpay/client.rb', line 41

def base_url
  @base_url
end

#chargesObject (readonly)

Recursos expostos.



46
47
48
# File 'lib/fluvpay/client.rb', line 46

def charges
  @charges
end

#internal_transfersObject (readonly)

Recursos expostos.



46
47
48
# File 'lib/fluvpay/client.rb', line 46

def internal_transfers
  @internal_transfers
end

#max_retriesInteger (readonly)

Returns tentativas extras em 429/5xx/conexão.

Returns:

  • (Integer)

    tentativas extras em 429/5xx/conexão.



43
44
45
# File 'lib/fluvpay/client.rb', line 43

def max_retries
  @max_retries
end

#sandboxObject (readonly)

Recursos expostos.



46
47
48
# File 'lib/fluvpay/client.rb', line 46

def sandbox
  @sandbox
end

#transactionsObject (readonly)

Recursos expostos.



46
47
48
# File 'lib/fluvpay/client.rb', line 46

def transactions
  @transactions
end

#withdrawalsObject (readonly)

Recursos expostos.



46
47
48
# File 'lib/fluvpay/client.rb', line 46

def withdrawals
  @withdrawals
end

Class Method Details

.new_idempotency_keyString

Gera um Idempotency-Key UUIDv4.

Returns:

  • (String)


87
88
89
# File 'lib/fluvpay/client.rb', line 87

def self.new_idempotency_key
  SecureRandom.uuid
end

.test_key?(api_key) ⇒ Boolean

Returns true se a chave informada tiver prefixo fluv_test_.

Returns:

  • (Boolean)

    true se a chave informada tiver prefixo fluv_test_.



81
82
83
# File 'lib/fluvpay/client.rb', line 81

def self.test_key?(api_key)
  api_key.to_s.start_with?("fluv_test_")
end

Instance Method Details

#request(method, path, params: nil, body: nil, idempotency_key: nil, retry_request: nil) ⇒ Object

Executa uma requisição e devolve o JSON já parseado (ou lança erro tipado).

Parameters:

  • method (Symbol, String)

    verbo HTTP (:get, :post).

  • path (String)

    caminho relativo à base_url (ex: “/charges/”).

  • params (Hash, nil) (defaults to: nil)

    parâmetros de query (valores nil são removidos).

  • body (Hash, nil) (defaults to: nil)

    corpo JSON (valores nil são removidos no topo).

  • idempotency_key (String, nil) (defaults to: nil)

    valor do header Idempotency-Key.

  • retry_request (Boolean, nil) (defaults to: nil)

    força ou desliga o retry; nil = automático.

Returns:

  • (Object)

    JSON parseado da resposta.

Raises:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/fluvpay/client.rb', line 100

def request(method, path, params: nil, body: nil, idempotency_key: nil, retry_request: nil)
  upper = method.to_s.upcase
  uri = build_uri(path, params)
  headers = default_headers
  headers["Idempotency-Key"] = idempotency_key unless idempotency_key.nil?

  payload = nil
  unless body.nil?
    headers["Content-Type"] = "application/json"
    payload = JSON.generate(clean_body(body))
  end

  retry_request = (upper == "GET" || (upper == "POST" && !idempotency_key.nil?)) if retry_request.nil?
  max_attempts = retry_request ? (@max_retries + 1) : 1

  attempt = 0
  last_exception = nil
  while attempt < max_attempts
    begin
      status, response, raw = perform(upper, uri, headers, payload)
    rescue Net::OpenTimeout, Net::ReadTimeout => e
      last_exception = e
      if should_retry_connection?(retry_request, attempt, max_attempts)
        sleep_backoff(attempt, nil)
        attempt += 1
        next
      end
      raise ConnectionError.new("Timeout ao conectar na FluvPay: #{e.message}")
    rescue SocketError, SystemCallError, IOError, OpenSSL::SSL::SSLError => e
      last_exception = e
      if should_retry_connection?(retry_request, attempt, max_attempts)
        sleep_backoff(attempt, nil)
        attempt += 1
        next
      end
      raise ConnectionError.new("Falha de conexão com a FluvPay: #{e.message}")
    end

    parsed = parse_json_body(raw)

    return parsed if status < 300

    if should_retry_status?(retry_request, status, attempt, max_attempts)
      sleep_backoff(attempt, retry_after_seconds(response))
      attempt += 1
      next
    end

    raise Errors.from_response(
      status,
      parsed.is_a?(Hash) ? parsed : nil,
      retry_after_header: header_value(response, "Retry-After")
    )
  end

  if last_exception
    raise ConnectionError.new(
      "Falha de conexão com a FluvPay após #{max_attempts} tentativas: #{last_exception.message}"
    )
  end
  raise ConnectionError.new("Falha de conexão com a FluvPay.")
end

#test_mode?Boolean

Returns true se a chave configurada for de sandbox (fluv_test_).

Returns:

  • (Boolean)

    true se a chave configurada for de sandbox (fluv_test_).



76
77
78
# File 'lib/fluvpay/client.rb', line 76

def test_mode?
  self.class.test_key?(@api_key)
end