diff --git a/apps/limiter/src/lim_accounting.erl b/apps/limiter/src/lim_accounting.erl deleted file mode 100644 index 397d7ee..0000000 --- a/apps/limiter/src/lim_accounting.erl +++ /dev/null @@ -1,177 +0,0 @@ --module(lim_accounting). - --include_lib("damsel/include/dmsl_accounter_thrift.hrl"). --include_lib("damsel/include/dmsl_base_thrift.hrl"). - --export([plan/3]). --export([hold/3]). --export([commit/3]). --export([rollback/3]). --export([get_plan/2]). --export([get_balance/2]). --export([create_account/2]). - --export([noncurrency/0]). - --type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). --type amount() :: dmsl_domain_thrift:'Amount'(). --type plan_id() :: dmsl_accounter_thrift:'PlanID'(). --type batch_id() :: dmsl_accounter_thrift:'BatchID'(). --type posting() :: dmsl_accounter_thrift:'Posting'(). --type batch() :: {batch_id(), [posting()]}. --type account_id() :: dmsl_accounter_thrift:'AccountID'(). --type lim_context() :: lim_context:t(). - --type balance() :: #{ - account_id := account_id(), - own_amount := amount(), - min_available_amount := amount(), - max_available_amount := amount(), - currency := currency() -}. - --type invalid_request_error() :: {invalid_request, list(binary())}. - --export_type([account_id/0]). --export_type([amount/0]). --export_type([balance/0]). --export_type([plan_id/0]). --export_type([batch/0]). --export_type([posting/0]). --export_type([batch_id/0]). --export_type([invalid_request_error/0]). - --define(NONCURRENCY, <<>>). - --spec plan(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. -plan(_PlanID, [], _LimitContext) -> - error(badarg); -plan(_PlanID, Batches, _LimitContext) when not is_list(Batches) -> - error(badarg); -plan(PlanID, Batches, LimitContext) -> - lists:foldl( - fun(Batch, _) -> hold(PlanID, Batch, LimitContext) end, - undefined, - Batches - ). - --spec hold(plan_id(), batch(), lim_context()) -> ok | {error, invalid_request_error()}. -hold(PlanID, Batch, LimitContext) -> - do('Hold', construct_plan_change(wrap_plan_id(PlanID), Batch), LimitContext). - --spec commit(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. -commit(PlanID, Batches, LimitContext) -> - do('CommitPlan', construct_plan(wrap_plan_id(PlanID), Batches), LimitContext). - --spec rollback(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. -rollback(PlanID, Batches, LimitContext) -> - do('RollbackPlan', construct_plan(wrap_plan_id(PlanID), Batches), LimitContext). - --spec get_plan(plan_id(), lim_context()) -> {ok, [batch()]} | {error, notfound}. -get_plan(PlanID, LimitContext) -> - case call_accounter('GetPlan', {wrap_plan_id(PlanID)}, LimitContext) of - {ok, #accounter_PostingPlan{batch_list = BatchList}} -> - {ok, decode_batch_list(BatchList)}; - {exception, #accounter_PlanNotFound{}} -> - {error, notfound} - end. - --spec get_balance(account_id(), lim_context()) -> {ok, balance()} | {error, notfound}. -get_balance(AccountID, LimitContext) -> - case call_accounter('GetAccountByID', {AccountID}, LimitContext) of - {ok, Result} -> - {ok, construct_balance(AccountID, Result)}; - {exception, #accounter_AccountNotFound{}} -> - {error, notfound} - end. - -do(Op, Plan, LimitContext) -> - case call_accounter(Op, {Plan}, LimitContext) of - {ok, _Clock} -> - ok; - {exception, Exception} -> - {error, {invalid_request, convert_exception(Exception)}} - end. - -construct_plan_change(PlanID, {BatchID, Postings}) -> - #accounter_PostingPlanChange{ - id = PlanID, - batch = #accounter_PostingBatch{ - id = BatchID, - postings = Postings - } - }. - -construct_plan(PlanID, Batches) -> - #accounter_PostingPlan{ - id = PlanID, - batch_list = [ - #accounter_PostingBatch{ - id = BatchID, - postings = Postings - } - || {BatchID, Postings} <- Batches - ] - }. - -decode_batch_list(BatchList) -> - [{BatchID, Postings} || #accounter_PostingBatch{id = BatchID, postings = Postings} <- BatchList]. - -construct_balance( - AccountID, - #accounter_Account{ - own_amount = OwnAmount, - min_available_amount = MinAvailableAmount, - max_available_amount = MaxAvailableAmount, - currency_sym_code = Currency - } -) -> - #{ - account_id => AccountID, - own_amount => OwnAmount, - min_available_amount => MinAvailableAmount, - max_available_amount => MaxAvailableAmount, - currency => Currency - }. - --spec noncurrency() -> currency(). -noncurrency() -> - ?NONCURRENCY. - --spec create_account(currency(), lim_context()) -> {ok, account_id()}. -create_account(CurrencyCode, LimitContext) -> - create_account(CurrencyCode, undefined, LimitContext). - -create_account(CurrencyCode, Description, LimitContext) -> - call_accounter( - 'CreateAccount', - {construct_prototype(CurrencyCode, Description)}, - LimitContext - ). - -construct_prototype(CurrencyCode, Description) -> - #accounter_AccountPrototype{ - currency_sym_code = CurrencyCode, - description = Description - }. - -%% - -wrap_plan_id(PlanID) -> - %% Accounter requires max 64 byte plan id - case byte_size(PlanID) < 64 of - true -> - %% For backward compatibility - PlanID; - false -> - base64:encode(crypto:hash(sha384, PlanID)) - end. - -call_accounter(Function, Args, LimitContext) -> - WoodyContext = lim_context:woody_context(LimitContext), - lim_client_woody:call(accounter, Function, Args, WoodyContext). - -convert_exception(#base_InvalidRequest{errors = Errors}) -> - Errors; -convert_exception(#accounter_InvalidPostingParams{wrong_postings = Errors}) -> - maps:fold(fun(_, Error, Acc) -> [Error | Acc] end, [], Errors). diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 64c1344..926889d 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -60,12 +60,14 @@ scope => limit_scope(), description => description(), op_behaviour => op_behaviour(), - currency_conversion => currency_conversion() + currency_conversion => currency_conversion(), + finalization_behaviour => finalization_behaviour() }. -type op_behaviour() :: #{operation_type() := addition | subtraction}. -type operation_type() :: invoice_payment_refund. -type currency_conversion() :: boolean(). +-type finalization_behaviour() :: normal | {invertable, session_presence}. -type lim_id() :: limproto_limiter_thrift:'LimitID'(). -type lim_version() :: dmsl_domain_thrift:'DataRevision'(). @@ -146,7 +148,8 @@ currency_conversion(_) -> {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), get_limit_error()}}. get_values(LimitChanges, LimitContext) -> do(fun() -> - Changes = unwrap(collect_changes(hold, LimitChanges, LimitContext)), + GroupedChanges = unwrap(collect_grouped_changes(hold, LimitChanges, LimitContext)), + Changes = lists:flatten(maps:values(GroupedChanges)), Names = lists:map(fun lim_liminator:get_name/1, Changes), unwrap(lim_liminator:get_values(Names, LimitContext)) end). @@ -155,49 +158,106 @@ get_values(LimitChanges, LimitContext) -> {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), get_limit_error()}}. get_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:get(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(hold, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + OperationIDForGroup = operation_id_for_group(OperationID, Group), + unwrap(OperationID, lim_liminator:get(OperationIDForGroup, Changes, LimitContext)) + end, + lists:flatten(maps:values(maps:map(F, GroupedChanges))) end). -spec hold_batch(operation_id(), lim_changes(), lim_context()) -> - {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), hold_error()}}. + {ok, [lim_liminator:limit_response()]} + | {error, config_error() | {processor(), hold_error()} | {operation_id(), lim_liminator:invalid_request_error()}}. hold_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:hold(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(hold, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + OperationIDForGroup = operation_id_for_group(OperationID, Group), + unwrap(OperationID, lim_liminator:hold(OperationIDForGroup, Changes, LimitContext)) + end, + lists:flatten(maps:values(maps:map(F, GroupedChanges))) end). -spec commit_batch(operation_id(), lim_changes(), lim_context()) -> - ok | {error, config_error() | {processor(), commit_error()}}. + ok + | {error, config_error() | {processor(), commit_error()} | {operation_id(), lim_liminator:invalid_request_error()}}. commit_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:commit(OperationID, unwrap(collect_changes(commit, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(commit, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + unwrap( + OperationID, + finalize( + OperationID, Group, Changes, LimitContext, fun lim_liminator:commit/3, fun lim_liminator:rollback/3 + ) + ) + end, + _ = maps:map(F, GroupedChanges), + ok end). -spec rollback_batch(operation_id(), lim_changes(), lim_context()) -> - ok | {error, config_error() | {processor(), rollback_error()}}. + ok + | {error, + config_error() | {processor(), rollback_error()} | {operation_id(), lim_liminator:invalid_request_error()}}. rollback_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:rollback(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(hold, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + unwrap( + OperationID, + finalize( + OperationID, Group, Changes, LimitContext, fun lim_liminator:rollback/3, fun lim_liminator:commit/3 + ) + ) + end, + _ = maps:map(F, GroupedChanges), + ok end). -collect_changes(_Stage, [], _LimitContext) -> - {ok, []}; -collect_changes(Stage, [LimitChange = #limiter_LimitChange{id = ID, version = Version} | Other], LimitContext) -> +finalize(OperationID, Group, Changes, LimitContext, NormalFun, InvertedFun) -> + OperationIDForGroup = operation_id_for_group(OperationID, Group), + case resolve_group_finalization_behaviour(Group, LimitContext) of + normal -> NormalFun(OperationIDForGroup, Changes, LimitContext); + inverted -> InvertedFun(OperationIDForGroup, Changes, LimitContext) + end. + +resolve_group_finalization_behaviour({_, normal}, _) -> + normal; +resolve_group_finalization_behaviour({ContextType, {invertable, session_presence}}, LimitContext) -> + case lim_context:get_value(ContextType, session, LimitContext) of + {ok, undefined} -> + normal; + {ok, _Some} -> + inverted; + {error, {unsupported, _}} -> + %% If context doesn't support session value then we treat it as + %% normal finalization. + normal; + {error, notfound} -> + normal + end. + +operation_id_for_group(OperationID, {_, normal}) -> + OperationID; +operation_id_for_group(OperationID, {_, {invertable, session_presence}}) -> + <>. + +collect_grouped_changes(Stage, LimitChanges, LimitContext) -> + collect_grouped_changes(Stage, LimitChanges, LimitContext, #{}). + +collect_grouped_changes(_, [], _, Acc) -> + {ok, Acc}; +collect_grouped_changes(Stage, [LimitChange | Other], LimitContext, Acc0) -> do(fun() -> + #limiter_LimitChange{id = ID, version = Version} = LimitChange, {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), Change = unwrap(Handler, Handler:make_change(Stage, LimitChange, Config, LimitContext)), - [Change | unwrap(collect_changes(Stage, Other, LimitContext))] + #{context_type := ContextType, finalization_behaviour := FinalizationBehaviour} = Config, + Group = {ContextType, FinalizationBehaviour}, + Acc1 = maps:update_with(Group, fun(Changes) -> [Change | Changes] end, [Change], Acc0), + unwrap(collect_grouped_changes(Stage, Other, LimitContext, Acc1)) end). get_handler(ID, Version, LimitContext) -> @@ -503,7 +563,8 @@ unmarshal_limit_config(#domain_LimitConfigObject{ scopes = Scopes, description = Description, op_behaviour = OpBehaviour, - currency_conversion = CurrencyConversion + currency_conversion = CurrencyConversion, + finalization_behaviour = FinalizationBehaviour } }) -> genlib_map:compact(#{ @@ -517,9 +578,17 @@ unmarshal_limit_config(#domain_LimitConfigObject{ scope => maybe_apply(Scopes, fun unmarshal_scope/1), description => Description, op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), - currency_conversion => CurrencyConversion =/= undefined + currency_conversion => CurrencyConversion =/= undefined, + finalization_behaviour => unmarshal_finalization_behaviour(FinalizationBehaviour) }). +unmarshal_finalization_behaviour(undefined) -> + normal; +unmarshal_finalization_behaviour({normal, #limiter_config_Normal{}}) -> + normal; +unmarshal_finalization_behaviour({invertable, {session_presence, #limiter_config_Inversed{}}}) -> + {invertable, session_presence}. + unmarshal_time_range_type({calendar, CalendarType}) -> {calendar, unmarshal_calendar_time_range_type(CalendarType)}; unmarshal_time_range_type({interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}) -> @@ -619,7 +688,8 @@ unmarshal_config_object_test() -> type => {turnover, number}, scope => ordsets:from_list([party, shop, {destination_field, [<<"path">>, <<"to">>, <<"field">>]}]), description => <<"description">>, - currency_conversion => true + currency_conversion => true, + finalization_behaviour => {invertable, session_presence} }, Object = #domain_LimitConfigObject{ ref = #domain_LimitConfigRef{id = <<"id">>}, @@ -639,7 +709,8 @@ unmarshal_config_object_test() -> }} ]), description = <<"description">>, - currency_conversion = #limiter_config_CurrencyConversion{} + currency_conversion = #limiter_config_CurrencyConversion{}, + finalization_behaviour = {invertable, {session_presence, #limiter_config_Inversed{}}} } }, ?assertEqual(Config, unmarshal_limit_config(Object)). diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index fa9c355..9a05695 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -82,6 +82,8 @@ get_value(provider_id, Operation, Context) -> get_provider_id(Operation, Context); get_value(terminal_id, Operation, Context) -> get_terminal_id(Operation, Context); +get_value(session, Operation, Context) -> + get_session(Operation, Context); get_value(payer_contact_email, Operation, Context) -> get_payer_contact_email(Operation, Context); get_value(sender, Operation, Context) -> @@ -129,6 +131,12 @@ get_value(ValueName, _Operation, _Context) -> } }). +-define(INVOICE_PAYMENT_SESSION(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + session = V + } +}). + get_owner_id(?INVOICE(Invoice)) -> {ok, Invoice#domain_Invoice.party_ref#domain_PartyConfigRef.id}; get_owner_id(_) -> @@ -209,6 +217,11 @@ get_terminal_id(Operation, ?INVOICE_PAYMENT_ROUTE(Route)) when get_terminal_id(_, _CtxInvoice) -> {error, notfound}. +get_session(invoice_payment, ?INVOICE_PAYMENT_SESSION(Session)) -> + {ok, Session}; +get_session(_, _CtxInvoice) -> + {error, notfound}. + get_payer_contact_email(Operation, ?INVOICE_PAYMENT(Payment)) when Operation == invoice_payment; Operation == invoice_payment_adjustment; diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index fecd526..dc2ebf7 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -25,19 +25,16 @@ -type hold_error() :: lim_rates:conversion_error() - | lim_accounting:invalid_request_error() | lim_turnover_metric:invalid_operation_currency_error() | lim_context:operation_context_not_supported_error() | lim_context:unsupported_error({payment_tool, atom()}). -type commit_error() :: {forbidden_operation_amount, forbidden_operation_amount_error()} - | lim_rates:conversion_error() - | lim_accounting:invalid_request_error(). + | lim_rates:conversion_error(). -type rollback_error() :: - lim_rates:conversion_error() - | lim_accounting:invalid_request_error(). + lim_rates:conversion_error(). -export_type([make_change_error/0]). -export_type([get_limit_error/0]). diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 9d5381d..2d8166b 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -55,6 +55,9 @@ {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}} ). +-define(finalization_behaviour_normal, {normal, #limiter_config_Normal{}}). +-define(finalization_behaviour_invertable_by_session, {invertable, {session_presence, #limiter_config_Inversed{}}}). + -define(time_range_day(), {calendar, {day, #limiter_config_TimeRangeTypeCalendarDay{}}} ). @@ -183,7 +186,11 @@ ?payproc_ctx_payment(?string, ?string, Cost, CaptureCost) ). --define(payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_LimitContext{ +-define(payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost), + ?payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost, undefined) +). + +-define(payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost, Session), #limiter_LimitContext{ payment_processing = #context_payproc_Context{ op = ?op_payment, invoice = #context_payproc_Invoice{ @@ -191,7 +198,8 @@ payment = #context_payproc_InvoicePayment{ payment = ?invoice_payment(Cost, CaptureCost), route = ?route() - } + }, + session = Session } } }). @@ -228,6 +236,8 @@ } }). +-define(payproc_ctx_session, #context_payproc_InvoicePaymentSession{}). + %% Wthdproc -define(auth_data(Sender, Receiver), diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 5562f19..241e9f4 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -76,6 +76,11 @@ -export([batch_commit_negative_less_ok/1]). -export([batch_commit_negative_more_ok/1]). +-export([batch_with_invertable_rollback_ok/1]). +-export([batch_with_invertable_rollback_with_session_ok/1]). +-export([batch_with_invertable_commit_ok/1]). +-export([batch_with_invertable_commit_with_session_ok/1]). + -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -87,7 +92,8 @@ all() -> {group, default}, {group, withdrawals}, {group, cashless}, - {group, idempotency} + {group, idempotency}, + {group, finalization_behaviour} ]. -spec groups() -> [{atom(), list(), [test_case_name()]}]. @@ -163,6 +169,12 @@ groups() -> full_commit_processes_idempotently, partial_commit_processes_idempotently, rollback_processes_idempotently + ]}, + {finalization_behaviour, [], [ + batch_with_invertable_rollback_ok, + batch_with_invertable_rollback_with_session_ok, + batch_with_invertable_commit_ok, + batch_with_invertable_commit_with_session_ok ]} ]. @@ -777,34 +789,10 @@ commit_with_destination_field_scope_ok(C) -> construct_request(C) -> ID = ?config(id, C), - {ID0, Version0} = configure_limit( - ?time_range_month(), - ?scopes([?scope_provider(), ?scope_payment_tool()]), - ?turnover_metric_amount(<<"RUB">>), - undefined, - genlib:format("~s/~B", [ID, 0]), - C - ), - {ID1, Version1} = configure_limit( - ?time_range_month(), - ?scopes([?scope_provider(), ?scope_payment_tool()]), - ?turnover_metric_amount(<<"RUB">>), - undefined, - genlib:format("~s/~B", [ID, 1]), - C - ), - {ID2, Version2} = configure_limit( - ?time_range_month(), - ?scopes([?scope_provider(), ?scope_payment_tool()]), - ?turnover_metric_amount(<<"RUB">>), - undefined, - genlib:format("~s/~B", [ID, 2]), - C - ), ?LIMIT_REQUEST(ID, [ - ?LIMIT_CHANGE(ID0, Version0), - ?LIMIT_CHANGE(ID1, Version1), - ?LIMIT_CHANGE(ID2, Version2) + construct_for_limit_change(ID, 0, ?turnover_metric_amount(<<"RUB">>), undefined, C), + construct_for_limit_change(ID, 1, ?turnover_metric_amount(<<"RUB">>), undefined, C), + construct_for_limit_change(ID, 2, ?turnover_metric_amount(<<"RUB">>), undefined, C) ]). -spec batch_hold_ok(config()) -> _. @@ -985,8 +973,85 @@ batch_commit_negative_more_ok(C) -> Request, Context, ?config(client, C) ). +%% Finalization behaviour group + +-spec batch_with_invertable_rollback_ok(config()) -> _. +batch_with_invertable_rollback_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ok = lim_client:rollback_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({0, 0, 0}, Request0, Context1, C). + +-spec batch_with_invertable_rollback_with_session_ok(config()) -> _. +batch_with_invertable_rollback_with_session_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), ?payproc_ctx_session), + ok = lim_client:rollback_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({1, 0, 0}, Request0, Context1, C). + +-spec batch_with_invertable_commit_ok(config()) -> _. +batch_with_invertable_commit_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ok = lim_client:commit_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({1, 1, 10}, Request0, Context1, C). + +-spec batch_with_invertable_commit_with_session_ok(config()) -> _. +batch_with_invertable_commit_with_session_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), ?payproc_ctx_session), + ok = lim_client:commit_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({0, 1, 10}, Request0, Context1, C). + +construct_request_with_invertable(C) -> + ID = ?config(id, C), + ?LIMIT_REQUEST(ID, [ + construct_for_limit_change(ID, 0, ?turnover_metric_number(), ?finalization_behaviour_invertable_by_session, C), + construct_for_limit_change(ID, 1, ?turnover_metric_number(), ?finalization_behaviour_normal, C), + construct_for_limit_change(ID, 2, ?turnover_metric_amount(<<"RUB">>), undefined, C) + ]). + +hold_and_assert_batch_with_invertable({Value0, Value1, Value2}, Request0, Context, C) -> + {ok, LimitStats} = lim_client:hold_batch(Request0, Context, ?config(client, C)), + %% NOTE Split operations for invertablity can break order of items in + %% response and mismatch it for limit changes provided in request. + [LimitState0, LimitState1, LimitState2] = lists:sort(LimitStats), + ?assertEqual(Value0, LimitState0#limiter_Limit.amount), + ?assertEqual(Value1, LimitState1#limiter_Limit.amount), + ?assertEqual(Value2, LimitState2#limiter_Limit.amount), + {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:get_values(Request0, Context, ?config(client, C)), + ok. + +assert_values_with_invertable({Value0, Value1, Value2}, Request0, Context, C) -> + {ok, LimitStats} = lim_client:get_values(Request0, Context, ?config(client, C)), + [LimitState0, LimitState1, LimitState2] = lists:sort(LimitStats), + ?assertEqual(Value0, LimitState0#limiter_Limit.amount), + ?assertEqual(Value1, LimitState1#limiter_Limit.amount), + ?assertEqual(Value2, LimitState2#limiter_Limit.amount), + ok. + %% +construct_for_limit_change(BaseID, Num, Metric, FinalizationBehaviour, C) -> + {ID, Version} = configure_limit( + ?time_range_month(), + ?scopes([?scope_provider(), ?scope_payment_tool()]), + Metric, + undefined, + genlib:format("~s/~B", [BaseID, Num]), + FinalizationBehaviour, + C + ), + ?LIMIT_CHANGE(ID, Version). + hold_and_assert_batch(Value, Request0, Context, C) -> {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:hold_batch(Request0, Context, ?config(client, C)), ?assertEqual(Value, LimitState0#limiter_Limit.amount), @@ -1029,7 +1094,10 @@ configure_limit(TimeRange, Scopes, Metric, C) -> configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, C) -> configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ?config(id, C), C). -configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, C) when is_list(Scopes) -> +configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, C) -> + configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, {normal, #limiter_config_Normal{}}, C). + +configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, FinalizationBehaviour, C) when is_list(Scopes) -> ContextType = case get_group_name(C) of withdrawals -> ?ctx_type_wthdproc(); @@ -1045,7 +1113,8 @@ configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, C) when is_li scopes = Scopes, description = <<"Description">>, op_behaviour = ?op_behaviour(?op_subtraction()), - currency_conversion = CurrencyConversion + currency_conversion = CurrencyConversion, + finalization_behaviour = FinalizationBehaviour }). create_limit_config(ID, #limiter_config_LimitConfig{} = LimitConfig) -> diff --git a/compose.yaml b/compose.yaml index ab3863b..6f3abb6 100644 --- a/compose.yaml +++ b/compose.yaml @@ -20,7 +20,7 @@ services: condition: service_healthy dmt: - image: ghcr.io/valitydev/dominant-v2:sha-90f5fa2 + image: ghcr.io/valitydev/dominant-v2:sha-815385c command: /opt/dmt/bin/dmt foreground healthcheck: test: "/opt/dmt/bin/dmt ping" diff --git a/rebar.config b/rebar.config index 9e0954a..7aeabe0 100644 --- a/rebar.config +++ b/rebar.config @@ -28,8 +28,8 @@ {deps, [ {prometheus, "4.11.0"}, {prometheus_cowboy, "0.1.9"}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.17"}}}, - {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {tag, "v2.1.0"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.27"}}}, + {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {tag, "v2.1.1"}}}, {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index 3f8b6b4..207a40a 100644 --- a/rebar.lock +++ b/rebar.lock @@ -13,7 +13,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"f831d3aa5fdfd0338b41af44d1eeffe810ca9708"}}, + {ref,"074ba8c024af7902ead5f24dc3d288c1922651c1"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -39,7 +39,7 @@ 0}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"1af3724af24dd8b5ad9ce2ae80cd42318b471397"}}, + {ref,"ced168a6ea11f80d62485b793b3605de68223e38"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.4.0">>},2},