Shared utility functions for the X402 library.
Centralises helpers for Base64 decoding, map access, decimal parsing, and decimal comparison that are used across payment processing modules.
Summary
Functions
Compares two {value, scale} decimal tuples.
Decodes a Base64 string with or without padding.
Finds the first non-nil value in a list.
Deletes both string and atom versions of a key from a map.
Puts a value into a map, preferring the existing key type (string over atom).
Retrieves a value from a map using either a string or atom key.
Retrieves a nested value from a map using a list of string/atom key pairs.
Parses a decimal value (integer or string) into a {value, scale} tuple
where value / 10^scale equals the original number.
Functions
@spec compare_decimal( {non_neg_integer(), non_neg_integer()}, {non_neg_integer(), non_neg_integer()} ) :: :lt | :eq | :gt
Compares two {value, scale} decimal tuples.
Returns :lt, :eq, or :gt.
Examples
iex> X402.Utils.compare_decimal({10, 1}, {1, 0})
:eq
iex> X402.Utils.compare_decimal({5, 1}, {1, 0})
:lt
iex> X402.Utils.compare_decimal({15, 1}, {1, 0})
:gt
Decodes a Base64 string with or without padding.
Examples
iex> X402.Utils.decode_base64("")
{:error, :invalid_base64}
iex> X402.Utils.decode_base64("aGVsbG8=")
{:ok, "hello"}
iex> X402.Utils.decode_base64("not-valid-!!!")
{:error, :invalid_base64}
Finds the first non-nil value in a list.
Examples
iex> X402.Utils.first_present([nil, nil, "found"])
"found"
iex> X402.Utils.first_present([nil, nil])
nil
iex> X402.Utils.first_present([false, "other"])
false
Deletes both string and atom versions of a key from a map.
Puts a value into a map, preferring the existing key type (string over atom).
Retrieves a value from a map using either a string or atom key.
Prefers the string key if present, falls back to atom key.
Examples
iex> X402.Utils.map_value(%{"key" => "string_val"}, {"key", :key})
"string_val"
iex> X402.Utils.map_value(%{key: "atom_val"}, {"key", :key})
"atom_val"
iex> X402.Utils.map_value(%{}, {"key", :key})
nil
Retrieves a nested value from a map using a list of string/atom key pairs.
@spec parse_decimal(term()) :: {:ok, {non_neg_integer(), non_neg_integer()}} | :error
Parses a decimal value (integer or string) into a {value, scale} tuple
where value / 10^scale equals the original number.
Returns :error for negative integers, non-numeric strings, or strings
without a leading digit (e.g. ".5").
Examples
iex> X402.Utils.parse_decimal(42)
{:ok, {42, 0}}
iex> X402.Utils.parse_decimal("12.50")
{:ok, {125, 1}}
iex> X402.Utils.parse_decimal("0.001")
{:ok, {1, 3}}
iex> X402.Utils.parse_decimal(".5")
:error
iex> X402.Utils.parse_decimal("")
:error
iex> X402.Utils.parse_decimal(-1)
:error