Fortunately, the IT environment knows the solution. Transaction processing divides the problem into smaller chunks. Every operation is considered separately, but all of them must succeed to apply changes. In that case, User-A won't lose money if the operation of updating User-B balance fails.
Let’s take a look at the code:
Ecto.Multi.new() |>Ecto.Multi.update(:payer, payer_changeset) |>Ecto.Multi.update(:reciever, reciever_changeset) |>Repo.transaction()
Atom argument - in update function - is just the name of the operation. It could be anything that is unique among these actions.
Of course, it would be also nice to handle result:
In case of success there will be returned tuple with :ok atom and instead of the updated structure as the second variable, you can expect the map with all updated structures.
Failure brings some helpful information too – like which operation failed (returns its unique atom), what is the failed value, and what has been changed so far (however not applied).
More about Ecto.Multi: https://hexdocs.pm/ecto/Ecto.Multi.html