ED-156: integration with limiter (#574)

This commit is contained in:
Boris 2021-06-23 14:08:28 +03:00 committed by GitHub
parent ce90c673ba
commit 6f6be75b32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 570 additions and 376 deletions

View File

@ -24,7 +24,8 @@
erl_health,
party_management,
prometheus,
prometheus_cowboy
prometheus_cowboy,
limiter_proto
]},
{env, []},
{modules, []},

View File

@ -1871,21 +1871,20 @@ process_cash_flow_building(Action, St) ->
Payment = get_payment(St),
Invoice = get_invoice(Opts),
Route = get_route(St),
Cash = Payment#domain_InvoicePayment.cost,
Timestamp = get_payment_created_at(Payment),
VS0 = reconstruct_payment_flow(Payment, #{}),
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS1),
ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision),
{ok, TurnoverLimits} = hold_payment_limits(ProviderTerms, Cash, Timestamp, St),
TurnoverLimits = get_turnover_limits(ProviderTerms),
ok = hg_limiter:hold_payment_limits(TurnoverLimits, Invoice, Payment),
FinalCashflow = calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS1, Revision, Opts),
_Clock = hg_accounting:hold(
construct_payment_plan_id(Invoice, Payment),
{1, FinalCashflow}
),
Events = [?cash_flow_changed(FinalCashflow)],
case hg_limiter:check_limits(TurnoverLimits, Timestamp) of
case hg_limiter:check_limits(TurnoverLimits, Invoice, Payment) of
{ok, _} ->
{next, {Events, hg_machine_action:set_timeout(0, Action)}};
{error, {limit_overflow, _}} ->
@ -2491,94 +2490,60 @@ try_request_interaction(undefined) ->
try_request_interaction(UserInteraction) ->
[?interaction_requested(UserInteraction)].
hold_payment_limits(ProviderTerms, Cash, Timestamp, St) ->
LimitChangeID = construct_limit_change_id(St),
get_provider_terms(St, Revision) ->
Opts = get_opts(St),
Route = get_route(St),
Payment = get_payment(St),
VS0 = reconstruct_payment_flow(Payment, #{}),
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
get_provider_terminal_terms(Route, VS1, Revision).
get_turnover_limits(ProviderTerms) ->
TurnoverLimitSelector = ProviderTerms#domain_PaymentsProvisionTerms.turnover_limits,
TurnoverLimits = hg_limiter:get_turnover_limits(TurnoverLimitSelector),
IDs = [T#domain_TurnoverLimit.id || T <- TurnoverLimits],
ok = hg_limiter:hold(construct_limit_change(IDs, LimitChangeID, Cash, Timestamp)),
{ok, TurnoverLimits}.
hg_limiter:get_turnover_limits(TurnoverLimitSelector).
commit_payment_limits(#st{capture_params = CaptureParams} = St) ->
Revision = get_payment_revision(St),
Invoice = get_invoice(get_opts(St)),
Payment = get_payment(St),
#payproc_InvoicePaymentCaptureParams{cash = CapturedCash} = CaptureParams,
#domain_InvoicePayment{cost = #domain_Cash{amount = PaymentAmount}} = get_payment(St),
case CapturedCash of
#domain_Cash{amount = Amount} when Amount < PaymentAmount ->
LimitChangeID = construct_limit_change_id(St),
LimitChanges = construct_limit_change(LimitChangeID, CapturedCash, St),
hg_limiter:partial_commit(LimitChanges);
_ ->
LimitChanges = construct_payment_limit_change(St),
hg_limiter:commit(LimitChanges)
end.
ProviderTerms = get_provider_terms(St, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms),
hg_limiter:commit_payment_limits(TurnoverLimits, Invoice, Payment, CapturedCash).
rollback_payment_limits(St) ->
LimitChanges = construct_payment_limit_change(St),
ok = hg_limiter:rollback(LimitChanges).
Revision = get_payment_revision(St),
Invoice = get_invoice(get_opts(St)),
Payment = get_payment(St),
ProviderTerms = get_provider_terms(St, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms),
hg_limiter:rollback_payment_limits(TurnoverLimits, Invoice, Payment).
hold_refund_limits(RefundSt, St) ->
hg_limiter:hold(construct_refund_limit_change(RefundSt, St)).
Invoice = get_invoice(get_opts(St)),
Payment = get_payment(St),
Refund = get_refund(RefundSt),
ProviderTerms = get_provider_terms(St, get_refund_revision(RefundSt)),
TurnoverLimits = get_turnover_limits(ProviderTerms),
hg_limiter:hold_refund_limits(TurnoverLimits, Invoice, Payment, Refund).
commit_refund_limits(RefundSt, St) ->
hg_limiter:commit(construct_refund_limit_change(RefundSt, St)).
Revision = get_refund_revision(RefundSt),
Invoice = get_invoice(get_opts(St)),
Refund = get_refund(RefundSt),
Payment = get_payment(St),
ProviderTerms = get_provider_terms(St, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms),
hg_limiter:commit_refund_limits(TurnoverLimits, Invoice, Payment, Refund).
rollback_refund_limits(RefundSt, St) ->
hg_limiter:rollback(construct_refund_limit_change(RefundSt, St)).
construct_limit_change_id(St) ->
Opts = get_opts(St),
Payment = get_payment(St),
Invoice = get_invoice(Opts),
ComplexID = hg_utils:construct_complex_id([
get_invoice_id(Invoice),
get_payment_id(Payment)
]),
genlib_string:join($., [<<"limiter">>, ComplexID]).
construct_refund_limit_change_id(RefundSt, St) ->
ComplexID = construct_refund_plan_id(RefundSt, St),
genlib_string:join($., [<<"limiter">>, ComplexID]).
construct_refund_limit_change(RefundSt, St) ->
Refund = get_refund(RefundSt),
RefundCash0 = Refund#domain_InvoicePaymentRefund.cash,
RefundCashAmount = RefundCash0#domain_Cash.amount,
RefundCash1 = RefundCash0#domain_Cash{amount = -RefundCashAmount},
LimitChangeID = construct_refund_limit_change_id(RefundSt, St),
construct_limit_change(LimitChangeID, RefundCash1, St).
construct_payment_limit_change(St) ->
LimitChangeID = construct_limit_change_id(St),
Revision = get_refund_revision(RefundSt),
Invoice = get_invoice(get_opts(St)),
Cash = Invoice#domain_Invoice.cost,
construct_limit_change(LimitChangeID, Cash, St).
construct_limit_change(LimitChangeID, Cash, St) ->
Timestamp = get_payment_created_at(get_payment(St)),
construct_limit_change(get_limit_ids(St), LimitChangeID, Cash, Timestamp).
construct_limit_change(IDs, LimitChangeID, Cash, Timestamp) ->
[
#proto_limiter_LimitChange{
id = LimitID,
change_id = LimitChangeID,
cash = Cash,
operation_timestamp = Timestamp
}
|| LimitID <- IDs
].
get_limit_ids(St) ->
Opts = get_opts(St),
Revision = get_payment_revision(St),
Refund = get_refund(RefundSt),
Payment = get_payment(St),
Route = get_route(St),
VS0 = reconstruct_payment_flow(Payment, #{}),
Varset = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
ProviderTerms = get_provider_terminal_terms(Route, Varset, Revision),
TurnoverLimitSelector = ProviderTerms#domain_PaymentsProvisionTerms.turnover_limits,
TurnoverLimits = hg_limiter:get_turnover_limits(TurnoverLimitSelector),
[T#domain_TurnoverLimit.id || T <- TurnoverLimits].
ProviderTerms = get_provider_terms(St, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms),
hg_limiter:rollback_refund_limits(TurnoverLimits, Invoice, Payment, Refund).
commit_payment_cashflow(St) ->
hg_accounting:commit(construct_payment_plan_id(St), get_cashflow_plan(St)).
@ -3474,6 +3439,9 @@ get_payment_revision(#st{payment = #domain_InvoicePayment{domain_revision = Revi
get_payment_payer(#st{payment = #domain_InvoicePayment{payer = Payer}}) ->
Payer.
get_refund_revision(#refund_st{refund = #domain_InvoicePaymentRefund{domain_revision = Revision}}) ->
Revision.
%%
get_activity_session(St) ->

View File

@ -1,24 +1,25 @@
-module(hg_limiter).
-include_lib("limiter_proto/include/lim_limiter_thrift.hrl").
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
-include_lib("damsel/include/dmsl_proto_limiter_thrift.hrl").
-type timestamp() :: binary().
-type turnover_selector() :: dmsl_domain_thrift:'TurnoverLimitSelector'().
-type turnover_limit() :: dmsl_domain_thrift:'TurnoverLimit'().
-type invoice() :: dmsl_domain_thrift:'Invoice'().
-type payment() :: dmsl_domain_thrift:'InvoicePayment'().
-type refund() :: dmsl_domain_thrift:'InvoicePaymentRefund'().
-type cash() :: dmsl_domain_thrift:'Cash'().
-type turnover_selector() :: dmsl_domain_thrift:'TurnoverLimitSelector'() | undefined.
-type turnover_limit() :: dmsl_domain_thrift:'TurnoverLimit'().
-export([get_turnover_limits/1]).
-export([check_limits/2]).
-export([hold/1]).
-export([hold/4]).
-export([commit/1]).
-export([partial_commit/1]).
-export([rollback/1]).
-export([rollback/4]).
-export([check_limits/3]).
-export([hold_payment_limits/3]).
-export([hold_refund_limits/4]).
-export([commit_payment_limits/4]).
-export([commit_refund_limits/4]).
-export([rollback_payment_limits/3]).
-export([rollback_refund_limits/4]).
-spec get_turnover_limits(turnover_selector()) -> [turnover_limit()].
-spec get_turnover_limits(turnover_selector() | undefined) -> [turnover_limit()].
get_turnover_limits(undefined) ->
logger:info("Operation limits haven't been set on provider terms."),
[];
@ -27,72 +28,262 @@ get_turnover_limits({value, Limits}) ->
get_turnover_limits(Ambiguous) ->
error({misconfiguration, {'Could not reduce selector to a value', Ambiguous}}).
-spec check_limits([turnover_limit()], timestamp()) ->
-spec check_limits([turnover_limit()], invoice(), payment()) ->
{ok, [hg_limiter_client:limit()]}
| {error, {limit_overflow, [binary()]}}.
check_limits(TurnoverLimits, Timestamp) ->
check_limits(TurnoverLimits, Invoice, Payment) ->
Context = gen_limit_context(Invoice, Payment),
try
check_limits(TurnoverLimits, Timestamp, [])
check_limits_(TurnoverLimits, Context, [])
catch
throw:limit_overflow ->
IDs = [T#domain_TurnoverLimit.id || T <- TurnoverLimits],
{error, {limit_overflow, IDs}}
end.
check_limits([], _, Limits) ->
check_limits_([], _, Limits) ->
{ok, Limits};
check_limits([T | TurnoverLimits], Timestamp, Acc) ->
check_limits_([T | TurnoverLimits], Context, Acc) ->
#domain_TurnoverLimit{id = LimitID} = T,
Limit = hg_limiter_client:get(LimitID, Timestamp),
#proto_limiter_Limit{
id = LimitID,
cash = Cash
Clock = get_latest_clock(),
Limit = hg_limiter_client:get(LimitID, Clock, Context),
#limiter_Limit{
amount = LimiterAmount
} = Limit,
LimiterAmount = Cash#domain_Cash.amount,
UpperBoundary = T#domain_TurnoverLimit.upper_boundary,
case LimiterAmount < UpperBoundary of
true ->
check_limits(TurnoverLimits, Timestamp, [Limit | Acc]);
check_limits_(TurnoverLimits, Context, [Limit | Acc]);
false ->
logger:info("Limit with id ~p overflowed", [LimitID]),
logger:info("Limit with id ~p overflowed, amount ~p upper boundary ~p", [
LimitID,
LimiterAmount,
UpperBoundary
]),
throw(limit_overflow)
end.
-spec hold([hg_limiter_client:limit()], hg_limiter_client:change_id(), cash(), timestamp()) -> ok.
hold(Limits, LimitChangeID, Cash, Timestamp) ->
hold(gen_limit_changes(Limits, LimitChangeID, Cash, Timestamp)).
-spec hold_payment_limits([turnover_limit()], invoice(), payment()) -> ok.
hold_payment_limits(TurnoverLimits, Invoice, Payment) ->
IDs = [T#domain_TurnoverLimit.id || T <- TurnoverLimits],
LimitChanges = gen_limit_payment_changes(IDs, Invoice, Payment),
Context = gen_limit_context(Invoice, Payment),
hold(LimitChanges, get_latest_clock(), Context).
-spec hold([hg_limiter_client:limit_change()]) -> ok.
hold(LimitChanges) ->
lists:foreach(fun hg_limiter_client:hold/1, LimitChanges).
-spec hold_refund_limits([turnover_limit()], invoice(), payment(), refund()) -> ok.
hold_refund_limits(TurnoverLimits, Invoice, Payment, Refund) ->
IDs = [T#domain_TurnoverLimit.id || T <- TurnoverLimits],
LimitChanges = gen_limit_refund_changes(IDs, Invoice, Payment, Refund),
Context = gen_limit_refund_context(Invoice, Payment, Refund),
hold(LimitChanges, get_latest_clock(), Context).
-spec commit([hg_limiter_client:limit_change()]) -> ok.
commit(LimitChanges) ->
lists:foreach(fun hg_limiter_client:commit/1, LimitChanges).
-spec commit_payment_limits([turnover_limit()], invoice(), payment(), cash() | undefined) -> ok.
commit_payment_limits(TurnoverLimits, Invoice, Payment, CapturedCash) ->
IDs = [T#domain_TurnoverLimit.id || T <- TurnoverLimits],
LimitChanges = gen_limit_payment_changes(IDs, Invoice, Payment),
Context = gen_limit_context(Invoice, Payment, CapturedCash),
commit(LimitChanges, get_latest_clock(), Context).
-spec partial_commit([hg_limiter_client:limit_change()]) -> ok.
partial_commit(LimitChanges) ->
lists:foreach(fun hg_limiter_client:partial_commit/1, LimitChanges).
-spec commit_refund_limits([turnover_limit()], invoice(), payment(), refund()) -> ok.
commit_refund_limits(TurnoverLimits, Invoice, Payment, Refund) ->
IDs = [T#domain_TurnoverLimit.id || T <- TurnoverLimits],
LimitChanges = gen_limit_refund_changes(IDs, Invoice, Payment, Refund),
Context = gen_limit_refund_context(Invoice, Payment, Refund),
commit(LimitChanges, get_latest_clock(), Context).
-spec rollback([hg_limiter_client:limit()], hg_limiter_client:change_id(), cash(), timestamp()) -> ok.
rollback(Limits, LimitChangeID, Cash, Timestamp) ->
rollback(
gen_limit_changes(Limits, LimitChangeID, Cash, Timestamp)
-spec rollback_payment_limits([turnover_limit()], invoice(), payment()) -> ok.
rollback_payment_limits(TurnoverLimits, Invoice, Payment) ->
#domain_InvoicePayment{cost = Cash} = Payment,
CapturedCash = Cash#domain_Cash{
amount = 0
},
commit_payment_limits(TurnoverLimits, Invoice, Payment, CapturedCash).
-spec rollback_refund_limits([turnover_limit()], invoice(), payment(), refund()) -> ok.
rollback_refund_limits(TurnoverLimits, Invoice, Payment, Refund0) ->
Refund1 = set_refund_amount(0, Refund0),
commit_refund_limits(TurnoverLimits, Invoice, Payment, Refund1).
-spec hold([hg_limiter_client:limit_change()], hg_limiter_client:clock(), hg_limiter_client:context()) -> ok.
hold(LimitChanges, Clock, Context) ->
lists:foreach(
fun(LimitChange) ->
hg_limiter_client:hold(LimitChange, Clock, Context)
end,
LimitChanges
).
-spec rollback([hg_limiter_client:limit_change()]) -> ok.
rollback(LimitChanges) ->
lists:foreach(fun hg_limiter_client:rollback/1, LimitChanges).
-spec commit([hg_limiter_client:limit_change()], hg_limiter_client:clock(), hg_limiter_client:context()) -> ok.
commit(LimitChanges, Clock, Context) ->
lists:foreach(
fun(LimitChange) ->
hg_limiter_client:commit(LimitChange, Clock, Context)
end,
LimitChanges
).
-spec gen_limit_changes([hg_limiter_client:limit()], hg_limiter_client:change_id(), cash(), timestamp()) ->
[hg_limiter_client:limit_change()].
gen_limit_changes(Limits, LimitChangeID, Cash, Timestamp) ->
[
#proto_limiter_LimitChange{
id = Limit#proto_limiter_Limit.id,
change_id = LimitChangeID,
cash = Cash,
operation_timestamp = Timestamp
gen_limit_context(Invoice, Payment) ->
gen_limit_context(Invoice, Payment, undefined).
gen_limit_context(Invoice, Payment, CapturedCash) ->
InvoiceCtx = marshal(invoice, Invoice),
PaymentCtx0 = marshal(payment, Payment),
PaymentCtx1 = PaymentCtx0#limiter_context_InvoicePayment{
capture_cost = maybe_marshal(cost, CapturedCash)
},
#limiter_context_LimitContext{
payment_processing = #limiter_context_ContextPaymentProcessing{
op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}},
invoice = InvoiceCtx#limiter_context_Invoice{effective_payment = PaymentCtx1}
}
|| Limit <- Limits
}.
gen_limit_refund_context(Invoice, Payment, Refund) ->
InvoiceCtx = marshal(invoice, Invoice),
PaymentCtx0 = marshal(payment, Payment),
RefundCtx = marshal(refund, Refund),
PaymentCtx1 = PaymentCtx0#limiter_context_InvoicePayment{
effective_refund = RefundCtx
},
#limiter_context_LimitContext{
payment_processing = #limiter_context_ContextPaymentProcessing{
op = {invoice_payment_refund, #limiter_context_PaymentProcessingOperationInvoicePaymentRefund{}},
invoice = InvoiceCtx#limiter_context_Invoice{effective_payment = PaymentCtx1}
}
}.
gen_limit_payment_changes(LimitIDs, Invoice, Payment) ->
[
#limiter_LimitChange{
id = ID,
change_id = construct_limit_change_id(ID, Invoice, Payment)
}
|| ID <- LimitIDs
].
gen_limit_refund_changes(LimitIDs, Invoice, Payment, Refund) ->
[
#limiter_LimitChange{
id = ID,
change_id = construct_limit_refund_change_id(ID, Invoice, Payment, Refund)
}
|| ID <- LimitIDs
].
construct_limit_change_id(LimitID, Invoice, Payment) ->
ComplexID = hg_utils:construct_complex_id([
LimitID,
get_invoice_id(Invoice),
get_payment_id(Payment)
]),
genlib_string:join($., [<<"limiter">>, ComplexID]).
construct_limit_refund_change_id(LimitID, Invoice, Payment, Refund) ->
ComplexID = hg_utils:construct_complex_id([
LimitID,
get_invoice_id(Invoice),
get_payment_id(Payment),
{refund_session, get_refund_id(Refund)}
]),
genlib_string:join($., [<<"limiter">>, ComplexID]).
get_invoice_id(#domain_Invoice{id = ID}) ->
ID.
get_payment_id(#domain_InvoicePayment{id = ID}) ->
ID.
get_refund_id(#domain_InvoicePaymentRefund{id = ID}) ->
ID.
get_latest_clock() ->
{latest, #limiter_LatestClock{}}.
set_refund_amount(Amount, Refund) ->
IFDefined = fun
(undefined, _) ->
undefined;
(Value, Fun) ->
Fun(Value)
end,
#domain_InvoicePaymentRefund{
cash = Cash,
cart = InvoiceCart
} = Refund,
Refund#domain_InvoicePaymentRefund{
cash = IFDefined(Cash, fun(C) -> set_cash_amount(Amount, C) end),
cart = IFDefined(InvoiceCart, fun(Cart) ->
#domain_InvoiceCart{lines = Lines} = Cart,
lists:foldl(
fun(Line, Acc) ->
#domain_InvoiceLine{price = Price} = Line,
Line1 = Line#domain_InvoiceLine{
price = set_cash_amount(Amount, Price)
},
[Line1 | Acc]
end,
[],
Lines
)
end)
}.
set_cash_amount(Amount, Cash) ->
Cash#domain_Cash{amount = Amount}.
marshal(invoice, Invoice) ->
#limiter_context_Invoice{
id = Invoice#domain_Invoice.id,
owner_id = Invoice#domain_Invoice.owner_id,
shop_id = Invoice#domain_Invoice.shop_id,
cost = marshal(cost, Invoice#domain_Invoice.cost),
created_at = Invoice#domain_Invoice.created_at
};
marshal(payment, Payment) ->
#limiter_context_InvoicePayment{
id = Payment#domain_InvoicePayment.id,
owner_id = Payment#domain_InvoicePayment.owner_id,
shop_id = Payment#domain_InvoicePayment.shop_id,
cost = marshal(cost, Payment#domain_InvoicePayment.cost),
created_at = Payment#domain_InvoicePayment.created_at,
flow = marshal(flow, Payment#domain_InvoicePayment.flow),
payer = marshal(payer, Payment#domain_InvoicePayment.payer)
};
marshal(refund, Refund) ->
#limiter_context_InvoicePaymentRefund{
id = Refund#domain_InvoicePaymentRefund.id,
cost = marshal(cost, Refund#domain_InvoicePaymentRefund.cash),
created_at = Refund#domain_InvoicePaymentRefund.created_at
};
marshal(cost, Cost) ->
#limiter_base_Cash{
amount = Cost#domain_Cash.amount,
currency = marshal(currency, Cost#domain_Cash.currency)
};
marshal(currency, #domain_CurrencyRef{symbolic_code = Currency}) ->
#limiter_base_CurrencyRef{
symbolic_code = Currency
};
marshal(flow, Flow) ->
case Flow of
{instant, #domain_InvoicePaymentFlowInstant{}} ->
{instant, #limiter_context_InvoicePaymentFlowInstant{}};
{hold, #domain_InvoicePaymentFlowHold{}} ->
{hold, #limiter_context_InvoicePaymentFlowHold{}}
end;
marshal(payer, Payer) ->
case Payer of
{payment_resource, #domain_PaymentResourcePayer{}} ->
{payment_resource, #limiter_context_PaymentResourcePayer{}};
{customer, #domain_CustomerPayer{}} ->
{customer, #limiter_context_CustomerPayer{}};
{recurrent, #domain_RecurrentPayer{}} ->
{recurrent, #limiter_context_RecurrentPayer{}}
end.
maybe_marshal(_, undefined) ->
undefined;
maybe_marshal(Type, Value) ->
marshal(Type, Value).

View File

@ -1,92 +1,66 @@
-module(hg_limiter_client).
-include_lib("damsel/include/dmsl_proto_limiter_thrift.hrl").
-include_lib("limiter_proto/include/lim_limiter_thrift.hrl").
-type limit_id() :: dmsl_proto_limiter_thrift:'LimitID'().
-type change_id() :: dmsl_proto_limiter_thrift:'LimitChangeID'().
-type limit() :: dmsl_proto_limiter_thrift:'Limit'().
-type limit_change() :: dmsl_proto_limiter_thrift:'LimitChange'().
-export([get/3]).
-export([hold/3]).
-export([commit/3]).
-type timestamp() :: binary().
-type limit() :: lim_limiter_thrift:'Limit'().
-type limit_id() :: lim_limiter_thrift:'LimitID'().
-type limit_change() :: lim_limiter_thrift:'LimitChange'().
-type context() :: lim_limiter_thrift:'LimitContext'().
-type clock() :: lim_limiter_thrift:'Clock'().
-export_type([limit/0]).
-export_type([limit_id/0]).
-export_type([change_id/0]).
-export_type([limit_change/0]).
-export_type([context/0]).
-export_type([clock/0]).
-export([get/2]).
-export([hold/1]).
-export([commit/1]).
-export([partial_commit/1]).
-export([rollback/1]).
-spec get(limit_id(), timestamp()) -> limit().
get(LimitID, Timestamp) ->
Args = {LimitID, Timestamp},
-spec get(limit_id(), clock(), context()) -> limit() | no_return().
get(LimitID, Clock, Context) ->
Args = {LimitID, Clock, Context},
Opts = hg_woody_wrapper:get_service_options(limiter),
case hg_woody_wrapper:call(limiter, 'Get', Args, Opts) of
{ok, Limit} ->
Limit;
{exception, #proto_limiter_LimitNotFound{}} ->
{exception, #limiter_LimitNotFound{}} ->
error({not_found, LimitID});
{exception, #'InvalidRequest'{errors = Errors}} ->
{exception, #'limiter_base_InvalidRequest'{errors = Errors}} ->
error({invalid_request, Errors})
end.
-spec hold(limit_change()) -> ok.
hold(LimitChange) ->
LimitID = LimitChange#proto_limiter_LimitChange.id,
-spec hold(limit_change(), clock(), context()) -> clock() | no_return().
hold(LimitChange, Clock, Context) ->
LimitID = LimitChange#limiter_LimitChange.id,
Opts = hg_woody_wrapper:get_service_options(limiter),
case hg_woody_wrapper:call(limiter, 'Hold', {LimitChange}, Opts) of
{ok, ok} ->
ok;
{exception, #proto_limiter_LimitNotFound{}} ->
Args = {LimitChange, Clock, Context},
case hg_woody_wrapper:call(limiter, 'Hold', Args, Opts) of
{ok, ClockUpdated} ->
ClockUpdated;
{exception, #limiter_LimitNotFound{}} ->
error({not_found, LimitID});
{exception, #'InvalidRequest'{errors = Errors}} ->
{exception, #'limiter_base_InvalidRequest'{errors = Errors}} ->
error({invalid_request, Errors})
end.
-spec commit(limit_change()) -> ok.
commit(LimitChange) ->
-spec commit(limit_change(), clock(), context()) -> clock() | no_return().
commit(LimitChange, Clock, Context) ->
Opts = hg_woody_wrapper:get_service_options(limiter),
case hg_woody_wrapper:call(limiter, 'Commit', {LimitChange}, Opts) of
{ok, ok} ->
ok;
{exception, #proto_limiter_LimitNotFound{}} ->
error({not_found, LimitChange#proto_limiter_LimitChange.id});
{exception, #proto_limiter_LimitChangeNotFound{}} ->
error({not_found, {limit_change, LimitChange#proto_limiter_LimitChange.change_id}});
{exception, #'InvalidRequest'{errors = Errors}} ->
error({invalid_request, Errors})
end.
-spec partial_commit(limit_change()) -> ok.
partial_commit(LimitChange) ->
Opts = hg_woody_wrapper:get_service_options(limiter),
case hg_woody_wrapper:call(limiter, 'PartialCommit', {LimitChange}, Opts) of
{ok, ok} ->
ok;
{exception, #proto_limiter_LimitNotFound{}} ->
error({not_found, LimitChange#proto_limiter_LimitChange.id});
{exception, #proto_limiter_LimitChangeNotFound{}} ->
error({not_found, {limit_change, LimitChange#proto_limiter_LimitChange.change_id}});
{exception, #proto_limiter_ForbiddenOperationAmount{amount = Amount, allowed_range = CashRange}} ->
error({forbidden_operation_amount, {Amount, CashRange}});
{exception, #'InvalidRequest'{errors = Errors}} ->
error({invalid_request, Errors})
end.
-spec rollback(limit_change()) -> ok.
rollback(LimitChange) ->
LimitID = LimitChange#proto_limiter_LimitChange.id,
Opts = hg_woody_wrapper:get_service_options(limiter),
case hg_woody_wrapper:call(limiter, 'Rollback', {LimitChange}, Opts) of
{ok, ok} ->
ok;
{exception, #proto_limiter_LimitNotFound{}} ->
error({not_found, LimitID});
{exception, #proto_limiter_LimitChangeNotFound{}} ->
error({not_found, {limit_change, LimitChange#proto_limiter_LimitChange.change_id}});
{exception, #'InvalidRequest'{errors = Errors}} ->
error({invalid_request, Errors})
Args = {LimitChange, Clock, Context},
case hg_woody_wrapper:call(limiter, 'Commit', Args, Opts) of
{ok, ClockUpdated} ->
ClockUpdated;
{exception, #limiter_LimitNotFound{}} ->
error({not_found, LimitChange#limiter_LimitChange.id});
{exception, #limiter_LimitChangeNotFound{}} ->
error({not_found, {limit_change, LimitChange#limiter_LimitChange.change_id}});
{exception, #limiter_base_InvalidRequest{errors = Errors}} ->
error({invalid_request, Errors});
{exception, #limiter_ForbiddenOperationAmount{} = Ex} ->
Amount = Ex#limiter_ForbiddenOperationAmount.amount,
CashRange = Ex#limiter_ForbiddenOperationAmount.allowed_range,
error({forbidden_op_amount, {Amount, CashRange}})
end.

View File

@ -194,7 +194,7 @@ start_app(hellgate = AppName) ->
}
},
limiter => #{
url => <<"http://127.0.0.1:30001/test/proxy/limiter/dummy">>,
url => <<"http://limiter:8022/v1/limiter">>,
transport_opts => #{}
}
}},

View File

@ -1,113 +1,76 @@
-module(hg_dummy_limiter).
-behaviour(hg_woody_wrapper).
-include_lib("limiter_proto/include/lim_limiter_thrift.hrl").
-include_lib("limiter_proto/include/lim_configurator_thrift.hrl").
-include_lib("hellgate/include/domain.hrl").
-export([new/0]).
-export([get/3]).
-export([hold/3]).
-export([commit/3]).
-type limit_id() :: binary().
-type amount() :: integer().
-export([create_config/2]).
-export([get_config/2]).
-export([handle_function/3]).
-export([get_port/0]).
-type client() :: woody_context:ctx().
-export([get_service_spec/0]).
-export([get_http_cowboy_spec/0]).
-export([init/0]).
-export([init/2]).
-export([delete/0]).
-export([get_amount/1]).
-type limit_id() :: lim_limiter_thrift:'LimitID'().
-type limit_change() :: lim_limiter_thrift:'LimitChange'().
-type limit_context() :: lim_limiter_thrift:'LimitContext'().
-type clock() :: lim_limiter_thrift:'Clock'().
-type limit_config_params() :: lim_configurator_thrift:'LimitCreateParams'().
-include_lib("damsel/include/dmsl_proto_limiter_thrift.hrl").
%%% API
-define(COWBOY_PORT, 30001).
-spec new() -> client().
new() ->
woody_context:new().
-define(limit(LimitID, Cash, Timestamp), #proto_limiter_Limit{
id = LimitID,
cash = Cash,
creation_time = Timestamp
}).
-spec get(limit_id(), limit_context(), client()) -> woody:result() | no_return().
get(LimitID, Context, Client) ->
call('Get', {LimitID, clock(), Context}, Client).
-spec get_port() -> integer().
get_port() ->
?COWBOY_PORT.
-spec hold(limit_change(), limit_context(), client()) -> woody:result() | no_return().
hold(LimitChange, Context, Client) ->
call('Hold', {LimitChange, clock(), Context}, Client).
-spec init() -> ok.
init() ->
hg_kv_store:put(limiter, #{}).
-spec commit(limit_change(), limit_context(), client()) -> woody:result() | no_return().
commit(LimitChange, Context, Client) ->
call('Commit', {LimitChange, clock(), Context}, Client).
-spec init(limit_id(), amount()) -> ok.
init(LimitID, Amount) ->
hg_kv_store:put(limiter, #{
LimitID => #{hold => false, amount => Amount}
}).
-spec create_config(limit_config_params(), client()) -> woody:result() | no_return().
create_config(LimitCreateParams, Client) ->
call_configurator('Create', {LimitCreateParams}, Client).
-spec delete() -> ok.
delete() ->
hg_kv_store:put(limiter, undefined).
-spec get_config(limit_id(), client()) -> woody:result() | no_return().
get_config(LimitConfigID, Client) ->
call_configurator('Get', {LimitConfigID}, Client).
-spec get_amount(limit_id()) -> integer().
get_amount(ID) ->
L = hg_kv_store:get(limiter),
case maps:get(ID, L, undefined) of
undefined ->
0;
Limit ->
maps:get(amount, Limit)
end.
%%% Internal functions
set_hold(LimitID, Flag) when is_boolean(Flag) ->
hg_kv_store:update(limiter, fun(Limiter) ->
L = maps:get(LimitID, Limiter, #{amount => 0}),
Limiter#{
LimitID => L#{hold => Flag}
-spec call(atom(), tuple(), client()) -> woody:result() | no_return().
call(Function, Args, Client) ->
Call = {{lim_limiter_thrift, 'Limiter'}, Function, Args},
Opts = #{
url => <<"http://limiter:8022/v1/limiter">>,
event_handler => scoper_woody_event_handler,
transport_opts => #{
max_connections => 10000
}
end).
},
woody_client:call(Call, Opts, Client).
commit_hold(ID, Amount) ->
hg_kv_store:update(limiter, fun(L) ->
case maps:get(ID, L) of
#{hold := false} ->
error;
#{hold := true, amount := A} ->
L#{
ID => #{hold => false, amount => A + Amount}
}
end
end).
-spec call_configurator(atom(), tuple(), client()) -> woody:result() | no_return().
call_configurator(Function, Args, Client) ->
Call = {{lim_configurator_thrift, 'Configurator'}, Function, Args},
Opts = #{
url => <<"http://limiter:8022/v1/configurator">>,
event_handler => scoper_woody_event_handler,
transport_opts => #{
max_connections => 10000
}
},
woody_client:call(Call, Opts, Client).
-spec handle_function(woody:func(), woody:args(), hg_woody_wrapper:handler_opts()) -> term() | no_return().
handle_function('Get', {LimitID, Timestamp}, _Opts) ->
Amount = get_amount(LimitID),
?limit(LimitID, ?cash(Amount, <<"RUB">>), Timestamp);
handle_function('Hold', {#proto_limiter_LimitChange{id = ID}}, _Opts) ->
set_hold(ID, true);
handle_function('Rollback', {#proto_limiter_LimitChange{id = ID}}, _Opts) ->
set_hold(ID, false);
handle_function('PartialCommit', {#proto_limiter_LimitChange{id = ID, cash = #domain_Cash{amount = Amount}}}, _Opts) ->
case commit_hold(ID, Amount) of
ok ->
ok;
error ->
throw({error, {ID, <<"hold not set before partial commit">>}})
end;
handle_function('Commit', {#proto_limiter_LimitChange{id = ID, cash = #domain_Cash{amount = Amount}}}, _Opts) ->
case commit_hold(ID, Amount) of
ok ->
ok;
error ->
throw({error, {ID, <<"hold not set before commit">>}})
end.
-spec get_service_spec() -> hg_proto:service_spec().
get_service_spec() ->
{"/test/proxy/limiter/dummy", {dmsl_proto_limiter_thrift, 'Limiter'}}.
-spec get_http_cowboy_spec() -> map().
get_http_cowboy_spec() ->
Dispatch = cowboy_router:compile([{'_', [{"/", ?MODULE, []}]}]),
#{
listener_ref => ?MODULE,
acceptors_count => 10,
transport_opts => [{port, ?COWBOY_PORT}],
proto_opts => #{env => #{dispatch => Dispatch}}
}.
-spec clock() -> clock().
clock() ->
{vector, #limiter_VectorClock{state = <<>>}}.

View File

@ -10,6 +10,8 @@
-include_lib("damsel/include/dmsl_payment_processing_thrift.hrl").
-include_lib("damsel/include/dmsl_payment_processing_errors_thrift.hrl").
-include_lib("damsel/include/dmsl_proto_limiter_thrift.hrl").
-include_lib("limiter_proto/include/lim_configurator_thrift.hrl").
-include_lib("limiter_proto/include/lim_limiter_thrift.hrl").
-include_lib("stdlib/include/assert.hrl").
@ -41,6 +43,7 @@
-export([payment_success/1]).
-export([payment_limit_success/1]).
-export([payment_limit_other_shop_success/1]).
-export([payment_limit_overflow/1]).
-export([refund_limit_success/1]).
-export([payment_partial_capture_limit_success/1]).
@ -189,6 +192,10 @@ init([]) ->
-type group_name() :: hg_ct_helper:group_name().
-type test_return() :: _ | no_return().
-define(PARTY_ID_WITH_LIMIT, <<"bIg merch limit">>).
-define(LIMIT_ID, <<"ID">>).
-define(LIMIT_UPPER_BOUNDARY, 100000).
cfg(Key, C) ->
hg_ct_helper:cfg(Key, C).
@ -221,6 +228,7 @@ groups() ->
[
{all_non_destructive_tests, [parallel], [
{group, base_payments},
% {group, operation_limits_legacy},
{group, operation_limits},
payment_w_customer_success,
@ -333,9 +341,11 @@ groups() ->
{operation_limits, [], [
payment_limit_success,
payment_limit_other_shop_success,
payment_limit_overflow,
refund_limit_success,
payment_partial_capture_limit_success
% TODO[limiter] uncomment after fix refund bug(increase amount after refund payment)
% refund_limit_success
]},
{refunds, [], [
@ -420,7 +430,13 @@ init_per_suite(C) ->
snowflake,
{cowboy, CowboySpec}
]),
ok = hg_domain:insert(construct_domain_fixture()),
{ok, #limiter_config_LimitConfig{}} = hg_dummy_limiter:create_config(
limiter_create_params(),
hg_dummy_limiter:new()
),
RootUrl = maps:get(hellgate_root_url, Ret),
PartyID = hg_utils:unique_id(),
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
@ -430,6 +446,7 @@ init_per_suite(C) ->
AnotherCustomerClient = hg_client_customer:start(hg_ct_helper:create_client(RootUrl, AnotherPartyID)),
ShopID = hg_ct_helper:create_party_and_shop(?cat(1), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
AnotherShopID = hg_ct_helper:create_party_and_shop(?cat(1), <<"RUB">>, ?tmpl(1), ?pinst(1), AnotherPartyClient),
{ok, SupPid} = supervisor:start_link(?MODULE, []),
_ = unlink(SupPid),
ok = start_kv_store(SupPid),
@ -449,7 +466,6 @@ init_per_suite(C) ->
],
ok = start_proxies([{hg_dummy_provider, 1, NewC}, {hg_dummy_inspector, 2, NewC}]),
_ = start_limiter(hg_dummy_limiter, NewC),
NewC.
-spec end_per_suite(config()) -> _.
@ -1002,72 +1018,89 @@ payment_success(C) ->
-spec payment_limit_success(config()) -> test_return().
payment_limit_success(C) ->
hg_dummy_limiter:init(),
PartyID = <<"bIg merch limit">>,
RootUrl = cfg(root_url, C),
PartyID = ?PARTY_ID_WITH_LIMIT,
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
ShopID = hg_ct_helper:create_party_and_shop(?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl, PartyID)),
?invoice_state(
?invoice_w_status(?invoice_paid()),
[?payment_state(_Payment)]
) = create_payment(PartyID, ShopID, 100000, Client).
-spec payment_limit_other_shop_success(config()) -> test_return().
payment_limit_other_shop_success(C) ->
RootUrl = cfg(root_url, C),
PartyID = ?PARTY_ID_WITH_LIMIT,
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
ShopID1 = hg_ct_helper:create_party_and_shop(?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
ShopID2 = hg_ct_helper:create_party_and_shop(?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl, PartyID)),
PaymentAmount = ?LIMIT_UPPER_BOUNDARY,
?invoice_state(
?invoice_w_status(?invoice_paid()),
[?payment_state(_Payment1)]
) = create_payment(PartyID, ShopID1, PaymentAmount, Client),
?invoice_state(
?invoice_w_status(?invoice_paid()),
[?payment_state(_Payment2)]
) = create_payment(PartyID, ShopID2, PaymentAmount, Client).
-spec payment_limit_overflow(config()) -> test_return().
payment_limit_overflow(C) ->
RootUrl = cfg(root_url, C),
PartyID = ?PARTY_ID_WITH_LIMIT,
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
ShopID = hg_ct_helper:create_party_and_shop(?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl, PartyID)),
?invoice_state(
?invoice_w_status(?invoice_paid()) = Invoice,
[?payment_state(Payment)]
) = create_payment(PartyID, ShopID, ?LIMIT_UPPER_BOUNDARY, Client),
Failure = create_payment_limit_overflow(PartyID, ShopID, 1000, Client),
#domain_Invoice{id = ID} = Invoice,
#domain_InvoicePayment{id = PaymentID} = Payment,
Limit = get_payment_limit(PartyID, ShopID, ID, PaymentID, 1000),
?assertMatch(#limiter_Limit{amount = ?LIMIT_UPPER_BOUNDARY}, Limit),
ok = payproc_errors:match(
'PaymentFailure',
Failure,
fun({authorization_failed, {provider_limit_exceeded, _}}) -> ok end
).
-spec refund_limit_success(config()) -> test_return().
refund_limit_success(C) ->
PartyID = ?PARTY_ID_WITH_LIMIT,
RootUrl = cfg(root_url, C),
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl, PartyID)),
ShopID = hg_ct_helper:create_party_and_shop(?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
InvoiceParams = make_invoice_params(PartyID, ShopID, <<"rubberduck">>, make_due_date(10), make_cash(42000)),
InvoiceID = create_invoice(InvoiceParams, Client),
[?invoice_created(?invoice_w_status(?invoice_unpaid()))] = next_event(InvoiceID, Client),
PaymentParams = make_payment_params(),
_PaymentID = execute_payment(InvoiceID, PaymentParams, Client),
?invoice_state(
?invoice_w_status(?invoice_paid()),
[?payment_state(_Payment)]
) = hg_client_invoicing:get(InvoiceID, Client),
hg_dummy_limiter:delete().
) = create_payment(PartyID, ShopID, 50000, Client),
-spec payment_limit_overflow(config()) -> test_return().
payment_limit_overflow(C) ->
LimitID = <<"1">>,
hg_dummy_limiter:init(LimitID, 100000),
PartyID = <<"bIg merch limit">>,
RootUrl = cfg(root_url, C),
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl, PartyID)),
ShopID2 = hg_ct_helper:create_party_and_shop(?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
InvoiceParams2 = make_invoice_params(PartyID, ShopID2, <<"rubberduck">>, make_due_date(10), make_cash(10000)),
InvoiceID2 = create_invoice(InvoiceParams2, Client),
[?invoice_created(?invoice_w_status(?invoice_unpaid()))] = next_event(InvoiceID2, Client),
PaymentParams = make_payment_params(),
?payment_state(?payment(PaymentID2)) = hg_client_invoicing:start_payment(InvoiceID2, PaymentParams, Client),
PaymentID2 = await_payment_started(InvoiceID2, PaymentID2, Client),
Failure = await_payment_rollback(InvoiceID2, PaymentID2, Client),
?invoice_state(
?invoice_w_status(?invoice_paid()) = Invoice,
[?payment_state(Payment)]
) = create_payment(PartyID, ShopID, 50000, Client),
?invoice(InvoiceID) = Invoice,
?payment(PaymentID) = Payment,
Failure = create_payment_limit_overflow(PartyID, ShopID, 50000, Client),
ok = payproc_errors:match(
'PaymentFailure',
Failure,
fun({authorization_failed, {provider_limit_exceeded, _}}) -> ok end
),
hg_dummy_limiter:delete().
-spec refund_limit_success(config()) -> test_return().
refund_limit_success(C) ->
hg_dummy_limiter:init(),
PartyID = <<"bIg merch limit">>,
RootUrl = cfg(root_url, C),
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl, PartyID)),
ShopID = hg_ct_helper:create_party_and_shop(?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
InvoiceParams = make_invoice_params(PartyID, ShopID, <<"rubberduck">>, make_due_date(10), make_cash(42000)),
InvoiceParams1 = InvoiceParams#payproc_InvoiceParams{
id = hg_utils:unique_id()
},
InvoiceID = create_invoice(InvoiceParams1, Client),
[?invoice_created(?invoice_w_status(?invoice_unpaid()))] = next_event(InvoiceID, Client),
PaymentID = execute_payment(InvoiceID, make_payment_params(), Client),
InvoiceParams2 = InvoiceParams#payproc_InvoiceParams{
id = hg_utils:unique_id()
},
InvoiceID2 = create_invoice(InvoiceParams2, Client),
[?invoice_created(?invoice_w_status(?invoice_unpaid()))] = next_event(InvoiceID2, Client),
_PaymentID2 = execute_payment(InvoiceID2, make_payment_params(), Client),
% Limit = get_payment_limit(PartyID, ShopID, InvoiceID, PaymentID, 50000),
% ct:print("Limit after overflow ~p", [Limit]),
% create a refund finally
RefundParams = make_refund_params(),
RefundID = execute_payment_refund(InvoiceID, PaymentID, RefundParams, Client),
@ -1076,17 +1109,19 @@ refund_limit_success(C) ->
% no more refunds for you
?invalid_payment_status(?refunded()) =
hg_client_invoicing:refund_payment(InvoiceID, PaymentID, RefundParams, Client),
?assertEqual(42000, hg_dummy_limiter:get_amount(<<"1">>)),
hg_dummy_limiter:delete().
% try payment after refund(limit was decreased)
?invoice_state(
?invoice_w_status(?invoice_paid()),
[?payment_state(_Payment)]
) = create_payment(PartyID, ShopID, 50000, Client).
-spec payment_partial_capture_limit_success(config()) -> test_return().
payment_partial_capture_limit_success(C) ->
hg_dummy_limiter:init(),
InitialCost = 1000 * 100,
PartialCost = 700 * 100,
PaymentParams = make_payment_params({hold, cancel}),
PartyID = <<"bIg merch limit">>,
PartyID = ?PARTY_ID_WITH_LIMIT,
RootUrl = cfg(root_url, C),
PartyClient = hg_client_party:start(PartyID, hg_ct_helper:create_client(RootUrl, PartyID)),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl, PartyID)),
@ -1116,10 +1151,54 @@ payment_partial_capture_limit_success(C) ->
?assertMatch(?payment_state(?payment_w_status(PaymentID, ?captured(Reason, Cash))), PaymentState),
#payproc_InvoicePayment{cash_flow = CF2} = PaymentState,
?assertNotEqual(undefined, CF2),
?assertNotEqual(CF1, CF2),
?assertNotEqual(CF1, CF2).
?assertEqual(PartialCost, hg_dummy_limiter:get_amount(<<"1">>)),
hg_dummy_limiter:delete().
create_payment(PartyID, ShopID, Amount, Client) ->
InvoiceParams = make_invoice_params(PartyID, ShopID, <<"rubberduck">>, make_due_date(10), make_cash(Amount)),
InvoiceID = create_invoice(InvoiceParams, Client),
[?invoice_created(?invoice_w_status(?invoice_unpaid()))] = next_event(InvoiceID, Client),
PaymentParams = make_payment_params(),
_PaymentID = execute_payment(InvoiceID, PaymentParams, Client),
hg_client_invoicing:get(InvoiceID, Client).
create_payment_limit_overflow(PartyID, ShopID, Amount, Client) ->
InvoiceParams = make_invoice_params(PartyID, ShopID, <<"rubberduck">>, make_due_date(10), make_cash(Amount)),
InvoiceID = create_invoice(InvoiceParams, Client),
[?invoice_created(?invoice_w_status(?invoice_unpaid()))] = next_event(InvoiceID, Client),
PaymentParams = make_payment_params(),
?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client),
PaymentID = await_payment_started(InvoiceID, PaymentID, Client),
await_payment_rollback(InvoiceID, PaymentID, Client).
get_payment_limit(PartyID, ShopID, InvoiceID, PaymentID, Amount) ->
Context = #limiter_context_LimitContext{
payment_processing = #limiter_context_ContextPaymentProcessing{
op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}},
invoice = #limiter_context_Invoice{
id = InvoiceID,
owner_id = PartyID,
shop_id = ShopID,
cost = #limiter_base_Cash{
amount = Amount,
currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>}
},
created_at = hg_datetime:format_now(),
effective_payment = #limiter_context_InvoicePayment{
id = PaymentID,
owner_id = PartyID,
shop_id = ShopID,
cost = #limiter_base_Cash{
amount = Amount,
currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>}
},
created_at = hg_datetime:format_now()
}
}
}
},
{ok, Limit} = hg_dummy_limiter:get(?LIMIT_ID, Context, hg_dummy_limiter:new()),
Limit.
-spec payment_success_ruleset(config()) -> test_return().
payment_success_ruleset(C) ->
@ -4665,14 +4744,6 @@ start_proxies(Proxies) ->
setup_proxies(Proxies) ->
ok = hg_domain:upsert(Proxies).
start_limiter(Module, Context) ->
IP = "127.0.0.1",
Port = hg_dummy_limiter:get_port(),
Opts = #{hellgate_root_url => cfg(root_url, Context)},
ChildSpec = hg_test_proxy:get_child_spec(Module, Module, IP, Port, Opts),
{ok, _} = supervisor:start_child(cfg(test_sup, Context), ChildSpec),
hg_test_proxy:get_url(Module, IP, Port).
start_kv_store(SupPid) ->
ChildSpec = #{
id => hg_kv_store,
@ -5833,7 +5904,7 @@ construct_domain_fixture() ->
),
?delegate(
<<"Provider with turnover limit">>,
{condition, {party, #domain_PartyCondition{id = <<"bIg merch limit">>}}},
{condition, {party, #domain_PartyCondition{id = ?PARTY_ID_WITH_LIMIT}}},
?ruleset(4)
),
?delegate(<<"Common">>, {constant, true}, ?ruleset(1))
@ -6738,12 +6809,8 @@ construct_domain_fixture() ->
turnover_limits =
{value, [
#domain_TurnoverLimit{
id = <<"1">>,
upper_boundary = 100000
},
#domain_TurnoverLimit{
id = <<"2">>,
upper_boundary = 100000
id = ?LIMIT_ID,
upper_boundary = ?LIMIT_UPPER_BOUNDARY
}
]}
}
@ -7523,3 +7590,12 @@ construct_term_set_for_partial_capture_provider_permit(Revision) ->
set_processing_deadline(Timeout, PaymentParams) ->
Deadline = woody_deadline:to_binary(woody_deadline:from_timeout(Timeout)),
PaymentParams#payproc_InvoicePaymentParams{processing_deadline = Deadline}.
limiter_create_params() ->
#limiter_cfg_LimitCreateParams{
id = ?LIMIT_ID,
name = <<"ShopMonthTurnover">>,
description = <<"description">>,
started_at = <<"2000-01-01T00:00:00Z">>,
body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}}
}.

View File

@ -49,7 +49,7 @@ get_service(eventsink) ->
get_service(fault_detector) ->
{fd_proto_fault_detector_thrift, 'FaultDetector'};
get_service(limiter) ->
{dmsl_proto_limiter_thrift, 'Limiter'}.
{lim_limiter_thrift, 'Limiter'}.
-spec get_service_spec(Name :: atom()) -> service_spec().
get_service_spec(Name) ->

View File

@ -36,6 +36,15 @@ services:
timeout: 1s
retries: 20
limiter:
image: dr2.rbkmoney.com/rbkmoney/limiter:c5572a9a22b3fea68213f32276b5272605aebec8
command: /opt/limiter/bin/limiter foreground
depends_on:
machinegun:
condition: service_healthy
shumway:
condition: service_healthy
shumway:
image: dr2.rbkmoney.com/rbkmoney/shumway:658c9aec229b5a70d745a49cb938bb1a132b5ca2
restart: unless-stopped

View File

@ -43,7 +43,8 @@
{party_client, {git, "https://github.com/rbkmoney/party_client_erlang.git", {branch, "master"}}},
{how_are_you, {git, "https://github.com/rbkmoney/how_are_you.git", {branch, "master"}}},
{erl_health, {git, "https://github.com/rbkmoney/erlang-health.git", {branch, "master"}}},
{fault_detector_proto, {git, "https://github.com/rbkmoney/fault-detector-proto.git", {branch, "master"}}}
{fault_detector_proto, {git, "https://github.com/rbkmoney/fault-detector-proto.git", {branch, "master"}}},
{limiter_proto, {git, "git@github.com:rbkmoney/limiter-proto.git", {branch, "master"}}}
]}.
{xref_checks, [

View File

@ -45,6 +45,10 @@
0},
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2},
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1},
{<<"limiter_proto">>,
{git,"git@github.com:rbkmoney/limiter-proto.git",
{ref,"d4b40ead589dd4dbd9d442397239c635d2a8382e"}},
0},
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2},
{<<"mg_proto">>,
{git,"https://github.com/rbkmoney/machinegun_proto.git",

View File

@ -46,7 +46,14 @@ namespaces:
processor:
url: http://dominant:8022/v1/stateproc
pool_size: 300
lim/config_v1:
processor:
url: http://limiter:8022/v1/stateproc/lim/config_v1
pool_size: 500
lim/range_v1:
processor:
url: http://limiter:8022/v1/stateproc/lim/range_v1
pool_size: 500
storage:
type: memory