Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- %%%--------------------------------------------------------------------
- %%% @doc A simple banking module. Allows a customer to make
- %%% usual transactions as if in a bank.
- %%%--------------------------------------------------------------------
- -module(bank_server).
- -behaviour(gen_server).
- %% API
- -export([start_link/0, new_user/2,
- login/2, deposit/3, withdraw/3,
- check_balance/2, stop/0, show_state/0]).
- %% gen_server callbacks
- -export([init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3]).
- -define(SERVER, ?MODULE).
- -record(state, {accounts}).
- -record(acct, {password, uniqueid, balance=0.0}).
- %%%===================================================================
- %%% API
- %%%===================================================================
- %%--------------------------------------------------------------------
- %% @doc
- %% Starts the server
- %%
- %% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
- %% @end
- %%--------------------------------------------------------------------
- start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
- %%--------------------------------------------------------------------
- %% @doc
- %% Registers a new user account in the bank
- %%
- %% @spec new_user(Name::string(), Password::string()) -> {ok} | {error, Error}
- %% @end
- %%--------------------------------------------------------------------
- new_user(Name, Password) ->
- gen_server:call(?SERVER, {new_user, Name, Password}).
- %%--------------------------------------------------------------------
- %% @doc
- %% Interface to login using username and password, receives a unique
- %% token in return
- %%
- %% @spec login(Name::string(), Password::string()) -> {ok, UniqueID} | {error, Error}
- %% @end
- %%--------------------------------------------------------------------
- login(Name, Password) ->
- gen_server:call(?SERVER, {login, Name, Password}).
- %%--------------------------------------------------------------------
- %% @doc
- %% Function to deposit money on a bank account
- %%
- %% @spec deposit(Name::string(), UniqueId::string(), Amount::float()) -> {ok, success} | {error, Error}
- %% @end
- %%--------------------------------------------------------------------
- deposit(Name, UniqueId, Amount) ->
- gen_server:call(?SERVER, {deposit, Name, UniqueId, Amount}).
- %%--------------------------------------------------------------------
- %% @doc
- %% Function to withdraw money from own bank account
- %%
- %% @spec withdraw(Name::string(), UniqueId::string(), Amount::float()) ->
- %% {ok, success} | {error, Error}
- %% @end
- %%--------------------------------------------------------------------
- withdraw(Name, UniqueId, Amount) ->
- gen_server:call(?SERVER, {withdraw, Name, UniqueId, Amount}).
- %%--------------------------------------------------------------------
- %% @doc
- %% Check available balance on own bank account
- %%
- %% @spec check_balance(Name::string(), UniqueId::string()) -> {ok, Balance} | {error, Error}
- %% @end
- %%--------------------------------------------------------------------
- check_balance(Name, UniqueId) ->
- gen_server:call(?SERVER, {check_balance, Name, UniqueId}).
- show_state() ->
- gen_server:call(?SERVER,show_state).
- %%--------------------------------------------------------------------
- %% @doc
- %% Stops the bank server
- %%
- %% @spec stop() -> ok
- %% @end
- %%--------------------------------------------------------------------
- stop() ->
- gen_server:cast(?SERVER, stop).
- %%%===================================================================
- %%% gen_server callbacks
- %%%===================================================================
- %%--------------------------------------------------------------------
- %% @private
- %% @doc
- %% Initializes the server
- %%
- %% @spec init(Args) -> {ok, State} |
- %% {ok, State, Timeout} |
- %% ignore |
- %% {stop, Reason}
- %% @end
- %%--------------------------------------------------------------------
- init([]) ->
- {ok, #state{accounts=orddict:new()}}.
- %%--------------------------------------------------------------------
- %% @private
- %% @doc
- %% Handling call messages
- %%
- %% @spec handle_call(Request, From, State) ->
- %% {reply, Reply, State} |
- %% {reply, Reply, State, Timeout} |
- %% {noreply, State} |
- %% {noreply, State, Timeout} |
- %% {stop, Reason, Reply, State} |
- %% {stop, Reason, State}
- %% @end
- %%--------------------------------------------------------------------
- handle_call(show_state, _From, State) ->
- {reply, State, State};
- handle_call({new_user, Name, Password}, _From, State) ->
- CheckResult = check_account_exists(Name, State#state.accounts),
- check_result_for_new_user_call(CheckResult, Name, Password, State);
- handle_call({login, Name, Password}, _From, State) ->
- CheckResult = check_account_exists(Name, State#state.accounts),
- check_result_for_login_call(CheckResult, Name, Password, State);
- handle_call({deposit, Name, UniqueId, Amount}, _From, State) ->
- CheckResult = check_account_exists(Name, State#state.accounts),
- check_result_for_deposit_call(CheckResult, Name, UniqueId, Amount, State);
- handle_call({withdraw, Name, UniqueId, Amount}, _From, State) ->
- CheckResult = check_account_exists(Name, State#state.accounts),
- check_result_for_withdraw_call(CheckResult, Name, UniqueId, Amount, State);
- handle_call({check_balance, Name, UniqueId}, _From, State) ->
- CheckResult = check_account_exists(Name, State#state.accounts),
- check_result_for_check_balance_call(CheckResult, UniqueId, State).
- %%--------------------------------------------------------------------
- %% @private
- %% @doc
- %% Handling cast messages
- %%
- %% @spec handle_cast(Msg, State) -> {noreply, State} |
- %% {noreply, State, Timeout} |
- %% {stop, Reason, State}
- %% @end
- %%--------------------------------------------------------------------
- handle_cast(stop, State) ->
- {stop, normal, State}.
- %%--------------------------------------------------------------------
- %% @private
- %% @doc
- %% Handling all non call/cast messages
- %%
- %% @spec handle_info(Info, State) -> {noreply, State} |
- %% {noreply, State, Timeout} |
- %% {stop, Reason, State}
- %% @end
- %%--------------------------------------------------------------------
- handle_info(_Info, State) ->
- {noreply, State}.
- %%--------------------------------------------------------------------
- %% @private
- %% @doc
- %% This function is called by a gen_server when it is about to
- %% terminate. It should be the opposite of Module:init/1 and do any
- %% necessary cleaning up. When it returns, the gen_server terminates
- %% with Reason. The return value is ignored.
- %%
- %% @spec terminate(Reason, State) -> void()
- %% @end
- %%--------------------------------------------------------------------
- terminate(_Reason, _State) ->
- ok.
- %%--------------------------------------------------------------------
- %% @private
- %% @doc
- %% Convert process state when code is changed
- %%
- %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
- %% @end
- %%--------------------------------------------------------------------
- code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
- %%%===================================================================
- %%% Internal functions
- %%%===================================================================
- check_account_exists(Name, AccList) ->
- check_account_exists_and_take_decision(orddict:find(Name, AccList)).
- check_account_exists_and_take_decision({ok, E}) ->
- {true, E};
- check_account_exists_and_take_decision(_Error) ->
- false.
- check_result_for_new_user_call(false, Name, Password, State) ->
- NewAcct = orddict:store(Name, #acct{
- password=Password,
- balance=0.0}, State#state.accounts),
- {reply, {ok}, State#state{accounts=NewAcct}};
- check_result_for_new_user_call(_, _Name, _Password, State) ->
- {reply, {error, account_exists}, State}.
- check_result_for_login_call({true, Acc}, Name, Password, State) ->
- StoredPwd = Acc#acct.password,
- if Password =:= StoredPwd ->
- UniqueId = generate_unique_key(),
- NewAcct = Acc#acct{uniqueid=UniqueId},
- NewAcctList = orddict:update(Name, fun(_Old) -> NewAcct end,
- State#state.accounts),
- {reply, {ok, UniqueId}, State#state{accounts = NewAcctList}};
- Password =/= StoredPwd ->
- {reply, {error, wrongpwd}, State}
- end;
- check_result_for_login_call(_, _Name, _Password, State) ->
- {reply, {error,nosuchuser}, State}.
- check_result_for_deposit_call({true, Acc}, Name, UniqueId, Amount, State) when Amount > 0 ->
- StoredUniqueId = Acc#acct.uniqueid,
- if UniqueId =:= StoredUniqueId ->
- NewAmount = Acc#acct.balance + Amount,
- NewAcct = Acc#acct{balance=NewAmount},
- NewAcctList = orddict:update(Name, fun(_Old) -> NewAcct end,
- State#state.accounts),
- {reply, {ok, success}, State#state{accounts = NewAcctList}};
- UniqueId =/= StoredUniqueId ->
- {reply, {error, uniqueid_mismatch}, State}
- end;
- check_result_for_deposit_call({true, _Acc}, _Name, _UniqueId, Amount, State) when Amount =< 0 ->
- {reply, {error, invalid_deposit_amount}, State};
- check_result_for_deposit_call(_, _Name, _UniqueId, _Amount, State) ->
- {reply, {error, nosuchuser}, State}.
- check_result_for_withdraw_call({true, Acc}, Name, UniqueId, Amount, State) when Amount > 0 ->
- StoredUniqueId = Acc#acct.uniqueid,
- if UniqueId =:= StoredUniqueId ->
- NewAmount = Acc#acct.balance - Amount,
- if NewAmount >= 0 ->
- NewAcct = Acc#acct{balance=NewAmount},
- NewAcctList = orddict:update(Name, fun(_Old) -> NewAcct end,
- State#state.accounts),
- {reply, {ok, success}, State#state{accounts = NewAcctList}};
- NewAmount < 0 ->
- {reply, {error, not_enough_funds}, State}
- end;
- UniqueId =/= StoredUniqueId ->
- {reply, {error, uniqueid_mismatch}, State}
- end;
- check_result_for_withdraw_call({true, _Acc}, _Name, _UniqueId, Amount, State) when Amount =< 0 ->
- {reply, {error, invalid_withdraw_amount}, State};
- check_result_for_withdraw_call(_, _Name, _UniqueId, _Amount, State) ->
- {reply, {error, nosuchuser}, State}.
- check_result_for_check_balance_call({true, Acc}, UniqueId, State) ->
- StoredUniqueId = Acc#acct.uniqueid,
- if UniqueId =:= StoredUniqueId ->
- Balance = Acc#acct.balance,
- {reply, {ok, Balance}, State};
- UniqueId =/= StoredUniqueId ->
- {reply, {error, uniqueid_mismatch}, State}
- end;
- check_result_for_check_balance_call(_, _UniqueId, State) ->
- {reply, {error, nosuchuser}, State}.
- generate_unique_key() ->
- binary:bin_to_list(base64:encode(crypto:strong_rand_bytes(16))).
- %%% EOF.
Add Comment
Please, Sign In to add comment