Vereinfacht API: use filter for contact lookup, drop extra required fields

- find_contact_by_email uses GET with filter[isExternal]=true and filter[email]
- vereinfacht_required_member_fields is now empty (API accepts minimal payload)
This commit is contained in:
Moritz 2026-03-04 19:22:27 +01:00
parent e4ddaf0dc3
commit dc2cff8ec4
Signed by: moritz
GPG key ID: 1020A035E5DD0824
3 changed files with 27 additions and 62 deletions

View file

@ -166,12 +166,12 @@ defmodule Mv.Vereinfacht.Client do
end
@doc """
Finds a finance contact by email (GET /finance-contacts, then match in response).
Finds a finance contact by email using the API filter.
The Vereinfacht API does not allow filter by email on this endpoint, so we
fetch the first page and find the contact client-side. Returns {:ok, contact_id}
if a contact with that email exists, {:error, :not_found} if none, or
{:error, reason} on API/network failure. Used before create for idempotency.
Uses GET /finance-contacts?filter[isExternal]=true&filter[email]=... so the API
returns only matching external contacts. Returns {:ok, contact_id} if a contact
exists, {:error, :not_found} if none, or {:error, reason} on API/network failure.
Used before create for idempotency.
"""
@spec find_contact_by_email(String.t()) :: {:ok, String.t()} | {:error, :not_found | term()}
def find_contact_by_email(email) when is_binary(email) do
@ -182,25 +182,17 @@ defmodule Mv.Vereinfacht.Client do
end
end
@find_contact_page_size 100
@find_contact_max_pages 100
defp do_find_contact_by_email(email) do
normalized = String.trim(email) |> String.downcase()
do_find_contact_by_email_page(1, normalized)
end
defp do_find_contact_by_email_page(page, _normalized) when page > @find_contact_max_pages do
{:error, :not_found}
end
defp do_find_contact_by_email_page(page, normalized) do
base = base_url() |> String.trim_trailing("/") |> then(&"#{&1}/finance-contacts")
url = base <> "?page[size]=#{@find_contact_page_size}&page[number]=#{page}"
encoded_email = URI.encode_www_form(email |> String.trim())
url = "#{base}?filter[isExternal]=true&filter[email]=#{encoded_email}"
case Req.get(url, [headers: headers(api_key())] ++ req_http_options()) do
{:ok, %{status: 200, body: body}} when is_map(body) ->
handle_find_contact_page_response(body, page, normalized)
case get_first_contact_id_from_list(body) do
nil -> {:error, :not_found}
id -> {:ok, id}
end
{:ok, %{status: status, body: body}} ->
{:error, {:http, status, extract_error_message(body)}}
@ -210,48 +202,12 @@ defmodule Mv.Vereinfacht.Client do
end
end
defp handle_find_contact_page_response(body, page, normalized) do
case find_contact_id_by_email_in_list(body, normalized) do
id when is_binary(id) -> {:ok, id}
nil -> maybe_find_contact_next_page(body, page, normalized)
end
defp get_first_contact_id_from_list(%{"data" => [%{"id" => id} | _]}) do
normalize_contact_id(id)
end
defp maybe_find_contact_next_page(body, page, normalized) do
data = Map.get(body, "data") || []
if length(data) < @find_contact_page_size,
do: {:error, :not_found},
else: do_find_contact_by_email_page(page + 1, normalized)
end
defp find_contact_id_by_email_in_list(%{"data" => list}, normalized) when is_list(list) do
Enum.find_value(list, fn
%{"id" => id, "attributes" => %{"email" => att_email, "isExternal" => true}}
when is_binary(att_email) ->
if att_email |> String.trim() |> String.downcase() == normalized do
normalize_contact_id(id)
else
nil
end
%{"id" => id, "attributes" => %{"email" => att_email, "isExternal" => "true"}}
when is_binary(att_email) ->
if att_email |> String.trim() |> String.downcase() == normalized do
normalize_contact_id(id)
else
nil
end
%{"id" => _id, "attributes" => _} ->
nil
_ ->
nil
end)
end
defp find_contact_id_by_email_in_list(_, _), do: nil
defp get_first_contact_id_from_list(%{"data" => []}), do: nil
defp get_first_contact_id_from_list(_), do: nil
defp normalize_contact_id(id) when is_binary(id), do: id
defp normalize_contact_id(id) when is_integer(id), do: to_string(id)