Moov.Client (Moov v1.0.0)

Copy Markdown View Source

Builds and sends requests to the Moov API.

Every Moov.* resource module (Moov.Accounts, Moov.Transfers, ...) is a thin, documented wrapper around this module - you can always drop down to request/4 directly for an endpoint that doesn't have a dedicated wrapper yet, or to pass low-level options none of the wrappers expose.

Creating a client

# explicit credentials
client = Moov.Client.new(public_key: "...", private_key: "...")

# or pull defaults from `config :moov, ...` (see `Moov.Config`)
client = Moov.Client.new()

# client-side / Moov.js flows authenticate with a bearer token instead
client = Moov.Client.new(access_token: token)

A Moov.Client is a plain, immutable struct - hold on to it, store it in a GenServer's state, build a fresh one per request, whatever suits your application. Nothing about this library requires a singleton or an OTP application tree.

Sending requests

Moov.Client.get(client, "/accounts/#{account_id}")
Moov.Client.post(client, "/accounts", %{account_type: "individual", ...})
Moov.Client.patch(client, "/accounts/#{account_id}", %{profile: ...})
Moov.Client.delete(client, "/accounts/#{account_id}")

All of these (and request/4 itself) return {:ok, body} on any 2xx response or {:error, %Moov.Error{}} otherwise. body is the decoded JSON response with its original camelCase string keys, exactly as Moov sent it. Raise instead of pattern matching with Moov.unwrap!/1.

Request options

Every sending function accepts:

  • :query - a map or keyword list of query string parameters
  • :headers - a list of {name, value} tuples to merge in (these win over any header this module sets, except where noted)
  • :idempotent - when true and no :idempotency_key is given, a random UUID v4 is generated once and reused across all retry attempts for this call. Moov.Transfers.create/3 sets this for you
  • :idempotency_key - an explicit X-Idempotency-Key value, e.g. one derived from your own job/event ID so retries across process restarts stay deduplicated too
  • :wait_for - set to "rail-response" to add the X-Wait-For header Moov's transfer/refund endpoints use to opt into a slower, fully populated synchronous response
  • :form_multipart - for multipart file uploads (Moov.Files, Moov.Images, dispute evidence), e.g. form_multipart: [file: {binary, filename: "id.png", content_type: "image/png"}]. Mutually exclusive with :json for a given call
  • :api_version, :max_retries, :receive_timeout - per-call overrides of the client's defaults

Summary

Functions

Sends a DELETE request.

Sends a GET request. See the moduledoc for shared options.

Builds a new client.

Sends a PATCH request with body as the JSON payload.

Sends a POST request with body as the JSON payload.

Sends a PUT request with body as the JSON payload.

The low-level entry point every other function in this module (and every Moov.* resource wrapper) ultimately calls. See the moduledoc for the full list of supported opts.

Types

auth()

@type auth() :: {:basic, String.t(), String.t()} | {:bearer, String.t()} | nil

method()

@type method() :: :get | :post | :patch | :put | :delete

t()

@type t() :: %Moov.Client{
  api_version: String.t(),
  auth: auth(),
  base_url: String.t(),
  max_retries: non_neg_integer(),
  receive_timeout: timeout(),
  req_options: keyword()
}

Functions

delete(client, path, opts \\ [])

@spec delete(t(), String.t(), keyword()) :: {:ok, term()} | {:error, Moov.Error.t()}

Sends a DELETE request.

get(client, path, opts \\ [])

@spec get(t(), String.t(), keyword()) :: {:ok, term()} | {:error, Moov.Error.t()}

Sends a GET request. See the moduledoc for shared options.

new(opts \\ [])

@spec new(keyword()) :: t()

Builds a new client.

Accepts everything Moov.Config resolves from application environment, plus:

  • :base_url - defaults to "https://api.moov.io"
  • :api_version - the X-Moov-Version to send on every request. Always set this explicitly in production - Moov silently falls back to legacy v2024.01.00 behavior if it's omitted entirely, which this library never does, but it's worth knowing the API does
  • :public_key / :private_key - Basic Auth credentials (server-side integrations)
  • :access_token - a bearer token (client-side / Moov.js integrations, typically obtained via Moov.AccessTokens.create/2); takes precedence over :public_key/:private_key if both are given
  • :max_retries - default number of retries for transient failures (default: 3)
  • :receive_timeout - default response timeout in milliseconds (default: 30_000)
  • :req_options - a keyword list merged into every Req.request/1 call, e.g. req_options: [plug: {Req.Test, MyApp.MoovStub}] in tests, or req_options: [connect_options: [proxy: ...]] behind a corporate proxy

patch(client, path, body \\ %{}, opts \\ [])

@spec patch(t(), String.t(), map(), keyword()) ::
  {:ok, term()} | {:error, Moov.Error.t()}

Sends a PATCH request with body as the JSON payload.

post(client, path, body \\ %{}, opts \\ [])

@spec post(t(), String.t(), map(), keyword()) ::
  {:ok, term()} | {:error, Moov.Error.t()}

Sends a POST request with body as the JSON payload.

put(client, path, body \\ %{}, opts \\ [])

@spec put(t(), String.t(), map(), keyword()) ::
  {:ok, term()} | {:error, Moov.Error.t()}

Sends a PUT request with body as the JSON payload.

request(client, method, path, opts \\ [])

@spec request(t(), method(), String.t(), keyword()) ::
  {:ok, term()} | {:error, Moov.Error.t()}

The low-level entry point every other function in this module (and every Moov.* resource wrapper) ultimately calls. See the moduledoc for the full list of supported opts.