Phoenix way to convert dollars and cents into just cents?

What is the best way to convert dollars and cents into just cents in a Phoenix Framework app?

Background

I’m storing currencies in the smallest denomination (cents) to avoid floating point math (everything is an integer). The user will still input currencies in dollars and cents.

e.g., User inputs $75.25 → stored as 7525 in the database.

LiveView Form Component

I have a form_component.ex with the following text input (using Alpine.js Mask for formatting):

<.input
  x-mask:dynamic="$money($input)"
  field={@form[:fee]}
  type="text"
  placeholder="0.00"
  label="Fee"
/>

and the following functions (also in form_component.ex), which converts the string “75.25” into the integer 7525 both on validation and on save:

def handle_event("validate", %{"assignments" => assignments_params}, socket) do
  assignments_params =
    Map.update!(assignments_params, "fee", fn fee -> dollars_to_cents(fee) end)

  changeset =
    socket.assigns.assignments
    |> Assigner.change_assignments(assignments_params)
    |> Map.put(:action, :validate)

  {:noreply, assign_form(socket, changeset)}
end

def handle_event("save", %{"assignments" => assignments_params}, socket) do
  assignments_params =
    Map.update!(assignments_params, "fee", fn fee -> dollars_to_cents(fee) end)

  save_assignments(socket, socket.assigns.action, assignments_params)
end

defp dollars_to_cents(""), do: nil

defp dollars_to_cents(dollars_and_cents_string) do
  {dollars_and_cents_float, _} = Float.parse(dollars_and_cents_string)
  trunc(dollars_and_cents_float * 100)
end

Questions

  1. Where should I put the dollars_to_cents helper function? I may use currencies elsewhere in the app and don’t want to duplicate code.
  2. Similarly, is there a way to convert the fee to cents in a central place (perhaps the schema?) to avoid having to convert before validation and before saving?
    • I’ve considered making a new “currency” input type but am unsure how that would work when saving

Thanks for any advice!

Not an expert, just my 2 cents:

defmodule CurrencyConverter do
  def to_cents(value) when is_binary(value) do
    {float_value, _} = Float.parse(value)
    to_cents(float_value)
  end

  def to_cents(value) when is_float(value) do
    trunc(value * 100)
  end
end

iex(2)> CurrencyConverter.to_cents("12.34")
1234
iex(3)> CurrencyConverter.to_cents(12.34)
1234
1 Like

I would avoid doing these half-brewed solutions if the application is important, just use a library that handles floating point well like Decimal or Money.

4 Likes

Not an expert, just my 2 cents

Pun intended? :smile: Thanks, looks like we can just define modules and keep them in some helper or lib directory.

2 Likes

Ah, Money is what I’m looking for (also stores currency as an integer). Thanks!

5 Likes