mirror of
https://github.com/valitydev/hellgate.git
synced 2024-11-06 02:45:20 +00:00
ED-156: integration with limiter (#574)
This commit is contained in:
parent
ce90c673ba
commit
6f6be75b32
@ -24,7 +24,8 @@
|
||||
erl_health,
|
||||
party_management,
|
||||
prometheus,
|
||||
prometheus_cowboy
|
||||
prometheus_cowboy,
|
||||
limiter_proto
|
||||
]},
|
||||
{env, []},
|
||||
{modules, []},
|
||||
|
@ -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) ->
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
@ -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 => #{}
|
||||
}
|
||||
}},
|
||||
|
@ -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 = <<>>}}.
|
||||
|
@ -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">>}}
|
||||
}.
|
||||
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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, [
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user