Packages
ukraine_nbuqr
0.1.0
Library for generation National Bank of Ukraine payment Quick Response Codes
Current section
Files
Jump to
Current section
Files
lib/qr_data/builder.ex
defmodule UkraineNbuqrEx.QrData.Builder do
@moduledoc """
A module for building National Bank of Ukraine (NBU) payment Quick Response (QR) code data structures.
"""
alias IbanEx.Parser, as: IbanParser
alias IbanEx.Formatter, as: IbanFormatter
alias UkraineTaxidEx, as: Taxid
alias UkraineNbuqrEx.{Amount, QrData}
import UkraineNbuqrEx.Commons, only: [ok: 1]
@length_restrictions %{
recipient: 70,
amount: 15,
tax_id: 10,
iban: 34,
purpose: 140
}
@doc """
Builds a QR data struct from the given parameters.
This builder validates and formats payment information according to NBU QR code specifications.
It enforces required fields and length restrictions for various payment details.
## Length Restrictions
The following maximum length restrictions are enforced:
- recipient: 70 characters
- amount: 15 characters
- tax_id: 10 characters
- iban: 34 characters
- purpose: 140 characters
## Usage
```elixir
iex> params = [
...> iban: "UA213223130000026007233566001",
...> tax_id: "12345678",
...> amount: "123.45",
...> recipient: "Company Name",
...> purpose: "Payment for services"
...> ]
...> UkraineNbuqrEx.QrData.Builder.build(params)
{:ok, %UkraineNbuqrEx.QrData{recipient: "Company Name", iban: "UA213223130000026007233566001", amount: "UAH123.45", tax_id: "12345678", purpose: "Payment for services"}} = UkraineNbuqrEx.QrData.Builder.build(params)
```
## Required Parameters
All of the following parameters are required:
- `:iban` - Ukrainian IBAN (must start with "UA")
- `:tax_id` - Valid Ukrainian tax identification number
- `:amount` - Payment amount (will be normalized)
- `:recipient` - Name of the payment recipient
- `:purpose` - Purpose of payment
The builder will return an error if any required parameter is missing or invalid.
## Validation
- IBAN is validated to ensure it's a valid Ukrainian IBAN
- Tax ID is validated using the UkraineTaxidEx library
- Amount is validated and normalized to ensure proper format
- All text fields are truncated to their maximum allowed lengths
"""
@spec build(params :: Keyword.t()) :: QrData.t()
def build(params) do
{params, %{}}
|> process_option(:iban, &iban/1)
|> process_option(:tax_id, &tax_id/1)
|> process_option(:amount, &amount/1)
|> process_option(:recipient)
|> process_option(:purpose)
|> case do
{:error, error} -> {:error, error}
{_options, map} -> {:ok, struct(QrData, map)}
end
end
defp iban(iban) do
case IbanParser.parse(iban) do
{:ok, %IbanEx.Iban{country_code: "UA"} = iban} -> iban |> IbanFormatter.compact() |> ok()
_ -> {:error, "Invalid IBAN"}
end
end
defp tax_id(tax_id) do
case Taxid.parse(tax_id) do
{:ok, %{code: code}, _tax_id_type} -> ok(code)
_ -> {:error, "Invalid Tax ID"}
end
end
defp amount(amount) do
amount
|> Amount.normalize()
|> ok()
end
defp process_option(options_and_data, name, processor \\ &ok/1)
defp process_option({:error, _error} = negative, _name, _processor), do: negative
defp process_option({options, data}, name, processor) do
options
|> Keyword.get(name)
|> case do
nil -> {:error, "Option #{name} is required"}
value -> do_process_option({options, data}, name, processor, value)
end
end
defp do_process_option({options, data}, name, processor, value) do
value
|> processor.()
|> limiter(name)
|> case do
{:ok, result} -> {options, Map.put_new(data, name, result)}
error -> error
end
end
defp limiter({:error, _error} = error, _name), do: error
defp limiter({:ok, value}, name), do: limiter(value, name)
defp limiter(value, name) when is_binary(value) do
length = Map.get(@length_restrictions, name, 0)
value
|> String.slice(0, length)
|> ok()
end
end