Packages

Production-grade Elixir client for the TrueLayer open banking API

Current section

Files

Jump to
truelayer_client lib truelayer_client.ex
Raw

lib/truelayer_client.ex

defmodule TruelayerClient do
@moduledoc """
Production-grade Elixir client for the [TrueLayer](https://truelayer.com) open banking API.
## Quick start
{:ok, client} = TruelayerClient.new(
environment: :sandbox,
client_id: System.fetch_env!("TRUELAYER_CLIENT_ID"),
client_secret: System.fetch_env!("TRUELAYER_CLIENT_SECRET"),
redirect_uri: "https://yourapp.com/callback",
signing_key_pem: File.read!("keys/signing_private.pem"),
signing_key_id: System.fetch_env!("TRUELAYER_KEY_ID"),
webhook_signing_secret: System.fetch_env!("TRUELAYER_WEBHOOK_SECRET")
)
## API modules
| Module | Responsibility |
|--------|----------------|
| `TruelayerClient.Auth` | OAuth2 tokens, auth links |
| `TruelayerClient.Payments` | Pay-ins, auth flow, refunds, payment links |
| `TruelayerClient.Payouts` | Merchant-account payouts |
| `TruelayerClient.Merchant` | Merchant accounts, sweeping |
| `TruelayerClient.Mandates` | VRP / sweeping mandates |
| `TruelayerClient.Data` | Accounts, balances, transactions, cards |
| `TruelayerClient.Verification` | Account holder name verification |
| `TruelayerClient.SignupPlus` | Embedded user-data collection |
| `TruelayerClient.Tracking` | Authorization-flow event tracking |
| `TruelayerClient.Webhooks` | HMAC verification + typed dispatch |
## Options
| Option | Type | Default | Notes |
|--------|------|---------|-------|
| `:client_id` | `String.t()` | required | |
| `:client_secret` | `String.t()` | required | |
| `:environment` | `:sandbox \\| :live` | `:sandbox` | |
| `:redirect_uri` | `String.t()` | `nil` | Required for auth-code flows |
| `:signing_key_pem` | `binary()` | `nil` | Required for Payments/Payouts/Mandates |
| `:signing_key_id` | `String.t()` | `nil` | Key ID from TrueLayer Console |
| `:webhook_signing_secret` | `binary()` | `nil` | HMAC-SHA256 webhook secret |
| `:webhook_replay_tolerance_sec` | `integer()` | `300` | |
| `:request_timeout_ms` | `integer()` | `30_000` | |
| `:max_retries` | `integer()` | `3` | |
| `:base_retry_delay_ms` | `integer()` | `300` | |
| `:token_store` | `module()` | `MemoryStore` | Implement `TokenStore` behaviour |
| `:telemetry_prefix` | `[atom()]` | `[:truelayer_client]` | |
"""
alias TruelayerClient.{Config, Error, HTTP, Idempotency, Signing, Webhooks}
@typedoc """
A fully initialised TruelayerClient instance.
Pass this struct to every domain module function, e.g.:
TruelayerClient.Payments.get_payment(client, payment_id)
"""
@type t :: %__MODULE__{
config: Config.t(),
http: Req.Request.t(),
signer: Signing.signer() | nil,
store_id: reference(),
idem_table: Idempotency.table(),
webhook_registry: Webhooks.registry()
}
@enforce_keys [:config, :http, :store_id, :idem_table, :webhook_registry]
defstruct [:config, :http, :signer, :store_id, :idem_table, :webhook_registry]
@doc """
Create a new TruelayerClient instance.
Returns `{:ok, %TruelayerClient{}}` or `{:error, %TruelayerClient.Error{}}`.
## Example
{:ok, client} = TruelayerClient.new(
environment: :sandbox,
client_id: "your-client-id",
client_secret: "your-secret",
signing_key_pem: File.read!("signing_private.pem"),
signing_key_id: "your-key-id"
)
"""
@spec new(keyword()) :: {:ok, t()} | {:error, Error.t()}
def new(opts) when is_list(opts) do
with {:ok, config} <- Config.new(opts),
{:ok, signer} <- build_signer(config) do
client = %__MODULE__{
config: config,
http: HTTP.build_client(config),
signer: signer,
store_id: make_ref(),
idem_table: Idempotency.new_table(),
webhook_registry: Webhooks.new_registry()
}
{:ok, client}
end
end
@doc """
Create a new TruelayerClient instance, raising on failure.
## Example
client = TruelayerClient.new!(environment: :sandbox, client_id: "id", client_secret: "s")
"""
@spec new!(keyword()) :: t()
def new!(opts) do
case new(opts) do
{:ok, client} -> client
{:error, error} -> raise error
end
end
@doc "Returns the configured environment (`:sandbox` or `:live`)."
@spec environment(t()) :: Config.environment()
def environment(%__MODULE__{config: %Config{environment: env}}), do: env
@doc "Returns `true` when the client is configured for the Sandbox environment."
@spec sandbox?(t()) :: boolean()
def sandbox?(%__MODULE__{config: %Config{environment: :sandbox}}), do: true
def sandbox?(%__MODULE__{}), do: false
# ── Private ───────────────────────────────────────────────────────────────────
defp build_signer(%Config{signing_key_pem: nil}), do: {:ok, nil}
defp build_signer(%Config{signing_key_pem: pem, signing_key_id: kid}) do
Signing.new_signer(pem, kid || "")
end
end