Packages

A complete, production-grade Elixir client for the Mercury Banking API (accounts, transactions, recipients, invoices, payments, treasury, webhooks, and more), with typed errors, retry with backoff, and lazy auto-paginating streams.

Current section

Files

Jump to
mercury_client lib mercury client.ex
Raw

lib/mercury/client.ex

defmodule Mercury.Client do
@moduledoc """
Holds configuration for talking to the Mercury Banking API and is passed
as the first argument to every function in `Mercury.Accounts`,
`Mercury.Transactions`, and the other resource modules.
Build one with `new/2`:
client = Mercury.Client.new("secret-token:mercury_production_...")
`Mercury.Client` is an immutable struct — it is cheap to build and safe to
share across processes.
"""
alias Mercury.Retry
@enforce_keys [:api_key, :base_url, :timeout, :retry, :req]
defstruct [
:api_key,
:base_url,
:timeout,
:retry,
:req,
:on_request,
:on_response
]
@type request_hook :: (method :: atom(), url :: String.t(), request_id :: String.t() -> any())
@type response_hook ::
(method :: atom(),
url :: String.t(),
request_id :: String.t(),
status :: pos_integer(),
duration_ms :: non_neg_integer() ->
any())
@type t :: %__MODULE__{
api_key: String.t(),
base_url: String.t(),
timeout: pos_integer(),
retry: Retry.t(),
req: Req.Request.t(),
on_request: request_hook() | nil,
on_response: response_hook() | nil
}
@default_base_url "https://api.mercury.com/api/v1"
@default_timeout 30_000
@doc """
Builds a new `Mercury.Client`.
`api_key` must include the `"secret-token:"` prefix, e.g.
`"secret-token:mercury_production_..."`.
## Options
* `:base_url` — override the API base URL. Default: `"#{@default_base_url}"`.
* `:timeout` — per-request timeout in milliseconds. Default: `#{@default_timeout}`.
* `:retry` — a `Mercury.Retry` struct. Default: `Mercury.Retry.default/0`.
* `:on_request` — request hook, see `t:request_hook/0`.
* `:on_response` — response hook, see `t:response_hook/0`.
* `:req_options` — extra options merged into the underlying `Req.new/1`
call (e.g. `:plug` for tests, `:connect_options` for proxies).
## Examples
Mercury.Client.new("secret-token:mercury_production_...")
Mercury.Client.new("secret-token:mercury_sandbox_...",
base_url: "https://api-sandbox.mercury.com/api/v1",
timeout: 10_000
)
"""
@spec new(api_key :: String.t(), opts :: keyword()) :: t()
def new(api_key, opts \\ [])
def new(api_key, _opts) when not is_binary(api_key) or api_key == "" do
raise ArgumentError,
"Mercury.Client.new/2 requires a non-empty api_key string " <>
"(expected the \"secret-token:...\" prefix)"
end
def new(api_key, opts) when is_binary(api_key) and is_list(opts) do
base_url = Keyword.get(opts, :base_url, @default_base_url)
timeout = Keyword.get(opts, :timeout, @default_timeout)
retry = Keyword.get(opts, :retry, Retry.default())
on_request = Keyword.get(opts, :on_request)
on_response = Keyword.get(opts, :on_response)
req_options = Keyword.get(opts, :req_options, [])
req =
req_options
|> Keyword.merge(
base_url: base_url,
receive_timeout: timeout,
retry: false,
headers: [
{"authorization", "Bearer " <> api_key},
{"accept", "application/json"},
{"x-mercury-sdk", Mercury.sdk_version()}
]
)
|> Req.new()
%__MODULE__{
api_key: api_key,
base_url: base_url,
timeout: timeout,
retry: retry,
req: req,
on_request: on_request,
on_response: on_response
}
end
end