Skip to content

Commit

Permalink
TD-821: Add support for deposit negative amount (#78)
Browse files Browse the repository at this point in the history
* added support for deposit negative amount

* fixed

* added adj tests
  • Loading branch information
WWWcool authored Nov 30, 2023
1 parent fe96a21 commit 26f859d
Show file tree
Hide file tree
Showing 10 changed files with 341 additions and 40 deletions.
2 changes: 1 addition & 1 deletion apps/ff_server/src/ff_deposit_codec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ marshal_deposit_state(DepositState, Context) ->
Adjustments = ff_deposit:adjustments(DepositState),
#deposit_DepositState{
id = marshal(id, ff_deposit:id(DepositState)),
body = marshal(cash, ff_deposit:body(DepositState)),
body = marshal(cash, ff_deposit:negative_body(DepositState)),
status = maybe_marshal(status, ff_deposit:status(DepositState)),
wallet_id = marshal(id, ff_deposit:wallet_id(DepositState)),
source_id = marshal(id, ff_deposit:source_id(DepositState)),
Expand Down
2 changes: 1 addition & 1 deletion apps/ff_server/src/ff_deposit_revert_codec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ marshal(revert_state, Revert) ->
wallet_id = marshal(id, ff_deposit_revert:wallet_id(Revert)),
source_id = marshal(id, ff_deposit_revert:source_id(Revert)),
status = marshal(status, ff_deposit_revert:status(Revert)),
body = marshal(cash, ff_deposit_revert:body(Revert)),
body = marshal(cash, ff_deposit_revert:negative_body(Revert)),
created_at = marshal(timestamp_ms, ff_deposit_revert:created_at(Revert)),
domain_revision = marshal(domain_revision, ff_deposit_revert:domain_revision(Revert)),
party_revision = marshal(party_revision, ff_deposit_revert:party_revision(Revert)),
Expand Down
142 changes: 129 additions & 13 deletions apps/ff_server/test/ff_deposit_handler_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
-export([create_source_notfound_test/1]).
-export([create_wallet_notfound_test/1]).
-export([create_ok_test/1]).
-export([create_negative_ok_test/1]).
-export([unknown_test/1]).
-export([get_context_test/1]).
-export([get_events_test/1]).
-export([create_adjustment_ok_test/1]).
-export([create_negative_adjustment_ok_test/1]).
-export([create_adjustment_unavailable_status_error_test/1]).
-export([create_adjustment_already_has_status_error_test/1]).
-export([create_revert_ok_test/1]).
-export([create_negative_revert_ok_test/1]).
-export([create_revert_inconsistent_revert_currency_error_test/1]).
-export([create_revert_insufficient_deposit_amount_error_test/1]).
-export([create_revert_invalid_revert_amount_error_test/1]).
Expand Down Expand Up @@ -68,13 +71,16 @@ groups() ->
create_source_notfound_test,
create_wallet_notfound_test,
create_ok_test,
create_negative_ok_test,
unknown_test,
get_context_test,
get_events_test,
create_adjustment_ok_test,
create_negative_adjustment_ok_test,
create_adjustment_unavailable_status_error_test,
create_adjustment_already_has_status_error_test,
create_revert_ok_test,
create_negative_revert_ok_test,
create_revert_inconsistent_revert_currency_error_test,
create_revert_insufficient_deposit_amount_error_test,
create_revert_invalid_revert_amount_error_test,
Expand Down Expand Up @@ -238,6 +244,35 @@ create_ok_test(C) ->
ff_codec:unmarshal(timestamp_ms, DepositState#deposit_DepositState.created_at)
).

-spec create_negative_ok_test(config()) -> test_return().
create_negative_ok_test(C) ->
EnvBody = make_cash({100, <<"RUB">>}),
#{
wallet_id := WalletID,
source_id := SourceID
} = prepare_standard_environment(EnvBody, C),
_ = process_deposit(WalletID, SourceID, EnvBody),
Body = make_cash({-100, <<"RUB">>}),
{DepositState, DepositID, ExternalID, _} = process_deposit(WalletID, SourceID, Body),
Expected = get_deposit(DepositID),
?assertEqual(DepositID, DepositState#deposit_DepositState.id),
?assertEqual(WalletID, DepositState#deposit_DepositState.wallet_id),
?assertEqual(SourceID, DepositState#deposit_DepositState.source_id),
?assertEqual(ExternalID, DepositState#deposit_DepositState.external_id),
?assertEqual(Body, DepositState#deposit_DepositState.body),
?assertEqual(
ff_deposit:domain_revision(Expected),
DepositState#deposit_DepositState.domain_revision
),
?assertEqual(
ff_deposit:party_revision(Expected),
DepositState#deposit_DepositState.party_revision
),
?assertEqual(
ff_deposit:created_at(Expected),
ff_codec:unmarshal(timestamp_ms, DepositState#deposit_DepositState.created_at)
).

-spec unknown_test(config()) -> test_return().
unknown_test(_C) ->
DepositID = <<"unknown_deposit">>,
Expand Down Expand Up @@ -303,6 +338,45 @@ create_adjustment_ok_test(C) ->
AdjustmentState#deposit_adj_AdjustmentState.changes_plan
).

-spec create_negative_adjustment_ok_test(config()) -> test_return().
create_negative_adjustment_ok_test(C) ->
#{
wallet_id := WalletID,
source_id := SourceID
} = prepare_standard_environment_with_deposit(C),
{_, DepositID, _, _} = process_deposit(WalletID, SourceID, make_cash({-50, <<"RUB">>})),
AdjustmentID = generate_id(),
ExternalID = generate_id(),
Params = #deposit_adj_AdjustmentParams{
id = AdjustmentID,
change =
{change_status, #deposit_adj_ChangeStatusRequest{
new_status = {failed, #deposit_status_Failed{failure = #'fistful_base_Failure'{code = <<"Ooops">>}}}
}},
external_id = ExternalID
},
{ok, AdjustmentState} = call_deposit('CreateAdjustment', {DepositID, Params}),
ExpectedAdjustment = get_adjustment(DepositID, AdjustmentID),

?assertEqual(AdjustmentID, AdjustmentState#deposit_adj_AdjustmentState.id),
?assertEqual(ExternalID, AdjustmentState#deposit_adj_AdjustmentState.external_id),
?assertEqual(
ff_adjustment:created_at(ExpectedAdjustment),
ff_codec:unmarshal(timestamp_ms, AdjustmentState#deposit_adj_AdjustmentState.created_at)
),
?assertEqual(
ff_adjustment:domain_revision(ExpectedAdjustment),
AdjustmentState#deposit_adj_AdjustmentState.domain_revision
),
?assertEqual(
ff_adjustment:party_revision(ExpectedAdjustment),
AdjustmentState#deposit_adj_AdjustmentState.party_revision
),
?assertEqual(
ff_deposit_adjustment_codec:marshal(changes_plan, ff_adjustment:changes_plan(ExpectedAdjustment)),
AdjustmentState#deposit_adj_AdjustmentState.changes_plan
).

-spec create_adjustment_unavailable_status_error_test(config()) -> test_return().
create_adjustment_unavailable_status_error_test(C) ->
#{
Expand Down Expand Up @@ -374,6 +448,44 @@ create_revert_ok_test(C) ->
RevertState#deposit_revert_RevertState.party_revision
).

-spec create_negative_revert_ok_test(config()) -> test_return().
create_negative_revert_ok_test(C) ->
#{
wallet_id := WalletID,
source_id := SourceID
} = prepare_standard_environment_with_deposit(make_cash({10000, <<"RUB">>}), C),
Body = make_cash({-5000, <<"RUB">>}),
{_, DepositID, _, _} = process_deposit(WalletID, SourceID, Body),
RevertID = generate_id(),
ExternalID1 = generate_id(),
Reason = generate_id(),
RevertParams = #deposit_revert_RevertParams{
id = RevertID,
body = Body,
external_id = ExternalID1,
reason = Reason
},
{ok, RevertState} = call_deposit('CreateRevert', {DepositID, RevertParams}),
succeeded = await_final_revert_status(DepositID, RevertID),
Expected = get_revert(DepositID, RevertID),

?assertEqual(RevertID, RevertState#deposit_revert_RevertState.id),
?assertEqual(ExternalID1, RevertState#deposit_revert_RevertState.external_id),
?assertEqual(Body, RevertState#deposit_revert_RevertState.body),
?assertEqual(Reason, RevertState#deposit_revert_RevertState.reason),
?assertEqual(
ff_deposit_revert:created_at(Expected),
ff_codec:unmarshal(timestamp_ms, RevertState#deposit_revert_RevertState.created_at)
),
?assertEqual(
ff_deposit_revert:domain_revision(Expected),
RevertState#deposit_revert_RevertState.domain_revision
),
?assertEqual(
ff_deposit_revert:party_revision(Expected),
RevertState#deposit_revert_RevertState.party_revision
).

-spec create_revert_inconsistent_revert_currency_error_test(config()) -> test_return().
create_revert_inconsistent_revert_currency_error_test(C) ->
#{
Expand Down Expand Up @@ -586,19 +698,7 @@ prepare_standard_environment_with_deposit(Body, C) ->
wallet_id := WalletID,
source_id := SourceID
} = Env = prepare_standard_environment(Body, C),
DepositID = generate_id(),
ExternalID = generate_id(),
Context = #{<<"NS">> => #{generate_id() => generate_id()}},
EncodedContext = ff_entity_context_codec:marshal(Context),
Params = #deposit_DepositParams{
id = DepositID,
wallet_id = WalletID,
source_id = SourceID,
body = Body,
external_id = ExternalID
},
{ok, _DepositState} = call_deposit('Create', {Params, EncodedContext}),
succeeded = await_final_deposit_status(DepositID),
{_, DepositID, ExternalID, Context} = process_deposit(WalletID, SourceID, Body),
Env#{
deposit_id => DepositID,
external_id => ExternalID,
Expand Down Expand Up @@ -631,6 +731,22 @@ prepare_standard_environment_with_revert(Body, C) ->
reason => Reason
}.

process_deposit(WalletID, SourceID, Body) ->
DepositID = generate_id(),
ExternalID = generate_id(),
Context = #{<<"NS">> => #{generate_id() => generate_id()}},
EncodedContext = ff_entity_context_codec:marshal(Context),
Params = #deposit_DepositParams{
id = DepositID,
wallet_id = WalletID,
source_id = SourceID,
body = Body,
external_id = ExternalID
},
{ok, DepositState} = call_deposit('Create', {Params, EncodedContext}),
succeeded = await_final_deposit_status(DepositID),
{DepositState, DepositID, ExternalID, Context}.

get_deposit(DepositID) ->
{ok, Machine} = ff_deposit_machine:get(DepositID),
ff_deposit_machine:deposit(Machine).
Expand Down
63 changes: 49 additions & 14 deletions apps/ff_transfer/src/ff_deposit.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
id := id(),
transfer_type := deposit,
body := body(),
is_negative := is_negative(),
params := transfer_params(),
party_revision => party_revision(),
domain_revision => domain_revision(),
Expand Down Expand Up @@ -148,6 +149,8 @@
-export([source_id/1]).
-export([id/1]).
-export([body/1]).
-export([negative_body/1]).
-export([is_negative/1]).
-export([status/1]).
-export([external_id/1]).
-export([party_revision/1]).
Expand Down Expand Up @@ -192,6 +195,7 @@
-type revert() :: ff_deposit_revert:revert().
-type revert_id() :: ff_deposit_revert:id().
-type body() :: ff_accounting:body().
-type is_negative() :: boolean().
-type cash() :: ff_cash:cash().
-type cash_range() :: ff_range:range(cash()).
-type action() :: machinery:action() | undefined.
Expand Down Expand Up @@ -250,6 +254,18 @@ source_id(T) ->
body(#{body := V}) ->
V.

-spec negative_body(deposit_state()) -> body().
negative_body(#{body := {Amount, Currency}, is_negative := true}) ->
{-1 * Amount, Currency};
negative_body(T) ->
body(T).

-spec is_negative(deposit_state()) -> is_negative().
is_negative(#{is_negative := V}) ->
V;
is_negative(_T) ->
false.

-spec status(deposit_state()) -> status() | undefined.
status(Deposit) ->
maps:get(status, Deposit, undefined).
Expand Down Expand Up @@ -424,7 +440,7 @@ apply_event(Ev, T0) ->

-spec apply_event_(event(), deposit_state() | undefined) -> deposit_state().
apply_event_({created, T}, undefined) ->
T;
apply_negative_body(T);
apply_event_({status_changed, S}, T) ->
maps:put(status, S, T);
apply_event_({limit_check, Details}, T) ->
Expand All @@ -436,6 +452,11 @@ apply_event_({revert, _Ev} = Event, T) ->
apply_event_({adjustment, _Ev} = Event, T) ->
apply_adjustment_event(Event, T).

apply_negative_body(T = #{body := {Amount, Currency}}) when Amount < 0 ->
T#{body => {-1 * Amount, Currency}, is_negative => true};
apply_negative_body(T) ->
T.

%% Internals

-spec do_start_revert(revert_params(), deposit_state()) ->
Expand Down Expand Up @@ -537,7 +558,7 @@ do_process_transfer(stop, _Deposit) ->

-spec create_p_transfer(deposit_state()) -> process_result().
create_p_transfer(Deposit) ->
FinalCashFlow = make_final_cash_flow(wallet_id(Deposit), source_id(Deposit), body(Deposit)),
FinalCashFlow = make_final_cash_flow(Deposit),
PTransferID = construct_p_transfer_id(id(Deposit)),
{ok, PostingsTransferEvents} = ff_postings_transfer:create(PTransferID, FinalCashFlow),
{continue, [{p_transfer, Ev} || Ev <- PostingsTransferEvents]}.
Expand Down Expand Up @@ -585,8 +606,11 @@ process_transfer_fail(limit_check, Deposit) ->
Failure = build_failure(limit_check, Deposit),
{undefined, [{status_changed, {failed, Failure}}]}.

-spec make_final_cash_flow(wallet_id(), source_id(), body()) -> final_cash_flow().
make_final_cash_flow(WalletID, SourceID, Body) ->
-spec make_final_cash_flow(deposit_state()) -> final_cash_flow().
make_final_cash_flow(Deposit) ->
WalletID = wallet_id(Deposit),
SourceID = source_id(Deposit),
Body = body(Deposit),
{ok, WalletMachine} = ff_wallet_machine:get(WalletID),
WalletAccount = ff_wallet:account(ff_wallet_machine:wallet(WalletMachine)),
{ok, SourceMachine} = ff_source_machine:get(SourceID),
Expand All @@ -595,10 +619,19 @@ make_final_cash_flow(WalletID, SourceID, Body) ->
Constants = #{
operation_amount => Body
},
Accounts = #{
{wallet, sender_source} => SourceAccount,
{wallet, receiver_settlement} => WalletAccount
},
Accounts =
case is_negative(Deposit) of
true ->
#{
{wallet, sender_source} => WalletAccount,
{wallet, receiver_settlement} => SourceAccount
};
false ->
#{
{wallet, sender_source} => SourceAccount,
{wallet, receiver_settlement} => WalletAccount
}
end,
CashFlowPlan = #{
postings => [
#{
Expand Down Expand Up @@ -777,7 +810,7 @@ validate_revert_start(Params, Deposit) ->
validate_revert_body(Params, Deposit) ->
do(fun() ->
valid = unwrap(validate_revert_currency(Params, Deposit)),
valid = unwrap(validate_revert_amount(Params)),
valid = unwrap(validate_revert_amount(Params, Deposit)),
valid = unwrap(validate_unreverted_amount(Params, Deposit))
end).

Expand Down Expand Up @@ -820,13 +853,15 @@ validate_unreverted_amount(Params, Deposit) ->
{error, {insufficient_deposit_amount, {RevertBody, Unreverted}}}
end.

-spec validate_revert_amount(revert_params()) ->
-spec validate_revert_amount(revert_params(), deposit_state()) ->
{ok, valid}
| {error, {invalid_revert_amount, Revert :: body()}}.
validate_revert_amount(Params) ->
validate_revert_amount(Params, Desposit) ->
#{body := {RevertAmount, _Currency} = RevertBody} = Params,
case RevertAmount of
Good when Good > 0 ->
case {RevertAmount, is_negative(Desposit)} of
{Good, false} when Good > 0 ->
{ok, valid};
{Good, true} when Good < 0 ->
{ok, valid};
_Other ->
{error, {invalid_revert_amount, RevertBody}}
Expand Down Expand Up @@ -976,7 +1011,7 @@ make_change_status_params(succeeded, {failed, _} = NewStatus, Deposit) ->
};
make_change_status_params({failed, _}, succeeded = NewStatus, Deposit) ->
CurrentCashFlow = effective_final_cash_flow(Deposit),
NewCashFlow = make_final_cash_flow(wallet_id(Deposit), source_id(Deposit), body(Deposit)),
NewCashFlow = make_final_cash_flow(Deposit),
#{
new_status => #{
new_status => NewStatus
Expand Down
Loading

0 comments on commit 26f859d

Please sign in to comment.