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 recipients.ex
Raw

lib/mercury/recipients.ex

defmodule Mercury.RecipientAttachment do
@moduledoc "A tax form attached to a recipient."
@type t :: %__MODULE__{
file_name: String.t(),
url: String.t(),
uploaded_at: DateTime.t() | nil,
form_type: String.t() | nil
}
defstruct [:file_name, :url, :uploaded_at, :form_type]
@doc false
def from_json(map) when is_map(map) do
%__MODULE__{
file_name: map["fileName"],
url: map["url"],
uploaded_at: Mercury.Types.datetime(map["uploadedAt"]),
form_type: map["formType"]
}
end
end
defmodule Mercury.Recipient do
@moduledoc """
A Mercury payment recipient.
Routing-method nested fields (`address`, `default_address`, `check_info`,
`electronic_routing_info`, `domestic_wire_routing_info`,
`international_wire_routing_info`, `real_time_payment_routing_info`) are
returned as plain maps with their original camelCase string keys.
"""
@type t :: %__MODULE__{
id: String.t(),
status: String.t(),
name: String.t(),
nickname: String.t() | nil,
emails: [String.t()],
contact_email: String.t() | nil,
default_payment_method: String.t(),
is_business: boolean() | nil,
date_last_paid: DateTime.t() | nil,
address: map() | nil,
default_address: map() | nil,
check_info: map() | nil,
electronic_routing_info: map() | nil,
domestic_wire_routing_info: map() | nil,
international_wire_routing_info: map() | nil,
real_time_payment_routing_info: map() | nil,
attachments: [Mercury.RecipientAttachment.t()]
}
defstruct [
:id,
:status,
:name,
:nickname,
:emails,
:contact_email,
:default_payment_method,
:is_business,
:date_last_paid,
:address,
:default_address,
:check_info,
:electronic_routing_info,
:domestic_wire_routing_info,
:international_wire_routing_info,
:real_time_payment_routing_info,
:attachments
]
@doc false
def from_json(map) when is_map(map) do
%__MODULE__{
id: map["id"],
status: map["status"],
name: map["name"],
nickname: map["nickname"],
emails: map["emails"] || [],
contact_email: map["contactEmail"],
default_payment_method: map["defaultPaymentMethod"],
is_business: map["isBusiness"],
date_last_paid: Mercury.Types.datetime(map["dateLastPaid"]),
address: map["address"],
default_address: map["defaultAddress"],
check_info: map["checkInfo"],
electronic_routing_info: map["electronicRoutingInfo"],
domestic_wire_routing_info: map["domesticWireRoutingInfo"],
international_wire_routing_info: map["internationalWireRoutingInfo"],
real_time_payment_routing_info: map["realTimePaymentRoutingInfo"],
attachments:
Mercury.Types.list_of(map["attachments"], &Mercury.RecipientAttachment.from_json/1)
}
end
end
defmodule Mercury.Recipients do
@moduledoc "The Mercury Recipients API — payment recipients and their tax form attachments."
alias Mercury.{Client, HTTP, Page, Pagination, Recipient, RecipientAttachment, Support}
@doc """
Creates a new recipient. `attrs` accepts snake_case keys, e.g.:
Mercury.Recipients.create(client, %{
name: "Acme Inc",
emails: ["ap@acme.com"],
electronic_routing_info: %{
account_number: "000123456789",
routing_number: "021000021",
electronic_account_type: :businessChecking,
address: %{...}
}
})
`POST /recipients`
"""
@spec create(client :: Client.t(), attrs :: map() | keyword()) ::
{:ok, Recipient.t()} | {:error, Exception.t()}
def create(%Client{} = client, attrs) do
with {:ok, body} <- HTTP.post(client, "recipients", attrs) do
{:ok, Recipient.from_json(body)}
end
end
@spec create!(client :: Client.t(), attrs :: map() | keyword()) :: Recipient.t()
def create!(client, attrs), do: Support.bang!(create(client, attrs))
@doc "Returns a single recipient by ID. `GET /recipients/{id}`"
@spec get(client :: Client.t(), recipient_id :: String.t()) ::
{:ok, Recipient.t()} | {:error, Exception.t()}
def get(%Client{} = client, recipient_id) do
with {:ok, body} <- HTTP.get(client, "recipients/#{recipient_id}") do
{:ok, Recipient.from_json(body)}
end
end
@spec get!(client :: Client.t(), recipient_id :: String.t()) :: Recipient.t()
def get!(client, recipient_id), do: Support.bang!(get(client, recipient_id))
@doc "Returns one page of recipients. `GET /recipients`"
@spec list(client :: Client.t(), opts :: keyword()) ::
{:ok, Page.t(Recipient.t())} | {:error, Exception.t()}
def list(%Client{} = client, opts \\ []) do
with {:ok, body} <- HTTP.get(client, "recipients", opts) do
{:ok, Page.from_json(body, "recipients", &Recipient.from_json/1)}
end
end
@spec list!(client :: Client.t(), opts :: keyword()) :: Page.t(Recipient.t())
def list!(client, opts \\ []), do: Support.bang!(list(client, opts))
@doc "A lazy, auto-paginating stream of every recipient."
@spec stream(client :: Client.t(), opts :: keyword()) :: Enumerable.t()
def stream(%Client{} = client, opts \\ []) do
opts = Keyword.new(opts)
Pagination.stream(fn cursor -> list(client, Keyword.put(opts, :start_after, cursor)) end)
end
@doc "Updates an existing recipient. `PUT /recipients/{id}`"
@spec update(client :: Client.t(), recipient_id :: String.t(), attrs :: map() | keyword()) ::
{:ok, Recipient.t()} | {:error, Exception.t()}
def update(%Client{} = client, recipient_id, attrs) do
with {:ok, body} <- HTTP.put(client, "recipients/#{recipient_id}", attrs) do
{:ok, Recipient.from_json(body)}
end
end
@spec update!(client :: Client.t(), recipient_id :: String.t(), attrs :: map() | keyword()) ::
Recipient.t()
def update!(client, recipient_id, attrs), do: Support.bang!(update(client, recipient_id, attrs))
@doc "Deletes a recipient. `DELETE /recipients/{id}`"
@spec delete(client :: Client.t(), recipient_id :: String.t()) :: :ok | {:error, Exception.t()}
def delete(%Client{} = client, recipient_id) do
case HTTP.delete(client, "recipients/#{recipient_id}") do
{:ok, _body} -> :ok
{:error, _error} = error -> error
end
end
@spec delete!(client :: Client.t(), recipient_id :: String.t()) :: :ok
def delete!(client, recipient_id), do: Support.bang!(delete(client, recipient_id))
@doc "Returns one page of org-wide recipient tax form attachments. `GET /recipients/attachments`"
@spec list_attachments(client :: Client.t(), opts :: keyword()) ::
{:ok, Page.t(RecipientAttachment.t())} | {:error, Exception.t()}
def list_attachments(%Client{} = client, opts \\ []) do
with {:ok, body} <- HTTP.get(client, "recipients/attachments", opts) do
{:ok, Page.from_json(body, "attachments", &RecipientAttachment.from_json/1)}
end
end
@spec list_attachments!(client :: Client.t(), opts :: keyword()) ::
Page.t(RecipientAttachment.t())
def list_attachments!(client, opts \\ []), do: Support.bang!(list_attachments(client, opts))
@doc "A lazy, auto-paginating stream of every org-wide recipient tax form attachment."
@spec stream_attachments(client :: Client.t(), opts :: keyword()) :: Enumerable.t()
def stream_attachments(%Client{} = client, opts \\ []) do
opts = Keyword.new(opts)
Pagination.stream(fn cursor ->
list_attachments(client, Keyword.put(opts, :start_after, cursor))
end)
end
@doc "Uploads a tax form attachment for a recipient. `POST /recipients/{id}/attachments`"
@spec upload_attachment(
client :: Client.t(),
recipient_id :: String.t(),
filename :: String.t(),
content :: binary()
) ::
{:ok, RecipientAttachment.t()} | {:error, Exception.t()}
def upload_attachment(%Client{} = client, recipient_id, filename, content)
when is_binary(content) do
with {:ok, body} <-
HTTP.upload_multipart(
client,
"recipients/#{recipient_id}/attachments",
"file",
filename,
content
) do
{:ok, RecipientAttachment.from_json(body)}
end
end
@spec upload_attachment!(
client :: Client.t(),
recipient_id :: String.t(),
filename :: String.t(),
content :: binary()
) ::
RecipientAttachment.t()
def upload_attachment!(client, recipient_id, filename, content),
do: Support.bang!(upload_attachment(client, recipient_id, filename, content))
end