mirror of
https://github.com/valitydev/limiter.git
synced 2024-11-06 00:55:22 +00:00
TD-304: Handle amount
limit body type (#8)
* Bump to valitydev/limiter-proto@6723e86. * Introduce _noncurrency_ concept instead of default currency which is kinda misleading. * Rename `lim_p_transfer` → `lim_posting`. * (Minor API break) Respond w/ 0 balance on uninitialized limit ranges.
This commit is contained in:
parent
0c6b2afd6b
commit
d36b97f44a
@ -9,9 +9,10 @@
|
||||
-export([rollback/3]).
|
||||
-export([get_plan/2]).
|
||||
-export([get_balance/2]).
|
||||
-export([get_default_currency/0]).
|
||||
-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'().
|
||||
@ -40,7 +41,7 @@
|
||||
-export_type([batch_id/0]).
|
||||
-export_type([invalid_request_error/0]).
|
||||
|
||||
-define(DEFAULT_CURRENCY, <<"RUB">>).
|
||||
-define(NONCURRENCY, <<>>).
|
||||
|
||||
-spec plan(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}.
|
||||
plan(_PlanID, [], _LimitContext) ->
|
||||
@ -133,9 +134,9 @@ construct_balance(
|
||||
currency => Currency
|
||||
}.
|
||||
|
||||
-spec get_default_currency() -> currency().
|
||||
get_default_currency() ->
|
||||
?DEFAULT_CURRENCY.
|
||||
-spec noncurrency() -> currency().
|
||||
noncurrency() ->
|
||||
?NONCURRENCY.
|
||||
|
||||
-spec create_account(currency(), lim_context()) -> {ok, account_id()}.
|
||||
create_account(CurrencyCode, LimitContext) ->
|
||||
@ -157,7 +158,7 @@ construct_prototype(CurrencyCode, Description) ->
|
||||
%%
|
||||
|
||||
call_accounter(Function, Args, LimitContext) ->
|
||||
{ok, WoodyContext} = lim_context:woody_context(LimitContext),
|
||||
WoodyContext = lim_context:woody_context(LimitContext),
|
||||
lim_client_woody:call(accounter, Function, Args, WoodyContext).
|
||||
|
||||
convert_exception(#'InvalidRequest'{errors = Errors}) ->
|
||||
|
@ -1,9 +1,6 @@
|
||||
-module(lim_body).
|
||||
|
||||
-export([get_body/3]).
|
||||
-export([create_body_from_cash/2]).
|
||||
|
||||
-type t() :: {amount, amount()} | {cash, cash()}.
|
||||
-export([get/3]).
|
||||
|
||||
-type amount() :: integer().
|
||||
-type cash() :: #{
|
||||
@ -14,32 +11,25 @@
|
||||
-type currency() :: lim_base_thrift:'CurrencySymbolicCode'().
|
||||
-type config() :: lim_config_machine:config().
|
||||
-type body_type() :: full | partial.
|
||||
-type get_body_error() :: notfound | lim_rates:conversion_error().
|
||||
|
||||
-export_type([t/0]).
|
||||
-export_type([amount/0]).
|
||||
-export_type([currency/0]).
|
||||
-export_type([cash/0]).
|
||||
-export_type([get_body_error/0]).
|
||||
|
||||
-spec get_body(body_type(), config(), lim_context:t()) -> {ok, t()} | {error, get_body_error()}.
|
||||
get_body(BodyType, Config = #{body_type := {cash, ConfigCurrency}}, LimitContext) ->
|
||||
ContextType = lim_config_machine:context_type(Config),
|
||||
{ok, Operation} = lim_context:get_operation(ContextType, LimitContext),
|
||||
case get_body_for_operation(BodyType, Operation, Config, LimitContext) of
|
||||
{ok, {cash, #{currency := ConfigCurrency}}} = Result ->
|
||||
Result;
|
||||
{ok, {cash, #{amount := Amount, currency := Currency}}} ->
|
||||
case lim_rates:get_converted_amount({Amount, Currency}, Config, LimitContext) of
|
||||
{ok, ConvertedAmount} ->
|
||||
{ok, create_body_from_cash(ConvertedAmount, ConfigCurrency)};
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
-import(lim_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
-spec get(body_type(), config(), lim_context:t()) ->
|
||||
{ok, cash()} | {error, notfound}.
|
||||
get(BodyType, Config, LimitContext) ->
|
||||
do(fun() ->
|
||||
ContextType = lim_config_machine:context_type(Config),
|
||||
{ok, Operation} = lim_context:get_operation(ContextType, LimitContext),
|
||||
Body = unwrap(get_body_for_operation(BodyType, Operation, Config, LimitContext)),
|
||||
apply_op_behaviour(Body, Config, LimitContext)
|
||||
end).
|
||||
|
||||
-spec get_body_for_operation(body_type(), lim_context:context_operation(), config(), lim_context:t()) ->
|
||||
{ok, t()} | {error, notfound}.
|
||||
{ok, cash()} | {error, notfound}.
|
||||
get_body_for_operation(full, invoice = Operation, Config, LimitContext) ->
|
||||
ContextType = lim_config_machine:context_type(Config),
|
||||
lim_context:get_from_context(ContextType, cost, Operation, LimitContext);
|
||||
@ -73,6 +63,18 @@ get_body_for_operation(partial, invoice_payment_refund = Operation, Config, Limi
|
||||
get_body_for_operation(partial, invoice_payment_chargeback, _Config, _LimitContext) ->
|
||||
{error, notfound}.
|
||||
|
||||
-spec create_body_from_cash(amount(), currency()) -> t().
|
||||
create_body_from_cash(Amount, Currency) ->
|
||||
{cash, #{amount => Amount, currency => Currency}}.
|
||||
apply_op_behaviour(Body, #{op_behaviour := ComputationConfig}, LimitContext) ->
|
||||
{ok, Operation} = lim_context:get_operation(payment_processing, LimitContext),
|
||||
case maps:get(Operation, ComputationConfig, undefined) of
|
||||
addition ->
|
||||
Body;
|
||||
subtraction ->
|
||||
invert_body(Body);
|
||||
undefined ->
|
||||
Body
|
||||
end;
|
||||
apply_op_behaviour(Body, _Config, _LimitContext) ->
|
||||
Body.
|
||||
|
||||
invert_body(Cash = #{amount := Amount}) ->
|
||||
Cash#{amount := -Amount}.
|
||||
|
@ -5,7 +5,6 @@
|
||||
-export([marshal/2]).
|
||||
-export([unmarshal/2]).
|
||||
-export([marshal_config/1]).
|
||||
-export([unmarshal_body_type/1]).
|
||||
-export([unmarshal_op_behaviour/1]).
|
||||
-export([unmarshal_params/1]).
|
||||
-export([maybe_apply/2]).
|
||||
@ -20,12 +19,18 @@
|
||||
-type decoded_value() :: decoded_value(any()).
|
||||
-type decoded_value(T) :: T.
|
||||
|
||||
-spec maybe_apply(any(), function()) -> any().
|
||||
-spec maybe_apply(T, fun((T) -> U)) -> U | undefined.
|
||||
maybe_apply(undefined, _) ->
|
||||
undefined;
|
||||
maybe_apply(Value, Fun) ->
|
||||
Fun(Value).
|
||||
|
||||
-spec maybe_apply(T, fun((T) -> U), Default) -> U | Default.
|
||||
maybe_apply(undefined, _, Default) ->
|
||||
Default;
|
||||
maybe_apply(Value, Fun, _Default) ->
|
||||
Fun(Value).
|
||||
|
||||
%% API
|
||||
|
||||
-spec marshal(type_name(), decoded_value()) -> encoded_value().
|
||||
@ -56,7 +61,6 @@ marshal_config(Config) ->
|
||||
id = lim_config_machine:id(Config),
|
||||
processor_type = lim_config_machine:processor_type(Config),
|
||||
description = lim_config_machine:description(Config),
|
||||
body_type = marshal_body_type(lim_config_machine:body_type(Config)),
|
||||
created_at = lim_config_machine:created_at(Config),
|
||||
started_at = lim_config_machine:started_at(Config),
|
||||
shard_size = lim_config_machine:shard_size(Config),
|
||||
@ -78,11 +82,6 @@ marshal_behaviour(subtraction) ->
|
||||
marshal_behaviour(addition) ->
|
||||
{addition, #limiter_config_Addition{}}.
|
||||
|
||||
marshal_body_type(amount) ->
|
||||
{amount, #limiter_config_LimitBodyTypeAmount{}};
|
||||
marshal_body_type({cash, Currency}) ->
|
||||
{cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}.
|
||||
|
||||
marshal_time_range_type({calendar, CalendarType}) ->
|
||||
{calendar, marshal_calendar_time_range_type(CalendarType)};
|
||||
marshal_time_range_type({interval, Amount}) ->
|
||||
@ -100,8 +99,15 @@ marshal_calendar_time_range_type(year) ->
|
||||
marshal_context_type(payment_processing) ->
|
||||
{payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}.
|
||||
|
||||
marshal_type(turnover) ->
|
||||
{turnover, #limiter_config_LimitTypeTurnover{}}.
|
||||
marshal_type({turnover, Metric}) ->
|
||||
{turnover, #limiter_config_LimitTypeTurnover{
|
||||
metric = marshal_turnover_metric(Metric)
|
||||
}}.
|
||||
|
||||
marshal_turnover_metric(number) ->
|
||||
{number, #limiter_config_LimitTurnoverNumber{}};
|
||||
marshal_turnover_metric({amount, Currency}) ->
|
||||
{amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}.
|
||||
|
||||
marshal_scope(Types) ->
|
||||
{multi, ordsets:from_list(lists:map(fun marshal_scope_type/1, ordsets:to_list(Types)))}.
|
||||
@ -145,7 +151,6 @@ unmarshal_timestamp(Timestamp) when is_binary(Timestamp) ->
|
||||
unmarshal_params(#limiter_config_LimitConfigParams{
|
||||
id = ID,
|
||||
description = Description,
|
||||
body_type = BodyType,
|
||||
started_at = StartedAt,
|
||||
shard_size = ShardSize,
|
||||
time_range_type = TimeRangeType,
|
||||
@ -156,7 +161,6 @@ unmarshal_params(#limiter_config_LimitConfigParams{
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
id => ID,
|
||||
body_type => unmarshal_body_type(BodyType),
|
||||
started_at => StartedAt,
|
||||
shard_size => ShardSize,
|
||||
time_range_type => unmarshal_time_range_type(TimeRangeType),
|
||||
@ -174,31 +178,43 @@ unmarshal_config(#limiter_config_LimitConfig{
|
||||
id = ID,
|
||||
processor_type = ProcessorType,
|
||||
description = Description,
|
||||
body_type = BodyType,
|
||||
created_at = CreatedAt,
|
||||
started_at = StartedAt,
|
||||
shard_size = ShardSize,
|
||||
time_range_type = TimeRangeType,
|
||||
context_type = ContextType,
|
||||
type = Type,
|
||||
type = TypeIn,
|
||||
scope = Scope,
|
||||
op_behaviour = OpBehaviour
|
||||
op_behaviour = OpBehaviour,
|
||||
body_type_deprecated = BodyTypeIn
|
||||
}) ->
|
||||
Type = maybe_apply(TypeIn, fun unmarshal_type/1),
|
||||
BodyType = maybe_apply(BodyTypeIn, fun unmarshal_body_type_deprecated/1),
|
||||
genlib_map:compact(#{
|
||||
id => ID,
|
||||
processor_type => ProcessorType,
|
||||
created_at => lim_time:from_rfc3339(CreatedAt),
|
||||
body_type => unmarshal_body_type(BodyType),
|
||||
started_at => StartedAt,
|
||||
shard_size => ShardSize,
|
||||
time_range_type => unmarshal_time_range_type(TimeRangeType),
|
||||
context_type => unmarshal_context_type(ContextType),
|
||||
type => maybe_apply(Type, fun unmarshal_type/1),
|
||||
type => derive_type(Type, BodyType),
|
||||
scope => maybe_apply(Scope, fun unmarshal_scope/1),
|
||||
description => Description,
|
||||
op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1)
|
||||
}).
|
||||
|
||||
derive_type(Type, undefined) ->
|
||||
% NOTE
|
||||
% Current protocol disallows configuring (deprecated) body type, thus we trust limit type.
|
||||
Type;
|
||||
derive_type({turnover, _}, {cash, Currency}) ->
|
||||
% NOTE
|
||||
% Treating limits with configured (deprecated) body type as turnover limits with amount metric.
|
||||
{turnover, {amount, Currency}};
|
||||
derive_type(undefined, {cash, Currency}) ->
|
||||
{turnover, {amount, Currency}}.
|
||||
|
||||
-spec unmarshal_op_behaviour(encoded_value()) -> decoded_value().
|
||||
unmarshal_op_behaviour(OpBehaviour) ->
|
||||
#limiter_config_OperationLimitBehaviour{
|
||||
@ -213,10 +229,8 @@ unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) ->
|
||||
unmarshal_behaviour({addition, #limiter_config_Addition{}}) ->
|
||||
addition.
|
||||
|
||||
-spec unmarshal_body_type(encoded_value()) -> decoded_value().
|
||||
unmarshal_body_type({amount, #limiter_config_LimitBodyTypeAmount{}}) ->
|
||||
amount;
|
||||
unmarshal_body_type({cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}) ->
|
||||
-spec unmarshal_body_type_deprecated(encoded_value()) -> decoded_value().
|
||||
unmarshal_body_type_deprecated({cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}) ->
|
||||
{cash, Currency}.
|
||||
|
||||
unmarshal_time_range_type({calendar, CalendarType}) ->
|
||||
@ -236,8 +250,13 @@ unmarshal_calendar_time_range_type({year, _}) ->
|
||||
unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) ->
|
||||
payment_processing.
|
||||
|
||||
unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{}}) ->
|
||||
turnover.
|
||||
unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}) ->
|
||||
{turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}.
|
||||
|
||||
unmarshal_turnover_metric({number, _}) ->
|
||||
number;
|
||||
unmarshal_turnover_metric({amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}) ->
|
||||
{amount, Currency}.
|
||||
|
||||
unmarshal_scope({single, Type}) ->
|
||||
ordsets:from_list([unmarshal_scope_type(Type)]);
|
||||
@ -263,23 +282,65 @@ unmarshal_scope_type({payment_tool, _}) ->
|
||||
-spec test() -> _.
|
||||
|
||||
-spec marshal_unmarshal_created_test() -> _.
|
||||
|
||||
marshal_unmarshal_created_test() ->
|
||||
Created =
|
||||
{created, #{
|
||||
id => <<"id">>,
|
||||
processor_type => <<"type">>,
|
||||
created_at => lim_time:now(),
|
||||
body_type => {cash, <<"RUB">>},
|
||||
started_at => <<"2000-01-01T00:00:00Z">>,
|
||||
shard_size => 7,
|
||||
time_range_type => {calendar, day},
|
||||
context_type => payment_processing,
|
||||
type => turnover,
|
||||
type => {turnover, number},
|
||||
scope => ordsets:from_list([party, shop]),
|
||||
description => <<"description">>
|
||||
}},
|
||||
Event = {ev, lim_time:machinery_now(), Created},
|
||||
?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))).
|
||||
|
||||
-spec unmarshal_created_w_deprecated_body_type_test_() -> [_TestGen].
|
||||
unmarshal_created_w_deprecated_body_type_test_() ->
|
||||
Now = lim_time:now(),
|
||||
Config = #limiter_config_LimitConfig{
|
||||
id = <<"ID">>,
|
||||
processor_type = <<"TurnoverProcessor">>,
|
||||
created_at = lim_time:to_rfc3339(Now),
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
shard_size = 42,
|
||||
time_range_type = {calendar, {day, #time_range_TimeRangeTypeCalendarDay{}}},
|
||||
context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}},
|
||||
body_type_deprecated = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"☭☭☭"/utf8>>}}
|
||||
},
|
||||
[
|
||||
?_assertMatch(
|
||||
{created, #{
|
||||
id := <<"ID">>,
|
||||
created_at := Now,
|
||||
type := {turnover, {amount, <<"☭☭☭"/utf8>>}}
|
||||
}},
|
||||
unmarshal_change(
|
||||
{created, #limiter_config_CreatedChange{
|
||||
limit_config = Config#limiter_config_LimitConfig{
|
||||
type = undefined
|
||||
}
|
||||
}}
|
||||
)
|
||||
),
|
||||
?_assertMatch(
|
||||
{created, #{
|
||||
id := <<"ID">>,
|
||||
created_at := Now,
|
||||
type := {turnover, {amount, <<"☭☭☭"/utf8>>}}
|
||||
}},
|
||||
unmarshal_change(
|
||||
{created, #limiter_config_CreatedChange{
|
||||
limit_config = Config#limiter_config_LimitConfig{
|
||||
type = {turnover, #limiter_config_LimitTypeTurnover{}}
|
||||
}
|
||||
}}
|
||||
)
|
||||
)
|
||||
].
|
||||
|
||||
-endif.
|
||||
|
@ -7,8 +7,6 @@
|
||||
-export([created_at/1]).
|
||||
-export([id/1]).
|
||||
-export([description/1]).
|
||||
-export([body_type/1]).
|
||||
-export([currency/1]).
|
||||
-export([started_at/1]).
|
||||
-export([shard_size/1]).
|
||||
-export([time_range_type/1]).
|
||||
@ -38,10 +36,9 @@
|
||||
-type processor() :: lim_router:processor().
|
||||
-type description() :: binary().
|
||||
|
||||
-type limit_type() :: turnover.
|
||||
-type limit_type() :: {turnover, lim_turnover_metric:t()}.
|
||||
-type limit_scope() :: ordsets:ordset(limit_scope_type()).
|
||||
-type limit_scope_type() :: party | shop | wallet | identity.
|
||||
-type body_type() :: {cash, currency()} | amount.
|
||||
-type shard_size() :: pos_integer().
|
||||
-type shard_id() :: binary().
|
||||
-type prefix() :: binary().
|
||||
@ -57,12 +54,11 @@
|
||||
id := lim_id(),
|
||||
processor_type := processor_type(),
|
||||
created_at := lim_time:timestamp_ms(),
|
||||
body_type := body_type(),
|
||||
started_at := timestamp(),
|
||||
shard_size := shard_size(),
|
||||
time_range_type := time_range_type(),
|
||||
context_type := context_type(),
|
||||
type => limit_type(),
|
||||
type := limit_type(),
|
||||
scope => limit_scope(),
|
||||
description => description(),
|
||||
op_behaviour => op_behaviour()
|
||||
@ -70,7 +66,6 @@
|
||||
|
||||
-type create_params() :: #{
|
||||
processor_type := processor_type(),
|
||||
body_type := body_type(),
|
||||
started_at := timestamp(),
|
||||
shard_size := shard_size(),
|
||||
time_range_type := time_range_type(),
|
||||
@ -88,16 +83,13 @@
|
||||
-type lim_change() :: lim_limiter_thrift:'LimitChange'().
|
||||
-type limit() :: lim_limiter_thrift:'Limit'().
|
||||
-type timestamp() :: lim_base_thrift:'Timestamp'().
|
||||
-type currency() :: lim_base_thrift:'CurrencySymbolicCode'().
|
||||
|
||||
-export_type([config/0]).
|
||||
-export_type([body_type/0]).
|
||||
-export_type([limit_type/0]).
|
||||
-export_type([limit_scope/0]).
|
||||
-export_type([time_range_type/0]).
|
||||
-export_type([time_range/0]).
|
||||
-export_type([create_params/0]).
|
||||
-export_type([currency/0]).
|
||||
-export_type([lim_id/0]).
|
||||
-export_type([lim_change/0]).
|
||||
-export_type([limit/0]).
|
||||
@ -180,16 +172,6 @@ description(#{description := ID}) ->
|
||||
description(_) ->
|
||||
undefined.
|
||||
|
||||
-spec body_type(config()) -> body_type().
|
||||
body_type(#{body_type := BodyType}) ->
|
||||
BodyType.
|
||||
|
||||
-spec currency(config()) -> currency() | undefined.
|
||||
currency(#{body_type := {cash, Currency}}) ->
|
||||
Currency;
|
||||
currency(#{body_type := amount}) ->
|
||||
undefined.
|
||||
|
||||
-spec started_at(config()) -> timestamp().
|
||||
started_at(#{started_at := Value}) ->
|
||||
Value.
|
||||
@ -206,11 +188,11 @@ time_range_type(#{time_range_type := Value}) ->
|
||||
processor_type(#{processor_type := Value}) ->
|
||||
Value.
|
||||
|
||||
-spec type(config()) -> lim_maybe:maybe(limit_type()).
|
||||
-spec type(config()) -> limit_type().
|
||||
type(#{type := Value}) ->
|
||||
Value;
|
||||
type(_) ->
|
||||
undefined.
|
||||
{turnover, number}.
|
||||
|
||||
-spec scope(config()) -> limit_scope().
|
||||
scope(#{scope := Value}) ->
|
||||
@ -232,7 +214,7 @@ op_behaviour(_) ->
|
||||
|
||||
-spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}.
|
||||
start(ID, Params, LimitContext) ->
|
||||
{ok, WoodyCtx} = lim_context:woody_context(LimitContext),
|
||||
WoodyCtx = lim_context:woody_context(LimitContext),
|
||||
Config = genlib_map:compact(Params#{id => ID, created_at => lim_time:now()}),
|
||||
case machinery:start(?NS, ID, [{created, Config}], get_backend(WoodyCtx)) of
|
||||
ok ->
|
||||
@ -245,7 +227,7 @@ start(ID, Params, LimitContext) ->
|
||||
-spec get(lim_id(), lim_context()) -> {ok, config()} | {error, notfound}.
|
||||
get(ID, LimitContext) ->
|
||||
do(fun() ->
|
||||
{ok, WoodyCtx} = lim_context:woody_context(LimitContext),
|
||||
WoodyCtx = lim_context:woody_context(LimitContext),
|
||||
Machine = unwrap(machinery:get(?NS, ID, get_backend(WoodyCtx))),
|
||||
collapse(Machine)
|
||||
end).
|
||||
|
@ -96,12 +96,11 @@ marshal_unmarshal_created_test() ->
|
||||
id => ID,
|
||||
processor_type => <<"type">>,
|
||||
created_at => lim_time:now(),
|
||||
body_type => {cash, <<"RUB">>},
|
||||
started_at => <<"2000-01-01T00:00:00Z">>,
|
||||
shard_size => 7,
|
||||
time_range_type => {calendar, day},
|
||||
context_type => payment_processing,
|
||||
type => turnover,
|
||||
type => {turnover, {amount, <<"RUB">>}},
|
||||
scope => ordsets:from_list([party]),
|
||||
description => <<"description">>
|
||||
}},
|
||||
|
@ -8,21 +8,19 @@
|
||||
|
||||
-export([handle_function/4]).
|
||||
|
||||
%%
|
||||
|
||||
-type lim_context() :: lim_context:t().
|
||||
-define(DEFAULT_CURRENCY, <<"RUB">>).
|
||||
|
||||
%%
|
||||
|
||||
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}.
|
||||
handle_function(Fn, Args, WoodyCtx, Opts) ->
|
||||
{ok, LimitContext} = lim_context:create(WoodyCtx),
|
||||
LimitContext = lim_context:create(WoodyCtx),
|
||||
scoper:scope(
|
||||
configurator,
|
||||
fun() -> handle_function_(Fn, Args, LimitContext, Opts) end
|
||||
).
|
||||
|
||||
-spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}.
|
||||
-spec handle_function_(woody:func(), woody:args(), lim_context:t(), woody:options()) -> {ok, woody:result()}.
|
||||
handle_function_(
|
||||
'CreateLegacy',
|
||||
{#limiter_cfg_LimitCreateParams{
|
||||
@ -30,7 +28,6 @@ handle_function_(
|
||||
name = Name,
|
||||
description = Description,
|
||||
started_at = StartedAt,
|
||||
body_type = BodyType,
|
||||
op_behaviour = OpBehaviour
|
||||
}},
|
||||
LimitContext,
|
||||
@ -43,7 +40,6 @@ handle_function_(
|
||||
genlib_map:compact(Config#{
|
||||
description => Description,
|
||||
started_at => StartedAt,
|
||||
body_type => lim_config_codec:unmarshal_body_type(BodyType),
|
||||
op_behaviour => lim_config_codec:maybe_apply(
|
||||
OpBehaviour,
|
||||
fun lim_config_codec:unmarshal_op_behaviour/1
|
||||
@ -77,7 +73,7 @@ handle_function_('Get', {LimitID}, LimitContext, _Opts) ->
|
||||
woody_error:raise(business, #limiter_cfg_LimitConfigNotFound{})
|
||||
end.
|
||||
|
||||
map_type(turnover) ->
|
||||
map_type({turnover, _}) ->
|
||||
<<"TurnoverProcessor">>;
|
||||
map_type(_) ->
|
||||
woody_error:raise(
|
||||
@ -88,7 +84,7 @@ map_type(_) ->
|
||||
mk_limit_config(<<"ShopDayTurnover">>) ->
|
||||
{ok, #{
|
||||
processor_type => <<"TurnoverProcessor">>,
|
||||
type => turnover,
|
||||
type => {turnover, {amount, ?DEFAULT_CURRENCY}},
|
||||
scope => ordsets:from_list([shop]),
|
||||
shard_size => 7,
|
||||
context_type => payment_processing,
|
||||
@ -97,7 +93,7 @@ mk_limit_config(<<"ShopDayTurnover">>) ->
|
||||
mk_limit_config(<<"PartyDayTurnover">>) ->
|
||||
{ok, #{
|
||||
processor_type => <<"TurnoverProcessor">>,
|
||||
type => turnover,
|
||||
type => {turnover, {amount, ?DEFAULT_CURRENCY}},
|
||||
scope => ordsets:from_list([party]),
|
||||
shard_size => 7,
|
||||
context_type => payment_processing,
|
||||
@ -106,7 +102,7 @@ mk_limit_config(<<"PartyDayTurnover">>) ->
|
||||
mk_limit_config(<<"ShopMonthTurnover">>) ->
|
||||
{ok, #{
|
||||
processor_type => <<"TurnoverProcessor">>,
|
||||
type => turnover,
|
||||
type => {turnover, {amount, ?DEFAULT_CURRENCY}},
|
||||
scope => ordsets:from_list([shop]),
|
||||
shard_size => 12,
|
||||
context_type => payment_processing,
|
||||
@ -115,7 +111,7 @@ mk_limit_config(<<"ShopMonthTurnover">>) ->
|
||||
mk_limit_config(<<"PartyMonthTurnover">>) ->
|
||||
{ok, #{
|
||||
processor_type => <<"TurnoverProcessor">>,
|
||||
type => turnover,
|
||||
type => {turnover, {amount, ?DEFAULT_CURRENCY}},
|
||||
scope => ordsets:from_list([party]),
|
||||
shard_size => 12,
|
||||
context_type => payment_processing,
|
||||
@ -124,7 +120,7 @@ mk_limit_config(<<"PartyMonthTurnover">>) ->
|
||||
mk_limit_config(<<"GlobalMonthTurnover">>) ->
|
||||
{ok, #{
|
||||
processor_type => <<"TurnoverProcessor">>,
|
||||
type => turnover,
|
||||
type => {turnover, {amount, ?DEFAULT_CURRENCY}},
|
||||
scope => ordsets:new(),
|
||||
shard_size => 12,
|
||||
context_type => payment_processing,
|
||||
|
@ -96,13 +96,13 @@
|
||||
-export_type([context_type/0]).
|
||||
-export_type([context_operation/0]).
|
||||
|
||||
-spec create(woody_context()) -> {ok, t()}.
|
||||
-spec create(woody_context()) -> t().
|
||||
create(WoodyContext) ->
|
||||
{ok, #{woody_context => WoodyContext}}.
|
||||
#{woody_context => WoodyContext}.
|
||||
|
||||
-spec woody_context(t()) -> {ok, woody_context()}.
|
||||
-spec woody_context(t()) -> woody_context().
|
||||
woody_context(Context) ->
|
||||
{ok, maps:get(woody_context, Context)}.
|
||||
maps:get(woody_context, Context).
|
||||
|
||||
-spec clock(t()) -> {ok, clock()} | {error, notfound}.
|
||||
clock(#{clock := Clock}) ->
|
||||
@ -294,7 +294,7 @@ unmarshal_payment_processing_invoice_payment_chargeback(#limiter_context_Invoice
|
||||
}).
|
||||
|
||||
unmarshal_cash(#limiter_base_Cash{amount = Amount, currency = #limiter_base_CurrencyRef{symbolic_code = Currency}}) ->
|
||||
lim_body:create_body_from_cash(Amount, Currency).
|
||||
#{amount => Amount, currency => Currency}.
|
||||
|
||||
unmarshal_string(Value) ->
|
||||
Value.
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}.
|
||||
handle_function(Fn, Args, WoodyCtx, Opts) ->
|
||||
{ok, LimitContext} = lim_context:create(WoodyCtx),
|
||||
LimitContext = lim_context:create(WoodyCtx),
|
||||
scoper:scope(
|
||||
limiter,
|
||||
fun() -> handle_function_(Fn, Args, LimitContext, Opts) end
|
||||
@ -79,8 +79,6 @@ handle_function_('Rollback', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Conte
|
||||
end.
|
||||
|
||||
-spec handle_get_error(_) -> no_return().
|
||||
handle_get_error({_, {range, notfound}}) ->
|
||||
woody_error:raise(business, #limiter_LimitNotFound{});
|
||||
handle_get_error(Error) ->
|
||||
handle_default_error(Error).
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
-module(lim_p_transfer).
|
||||
|
||||
-include_lib("damsel/include/dmsl_accounter_thrift.hrl").
|
||||
|
||||
-export([construct_posting/2]).
|
||||
-export([reverse_posting/1]).
|
||||
|
||||
-type posting() :: lim_accounting:posting().
|
||||
-type body() :: lim_body:t().
|
||||
|
||||
-spec construct_posting(lim_range_machine:time_range_ext(), body()) -> posting().
|
||||
construct_posting(#{account_id_from := From, account_id_to := To}, {cash, #{amount := Amount, currency := Currency}}) ->
|
||||
#accounter_Posting{
|
||||
from_id = From,
|
||||
to_id = To,
|
||||
amount = Amount,
|
||||
currency_sym_code = Currency,
|
||||
description = <<>>
|
||||
};
|
||||
construct_posting(#{account_id_from := From, account_id_to := To}, {amount, Amount}) ->
|
||||
#accounter_Posting{
|
||||
from_id = From,
|
||||
to_id = To,
|
||||
amount = Amount,
|
||||
currency_sym_code = lim_accounting:get_default_currency(),
|
||||
description = <<>>
|
||||
}.
|
||||
|
||||
-spec reverse_posting(posting()) -> posting().
|
||||
reverse_posting(Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo}) ->
|
||||
Posting#accounter_Posting{
|
||||
from_id = AccountTo,
|
||||
to_id = AccountFrom
|
||||
}.
|
32
apps/limiter/src/lim_posting.erl
Normal file
32
apps/limiter/src/lim_posting.erl
Normal file
@ -0,0 +1,32 @@
|
||||
-module(lim_posting).
|
||||
|
||||
-include_lib("damsel/include/dmsl_accounter_thrift.hrl").
|
||||
|
||||
-export([new/3]).
|
||||
-export([reverse/1]).
|
||||
|
||||
-type posting() :: lim_accounting:posting().
|
||||
|
||||
-spec new(lim_range_machine:time_range_ext(), lim_body:amount(), lim_body:currency()) ->
|
||||
posting().
|
||||
new(#{account_id_from := From, account_id_to := To}, Amount, Currency) ->
|
||||
reverse_negative_posting(#accounter_Posting{
|
||||
from_id = From,
|
||||
to_id = To,
|
||||
amount = Amount,
|
||||
currency_sym_code = Currency,
|
||||
description = <<>>
|
||||
}).
|
||||
|
||||
reverse_negative_posting(Posting = #accounter_Posting{amount = Amount}) when Amount < 0 ->
|
||||
PostingReversed = reverse(Posting),
|
||||
PostingReversed#accounter_Posting{amount = -Amount};
|
||||
reverse_negative_posting(Posting) ->
|
||||
Posting.
|
||||
|
||||
-spec reverse(posting()) -> posting().
|
||||
reverse(Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo}) ->
|
||||
Posting#accounter_Posting{
|
||||
from_id = AccountTo,
|
||||
to_id = AccountFrom
|
||||
}.
|
@ -39,7 +39,7 @@
|
||||
-type id() :: binary().
|
||||
-type time_range_type() :: lim_config_machine:time_range_type().
|
||||
-type time_range() :: lim_config_machine:time_range().
|
||||
-type currency() :: lim_config_machine:currency().
|
||||
-type currency() :: lim_body:currency().
|
||||
|
||||
-type limit_range_state() :: #{
|
||||
id := id(),
|
||||
@ -109,27 +109,27 @@ ranges(#{ranges := Ranges}) ->
|
||||
ranges(_State) ->
|
||||
[].
|
||||
|
||||
-spec currency(limit_range_state()) -> currency().
|
||||
-spec currency(limit_range_state() | create_params()) -> currency().
|
||||
currency(#{currency := Currency}) ->
|
||||
Currency;
|
||||
currency(_State) ->
|
||||
lim_accounting:get_default_currency().
|
||||
lim_accounting:noncurrency().
|
||||
|
||||
%%% API
|
||||
|
||||
-spec get(id(), lim_context()) -> {ok, limit_range_state()} | {error, notfound}.
|
||||
get(ID, LimitContext) ->
|
||||
{ok, WoodyCtx} = lim_context:woody_context(LimitContext),
|
||||
get_state(ID, WoodyCtx).
|
||||
get_state(ID, lim_context:woody_context(LimitContext)).
|
||||
|
||||
-spec ensure_exists(create_params(), time_range(), lim_context()) -> {ok, time_range_ext()}.
|
||||
ensure_exists(Params = #{id := ID, currency := Currency}, TimeRange, LimitContext) ->
|
||||
{ok, WoodyCtx} = lim_context:woody_context(LimitContext),
|
||||
ensure_exists(Params = #{id := ID}, TimeRange, LimitContext) ->
|
||||
WoodyCtx = lim_context:woody_context(LimitContext),
|
||||
case get_state(ID, WoodyCtx) of
|
||||
{ok, State} ->
|
||||
ensure_range_exist_in_state(TimeRange, State, WoodyCtx);
|
||||
{error, notfound} ->
|
||||
_ = start(ID, Params, [new_time_range_ext(TimeRange, Currency, WoodyCtx)], WoodyCtx),
|
||||
TimeRangeExt = new_time_range_ext(TimeRange, currency(Params), WoodyCtx),
|
||||
_ = start(ID, Params, [TimeRangeExt], WoodyCtx),
|
||||
{ok, State} = get_state(ID, WoodyCtx),
|
||||
get_range(TimeRange, State)
|
||||
end.
|
||||
@ -162,9 +162,7 @@ get_range_balance(ID, TimeRange, LimitContext) ->
|
||||
|
||||
-spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result().
|
||||
init(Events, _Machine, _HandlerArgs, _HandlerOpts) ->
|
||||
#{
|
||||
events => emit_events(Events)
|
||||
}.
|
||||
#{events => emit_events(Events)}.
|
||||
|
||||
-spec process_call(args(range_call()), machine(), handler_args(), handler_opts()) ->
|
||||
{response(time_range_ext()), result()} | no_return().
|
||||
@ -196,7 +194,7 @@ find_time_range(TimeRange, [_Head | Rest]) ->
|
||||
find_time_range(TimeRange, Rest).
|
||||
|
||||
new_time_range_ext(TimeRange, Currency, WoodyCtx) ->
|
||||
{ok, LimitContext} = lim_context:create(WoodyCtx),
|
||||
LimitContext = lim_context:create(WoodyCtx),
|
||||
{ok, AccountIDFrom} = lim_accounting:create_account(Currency, LimitContext),
|
||||
{ok, AccountIDTo} = lim_accounting:create_account(Currency, LimitContext),
|
||||
TimeRange#{
|
||||
|
@ -2,10 +2,8 @@
|
||||
|
||||
-include_lib("xrates_proto/include/xrates_rate_thrift.hrl").
|
||||
|
||||
-export([get_converted_amount/3]).
|
||||
-export([convert/4]).
|
||||
|
||||
-type amount() :: dmsl_domain_thrift:'Amount'().
|
||||
-type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'().
|
||||
-type limit_context() :: lim_context:t().
|
||||
-type config() :: lim_config_machine:config().
|
||||
|
||||
@ -17,37 +15,32 @@
|
||||
-define(DEFAULT_FACTOR, 1.1).
|
||||
-define(DEFAULT_FACTOR_NAME, <<"DEFAULT">>).
|
||||
|
||||
-spec get_converted_amount({amount(), currency()}, config(), limit_context()) ->
|
||||
{ok, amount()}
|
||||
-spec convert(lim_body:cash(), lim_body:currency(), config(), limit_context()) ->
|
||||
{ok, lim_body:cash()}
|
||||
| {error, conversion_error()}.
|
||||
get_converted_amount(Cash = {_Amount, Currency}, Config, LimitContext) ->
|
||||
Factor = get_exchange_factor(Currency),
|
||||
case
|
||||
call_rates(
|
||||
'GetConvertedAmount',
|
||||
{<<"CBR">>, construct_conversion_request(Cash, Config, LimitContext)},
|
||||
LimitContext
|
||||
)
|
||||
of
|
||||
convert(#{amount := Amount, currency := Currency}, DestinationCurrency, Config, LimitContext) ->
|
||||
ContextType = lim_config_machine:context_type(Config),
|
||||
{ok, Timestamp} = lim_context:get_from_context(ContextType, created_at, LimitContext),
|
||||
Request = #rate_ConversionRequest{
|
||||
source = Currency,
|
||||
destination = DestinationCurrency,
|
||||
amount = Amount,
|
||||
datetime = Timestamp
|
||||
},
|
||||
case call_rates('GetConvertedAmount', {<<"CBR">>, Request}, LimitContext) of
|
||||
{ok, #base_Rational{p = P, q = Q}} ->
|
||||
Rational = genlib_rational:new(P, Q),
|
||||
{ok, genlib_rational:round(genlib_rational:mul(Rational, Factor))};
|
||||
Factor = get_exchange_factor(Currency),
|
||||
{ok, #{
|
||||
amount => genlib_rational:round(genlib_rational:mul(Rational, Factor)),
|
||||
currency => DestinationCurrency
|
||||
}};
|
||||
{exception, #rate_QuoteNotFound{}} ->
|
||||
{error, quote_not_found};
|
||||
{exception, #rate_CurrencyNotFound{}} ->
|
||||
{error, currency_not_found}
|
||||
end.
|
||||
|
||||
construct_conversion_request({Amount, Currency}, Config = #{body_type := {cash, DestinationCurrency}}, LimitContext) ->
|
||||
ContextType = lim_config_machine:context_type(Config),
|
||||
{ok, Timestamp} = lim_context:get_from_context(ContextType, created_at, LimitContext),
|
||||
#rate_ConversionRequest{
|
||||
source = Currency,
|
||||
destination = DestinationCurrency,
|
||||
amount = Amount,
|
||||
datetime = Timestamp
|
||||
}.
|
||||
|
||||
get_exchange_factor(Currency) ->
|
||||
Factors = genlib_app:env(?APP, exchange_factors, #{}),
|
||||
case maps:get(Currency, Factors, undefined) of
|
||||
@ -65,5 +58,4 @@ get_exchange_factor(Currency) ->
|
||||
%%
|
||||
|
||||
call_rates(Function, Args, LimitContext) ->
|
||||
{ok, WoodyContext} = lim_context:woody_context(LimitContext),
|
||||
lim_client_woody:call(xrates, Function, Args, WoodyContext).
|
||||
lim_client_woody:call(xrates, Function, Args, lim_context:woody_context(LimitContext)).
|
||||
|
61
apps/limiter/src/lim_turnover_metric.erl
Normal file
61
apps/limiter/src/lim_turnover_metric.erl
Normal file
@ -0,0 +1,61 @@
|
||||
-module(lim_turnover_metric).
|
||||
|
||||
-export([compute/4]).
|
||||
|
||||
-type amount() :: lim_body:amount().
|
||||
-type currency() :: lim_base_thrift:'CurrencySymbolicCode'().
|
||||
-type stage() :: hold | commit.
|
||||
-type t() :: number | {amount, currency()}.
|
||||
|
||||
-export_type([t/0]).
|
||||
|
||||
%%
|
||||
|
||||
-spec compute(t(), stage(), lim_config_machine:config(), lim_context:t()) ->
|
||||
{ok, amount()} | {error, lim_rates:conversion_error()}.
|
||||
compute(number, hold, Config, LimitContext) ->
|
||||
#{amount := Amount} = get_body(Config, LimitContext),
|
||||
{ok, sign(Amount)};
|
||||
compute(number, commit, Config, LimitContext) ->
|
||||
case get_commit_body(Config, LimitContext) of
|
||||
#{amount := Amount} when Amount /= 0 ->
|
||||
{ok, sign(Amount)};
|
||||
#{amount := 0} ->
|
||||
% Zero amount operation currently means "rollback" in the protocol.
|
||||
{ok, 0}
|
||||
end;
|
||||
compute({amount, Currency}, hold, Config, LimitContext) ->
|
||||
Body = get_body(Config, LimitContext),
|
||||
denominate(Body, Currency, Config, LimitContext);
|
||||
compute({amount, Currency}, commit, Config, LimitContext) ->
|
||||
Body = get_commit_body(Config, LimitContext),
|
||||
denominate(Body, Currency, Config, LimitContext).
|
||||
|
||||
get_body(Config, LimitContext) ->
|
||||
{ok, Body} = lim_body:get(full, Config, LimitContext),
|
||||
Body.
|
||||
|
||||
get_commit_body(Config, LimitContext) ->
|
||||
case lim_body:get(partial, Config, LimitContext) of
|
||||
{ok, Body} ->
|
||||
Body;
|
||||
{error, notfound} ->
|
||||
get_body(Config, LimitContext)
|
||||
end.
|
||||
|
||||
%%
|
||||
|
||||
denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitContext) ->
|
||||
{ok, Amount};
|
||||
denominate(Body = #{}, DestinationCurrency, Config, LimitContext) ->
|
||||
case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of
|
||||
{ok, #{amount := AmountConverted}} ->
|
||||
{ok, AmountConverted};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
sign(Amount) when Amount > 0 ->
|
||||
+1;
|
||||
sign(Amount) when Amount < 0 ->
|
||||
-1.
|
@ -25,16 +25,16 @@
|
||||
-type get_limit_error() :: {range, notfound}.
|
||||
|
||||
-type hold_error() ::
|
||||
lim_body:get_body_error()
|
||||
lim_rates:conversion_error()
|
||||
| lim_accounting:invalid_request_error().
|
||||
|
||||
-type commit_error() ::
|
||||
{forbidden_operation_amount, forbidden_operation_amount_error()}
|
||||
| lim_body:get_body_error()
|
||||
| lim_rates:conversion_error()
|
||||
| lim_accounting:invalid_request_error().
|
||||
|
||||
-type rollback_error() ::
|
||||
lim_body:get_body_error()
|
||||
lim_rates:conversion_error()
|
||||
| lim_accounting:invalid_request_error().
|
||||
|
||||
-export_type([get_limit_error/0]).
|
||||
@ -42,14 +42,13 @@
|
||||
-export_type([commit_error/0]).
|
||||
-export_type([rollback_error/0]).
|
||||
|
||||
-import(lim_pipeline, [do/1, unwrap/1, unwrap/2]).
|
||||
-import(lim_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
-spec get_limit(lim_id(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}.
|
||||
get_limit(LimitID, Config, LimitContext) ->
|
||||
do(fun() ->
|
||||
{LimitRangeID, TimeRange} = compute_limit_time_range_location(LimitID, Config, LimitContext),
|
||||
#{max_available_amount := Amount} =
|
||||
unwrap(range, lim_range_machine:get_range_balance(LimitRangeID, TimeRange, LimitContext)),
|
||||
Amount = find_range_balance_amount(LimitRangeID, TimeRange, LimitContext),
|
||||
#limiter_Limit{
|
||||
id = LimitRangeID,
|
||||
amount = Amount,
|
||||
@ -58,12 +57,20 @@ get_limit(LimitID, Config, LimitContext) ->
|
||||
}
|
||||
end).
|
||||
|
||||
find_range_balance_amount(LimitRangeID, TimeRange, LimitContext) ->
|
||||
case lim_range_machine:get_range_balance(LimitRangeID, TimeRange, LimitContext) of
|
||||
{ok, #{max_available_amount := Amount}} ->
|
||||
Amount;
|
||||
{error, notfound} ->
|
||||
0
|
||||
end.
|
||||
|
||||
-spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}.
|
||||
hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) ->
|
||||
do(fun() ->
|
||||
TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext),
|
||||
Body = unwrap(lim_body:get_body(full, Config, LimitContext)),
|
||||
Posting = construct_posting(TimeRangeAccount, Body, Config, LimitContext),
|
||||
Metric = unwrap(compute_metric(hold, Config, LimitContext)),
|
||||
Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)),
|
||||
unwrap(lim_accounting:hold(construct_plan_id(LimitChange), {1, [Posting]}, LimitContext))
|
||||
end).
|
||||
|
||||
@ -95,8 +102,8 @@ commit(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -
|
||||
rollback(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) ->
|
||||
do(fun() ->
|
||||
TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext),
|
||||
Body = unwrap(lim_body:get_body(full, Config, LimitContext)),
|
||||
Posting = construct_posting(TimeRangeAccount, Body, Config, LimitContext),
|
||||
Metric = unwrap(compute_metric(hold, Config, LimitContext)),
|
||||
Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)),
|
||||
unwrap(lim_accounting:rollback(construct_plan_id(LimitChange), [{1, [Posting]}], LimitContext))
|
||||
end).
|
||||
|
||||
@ -113,7 +120,7 @@ ensure_limit_time_range(LimitID, Config, LimitContext) ->
|
||||
id => LimitRangeID,
|
||||
type => lim_config_machine:time_range_type(Config),
|
||||
created_at => Timestamp,
|
||||
currency => lim_config_machine:currency(Config)
|
||||
currency => currency(Config)
|
||||
}),
|
||||
unwrap(lim_range_machine:ensure_exists(CreateParams, TimeRange, LimitContext)).
|
||||
|
||||
@ -127,22 +134,24 @@ construct_range_id(LimitID, Timestamp, Config, LimitContext) ->
|
||||
<<LimitID/binary, Prefix/binary, "/", ShardID/binary>>.
|
||||
|
||||
construct_commit_plan(TimeRangeAccount, Config, LimitContext) ->
|
||||
Body = unwrap(lim_body:get_body(full, Config, LimitContext)),
|
||||
MaybePartialBody = lim_body:get_body(partial, Config, LimitContext),
|
||||
construct_commit_postings(TimeRangeAccount, Body, MaybePartialBody, Config, LimitContext).
|
||||
MetricHold = unwrap(compute_metric(hold, Config, LimitContext)),
|
||||
MetricCommit = unwrap(compute_metric(commit, Config, LimitContext)),
|
||||
construct_commit_postings(TimeRangeAccount, MetricHold, MetricCommit, Config).
|
||||
|
||||
construct_commit_postings(TimeRangeAccount, Full, MaybePartialBody, Config, LimitContext) ->
|
||||
OriginalHoldPosting = construct_posting(TimeRangeAccount, Full, Config, LimitContext),
|
||||
case determine_commit_intent(MaybePartialBody, Full) of
|
||||
commit ->
|
||||
construct_commit_postings(TimeRangeAccount, MetricHold, MetricCommit, Config) ->
|
||||
OriginalHoldPosting = lim_posting:new(TimeRangeAccount, MetricHold, currency(Config)),
|
||||
case MetricCommit of
|
||||
MetricHold ->
|
||||
% Commit-time metric is equal to hold-time metric
|
||||
[{commit, [{1, [OriginalHoldPosting]}]}];
|
||||
rollback ->
|
||||
0 ->
|
||||
% Commit-time metric is 0, this is rollback
|
||||
[{rollback, [{1, [OriginalHoldPosting]}]}];
|
||||
{commit, Partial} ->
|
||||
_MetricPartial ->
|
||||
% Partial body is less than full body
|
||||
ok = unwrap(assert_partial_body(Partial, Full)),
|
||||
ReverseHoldPosting = lim_p_transfer:reverse_posting(OriginalHoldPosting),
|
||||
PartialHoldPosting = construct_posting(TimeRangeAccount, Partial, Config, LimitContext),
|
||||
ok = unwrap(validate_metric(MetricCommit, MetricHold)),
|
||||
ReverseHoldPosting = lim_posting:reverse(OriginalHoldPosting),
|
||||
PartialHoldPosting = lim_posting:new(TimeRangeAccount, MetricCommit, currency(Config)),
|
||||
PartialBatch = [ReverseHoldPosting, PartialHoldPosting],
|
||||
[
|
||||
{hold, {2, PartialBatch}},
|
||||
@ -153,71 +162,36 @@ construct_commit_postings(TimeRangeAccount, Full, MaybePartialBody, Config, Limi
|
||||
]
|
||||
end.
|
||||
|
||||
determine_commit_intent({error, notfound}, _FullBody) ->
|
||||
% No partial body specified
|
||||
commit;
|
||||
determine_commit_intent({ok, FullBody}, FullBody) ->
|
||||
% Partial body is equal to full body
|
||||
commit;
|
||||
determine_commit_intent({ok, {amount, 0}}, _FullBody) ->
|
||||
% Partial body is 0, this is rollback
|
||||
rollback;
|
||||
determine_commit_intent({ok, {cash, #{amount := 0}}}, _FullBody) ->
|
||||
% Partial body is 0, this is rollback
|
||||
rollback;
|
||||
determine_commit_intent({ok, Partial}, _FullBody) ->
|
||||
{commit, Partial}.
|
||||
compute_metric(Stage, Config, LimitContext) ->
|
||||
{turnover, Metric} = lim_config_machine:type(Config),
|
||||
lim_turnover_metric:compute(Metric, Stage, Config, LimitContext).
|
||||
|
||||
construct_posting(TimeRangeAccount, Body, Config, LimitContext) ->
|
||||
apply_op_behaviour(lim_p_transfer:construct_posting(TimeRangeAccount, Body), Config, LimitContext).
|
||||
currency(#{type := {turnover, {amount, Currency}}}) ->
|
||||
Currency;
|
||||
currency(#{type := {turnover, number}}) ->
|
||||
lim_accounting:noncurrency().
|
||||
|
||||
apply_op_behaviour(Posting, #{op_behaviour := ComputationConfig}, LimitContext) ->
|
||||
{ok, Operation} = lim_context:get_operation(payment_processing, LimitContext),
|
||||
case maps:get(Operation, ComputationConfig, undefined) of
|
||||
subtraction ->
|
||||
lim_p_transfer:reverse_posting(Posting);
|
||||
Type when Type =:= undefined orelse Type =:= additional ->
|
||||
Posting
|
||||
end;
|
||||
apply_op_behaviour(Body, _Config, _LimitContext) ->
|
||||
Body.
|
||||
|
||||
assert_partial_body(
|
||||
{cash, #{amount := Partial, currency := Currency}},
|
||||
{cash, #{amount := Full, currency := Currency}}
|
||||
) ->
|
||||
compare_amount(Partial, Full, Currency);
|
||||
assert_partial_body(
|
||||
{cash, #{amount := Partial, currency := PartialCurrency}},
|
||||
{cash, #{amount := Full, currency := FullCurrency}}
|
||||
) ->
|
||||
erlang:error({invalid_partial_cash, {Partial, PartialCurrency}, {Full, FullCurrency}}).
|
||||
|
||||
compare_amount(Partial, Full, Currency) when Full > 0 ->
|
||||
case Partial < Full of
|
||||
validate_metric(MetricCommit, MetricHold) when MetricHold > 0 ->
|
||||
case MetricCommit < MetricHold of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
{error,
|
||||
{forbidden_operation_amount,
|
||||
genlib_map:compact(#{
|
||||
type => positive,
|
||||
partial => Partial,
|
||||
full => Full,
|
||||
currency => Currency
|
||||
})}}
|
||||
{forbidden_operation_amount, #{
|
||||
type => positive,
|
||||
partial => MetricCommit,
|
||||
full => MetricHold
|
||||
}}}
|
||||
end;
|
||||
compare_amount(Partial, Full, Currency) when Full < 0 ->
|
||||
case Partial > Full of
|
||||
validate_metric(MetricCommit, MetricHold) when MetricHold < 0 ->
|
||||
case MetricCommit > MetricHold of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
{error,
|
||||
{forbidden_operation_amount,
|
||||
genlib_map:compact(#{
|
||||
type => negative,
|
||||
partial => Partial,
|
||||
full => Full,
|
||||
currency => Currency
|
||||
})}}
|
||||
{forbidden_operation_amount, #{
|
||||
type => negative,
|
||||
partial => MetricCommit,
|
||||
full => MetricHold
|
||||
}}}
|
||||
end.
|
||||
|
@ -85,8 +85,7 @@ legacy_create_config(C) ->
|
||||
id = ID,
|
||||
name = <<"GlobalMonthTurnover">>,
|
||||
description = Description,
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
body_type = ?body_type_cash()
|
||||
started_at = <<"2000-01-01T00:00:00Z">>
|
||||
},
|
||||
?assertMatch(
|
||||
{ok, #limiter_config_LimitConfig{
|
||||
@ -105,10 +104,9 @@ create_config(C) ->
|
||||
id = ?config(limit_id, C),
|
||||
description = Description,
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
body_type = ?body_type_cash(<<"RUB">>),
|
||||
shard_size = 4,
|
||||
time_range_type = ?time_range_week(),
|
||||
type = ?lim_type_turnover(),
|
||||
type = ?lim_type_turnover(?turnover_metric_amount()),
|
||||
scope = ?scope([
|
||||
?scope_shop(),
|
||||
?scope_party()
|
||||
@ -130,7 +128,6 @@ create_config_single_scope(C) ->
|
||||
Params = #limiter_config_LimitConfigParams{
|
||||
id = ?config(limit_id, C),
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
body_type = ?body_type_cash(),
|
||||
time_range_type = ?time_range_week(),
|
||||
shard_size = 1,
|
||||
type = ?lim_type_turnover(),
|
||||
@ -157,8 +154,7 @@ prepare_environment(ID, LimitName, _C) ->
|
||||
id = ID,
|
||||
name = LimitName,
|
||||
description = <<"description">>,
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
body_type = ?body_type_cash()
|
||||
started_at = <<"2000-01-01T00:00:00Z">>
|
||||
},
|
||||
{ok, LimitConfig} = lim_client:legacy_create_config(Params, Client),
|
||||
#{config => LimitConfig, client => Client}.
|
||||
|
@ -17,13 +17,15 @@
|
||||
-define(scope_party(), {party, #limiter_config_LimitScopeEmptyDetails{}}).
|
||||
-define(scope_shop(), {shop, #limiter_config_LimitScopeEmptyDetails{}}).
|
||||
|
||||
-define(body_type_cash(), ?body_type_cash(?currency)).
|
||||
-define(body_type_cash(Currency),
|
||||
{cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}
|
||||
-define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())).
|
||||
-define(lim_type_turnover(Metric),
|
||||
{turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}
|
||||
).
|
||||
|
||||
-define(lim_type_turnover(),
|
||||
{turnover, #limiter_config_LimitTypeTurnover{}}
|
||||
-define(turnover_metric_number(), {number, #limiter_config_LimitTurnoverNumber{}}).
|
||||
-define(turnover_metric_amount(), ?turnover_metric_amount(?currency)).
|
||||
-define(turnover_metric_amount(Currency),
|
||||
{amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}
|
||||
).
|
||||
|
||||
-define(time_range_day(),
|
||||
@ -48,8 +50,19 @@
|
||||
{payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}
|
||||
).
|
||||
|
||||
-define(op_invoice(), {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}).
|
||||
-define(op_invoice_payment(), {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}).
|
||||
|
||||
-define(ctx_invoice(Cost), #limiter_context_LimitContext{
|
||||
payment_processing = #limiter_context_ContextPaymentProcessing{
|
||||
op = ?op_invoice(),
|
||||
invoice = #limiter_context_Invoice{
|
||||
created_at = <<"2000-01-01T00:00:00Z">>,
|
||||
cost = Cost
|
||||
}
|
||||
}
|
||||
}).
|
||||
|
||||
-define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)).
|
||||
|
||||
-define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{
|
||||
|
@ -19,6 +19,7 @@
|
||||
-export([partial_commit_with_exchange/1]).
|
||||
-export([commit_with_exchange/1]).
|
||||
-export([get_rate/1]).
|
||||
-export([get_limit_ok/1]).
|
||||
-export([get_limit_notfound/1]).
|
||||
-export([hold_ok/1]).
|
||||
-export([commit_ok/1]).
|
||||
@ -34,6 +35,11 @@
|
||||
-export([partial_commit_processes_idempotently/1]).
|
||||
-export([rollback_processes_idempotently/1]).
|
||||
|
||||
-export([commit_number_ok/1]).
|
||||
-export([rollback_number_ok/1]).
|
||||
-export([commit_refund_keep_number_unchanged/1]).
|
||||
-export([partial_commit_number_counts_as_single_op/1]).
|
||||
|
||||
-type group_name() :: atom().
|
||||
-type test_case_name() :: atom().
|
||||
|
||||
@ -45,6 +51,7 @@
|
||||
all() ->
|
||||
[
|
||||
{group, default},
|
||||
{group, cashless},
|
||||
{group, idempotency}
|
||||
].
|
||||
|
||||
@ -56,6 +63,7 @@ groups() ->
|
||||
partial_commit_with_exchange,
|
||||
commit_with_exchange,
|
||||
get_rate,
|
||||
get_limit_ok,
|
||||
get_limit_notfound,
|
||||
hold_ok,
|
||||
commit_ok,
|
||||
@ -66,6 +74,12 @@ groups() ->
|
||||
partial_commit_inexistent_hold_fails,
|
||||
commit_multirange_limit_ok
|
||||
]},
|
||||
{cashless, [parallel], [
|
||||
commit_number_ok,
|
||||
rollback_number_ok,
|
||||
commit_refund_keep_number_unchanged,
|
||||
partial_commit_number_counts_as_single_op
|
||||
]},
|
||||
{idempotency, [parallel], [
|
||||
commit_processes_idempotently,
|
||||
full_commit_processes_idempotently,
|
||||
@ -129,8 +143,7 @@ end_per_testcase(_Name, C) ->
|
||||
commit_with_default_exchange(C) ->
|
||||
Rational = #base_Rational{p = 1000000, q = 100},
|
||||
_ = mock_exchange(Rational, C),
|
||||
_ = configure_limit(?time_range_month(), ?global(), C),
|
||||
ID = ?config(id, C),
|
||||
ID = configure_limit(?time_range_month(), ?global(), C),
|
||||
Context = #limiter_context_LimitContext{
|
||||
payment_processing = #limiter_context_ContextPaymentProcessing{
|
||||
op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}},
|
||||
@ -150,8 +163,7 @@ commit_with_default_exchange(C) ->
|
||||
partial_commit_with_exchange(C) ->
|
||||
Rational = #base_Rational{p = 800000, q = 100},
|
||||
_ = mock_exchange(Rational, C),
|
||||
_ = configure_limit(?time_range_month(), ?global(), C),
|
||||
ID = ?config(id, C),
|
||||
ID = configure_limit(?time_range_month(), ?global(), C),
|
||||
Context = #limiter_context_LimitContext{
|
||||
payment_processing = #limiter_context_ContextPaymentProcessing{
|
||||
op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}},
|
||||
@ -177,8 +189,7 @@ partial_commit_with_exchange(C) ->
|
||||
commit_with_exchange(C) ->
|
||||
Rational = #base_Rational{p = 1000000, q = 100},
|
||||
_ = mock_exchange(Rational, C),
|
||||
_ = configure_limit(?time_range_month(), ?global(), C),
|
||||
ID = ?config(id, C),
|
||||
ID = configure_limit(?time_range_month(), ?global(), C),
|
||||
Context = #limiter_context_LimitContext{
|
||||
payment_processing = #limiter_context_ContextPaymentProcessing{
|
||||
op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}},
|
||||
@ -212,40 +223,33 @@ get_rate(C) ->
|
||||
WoodyContext
|
||||
).
|
||||
|
||||
-spec get_limit_ok(config()) -> _.
|
||||
get_limit_ok(C) ->
|
||||
ID = configure_limit(?time_range_month(), ?global(), C),
|
||||
Context = ?ctx_invoice(_Cost = undefined),
|
||||
?assertMatch(
|
||||
{ok, #limiter_Limit{amount = 0}},
|
||||
lim_client:get(ID, Context, ?config(client, C))
|
||||
).
|
||||
|
||||
-spec get_limit_notfound(config()) -> _.
|
||||
get_limit_notfound(C) ->
|
||||
_ = configure_limit(?time_range_month(), ?global(), C),
|
||||
Context = #limiter_context_LimitContext{
|
||||
payment_processing = #limiter_context_ContextPaymentProcessing{
|
||||
op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}},
|
||||
invoice = #limiter_context_Invoice{created_at = <<"2000-01-01T00:00:00Z">>}
|
||||
}
|
||||
},
|
||||
{exception, #limiter_LimitNotFound{}} = lim_client:get(?config(id, C), Context, ?config(client, C)).
|
||||
Context = ?ctx_invoice(_Cost = undefined),
|
||||
?assertEqual(
|
||||
{exception, #limiter_LimitNotFound{}},
|
||||
lim_client:get(<<"NOSUCHLIMITID">>, Context, ?config(client, C))
|
||||
).
|
||||
|
||||
-spec hold_ok(config()) -> _.
|
||||
hold_ok(C) ->
|
||||
_ = configure_limit(?time_range_month(), ?global(), C),
|
||||
ID = ?config(id, C),
|
||||
Context = #limiter_context_LimitContext{
|
||||
payment_processing = #limiter_context_ContextPaymentProcessing{
|
||||
op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}},
|
||||
invoice = #limiter_context_Invoice{
|
||||
created_at = <<"2000-01-01T00:00:00Z">>,
|
||||
cost = #limiter_base_Cash{
|
||||
amount = 10,
|
||||
currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ID = configure_limit(?time_range_month(), ?global(), C),
|
||||
Context = ?ctx_invoice(?cash(10)),
|
||||
{ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
|
||||
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)).
|
||||
|
||||
-spec commit_ok(config()) -> _.
|
||||
commit_ok(C) ->
|
||||
_ = configure_limit(?time_range_month(), ?global(), C),
|
||||
ID = ?config(id, C),
|
||||
ID = configure_limit(?time_range_month(), ?global(), C),
|
||||
Context = #limiter_context_LimitContext{
|
||||
payment_processing = #limiter_context_ContextPaymentProcessing{
|
||||
op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}},
|
||||
@ -263,8 +267,7 @@ commit_ok(C) ->
|
||||
|
||||
-spec rollback_ok(config()) -> _.
|
||||
rollback_ok(C) ->
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
ID = ?config(id, C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
Context0 = ?ctx_invoice_payment(?cash(10), ?cash(10)),
|
||||
Context1 = ?ctx_invoice_payment(?cash(10), ?cash(0)),
|
||||
Change = ?LIMIT_CHANGE(ID),
|
||||
@ -273,11 +276,10 @@ rollback_ok(C) ->
|
||||
|
||||
-spec refund_ok(config()) -> _.
|
||||
refund_ok(C) ->
|
||||
ID = ?config(id, C),
|
||||
Client = ?config(client, C),
|
||||
OwnerID = <<"WWWcool Ltd">>,
|
||||
ShopID = <<"shop">>,
|
||||
_ = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C),
|
||||
ID = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C),
|
||||
Context0 = ?ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)),
|
||||
RefundContext1 = ?ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)),
|
||||
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client),
|
||||
@ -287,13 +289,12 @@ refund_ok(C) ->
|
||||
|
||||
-spec get_config_ok(config()) -> _.
|
||||
get_config_ok(C) ->
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
{ok, #limiter_config_LimitConfig{}} = lim_client:get_config(?config(id, C), ?config(client, C)).
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
{ok, #limiter_config_LimitConfig{}} = lim_client:get_config(ID, ?config(client, C)).
|
||||
|
||||
-spec commit_inexistent_hold_fails(config()) -> _.
|
||||
commit_inexistent_hold_fails(C) ->
|
||||
ID = ?config(id, C),
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(42), undefined),
|
||||
% NOTE
|
||||
% We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter
|
||||
@ -303,8 +304,7 @@ commit_inexistent_hold_fails(C) ->
|
||||
|
||||
-spec partial_commit_inexistent_hold_fails(config()) -> _.
|
||||
partial_commit_inexistent_hold_fails(C) ->
|
||||
ID = ?config(id, C),
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(42), ?cash(21)),
|
||||
% NOTE
|
||||
% We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter
|
||||
@ -318,12 +318,11 @@ commit_multirange_limit_ok(C) ->
|
||||
Client = ?config(client, C),
|
||||
Params = #limiter_config_LimitConfigParams{
|
||||
id = ID,
|
||||
body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}},
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
shard_size = 12,
|
||||
time_range_type = {calendar, {month, #time_range_TimeRangeTypeCalendarMonth{}}},
|
||||
context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}},
|
||||
type = {turnover, #limiter_config_LimitTypeTurnover{}},
|
||||
time_range_type = ?time_range_month(),
|
||||
context_type = ?ctx_type_payproc(),
|
||||
type = ?lim_type_turnover(?turnover_metric_amount(<<"RUB">>)),
|
||||
scope = ?scope([]),
|
||||
op_behaviour = #limiter_config_OperationLimitBehaviour{}
|
||||
},
|
||||
@ -354,9 +353,8 @@ commit_multirange_limit_ok(C) ->
|
||||
|
||||
-spec commit_processes_idempotently(config()) -> _.
|
||||
commit_processes_idempotently(C) ->
|
||||
ID = ?config(id, C),
|
||||
Client = ?config(client, C),
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(42), undefined),
|
||||
Change = ?LIMIT_CHANGE(ID),
|
||||
{ok, _} = lim_client:hold(Change, Context, Client),
|
||||
@ -368,9 +366,8 @@ commit_processes_idempotently(C) ->
|
||||
|
||||
-spec full_commit_processes_idempotently(config()) -> _.
|
||||
full_commit_processes_idempotently(C) ->
|
||||
ID = ?config(id, C),
|
||||
Client = ?config(client, C),
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
Cost = ?cash(42),
|
||||
Context = ?ctx_invoice_payment(Cost, Cost),
|
||||
Change = ?LIMIT_CHANGE(ID),
|
||||
@ -383,9 +380,8 @@ full_commit_processes_idempotently(C) ->
|
||||
|
||||
-spec partial_commit_processes_idempotently(config()) -> _.
|
||||
partial_commit_processes_idempotently(C) ->
|
||||
ID = ?config(id, C),
|
||||
Client = ?config(client, C),
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(42), ?cash(40)),
|
||||
Change = ?LIMIT_CHANGE(ID),
|
||||
{ok, _} = lim_client:hold(Change, Context, Client),
|
||||
@ -397,9 +393,8 @@ partial_commit_processes_idempotently(C) ->
|
||||
|
||||
-spec rollback_processes_idempotently(config()) -> _.
|
||||
rollback_processes_idempotently(C) ->
|
||||
ID = ?config(id, C),
|
||||
Client = ?config(client, C),
|
||||
_ = configure_limit(?time_range_week(), ?global(), C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(42), ?cash(0)),
|
||||
Change = ?LIMIT_CHANGE(ID),
|
||||
{ok, _} = lim_client:hold(Change, Context, Client),
|
||||
@ -411,30 +406,98 @@ rollback_processes_idempotently(C) ->
|
||||
|
||||
%%
|
||||
|
||||
-spec commit_number_ok(config()) -> _.
|
||||
commit_number_ok(C) ->
|
||||
Client = ?config(client, C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(10), ?cash(10)),
|
||||
{ok, LimitState0} = lim_client:get(ID, Context, Client),
|
||||
_ = hold_and_commit(?LIMIT_CHANGE(ID), Context, Client),
|
||||
{ok, LimitState1} = lim_client:get(ID, Context, Client),
|
||||
?assertEqual(
|
||||
LimitState1#limiter_Limit.amount,
|
||||
LimitState0#limiter_Limit.amount + 1
|
||||
).
|
||||
|
||||
-spec rollback_number_ok(config()) -> _.
|
||||
rollback_number_ok(C) ->
|
||||
Client = ?config(client, C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(10), ?cash(10)),
|
||||
ContextRollback = ?ctx_invoice_payment(?cash(10), ?cash(0)),
|
||||
{ok, LimitState0} = lim_client:get(ID, Context, Client),
|
||||
_ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextRollback, Client),
|
||||
{ok, LimitState1} = lim_client:get(ID, Context, Client),
|
||||
?assertEqual(
|
||||
LimitState1#limiter_Limit.amount,
|
||||
LimitState0#limiter_Limit.amount
|
||||
).
|
||||
|
||||
-spec commit_refund_keep_number_unchanged(config()) -> _.
|
||||
commit_refund_keep_number_unchanged(C) ->
|
||||
Client = ?config(client, C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
|
||||
Cost = ?cash(10),
|
||||
CaptureCost = ?cash(8),
|
||||
RefundCost = ?cash(5),
|
||||
PaymentContext = ?ctx_invoice_payment(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost),
|
||||
RefundContext = ?ctx_invoice_payment_refund(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost, RefundCost),
|
||||
{ok, LimitState0} = lim_client:get(ID, PaymentContext, Client),
|
||||
_ = hold_and_commit(?LIMIT_CHANGE(ID, 1), PaymentContext, Client),
|
||||
_ = hold_and_commit(?LIMIT_CHANGE(ID, 2), RefundContext, Client),
|
||||
{ok, LimitState1} = lim_client:get(ID, PaymentContext, Client),
|
||||
?assertEqual(
|
||||
% Expected to be the same because refund decreases counter given limit config
|
||||
LimitState1#limiter_Limit.amount,
|
||||
LimitState0#limiter_Limit.amount
|
||||
).
|
||||
|
||||
-spec partial_commit_number_counts_as_single_op(config()) -> _.
|
||||
partial_commit_number_counts_as_single_op(C) ->
|
||||
Client = ?config(client, C),
|
||||
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
|
||||
Context = ?ctx_invoice_payment(?cash(10), ?cash(10)),
|
||||
ContextPartial = ?ctx_invoice_payment(?cash(10), ?cash(5)),
|
||||
{ok, LimitState0} = lim_client:get(ID, Context, Client),
|
||||
_ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextPartial, Client),
|
||||
{ok, LimitState1} = lim_client:get(ID, Context, Client),
|
||||
?assertEqual(
|
||||
LimitState1#limiter_Limit.amount,
|
||||
LimitState0#limiter_Limit.amount + 1
|
||||
).
|
||||
|
||||
%%
|
||||
|
||||
gen_change_id(LimitID, ChangeID) ->
|
||||
genlib:format("~s/~p", [LimitID, ChangeID]).
|
||||
|
||||
hold_and_commit(Change, Context, Client) ->
|
||||
hold_and_commit(Change, Context, Context, Client).
|
||||
|
||||
hold_and_commit(Change, Context, ContextCommit, Client) ->
|
||||
{ok, {vector, _}} = lim_client:hold(Change, Context, Client),
|
||||
{ok, {vector, _}} = lim_client:commit(Change, Context, Client).
|
||||
{ok, {vector, _}} = lim_client:commit(Change, ContextCommit, Client).
|
||||
|
||||
mock_exchange(Rational, C) ->
|
||||
lim_mock:mock_services([{xrates, fun('GetConvertedAmount', _) -> {ok, Rational} end}], C).
|
||||
|
||||
configure_limit(TimeRange, Scope, C) ->
|
||||
configure_limit(TimeRange, Scope, ?turnover_metric_amount(<<"RUB">>), C).
|
||||
|
||||
configure_limit(TimeRange, Scope, Metric, C) ->
|
||||
ID = ?config(id, C),
|
||||
Params = #limiter_config_LimitConfigParams{
|
||||
id = ID,
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
body_type = ?body_type_cash(<<"RUB">>),
|
||||
time_range_type = TimeRange,
|
||||
shard_size = 1,
|
||||
type = ?lim_type_turnover(),
|
||||
type = ?lim_type_turnover(Metric),
|
||||
scope = Scope,
|
||||
context_type = ?ctx_type_payproc(),
|
||||
op_behaviour = ?op_behaviour(?op_subtraction())
|
||||
},
|
||||
{ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)).
|
||||
{ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)),
|
||||
ID.
|
||||
|
||||
gen_unique_id(Prefix) ->
|
||||
genlib:format("~s/~B", [Prefix, lim_time:now()]).
|
||||
|
@ -25,7 +25,8 @@
|
||||
% readability.
|
||||
{elvis_style, used_ignored_variable, disable},
|
||||
% Tests are usually more comprehensible when a bit more verbose.
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 20}}
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 20}},
|
||||
{elvis_style, god_modules, #{limit => 50}}
|
||||
]
|
||||
},
|
||||
#{
|
||||
|
@ -25,7 +25,7 @@
|
||||
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1},
|
||||
{<<"limiter_proto">>,
|
||||
{git,"https://github.com/valitydev/limiter-proto.git",
|
||||
{ref,"d390910cd246f2356f10c2db410ecf93e55eff4d"}},
|
||||
{ref,"6723e862157a7f78194a64271899c2ef1581e177"}},
|
||||
0},
|
||||
{<<"machinery">>,
|
||||
{git,"https://github.com/valitydev/machinery-erlang.git",
|
||||
|
Loading…
Reference in New Issue
Block a user