Money v2.2.2 Money View Source

Money implements a set of functions to store, retrieve, convert and perform arithmetic on a Money.t type that is composed of a currency code and a decimal currency amount.

Money is very opinionated in the interests of serving as a dependable library that can underpin accounting and financial applications.

This opinion expressed by ensuring that:

  1. Money must always have both a amount and a currency code.

  2. The currency code must always be valid.

  3. Money arithmetic can only be performed when both operands are of the same currency.

  4. Money amounts are represented as a Decimal.

  5. Money is serialised to the database as a custom Postgres composite type that includes both the amount and the currency. Therefore for Ecto serialization Postgres is assumed as the data store. Serialization is entirely optional and Ecto is not a package dependency.

  6. All arithmetic functions work in fixed point decimal. No rounding occurs automatically (unless expressly called out for a function).

  7. Explicit rounding obeys the rounding rules for a given currency. The rounding rules are defined by the Unicode consortium in its CLDR repository as implemented by the hex package ex_cldr. These rules define the number of fractional digits for a currency and the rounding increment where appropriate.

Link to this section Summary

Types

t()

Money is composed of an atom representation of an ISO4217 currency code and a Decimal representation of an amount

Functions

Add two Money values

Add two Money values and raise on error

Compares two Money values numerically. If the first number is greater than the second :gt is returned, if less than :lt is returned, if both numbers are equal :eq is returned

Compares two Money values numerically and raises on error

Compares two Money values numerically. If the first number is greater than the second #Integer<1> is returned, if less than Integer<-1> is returned. Otherwise, if both numbers are equal Integer<0> is returned

Compares two Money values numerically and raises on error

Divide a Money value by a number

Divide a Money value by a number and raise on error

Returns a boolean indicating if two Money values are equal

Returns a %Money{} struct from a currency code and a float amount, or an error tuple of the form {:error, {exception, message}}

Returns a %Money{} struct from a currency code and a float amount, or raises an exception if the currency code is invalid

Convert an integer representation of money into a Money struct

Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. The format of the argument is a 2-tuple where

Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. Raises an exception if the currency code is invalid

Multiply a Money value by a number

Multiply a Money value by a number and raise on error

Returns a %Money{} struct from a currency code and a currency amount or an error tuple of the form {:error, {exception, message}}

Returns a %Money{} struct from a currency code and a currency amount. Raises an exception if the current code is invalid

Calls Decimal.reduce/1 on the given %Money{}

Round a Money value into the acceptable range for the requested currency

Split a Money value into a number of parts maintaining the currency’s precision and rounding and ensuring that the parts sum to the original amount

Subtract one Money value struct from another

Subtract one Money value struct from another and raise on error

Convert money from one currency to another and raises on error

Returns the amount part of a Money type as a Decimal

Returns a tuple comprising the currency code, integer amount, exponent and remainder

Returns a formatted string representation of a Money{}

Returns a formatted string representation of a Money{} or raises if there is an error

Link to this section Types

Link to this type currency_code() View Source
currency_code() :: atom() | String.t()
Link to this type t() View Source
t() :: %Money{amount: Decimal.t(), currency: atom()}

Money is composed of an atom representation of an ISO4217 currency code and a Decimal representation of an amount.

Link to this section Functions

Link to this function add(money1, money2) View Source
add(money_1 :: Money.t(), money_2 :: Money.t()) :: Money.t()

Add two Money values.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • {:ok, money} or

  • {:error, reason}

Example

iex> Money.add Money.new(:USD, 200), Money.new(:USD, 100)
{:ok, Money.new(:USD, 300)}

iex> Money.add Money.new(:USD, 200), Money.new(:AUD, 100)
{:error, {ArgumentError, "Cannot add monies with different currencies. " <>
  "Received :USD and :AUD."}}

Add two Money values and raise on error.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • {:ok, money} or

  • raises an exception

Examples

iex> Money.add! Money.new(:USD, 200), Money.new(:USD, 100)
#Money<:USD, 300>

Money.add! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot add two %Money{} with different currencies. Received :USD and :CAD.
Link to this function cmp(money1, money2) View Source
cmp(money_1 :: Money.t(), money_2 :: Money.t()) ::
  :gt | :eq | :lt | {:error, {Exception.t(), String.t()}}

Compares two Money values numerically. If the first number is greater than the second :gt is returned, if less than :lt is returned, if both numbers are equal :eq is returned.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • :gt | :eq | :lt or

  • {:error, {Exception.t, String.t}}

Examples

iex> Money.cmp Money.new(:USD, 200), Money.new(:USD, 100)
:gt

iex> Money.cmp Money.new(:USD, 200), Money.new(:USD, 200)
:eq

iex> Money.cmp Money.new(:USD, 200), Money.new(:USD, 500)
:lt

iex> Money.cmp Money.new(:USD, 200), Money.new(:CAD, 500)
{:error,
 {ArgumentError,
  "Cannot compare monies with different currencies. Received :USD and :CAD."}}

Compares two Money values numerically and raises on error.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • :gt | :eq | :lt or

  • raises an exception

Examples

Money.cmp! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot compare monies with different currencies. Received :USD and :CAD.
Link to this function compare(money1, money2) View Source
compare(money_1 :: Money.t(), money_2 :: Money.t()) ::
  -1 | 0 | 1 | {:error, {Exception.t(), String.t()}}

Compares two Money values numerically. If the first number is greater than the second #Integer<1> is returned, if less than Integer<-1> is returned. Otherwise, if both numbers are equal Integer<0> is returned.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • -1 | 0 | 1 or

  • {:error, {Exception.t, String.t}}

Examples

iex> Money.compare Money.new(:USD, 200), Money.new(:USD, 100)
1

iex> Money.compare Money.new(:USD, 200), Money.new(:USD, 200)
0

iex> Money.compare Money.new(:USD, 200), Money.new(:USD, 500)
-1

iex> Money.compare Money.new(:USD, 200), Money.new(:CAD, 500)
{:error,
 {ArgumentError,
  "Cannot compare monies with different currencies. Received :USD and :CAD."}}
Link to this function compare!(money_1, money_2) View Source

Compares two Money values numerically and raises on error.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • -1 | 0 | 1 or

  • raises an exception

Examples

Money.compare! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot compare monies with different currencies. Received :USD and :CAD.

Divide a Money value by a number.

Options

  • money is any valid Money.t types returned by Money.new/2

  • number is an integer, float or Decimal.t

Note that dividing one %Money{} by another is not supported.

Returns

  • {:ok, money} or

  • {:error, reason}

Example

iex> Money.div Money.new(:USD, 200), 2
{:ok, Money.new(:USD, 100)}

iex> Money.div(Money.new(:USD, 200), "xx")
{:error, {ArgumentError, "Cannot divide money by \"xx\""}}

Divide a Money value by a number and raise on error.

Options

  • money is any valid Money.t types returned by Money.new/2

  • number is an integer, float or Decimal.t

Returns

  • a Money.t struct or

  • raises an exception

Examples

iex> Money.div Money.new(:USD, 200), 2
{:ok, Money.new(:USD, 100)}

Money.div(Money.new(:USD, 200), "xx")
** (ArgumentError) "Cannot divide money by \"xx\""]}}
Link to this function equal?(arg1, arg2) View Source
equal?(money_1 :: Money.t(), money_2 :: Money.t()) :: boolean()

Returns a boolean indicating if two Money values are equal

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • true or false

Example

iex> Money.equal? Money.new(:USD, 200), Money.new(:USD, 200)
true

iex> Money.equal? Money.new(:USD, 200), Money.new(:USD, 100)
false
Link to this function from_float(currency_code, amount) View Source
from_float(currency_code(), float()) ::
  Money.t() | {:error, {Exception.t(), String.t()}}

Returns a %Money{} struct from a currency code and a float amount, or an error tuple of the form {:error, {exception, message}}.

Floats are fraught with danger in computer arithmetic due to the unexpected loss of precision during rounding. The IEEE754 standard indicates that a number with a precision of 16 digits should round-trip convert without loss of fidelity. This function supports numbers with a precision up to 15 digits and will error if the provided amount is outside that range.

Note that Money cannot detect lack of precision or rounding errors introduced upstream. This function therefore should be used with great care and its use should be considered potentially harmful.

Options

  • currency_code is an ISO4217 three-character upcased binary or atom

  • amount is a float

Examples

iex> Money.from_float 1.23456, :USD
#Money<:USD, 1.23456>

iex> Money.from_float 1.234567890987656, :USD
{:error,
  {Money.InvalidAmountError,
    "The precision of the float 1.234567890987656 is " <>
    "greater than 15 which could lead to unexpected results. " <>
    "Reduce the precision or call Money.new/2 with a Decimal or String amount"}}
Link to this function from_float!(currency_code, amount) View Source
from_float!(currency_code(), float()) :: Money.t() | no_return()

Returns a %Money{} struct from a currency code and a float amount, or raises an exception if the currency code is invalid.

See Money.from_float/2 for further information.

Note that Money cannot detect lack of precision or rounding errors introduced upstream. This function therefore should be used with great care and its use should be considered potentially harmful.

Options

  • currency_code is an ISO4217 three-character upcased binary or atom

  • amount is a float

Examples

iex> Money.from_float!(:USD, 1.234)
#Money<:USD, 1.234>

Money.from_float!(:USD, 1.234567890987654)
#=> ** (Money.InvalidAmountError) The precision of the float 1.234567890987654 is greater than 15 which could lead to unexpected results. Reduce the precision or call Money.new/2 with a Decimal or String amount
    (ex_money) lib/money.ex:293: Money.from_float!/2
Link to this function from_integer(amount, currency) View Source

Convert an integer representation of money into a Money struct.

This is the inverse operation of Money.to_integer_exp/1. Note that the ISO definition of currency digits (subunit) is always used. This is, in some cases like the Colombian Peso (COP) different to the CLDR definition.

Options

  • integer is an integer representation of a mooney item including any decimal digits. ie. 20000 would interpreted to mean $200.00

  • currency is the currency code for the integer. The assumed decimal places is derived from the currency code.

Returns

  • A Money struct or

  • {:error, {Cldr.UnknownCurrencyError, message}}

Examples

iex> Money.from_integer(20000, :USD)
#Money<:USD, 200.00>

iex> Money.from_integer(200, :JPY)
#Money<:JPY, 200>

iex> Money.from_integer(20012, :USD)
#Money<:USD, 200.12>

iex> Money.from_integer(20012, :COP)
#Money<:COP, 200.12>
Link to this function from_tuple(arg) View Source
from_tuple({binary(), number()}) :: Money.t()

Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. The format of the argument is a 2-tuple where:

Options

  • currency_code is an ISO4217 three-character upcased binary

  • amount is an integer or Decimal

This function is typically called from Ecto when it’s loading a %Money{} struct from the database.

Example

iex> Money.from_tuple({"USD", 100})
#Money<:USD, 100>

iex> Money.from_tuple({100, "USD"})
#Money<:USD, 100>

Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. Raises an exception if the currency code is invalid.

Options

  • currency_code is an ISO4217 three-character upcased binary

  • amount is an integer, float or Decimal

This function is typically called from Ecto when it’s loading a %Money{} struct from the database.

Example

iex> Money.from_tuple!({"USD", 100})
#Money<:USD, 100>

Money.from_tuple!({"NO!", 100})
** (Money.UnknownCurrencyError) Currency "NO!" is not known
    (ex_money) lib/money.ex:130: Money.new!/1
Link to this function get_env(key, default, atom) View Source
Link to this function known_current_currencies() View Source
Link to this function known_historic_currencies() View Source
Link to this function known_tender_currencies() View Source

Multiply a Money value by a number.

Options

  • money is any valid Money.t type returned by Money.new/2

  • number is an integer, float or Decimal.t

Note that multipling one %Money{} by another is not supported.

Returns

  • {:ok, money} or

  • {:error, reason}

Example

iex> Money.mult(Money.new(:USD, 200), 2)
{:ok, Money.new(:USD, 400)}

iex> Money.mult(Money.new(:USD, 200), "xx")
{:error, {ArgumentError, "Cannot multiply money by \"xx\""}}

Multiply a Money value by a number and raise on error.

Options

  • money is any valid Money.t types returned by Money.new/2

  • number is an integer, float or Decimal.t

Returns

  • a Money.t or

  • raises an exception

Examples

iex> Money.mult!(Money.new(:USD, 200), 2)
#Money<:USD, 400>

Money.mult!(Money.new(:USD, 200), :invalid)
** (ArgumentError) Cannot multiply money by :invalid
Link to this function new(currency_code, amount) View Source
new(currency_code(), integer() | Decimal.t() | String.t()) ::
  Money.t() | {:error, {Exceptiom.t(), String.t()}}

Returns a %Money{} struct from a currency code and a currency amount or an error tuple of the form {:error, {exception, message}}.

Options

  • currency_code is an ISO4217 three-character upcased binary or atom

  • amount is an integer, string or Decimal

Note that the currency_code and amount arguments can be supplied in either order,

Examples

iex> Money.new(:USD, 100)
#Money<:USD, 100>

iex> Money.new(100, :USD)
#Money<:USD, 100>

iex> Money.new("USD", 100)
#Money<:USD, 100>

iex> Money.new("thb", 500)
#Money<:THB, 500>

iex> Money.new("EUR", Decimal.new(100))
#Money<:EUR, 100>

iex> Money.new(:EUR, "100.30")
#Money<:EUR, 100.30>

iex> Money.new(:XYZZ, 100)
{:error, {Money.UnknownCurrencyError, "The currency :XYZZ is invalid"}}

iex(1)> Money.new 123.445, :USD
{:error,
 {Money.InvalidAmountError,
  "Float amounts are not supported in new/2 due to potenial " <>
  "rounding and precision issues.  If absolutely required, " <>
  "use Money.from_float/2"}}
Link to this function new!(currency_code, amount) View Source

Returns a %Money{} struct from a currency code and a currency amount. Raises an exception if the current code is invalid.

Options

  • currency_code is an ISO4217 three-character upcased binary or atom

  • amount is an integer, float or Decimal

Examples

Money.new!(:XYZZ, 100)
** (Money.UnknownCurrencyError) Currency :XYZZ is not known
  (ex_money) lib/money.ex:177: Money.new!/2

Calls Decimal.reduce/1 on the given %Money{}

This will reduce the coefficient and exponent of the decimal amount in a standard way that may aid in native comparison of %Money{} items.

Example

iex> x = %Money{currency: :USD, amount: %Decimal{sign: 1, coef: 42, exp: 0}}
#Money<:USD, 42>
iex> y = %Money{currency: :USD, amount: %Decimal{sign: 1, coef: 4200000000, exp: -8}}
#Money<:USD, 42.00000000>
iex> x == y
false
iex> y = Money.reduce(x)
#Money<:USD, 42>
iex> x == y
true
Link to this function round(money, opts \\ []) View Source
round(Money.t(), Keyword.t()) :: Money.t()

Round a Money value into the acceptable range for the requested currency.

Arguments

  • money is a %Money{} struct

  • opts is a keyword list of options

Options

  • :rounding_mode that defines how the number will be rounded. See Decimal.Context. The default is :half_even which is also known as “banker’s rounding”

  • :currency_digits which determines the rounding increment. The valid options are :cash, :accounting and :iso. The default is :iso. The rounding increment applies to currencies such as :AUD and :CHF which have an accounting increment of 0.01 but a minimum cash increment of 0.05.

Notes

There are two kinds of rounding applied:

  1. Round to the appropriate number of fractional digits

  2. Apply an appropriate rounding increment. Most currencies round to the same precision as the number of decimal digits, but some such as :AUD and :CHF round to a minimum such as 0.05 when its a cash amount.

Examples

iex> Money.round Money.new("123.73", :CHF), currency_digits: :cash
#Money<:CHF, 123.75>

iex> Money.round Money.new("123.7456", :CHF)
#Money<:CHF, 123.75>

Money.round Money.new("123.7456", :JPY)
#Money<:JPY, 124>

Split a Money value into a number of parts maintaining the currency’s precision and rounding and ensuring that the parts sum to the original amount.

Options

  • money is a %Money{} struct

  • parts is an integer number of parts into which the money is split

Returns a tuple {dividend, remainder} as the function result derived as follows:

  1. Round the money amount to the required currency precision using Money.round/1

  2. Divide the result of step 1 by the integer divisor

  3. Round the result of the division to the precision of the currency using Money.round/1

  4. Return two numbers: the result of the division and any remainder that could not be applied given the precision of the currency.

Examples

Money.split Money.new(123.5, :JPY), 3
{¥41, ¥1}

Money.split Money.new(123.4, :JPY), 3
{¥41, ¥0}

Money.split Money.new(123.7, :USD), 9
{$13.74, $0.04}
Link to this function sub(money1, money2) View Source
sub(money_1 :: Money.t(), money_2 :: Money.t()) ::
  {:ok, Money.t()} | {:error, {Exception.t(), String.t()}}

Subtract one Money value struct from another.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • {:ok, money} or

  • {:error, reason}

Example

iex> Money.sub Money.new(:USD, 200), Money.new(:USD, 100)
{:ok, Money.new(:USD, 100)}
Link to this function sub!(a, b) View Source
sub!(money_1 :: Money.t(), money_2 :: Money.t()) :: Money.t() | none()

Subtract one Money value struct from another and raise on error.

Returns either {:ok, money} or {:error, reason}.

Options

  • money_1 and money_2 are any valid Money.t types returned by Money.new/2

Returns

  • a Money.t struct or

  • raises an exception

Examples

iex> Money.sub! Money.new(:USD, 200), Money.new(:USD, 100)
#Money<:USD, 100>

Money.sub! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot subtract monies with different currencies. Received :USD and :CAD.
Link to this function to_currency(money, to_currency, rates \\ Money.ExchangeRates.latest_rates()) View Source
to_currency(Money.t(), Money.currency_code(), Map.t()) ::
  {:ok, Map.t()} | {:error, {Exception.t(), String.t()}}

Convert money from one currency to another.

Options

  • money is any Money.t struct returned by Cldr.Currency.new/2

  • to_currency is a valid currency code into which the money is converted

  • rates is a Map of currency rates where the map key is an upcased atom or string and the value is a Decimal conversion factor. The default is the latest available exchange rates returned from Money.ExchangeRates.latest_rates()

Examples

Money.to_currency(Money.new(:USD, 100), :AUD, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)})
{:ok, #Money<:AUD, 73.4500>}

Money.to_currency(Money.new("USD", 100), "AUD", %{"USD" => Decimal.new(1), "AUD" => Decimal.new(0.7345)})
{:ok, #Money<:AUD, 73.4500>}

iex> Money.to_currency Money.new(:USD, 100) , :AUDD, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
{:error, {Cldr.UnknownCurrencyError, "The currency :AUDD is invalid"}}

iex> Money.to_currency Money.new(:USD, 100) , :CHF, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
{:error, {Money.ExchangeRateError, "No exchange rate is available for currency :CHF"}}
Link to this function to_currency!(money, currency) View Source

Convert money from one currency to another and raises on error

Options

  • money is any Money.t struct returned by Cldr.Currency.new/2

  • to_currency is a valid currency code into which the money is converted

  • rates is a Map of currency rates where the map key is an upcased atom or string and the value is a Decimal conversion factor. The default is the latest available exchange rates returned from Money.ExchangeRates.latest_rates()

Examples

iex> Money.to_currency! Money.new(:USD, 100) , :AUD, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
#Money<:AUD, 73.4500>

iex> Money.to_currency! Money.new("USD", 100) , "AUD", %{"USD" => Decimal.new(1), "AUD" => Decimal.new(0.7345)}
#Money<:AUD, 73.4500>

Money.to_currency! Money.new(:USD, 100) , :ZZZ, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
** (Cldr.UnknownCurrencyError) Currency :ZZZ is not known
Link to this function to_currency!(money, currency, rates) View Source
Link to this function to_decimal(money) View Source
to_decimal(money :: Money.t()) :: Decimal.t()

Returns the amount part of a Money type as a Decimal

Options

  • money is any valid Money.t type returned by Money.new/2

Returns

  • a Decimal.t

Example

iex> m = Money.new("USD", 100)
iex> Money.to_decimal(m)
#Decimal<100>
Link to this function to_integer_exp(money, opts \\ []) View Source

Returns a tuple comprising the currency code, integer amount, exponent and remainder

Some services require submission of money items as an integer with an implied exponent that is appropriate to the currency.

Rather than return only the integer, Money.to_integer_exp returns the currency code, integer, exponent and remainder. The remainder is included because to return an integer money with an implied exponent the Money has to be rounded potentially leaving a remainder.

Options

Notes

  • Since the returned integer is expected to have the implied fractional digits the Money needs to be rounded which is what this function does.

Example

iex> m = Money.new(:USD, "200.012356")
#Money<:USD, 200.012356>
iex> Money.to_integer_exp(m)
{:USD, 20001, -2, Money.new(:USD, "0.002356")}

iex> m = Money.new(:USD, "200.00")
#Money<:USD, 200.00>
iex> Money.to_integer_exp(m)
{:USD, 20000, -2, Money.new(:USD, "0.00")}
Link to this function to_string(money, options \\ []) View Source

Returns a formatted string representation of a Money{}.

Formatting is performed according to the rules defined by CLDR. See Cldr.Number.to_string/2 for formatting options. The default is to format as a currency which applies the appropriate rounding and fractional digits for the currency.

Options

  • money_1 is any valid Money.t type returned by Money.new/2

Returns

  • {:ok, string} or

  • {:error, reason}

Examples

iex> Money.to_string Money.new(:USD, 1234)
{:ok, "$1,234.00"}

iex> Money.to_string Money.new(:JPY, 1234)
{:ok, "¥1,234"}

iex> Money.to_string Money.new(:THB, 1234)
{:ok, "THB1,234.00"}

iex> Money.to_string Money.new(:USD, 1234), format: :long
{:ok, "1,234 US dollars"}
Link to this function to_string!(money, options \\ []) View Source

Returns a formatted string representation of a Money{} or raises if there is an error.

Formatting is performed according to the rules defined by CLDR. See Cldr.Number.to_string!/2 for formatting options. The default is to format as a currency which applies the appropriate rounding and fractional digits for the currency.

Examples

iex> Money.to_string! Money.new(:USD, 1234)
"$1,234.00"

iex> Money.to_string! Money.new(:JPY, 1234)
"¥1,234"

iex> Money.to_string! Money.new(:THB, 1234)
"THB1,234.00"

iex> Money.to_string! Money.new(:USD, 1234), format: :long
"1,234 US dollars"
Link to this function validate_currency(currency_code) View Source