Packages
tokenio_client
1.0.0
Production-grade Elixir client for the Token.io Open Banking platform (Payments v2, VRP, AIS, Banks, Refunds, Payouts, Settlement, Transfers, Tokens, Token Requests, Account on File, Sub-TPPs, Auth Keys, Reports, Webhooks, Verification).
Current section
Files
Jump to
Current section
Files
lib/tokenio/apis.ex
defmodule TokenioClient.AIS do
@moduledoc """
Token.io Account Information Services (AIS) API.
Provides read access to bank account data once a PSU has granted an AIS
consent: accounts, balances, standing orders, and transactions.
"""
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Account do
@moduledoc "A bank account returned from the AIS API."
@type t :: %__MODULE__{
id: String.t(),
display_name: String.t() | nil,
type: String.t() | nil,
currency: String.t() | nil,
bank_id: String.t() | nil,
iban: String.t() | nil,
account_number: String.t() | nil,
sort_code: String.t() | nil,
bic: String.t() | nil,
status: String.t() | nil,
created_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil,
raw: map()
}
defstruct [
:id,
:display_name,
:type,
:currency,
:bank_id,
:iban,
:account_number,
:sort_code,
:bic,
:status,
:created_at,
:updated_at,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
display_name: m["displayName"],
type: m["type"],
currency: m["currency"],
bank_id: m["bankId"],
iban: m["iban"],
account_number: m["accountNumber"],
sort_code: m["sortCode"],
bic: m["bic"],
status: m["status"],
created_at: Types.parse_datetime(m["createdDateTime"]),
updated_at: Types.parse_datetime(m["updatedDateTime"]),
raw: m
}
end
end
defmodule Balance do
@moduledoc "Balance details for an AIS account."
@type t :: %__MODULE__{
account_id: String.t(),
current: Types.Amount.t() | nil,
available: Types.Amount.t() | nil,
credit_limit: Types.Amount.t() | nil,
updated_at: DateTime.t() | nil
}
defstruct [:account_id, :current, :available, :credit_limit, :updated_at]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
account_id: m["accountId"],
current: Types.Amount.from_map(m["current"]),
available: Types.Amount.from_map(m["available"]),
credit_limit: Types.Amount.from_map(m["creditLimit"]),
updated_at: Types.parse_datetime(m["updatedDateTime"])
}
end
end
defmodule Transaction do
@moduledoc "An account transaction."
@type t :: %__MODULE__{
id: String.t(),
account_id: String.t(),
amount: Types.Amount.t() | nil,
type: String.t() | nil,
status: String.t() | nil,
description: String.t() | nil,
merchant_name: String.t() | nil,
booking_date_time: DateTime.t() | nil,
value_date_time: DateTime.t() | nil,
raw: map()
}
defstruct [
:id,
:account_id,
:amount,
:type,
:status,
:description,
:merchant_name,
:booking_date_time,
:value_date_time,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
account_id: m["accountId"],
amount: Types.Amount.from_map(m["amount"]),
type: m["type"],
status: m["status"],
description: m["description"],
merchant_name: m["merchantName"],
booking_date_time: Types.parse_datetime(m["bookingDateTime"]),
value_date_time: Types.parse_datetime(m["valueDateTime"]),
raw: m
}
end
end
defmodule StandingOrder do
@moduledoc "A recurring payment standing order."
@type t :: %__MODULE__{
id: String.t(),
account_id: String.t(),
amount: Types.Amount.t() | nil,
frequency: String.t() | nil,
next_payment_date: String.t() | nil,
status: String.t() | nil
}
defstruct [:id, :account_id, :amount, :frequency, :next_payment_date, :status]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
account_id: m["accountId"],
amount: Types.Amount.from_map(m["amount"]),
frequency: m["frequency"],
next_payment_date: m["nextPaymentDate"],
status: m["status"]
}
end
end
@doc "List accounts. Requires `:limit` option."
@spec list_accounts(Client.t(), keyword()) ::
{:ok, %{accounts: [Account.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_accounts(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.fetch!(opts, :limit))},
{"offset", opts[:offset]}
]
with {:ok, resp} <- HTTP.get(http, "/accounts", query: query) do
{:ok,
%{
accounts: Enum.map(resp["accounts"] || [], &Account.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve a single account by ID."
@spec get_account(Client.t(), String.t()) :: {:ok, Account.t()} | {:error, Error.t()}
def get_account(%Client{http: http}, account_id) do
with {:ok, resp} <- HTTP.get(http, "/accounts/" <> account_id) do
{:ok, Account.from_map(resp["account"])}
end
end
@doc "List balances for all accounts."
@spec list_balances(Client.t(), keyword()) ::
{:ok, %{balances: [Balance.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_balances(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.fetch!(opts, :limit))},
{"offset", opts[:offset]}
]
with {:ok, resp} <- HTTP.get(http, "/accounts/balances", query: query) do
{:ok,
%{
balances: Enum.map(resp["balances"] || [], &Balance.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve balance for a specific account."
@spec get_balance(Client.t(), String.t()) :: {:ok, Balance.t()} | {:error, Error.t()}
def get_balance(%Client{http: http}, account_id) do
with {:ok, resp} <- HTTP.get(http, "/accounts/" <> account_id <> "/balance") do
{:ok, Balance.from_map(resp["balance"])}
end
end
@doc "List transactions for an account. Requires `:limit` option."
@spec list_transactions(Client.t(), String.t(), keyword()) ::
{:ok, %{transactions: [Transaction.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_transactions(%Client{http: http}, account_id, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.fetch!(opts, :limit))},
{"offset", opts[:offset]}
]
path = "/accounts/" <> account_id <> "/transactions"
with {:ok, resp} <- HTTP.get(http, path, query: query) do
{:ok,
%{
transactions: Enum.map(resp["transactions"] || [], &Transaction.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve a single transaction."
@spec get_transaction(Client.t(), String.t(), String.t()) ::
{:ok, Transaction.t()} | {:error, Error.t()}
def get_transaction(%Client{http: http}, account_id, tx_id) do
path = "/accounts/" <> account_id <> "/transactions/" <> tx_id
with {:ok, resp} <- HTTP.get(http, path) do
{:ok, Transaction.from_map(resp["transaction"])}
end
end
@doc "List standing orders."
@spec list_standing_orders(Client.t(), keyword()) ::
{:ok, %{standing_orders: [StandingOrder.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_standing_orders(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.fetch!(opts, :limit))},
{"offset", opts[:offset]}
]
with {:ok, resp} <- HTTP.get(http, "/accounts/standing-orders", query: query) do
{:ok,
%{
standing_orders: Enum.map(resp["standingOrders"] || [], &StandingOrder.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve a single standing order."
@spec get_standing_order(Client.t(), String.t(), String.t()) ::
{:ok, StandingOrder.t()} | {:error, Error.t()}
def get_standing_order(%Client{http: http}, account_id, so_id) do
path = "/accounts/" <> account_id <> "/standing-orders/" <> so_id
with {:ok, resp} <- HTTP.get(http, path) do
{:ok, StandingOrder.from_map(resp["standingOrder"])}
end
end
end
# =============================================================================
defmodule TokenioClient.Banks do
@moduledoc "Token.io Banks v1 and v2 APIs for listing supported financial institutions."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Bank do
@moduledoc "A financial institution supported by Token.io."
@type t :: %__MODULE__{
id: String.t(),
name: String.t(),
display_name: String.t() | nil,
logo_uri: String.t() | nil,
country: String.t() | nil,
capabilities: [String.t()],
bic: String.t() | nil,
provider: String.t() | nil,
requires_callback_url: boolean(),
open_banking_standard: String.t() | nil,
enabled: boolean(),
raw: map()
}
defstruct [
:id,
:name,
:display_name,
:logo_uri,
:country,
:bic,
:provider,
:open_banking_standard,
capabilities: [],
requires_callback_url: false,
enabled: false,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
name: m["name"] || "",
display_name: m["displayName"],
logo_uri: m["logoUri"] || m["logo"],
country: m["country"],
capabilities: m["capabilities"] || [],
bic: m["bic"],
provider: m["provider"],
requires_callback_url: m["requiresCallbackUrl"] == true,
open_banking_standard: m["openBankingStandard"],
enabled: m["enabled"] == true,
raw: m
}
end
end
@doc "List banks using the v1 API."
@spec list_v1(Client.t(), keyword()) ::
{:ok, %{banks: [Bank.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_v1(%Client{http: http}, opts \\ []) do
with {:ok, resp} <- HTTP.get(http, "/banks", query: banks_query(opts)) do
{:ok,
%{
banks: Enum.map(resp["banks"] || [], &Bank.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "List banks using the v2 API."
@spec list_v2(Client.t(), keyword()) ::
{:ok, %{banks: [Bank.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_v2(%Client{http: http}, opts \\ []) do
with {:ok, resp} <- HTTP.get(http, "/v2/banks", query: banks_query(opts)) do
{:ok,
%{
banks: Enum.map(resp["banks"] || [], &Bank.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "List supported countries."
@spec list_countries(Client.t()) :: {:ok, [String.t()]} | {:error, Error.t()}
def list_countries(%Client{http: http}) do
with {:ok, resp} <- HTTP.get(http, "/banks/countries") do
{:ok, resp["countries"] || []}
end
end
@spec banks_query(keyword()) :: [{String.t(), String.t() | nil}]
defp banks_query(opts) do
base = [
{"limit", Integer.to_string(Keyword.get(opts, :limit, 50))},
{"search", opts[:search]},
{"country", opts[:country]},
{"provider", opts[:provider]},
{"sort", opts[:sort]}
]
base
|> Kernel.++(Enum.map(opts[:ids] || [], &{"ids", &1}))
|> Kernel.++(Enum.map(opts[:capabilities] || [], &{"capabilities", &1}))
end
end
# =============================================================================
defmodule TokenioClient.Refunds do
@moduledoc "Token.io Refunds API — initiate and track payment refunds."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Refund do
@moduledoc "A refund resource."
@terminal ~w[INITIATION_COMPLETED INITIATION_REJECTED INITIATION_FAILED]
@type t :: %__MODULE__{
id: String.t(),
transfer_id: String.t() | nil,
status: String.t(),
status_reason_information: String.t() | nil,
error_info: Types.ErrorInfo.t() | nil,
created_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil,
raw: map()
}
defstruct [
:id,
:transfer_id,
:status,
:status_reason_information,
:error_info,
:created_at,
:updated_at,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
transfer_id: m["transferId"],
status: m["status"] || "",
status_reason_information: m["statusReasonInformation"],
error_info: Types.ErrorInfo.from_map(m["errorInfo"]),
created_at: Types.parse_datetime(m["createdDateTime"]),
updated_at: Types.parse_datetime(m["updatedDateTime"]),
raw: m
}
end
@spec final?(t()) :: boolean()
def final?(%__MODULE__{status: s}), do: s in @terminal
end
@doc "Initiate a refund for a completed payment."
@spec initiate(Client.t(), map()) :: {:ok, Refund.t()} | {:error, Error.t()}
def initiate(%Client{http: http}, params) do
initiation =
%{}
|> put_if("transferId", params[:transfer_id])
|> put_if("refId", params[:ref_id])
|> put_if("amount", Types.encode_amount(params[:amount]))
|> put_if("remittanceInformationPrimary", params[:remittance_information_primary])
|> put_if("debtor", Types.PartyAccount.to_map(params[:debtor]))
|> put_if("creditor", Types.PartyAccount.to_map(params[:creditor]))
with {:ok, resp} <- HTTP.post(http, "/refunds", %{"refundInitiation" => initiation}) do
{:ok, Refund.from_map(resp["refund"])}
end
end
@doc "Retrieve a refund by ID."
@spec get(Client.t(), String.t()) :: {:ok, Refund.t()} | {:error, Error.t()}
def get(%Client{http: http}, refund_id) do
with {:ok, resp} <- HTTP.get(http, "/refunds/" <> refund_id) do
{:ok, Refund.from_map(resp["refund"])}
end
end
@doc "List refunds. Requires `:limit` option."
@spec list(Client.t(), keyword()) ::
{:ok, %{refunds: [Refund.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.fetch!(opts, :limit))},
{"offset", opts[:offset]},
{"transferId", opts[:transfer_id]},
{"createdAfter", opts[:created_after]},
{"createdBefore", opts[:created_before]}
]
with {:ok, resp} <- HTTP.get(http, "/refunds", query: query) do
{:ok,
%{
refunds: Enum.map(resp["refunds"] || [], &Refund.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.Payouts do
@moduledoc "Token.io Payouts API — send money from settlement accounts."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Payout do
@moduledoc "A payout resource."
@terminal ~w[INITIATION_COMPLETED INITIATION_REJECTED INITIATION_FAILED]
@type t :: %__MODULE__{
id: String.t(),
status: String.t(),
error_info: Types.ErrorInfo.t() | nil,
created_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil,
raw: map()
}
defstruct [:id, :status, :error_info, :created_at, :updated_at, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
status: m["status"] || "",
error_info: Types.ErrorInfo.from_map(m["errorInfo"]),
created_at: Types.parse_datetime(m["createdDateTime"]),
updated_at: Types.parse_datetime(m["updatedDateTime"]),
raw: m
}
end
@spec final?(t()) :: boolean()
def final?(%__MODULE__{status: s}), do: s in @terminal
end
@doc "Initiate a payout."
@spec initiate(Client.t(), map()) :: {:ok, Payout.t()} | {:error, Error.t()}
def initiate(%Client{http: http}, params) do
initiation =
%{}
|> put_if("refId", params[:ref_id])
|> put_if("amount", Types.encode_amount(params[:amount]))
|> put_if("creditor", Types.PartyAccount.to_map(params[:creditor]))
|> put_if("debtor", Types.PartyAccount.to_map(params[:debtor]))
|> put_if("remittanceInformationPrimary", params[:remittance_information_primary])
|> put_if("executionDate", params[:execution_date])
|> put_if("localInstrument", params[:local_instrument])
with {:ok, resp} <- HTTP.post(http, "/payouts", %{"payoutInitiation" => initiation}) do
{:ok, Payout.from_map(resp["payout"])}
end
end
@doc "Retrieve a payout by ID."
@spec get(Client.t(), String.t()) :: {:ok, Payout.t()} | {:error, Error.t()}
def get(%Client{http: http}, payout_id) do
with {:ok, resp} <- HTTP.get(http, "/payouts/" <> payout_id) do
{:ok, Payout.from_map(resp["payout"])}
end
end
@doc "List payouts. Requires `:limit` option."
@spec list(Client.t(), keyword()) ::
{:ok, %{payouts: [Payout.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.fetch!(opts, :limit))},
{"offset", opts[:offset]},
{"createdAfter", opts[:created_after]},
{"createdBefore", opts[:created_before]}
]
with {:ok, resp} <- HTTP.get(http, "/payouts", query: query) do
{:ok,
%{
payouts: Enum.map(resp["payouts"] || [], &Payout.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.Settlement do
@moduledoc "Token.io Settlement Accounts API — virtual accounts, rules, and transactions."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Account do
@moduledoc "A virtual settlement account."
@type t :: %__MODULE__{
id: String.t(),
name: String.t() | nil,
currency: String.t() | nil,
iban: String.t() | nil,
account_number: String.t() | nil,
sort_code: String.t() | nil,
balance: String.t() | nil,
created_at: DateTime.t() | nil,
raw: map()
}
defstruct [
:id,
:name,
:currency,
:iban,
:account_number,
:sort_code,
:balance,
:created_at,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
name: m["name"],
currency: m["currency"],
iban: m["iban"],
account_number: m["accountNumber"],
sort_code: m["sortCode"],
balance: m["balance"],
created_at: Types.parse_datetime(m["createdDateTime"]),
raw: m
}
end
end
defmodule Rule do
@moduledoc "A settlement rule for automatic fund management."
@type t :: %__MODULE__{
id: String.t(),
account_id: String.t() | nil,
rule_type: String.t() | nil,
threshold_amount: String.t() | nil,
currency: String.t() | nil,
enabled: boolean(),
raw: map()
}
defstruct [
:id,
:account_id,
:rule_type,
:threshold_amount,
:currency,
enabled: false,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
account_id: m["accountId"],
rule_type: m["ruleType"],
threshold_amount: m["thresholdAmount"],
currency: m["currency"],
enabled: m["enabled"] == true,
raw: m
}
end
end
defmodule Transaction do
@moduledoc "A settlement account transaction."
@type t :: %__MODULE__{
id: String.t(),
account_id: String.t() | nil,
amount: Types.Amount.t() | nil,
type: String.t() | nil,
status: String.t() | nil,
reference: String.t() | nil,
created_at: DateTime.t() | nil,
raw: map()
}
defstruct [:id, :account_id, :amount, :type, :status, :reference, :created_at, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
account_id: m["accountId"],
amount: Types.Amount.from_map(m["amount"]),
type: m["type"],
status: m["status"],
reference: m["reference"],
created_at: Types.parse_datetime(m["createdDateTime"]),
raw: m
}
end
end
@doc "Create a virtual settlement account."
@spec create_account(Client.t(), String.t(), keyword()) ::
{:ok, Account.t()} | {:error, Error.t()}
def create_account(%Client{http: http}, currency, opts \\ []) do
body = put_if(%{"currency" => currency}, "name", opts[:name])
with {:ok, resp} <- HTTP.post(http, "/virtual-accounts", body) do
{:ok, Account.from_map(resp["virtualAccount"])}
end
end
@doc "List settlement accounts."
@spec list_accounts(Client.t(), keyword()) ::
{:ok, %{accounts: [Account.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_accounts(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.get(opts, :limit, 50))},
{"offset", opts[:offset]}
]
with {:ok, resp} <- HTTP.get(http, "/virtual-accounts", query: query) do
{:ok,
%{
accounts: Enum.map(resp["virtualAccounts"] || [], &Account.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve a settlement account by ID."
@spec get_account(Client.t(), String.t()) :: {:ok, Account.t()} | {:error, Error.t()}
def get_account(%Client{http: http}, account_id) do
with {:ok, resp} <- HTTP.get(http, "/virtual-accounts/" <> account_id) do
{:ok, Account.from_map(resp["virtualAccount"])}
end
end
@doc "List transactions for a settlement account."
@spec list_transactions(Client.t(), String.t(), keyword()) ::
{:ok, %{transactions: [Transaction.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_transactions(%Client{http: http}, account_id, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.get(opts, :limit, 50))},
{"offset", opts[:offset]}
]
path = "/virtual-accounts/" <> account_id <> "/transactions"
with {:ok, resp} <- HTTP.get(http, path, query: query) do
{:ok,
%{
transactions: Enum.map(resp["transactions"] || [], &Transaction.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve a single settlement transaction."
@spec get_transaction(Client.t(), String.t(), String.t()) ::
{:ok, Transaction.t()} | {:error, Error.t()}
def get_transaction(%Client{http: http}, account_id, tx_id) do
path = "/virtual-accounts/" <> account_id <> "/transactions/" <> tx_id
with {:ok, resp} <- HTTP.get(http, path) do
{:ok, Transaction.from_map(resp["transaction"])}
end
end
@doc "Create a settlement rule for an account."
@spec create_rule(Client.t(), String.t(), map()) :: {:ok, Rule.t()} | {:error, Error.t()}
def create_rule(%Client{http: http}, account_id, params) do
body =
%{}
|> put_if("ruleType", params[:rule_type])
|> put_if("thresholdAmount", params[:threshold_amount])
|> put_if("currency", params[:currency])
path = "/virtual-accounts/" <> account_id <> "/settlement-rules"
with {:ok, resp} <- HTTP.post(http, path, body) do
{:ok, Rule.from_map(resp["settlementRule"])}
end
end
@doc "List settlement rules for an account."
@spec list_rules(Client.t(), String.t(), keyword()) ::
{:ok, %{rules: [Rule.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list_rules(%Client{http: http}, account_id, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.get(opts, :limit, 50))},
{"offset", opts[:offset]}
]
path = "/virtual-accounts/" <> account_id <> "/settlement-rules"
with {:ok, resp} <- HTTP.get(http, path, query: query) do
{:ok,
%{
rules: Enum.map(resp["settlementRules"] || [], &Rule.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Delete a settlement rule."
@spec delete_rule(Client.t(), String.t(), String.t()) :: :ok | {:error, Error.t()}
def delete_rule(%Client{http: http}, account_id, rule_id) do
path = "/virtual-accounts/" <> account_id <> "/settlement-rules/" <> rule_id
with {:ok, _} <- HTTP.delete(http, path) do
:ok
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.Transfers do
@moduledoc "Token.io Transfers API — Payments v1 token redemption."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Transfer do
@moduledoc "A transfer resource (Payments v1)."
@terminal ~w[SUCCESS FAILED CANCELED]
@type t :: %__MODULE__{
id: String.t(),
token_id: String.t() | nil,
status: String.t(),
refund_details: Types.RefundDetails.t() | nil,
created_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil,
raw: map()
}
defstruct [:id, :token_id, :status, :refund_details, :created_at, :updated_at, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
token_id: m["tokenId"],
status: m["status"] || "",
refund_details: Types.RefundDetails.from_map(m["refundDetails"]),
created_at: Types.parse_datetime(m["createdDateTime"]),
updated_at: Types.parse_datetime(m["updatedDateTime"]),
raw: m
}
end
@spec final?(t()) :: boolean()
def final?(%__MODULE__{status: s}), do: s in @terminal
end
@doc "Redeem a payment token."
@spec redeem(Client.t(), String.t(), keyword()) :: {:ok, Transfer.t()} | {:error, Error.t()}
def redeem(%Client{http: http}, token_id, opts \\ []) do
body =
%{"tokenId" => token_id}
|> put_if("refId", opts[:ref_id])
|> put_if("amount", Types.encode_amount(opts[:amount]))
with {:ok, resp} <- HTTP.post(http, "/transfers", body) do
{:ok, Transfer.from_map(resp["transfer"])}
end
end
@doc "Retrieve a transfer by ID."
@spec get(Client.t(), String.t()) :: {:ok, Transfer.t()} | {:error, Error.t()}
def get(%Client{http: http}, transfer_id) do
with {:ok, resp} <- HTTP.get(http, "/transfers/" <> transfer_id) do
{:ok, Transfer.from_map(resp["transfer"])}
end
end
@doc "List transfers. Requires `:limit` option."
@spec list(Client.t(), keyword()) ::
{:ok, %{transfers: [Transfer.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.fetch!(opts, :limit))},
{"offset", opts[:offset]},
{"tokenId", opts[:token_id]},
{"createdAfter", opts[:created_after]},
{"createdBefore", opts[:created_before]}
]
with {:ok, resp} <- HTTP.get(http, "/transfers", query: query) do
{:ok,
%{
transfers: Enum.map(resp["transfers"] || [], &Transfer.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.Tokens do
@moduledoc "Token.io Tokens API — list, retrieve, and cancel authorisation tokens."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Token do
@moduledoc "An authorisation token."
@type t :: %__MODULE__{
id: String.t(),
type: String.t() | nil,
status: String.t() | nil,
created_at: DateTime.t() | nil,
expires_at: DateTime.t() | nil,
raw: map()
}
defstruct [:id, :type, :status, :created_at, :expires_at, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
type: m["type"],
status: m["status"],
created_at: Types.parse_datetime(m["createdDateTime"]),
expires_at: Types.parse_datetime(m["expiresDateTime"]),
raw: m
}
end
end
@doc "List tokens."
@spec list(Client.t(), keyword()) ::
{:ok, %{tokens: [Token.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.get(opts, :limit, 50))},
{"offset", opts[:offset]}
]
with {:ok, resp} <- HTTP.get(http, "/tokens", query: query) do
{:ok,
%{
tokens: Enum.map(resp["tokens"] || [], &Token.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve a token by ID."
@spec get(Client.t(), String.t()) :: {:ok, Token.t()} | {:error, Error.t()}
def get(%Client{http: http}, token_id) do
with {:ok, resp} <- HTTP.get(http, "/tokens/" <> token_id) do
{:ok, Token.from_map(resp["token"])}
end
end
@doc "Cancel a token."
@spec cancel(Client.t(), String.t()) :: {:ok, Token.t()} | {:error, Error.t()}
def cancel(%Client{http: http}, token_id) do
with {:ok, resp} <- HTTP.put(http, "/tokens/" <> token_id <> "/cancel", %{}) do
{:ok, Token.from_map(resp["token"])}
end
end
end
# =============================================================================
defmodule TokenioClient.TokenRequests do
@moduledoc "Token.io Token Requests API — Payments v1 / AIS legacy flow."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
defmodule TokenRequest do
@moduledoc "A stored token request."
@type t :: %__MODULE__{
token_request_id: String.t(),
status: String.t() | nil,
redirect_url: String.t() | nil,
token_id: String.t() | nil,
raw: map()
}
defstruct [:token_request_id, :status, :redirect_url, :token_id, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
token_request_id: m["tokenRequestId"],
status: m["status"],
redirect_url: m["redirectUrl"],
token_id: m["tokenId"],
raw: m
}
end
end
@doc "Store a new token request."
@spec store(Client.t(), map()) :: {:ok, TokenRequest.t()} | {:error, Error.t()}
def store(%Client{http: http}, params) do
body =
%{"requestPayload" => params[:request_payload]}
|> put_if("options", params[:options])
|> put_if("redirectUrl", params[:redirect_url])
with {:ok, resp} <- HTTP.post(http, "/token-requests", body) do
{:ok, TokenRequest.from_map(resp["tokenRequest"])}
end
end
@doc "Retrieve a token request by ID."
@spec get(Client.t(), String.t()) :: {:ok, TokenRequest.t()} | {:error, Error.t()}
def get(%Client{http: http}, request_id) do
with {:ok, resp} <- HTTP.get(http, "/token-requests/" <> request_id) do
{:ok, TokenRequest.from_map(resp["tokenRequest"])}
end
end
@doc "Get the result of a completed token request."
@spec get_result(Client.t(), String.t()) :: {:ok, map()} | {:error, Error.t()}
def get_result(%Client{http: http}, request_id) do
with {:ok, resp} <- HTTP.get(http, "/token-requests/" <> request_id <> "/result") do
{:ok, resp["tokenRequestResult"] || resp}
end
end
@doc "Initiate bank authorization for a token request."
@spec initiate_bank_auth(Client.t(), String.t(), String.t()) ::
{:ok, map()} | {:error, Error.t()}
def initiate_bank_auth(%Client{http: http}, request_id, bank_id) do
HTTP.post(http, "/token-requests/" <> request_id <> "/authorize", %{"bankId" => bank_id})
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.AccountOnFile do
@moduledoc "Token.io Account on File API — tokenized account storage."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule TokenizedAccount do
@moduledoc "A tokenized (stored) bank account reference."
@type t :: %__MODULE__{
id: String.t(),
bank_id: String.t() | nil,
status: String.t() | nil,
display_name: String.t() | nil,
currency: String.t() | nil,
created_at: DateTime.t() | nil,
raw: map()
}
defstruct [:id, :bank_id, :status, :display_name, :currency, :created_at, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
bank_id: m["bankId"],
status: m["status"],
display_name: m["displayName"],
currency: m["currency"],
created_at: Types.parse_datetime(m["createdDateTime"]),
raw: m
}
end
end
@doc "Create a new tokenized account."
@spec create(Client.t(), String.t(), keyword()) ::
{:ok, TokenizedAccount.t()} | {:error, Error.t()}
def create(%Client{http: http}, bank_id, opts \\ []) do
body =
%{"bankId" => bank_id}
|> put_if("callbackUrl", opts[:callback_url])
|> put_if("callbackState", opts[:callback_state])
with {:ok, resp} <- HTTP.post(http, "/tokenized-accounts", body) do
{:ok, TokenizedAccount.from_map(resp["tokenizedAccount"])}
end
end
@doc "Retrieve a tokenized account by ID."
@spec get(Client.t(), String.t()) :: {:ok, TokenizedAccount.t()} | {:error, Error.t()}
def get(%Client{http: http}, id) do
with {:ok, resp} <- HTTP.get(http, "/tokenized-accounts/" <> id) do
{:ok, TokenizedAccount.from_map(resp["tokenizedAccount"])}
end
end
@doc "Delete a tokenized account."
@spec delete(Client.t(), String.t()) :: :ok | {:error, Error.t()}
def delete(%Client{http: http}, id) do
with {:ok, _} <- HTTP.delete(http, "/tokenized-accounts/" <> id) do
:ok
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.SubTPPs do
@moduledoc "Token.io Sub-TPPs API — manage unregulated TPPs under a regulated parent."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule SubTPP do
@moduledoc "A sub-TPP entity."
@type t :: %__MODULE__{
id: String.t(),
name: String.t() | nil,
status: String.t() | nil,
created_at: DateTime.t() | nil,
raw: map()
}
defstruct [:id, :name, :status, :created_at, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
name: m["name"],
status: m["status"],
created_at: Types.parse_datetime(m["createdDateTime"]),
raw: m
}
end
end
@doc "Create a sub-TPP."
@spec create(Client.t(), String.t(), keyword()) :: {:ok, SubTPP.t()} | {:error, Error.t()}
def create(%Client{http: http}, name, opts \\ []) do
body = put_if(%{"name" => name}, "displayName", opts[:display_name])
with {:ok, resp} <- HTTP.post(http, "/sub-tpps", body) do
{:ok, SubTPP.from_map(resp["subTpp"])}
end
end
@doc "List sub-TPPs."
@spec list(Client.t(), keyword()) ::
{:ok, %{sub_tpps: [SubTPP.t()], page_info: Types.PageInfo.t() | nil}}
| {:error, Error.t()}
def list(%Client{http: http}, opts \\ []) do
query = [
{"limit", Integer.to_string(Keyword.get(opts, :limit, 50))},
{"offset", opts[:offset]}
]
with {:ok, resp} <- HTTP.get(http, "/sub-tpps", query: query) do
{:ok,
%{
sub_tpps: Enum.map(resp["subTpps"] || [], &SubTPP.from_map/1),
page_info: Types.PageInfo.from_map(resp["pageInfo"])
}}
end
end
@doc "Retrieve a sub-TPP by ID."
@spec get(Client.t(), String.t()) :: {:ok, SubTPP.t()} | {:error, Error.t()}
def get(%Client{http: http}, id) do
with {:ok, resp} <- HTTP.get(http, "/sub-tpps/" <> id) do
{:ok, SubTPP.from_map(resp["subTpp"])}
end
end
@doc "Delete a sub-TPP."
@spec delete(Client.t(), String.t()) :: :ok | {:error, Error.t()}
def delete(%Client{http: http}, id) do
with {:ok, _} <- HTTP.delete(http, "/sub-tpps/" <> id) do
:ok
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.AuthKeys do
@moduledoc "Token.io Authentication Keys API — manage RSA/EC signing keys."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule MemberKey do
@moduledoc "A registered public authentication key."
@type t :: %__MODULE__{
id: String.t(),
algorithm: String.t() | nil,
level: String.t() | nil,
status: String.t() | nil,
created_at: DateTime.t() | nil,
raw: map()
}
defstruct [:id, :algorithm, :level, :status, :created_at, raw: %{}]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
id: m["id"],
algorithm: m["algorithm"],
level: m["level"],
status: m["status"],
created_at: Types.parse_datetime(m["createdDateTime"]),
raw: m
}
end
end
@doc "Submit a new public key."
@spec submit(Client.t(), String.t(), keyword()) ::
{:ok, MemberKey.t()} | {:error, Error.t()}
def submit(%Client{http: http}, public_key, opts \\ []) do
body =
%{"publicKey" => public_key}
|> put_if("algorithm", opts[:algorithm])
|> put_if("level", opts[:level])
with {:ok, resp} <- HTTP.post(http, "/member-keys", body) do
{:ok, MemberKey.from_map(resp["memberKey"])}
end
end
@doc "List all registered keys."
@spec list(Client.t()) :: {:ok, [MemberKey.t()]} | {:error, Error.t()}
def list(%Client{http: http}) do
with {:ok, resp} <- HTTP.get(http, "/member-keys") do
{:ok, Enum.map(resp["memberKeys"] || [], &MemberKey.from_map/1)}
end
end
@doc "Retrieve a key by ID."
@spec get(Client.t(), String.t()) :: {:ok, MemberKey.t()} | {:error, Error.t()}
def get(%Client{http: http}, key_id) do
with {:ok, resp} <- HTTP.get(http, "/member-keys/" <> key_id) do
{:ok, MemberKey.from_map(resp["memberKey"])}
end
end
@doc "Delete a key by ID."
@spec delete(Client.t(), String.t()) :: :ok | {:error, Error.t()}
def delete(%Client{http: http}, key_id) do
with {:ok, _} <- HTTP.delete(http, "/member-keys/" <> key_id) do
:ok
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end
# =============================================================================
defmodule TokenioClient.Reports do
@moduledoc "Token.io Reports API — bank operational status."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
defmodule BankStatus do
@moduledoc "Operational status for a single bank connection."
@type t :: %__MODULE__{
bank_id: String.t(),
status: String.t() | nil,
status_message: String.t() | nil,
ais_available: boolean(),
pis_available: boolean(),
vrp_available: boolean(),
raw: map()
}
defstruct [
:bank_id,
:status,
:status_message,
ais_available: false,
pis_available: false,
vrp_available: false,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
%__MODULE__{
bank_id: m["bankId"] || "",
status: m["status"],
status_message: m["statusMessage"],
ais_available: m["aisAvailable"] == true,
pis_available: m["pisAvailable"] == true,
vrp_available: m["vrpAvailable"] == true,
raw: m
}
end
end
@doc "List operational statuses for all banks."
@spec list_bank_statuses(Client.t()) :: {:ok, [BankStatus.t()]} | {:error, Error.t()}
def list_bank_statuses(%Client{http: http}) do
with {:ok, resp} <- HTTP.get(http, "/bank-statuses") do
{:ok, Enum.map(resp["bankStatuses"] || [], &BankStatus.from_map/1)}
end
end
@doc "Retrieve the status for a specific bank."
@spec get_bank_status(Client.t(), String.t()) :: {:ok, BankStatus.t()} | {:error, Error.t()}
def get_bank_status(%Client{http: http}, bank_id) do
with {:ok, resp} <- HTTP.get(http, "/bank-statuses/" <> bank_id) do
{:ok, BankStatus.from_map(resp["bankStatus"])}
end
end
end
# =============================================================================
defmodule TokenioClient.Verification do
@moduledoc "Token.io Verification API — account ownership verification."
alias TokenioClient.Client
alias TokenioClient.Error
alias TokenioClient.HTTP.Client, as: HTTP
alias TokenioClient.Types
defmodule Check do
@moduledoc "An account verification check result."
@type t :: %__MODULE__{
id: String.t(),
status: String.t(),
account_verified: boolean() | nil,
name_matched: boolean() | nil,
redirect_url: String.t() | nil,
error_info: Types.ErrorInfo.t() | nil,
created_at: DateTime.t() | nil,
raw: map()
}
defstruct [
:id,
:status,
:account_verified,
:name_matched,
:redirect_url,
:error_info,
:created_at,
raw: %{}
]
@spec from_map(map() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(m) do
auth = m["authentication"] || %{}
%__MODULE__{
id: m["id"],
status: m["status"] || "",
account_verified: m["accountVerified"],
name_matched: m["nameMatched"],
redirect_url: auth["redirectUrl"],
error_info: Types.ErrorInfo.from_map(m["errorInfo"]),
created_at: Types.parse_datetime(m["createdDateTime"]),
raw: m
}
end
@doc "Returns `true` when the verification check is in a terminal state."
@spec final?(t()) :: boolean()
def final?(%__MODULE__{status: s}), do: s in ~w[COMPLETED FAILED]
end
@doc """
Initiate an account ownership verification check.
### Required fields
- `:bank_id`
- `:account` — map with at least one of `:account_number`/`:sort_code` or `:iban`
"""
@spec initiate(Client.t(), map()) :: {:ok, Check.t()} | {:error, Error.t()}
def initiate(%Client{http: http}, params) do
account_params = params[:account] || %{}
account =
%{}
|> put_if(
"accountNumber",
account_params[:account_number] || account_params["accountNumber"]
)
|> put_if("sortCode", account_params[:sort_code] || account_params["sortCode"])
|> put_if("iban", account_params[:iban] || account_params["iban"])
|> put_if("name", account_params[:name] || account_params["name"])
body =
%{"bankId" => params[:bank_id], "account" => account}
|> put_if("callbackUrl", params[:callback_url])
|> put_if("callbackState", params[:callback_state])
with {:ok, resp} <- HTTP.post(http, "/verification", body) do
{:ok, Check.from_map(resp["verification"])}
end
end
defp put_if(map, _k, nil), do: map
defp put_if(map, k, v), do: Map.put(map, k, v)
end