This project was developed as part of the Stone Tech Challenge. It intends to be a financial system able to do arbitrary precision financial operations in compliance with ISO 4217. It also was my first contact with Elixir and the functional programming paradigm.
- Elixir 1.7
- Poison to parse JSON
- Decimal to arithmetic operations with arbitrary precision
- HTTPotion to do HTTP requests and get currency exchange rates
- ExDoc to generate documentation
git clone https://github.com/lucaslrolim/stone-challenge
cd /stone-challenge
mix deps.get
to install dependenciesiex -S mix
to start Elixir's interactive shell
The project has five modules,but three are used to store customize structures and utility functions. The core modules are Currency
and FinancialSystem
.
The Currency module is used to create new elements to be operated by the financial system and ensure that these elements are in compliance with ISO 4217 standards. Besides that, it also implements arbitrary precision money operations, as sum and multiplication.
The following operations are available:
- Sum as
sum(money_a ,money_b)
- Subtraction as
sub(money_a ,money_b)
- Multiplication as
mult(money, number)
- Disivion as
div(money, number)
These functions were all built on top of the Decimal library. Also, the operations are all done in the Money
customized structure, stored in the Money module.
A JSON file is used to get which are the valid currency codes and check if the user's input is correct.
This is the project's core module. It implements operations on Money
and Account
structures using the basic functions built on Currency
module. An important aspect is that each account can operate on just one currency. Like in traditional bank accounts, the user needs to create different accounts to store different currencies, but it's possible to do an international transfer without the need to create a new account to this.
The following operations are implemented:
- Deposit as
deposit!(account, deposit_value, deposit_curency)
- Withdrawal as
withdrawal!(account, withdrawal_value, withdrawal_curency)
- Transfer as
transfer!(sender_account, receiver_account, value)
- International transfer as
transfer_international!(sender_account, receiver_account, to_currency, value)
- Check account balance as
check_account_balance(account)
Also, there are functions to split a value among multiple accounts according to given percents. These functions are split_value
and split_transfer
.
Split transfer function splits a value among N other given accounts. The distribution of the value follows the argument percents
. This argument is a list that must sum 1.
iex> sender_account = FinancialSystem.create_account!(1, "Fidalgo", :BRL)
iex> sender_account = FinancialSystem.deposit!(sender_account,10, :BRL)
iex> FinancialSystem.check_account_balance(sender_account)
"R$10.00"
iex> receiver_1 = FinancialSystem.create_account!(2, "Mineirinho", :BRL)
iex> receiver_2 = FinancialSystem.create_account!(3, "Caju", :BRL)
iex> {sender_account, [receiver_1,receiver_2]} = FinancialSystem.split_transfer!(sender_account,[receiver_1,receiver_2], 10, [0.6,0.4])
iex> FinancialSystem.check_account_balance(sender_account)
"R$0.00"
iex> FinancialSystem.check_account_balance(receiver_1)
"R$6.00"
iex> FinancialSystem.check_account_balance(receiver_2)
"R$4.00"
The split_value
function works in a similar way. It uses a list of accounts to split a deposit or withdrawal. The operation to be performed must be informed as a function argument. E.g:
iex> account_1 = FinancialSystem.create_account!(1, "Proximus", :BRL)
iex> account_2 = FinancialSystem.create_account!(2, "Bolinha", :BRL)
iex> account_3 = FinancialSystem.create_account!(3, "Botafogo", :BRL)
iex> [account_1,account_2,account_3] = FinancialSystem.split_value!([account_1, account_2, account_3],10,[0.5, 0.3, 0.2], &FinancialSystem.deposit!/3)
iex> FinancialSystem.check_account_balance(account_1)
"R$5.00"
iex> FinancialSystem.check_account_balance(account_2)
"R$3.00"
iex> FinancialSystem.check_account_balance(account_3)
"R$2.00"
Finally, the Open Exchange Rates API was used to implement the exchange
function. So the conversion rates are handled at execution time and need an internet connection to works.
The basic usage instructions are explained on the documentation page.
As a general rule positive tests were implemented as doctests and negative tests (errors) as unit tests in the test folder.
- Travis to continuous integration
- Coveralls to coverage tests
- Elixir Formatter to Elixir's style and conventions
- ElixirLS to debbug and Dialyzer analysis