mirror of
https://github.com/valitydev/hellgate.git
synced 2024-11-06 02:45:20 +00:00
TD-550: Limit hold exception to reject route (#64)
* Refactor `hg_routing:to_rejected_route/2` * Adds new limiter hold exceptions handling * Adds rejected routes log message on limit overflow * Adds testcase for limit hold fail with multiple route candidates
This commit is contained in:
parent
de0c01cfe4
commit
bfccd439a3
@ -21,6 +21,8 @@
|
||||
-include_lib("damsel/include/dmsl_payproc_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_payproc_error_thrift.hrl").
|
||||
|
||||
-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl").
|
||||
|
||||
-include_lib("hellgate/include/domain.hrl").
|
||||
-include_lib("hellgate/include/allocation.hrl").
|
||||
|
||||
@ -839,6 +841,22 @@ log_rejected_routes(no_route_found, RejectedRoutes, Varset) ->
|
||||
logger:get_process_metadata()
|
||||
),
|
||||
ok;
|
||||
log_rejected_routes(limit_hold_reject, RejectedRoutes, _Varset) ->
|
||||
_ = logger:log(
|
||||
warning,
|
||||
"Limiter hold error caused route candidates to be rejected: ~p",
|
||||
[RejectedRoutes],
|
||||
logger:get_process_metadata()
|
||||
),
|
||||
ok;
|
||||
log_rejected_routes(limit_overflow_reject, RejectedRoutes, _Varset) ->
|
||||
_ = logger:log(
|
||||
info,
|
||||
"Limit overflow caused route candidates to be rejected: ~p",
|
||||
[RejectedRoutes],
|
||||
logger:get_process_metadata()
|
||||
),
|
||||
ok;
|
||||
log_rejected_routes(rejected_route_found, RejectedRoutes, Varset) ->
|
||||
_ = logger:log(
|
||||
info,
|
||||
@ -2221,15 +2239,8 @@ process_routing(Action, St) ->
|
||||
CreatedAt = get_payment_created_at(Payment),
|
||||
PaymentInstitutionRef = get_payment_institution_ref(Opts),
|
||||
MerchantTerms = get_merchant_payments_terms(Opts, Revision, CreatedAt, VS1),
|
||||
VS2 = collect_refund_varset(
|
||||
MerchantTerms#domain_PaymentsServiceTerms.refunds,
|
||||
PaymentTool,
|
||||
VS1
|
||||
),
|
||||
VS3 = collect_chargeback_varset(
|
||||
MerchantTerms#domain_PaymentsServiceTerms.chargebacks,
|
||||
VS2
|
||||
),
|
||||
VS2 = collect_refund_varset(MerchantTerms#domain_PaymentsServiceTerms.refunds, PaymentTool, VS1),
|
||||
VS3 = collect_chargeback_varset(MerchantTerms#domain_PaymentsServiceTerms.chargebacks, VS2),
|
||||
PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision),
|
||||
try
|
||||
Payer = get_payment_payer(St),
|
||||
@ -2243,8 +2254,6 @@ process_routing(Action, St) ->
|
||||
AvailableRoutes = filter_out_attempted_routes(AllRoutes, St),
|
||||
%% Since this is routing step then current attempt is not yet accounted for in `St`.
|
||||
Iter = get_iter(St) + 1,
|
||||
%% TODO Investigate necessity of so many `hg_routing:to_payment_route/1` calls.
|
||||
%% Here, in `filter_out_attempted_routes/2` and in `filter_limit_overflow_routes/3`.
|
||||
Events = handle_gathered_route_result(
|
||||
filter_limit_overflow_routes(AvailableRoutes, VS3, Iter, St),
|
||||
[hg_routing:to_payment_route(R) || R <- AllRoutes],
|
||||
@ -2525,7 +2534,7 @@ process_result({payment, processing_accounter}, Action, St) ->
|
||||
process_result({payment, routing_failure}, Action, St = #st{failure = Failure}) ->
|
||||
NewAction = hg_machine_action:set_timeout(0, Action),
|
||||
Routes = get_candidate_routes(St),
|
||||
_ = rollback_payment_limits(Routes, get_iter(St), St),
|
||||
_ = rollback_payment_limits(Routes, get_iter(St), St, [ignore_business_error]),
|
||||
{done, {[?payment_status_changed(?failed(Failure))], NewAction}};
|
||||
process_result({payment, processing_failure}, Action, St = #st{failure = Failure}) ->
|
||||
NewAction = hg_machine_action:set_timeout(0, Action),
|
||||
@ -2745,55 +2754,80 @@ get_provider_terms(St, Revision) ->
|
||||
hg_routing:get_payment_terms(Route, VS1, Revision).
|
||||
|
||||
filter_limit_overflow_routes(Routes, VS, Iter, St) ->
|
||||
ok = hold_limit_routes(Routes, VS, Iter, St),
|
||||
RejectedContext = #{rejected_routes => []},
|
||||
case get_limit_overflow_routes(Routes, VS, St, RejectedContext) of
|
||||
{UsableRoutes, _HoldRejectedRoutes} = hold_limit_routes(Routes, VS, Iter, St),
|
||||
case get_limit_overflow_routes(UsableRoutes, VS, St) of
|
||||
{[], _RejectedRoutesOut} ->
|
||||
{error, not_found};
|
||||
{RoutesNoOverflow, _} ->
|
||||
{ok, RoutesNoOverflow}
|
||||
end.
|
||||
|
||||
get_limit_overflow_routes(Routes, VS, St, RejectedRoutes) ->
|
||||
get_limit_overflow_routes(Routes, VS, St) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(Opts),
|
||||
lists:foldl(
|
||||
fun(Route, {RoutesNoOverflowIn, RejectedIn}) ->
|
||||
{_Routes, RejectedRoutes} =
|
||||
Result = lists:foldl(
|
||||
fun(Route, {RoutesNoOverflowIn, RejectedIn}) ->
|
||||
PaymentRoute = hg_routing:to_payment_route(Route),
|
||||
ProviderTerms = hg_routing:get_payment_terms(PaymentRoute, VS, Revision),
|
||||
TurnoverLimits = get_turnover_limits(ProviderTerms),
|
||||
case hg_limiter:check_limits(TurnoverLimits, Invoice, Payment, PaymentRoute) of
|
||||
{ok, _} ->
|
||||
{[Route | RoutesNoOverflowIn], RejectedIn};
|
||||
{error, {limit_overflow, IDs}} ->
|
||||
RejectedRoute = hg_routing:to_rejected_route(Route, {'LimitOverflow', IDs}),
|
||||
{RoutesNoOverflowIn, [RejectedRoute | RejectedIn]}
|
||||
end
|
||||
end,
|
||||
{[], []},
|
||||
Routes
|
||||
),
|
||||
erlang:length(RejectedRoutes) > 0 andalso
|
||||
log_rejected_routes(limit_overflow_reject, RejectedRoutes, VS),
|
||||
Result.
|
||||
|
||||
-spec hold_limit_routes([hg_routing:route()], hg_varset:varset(), pos_integer(), st()) ->
|
||||
{[hg_routing:route()], [hg_routing:rejected_route()]}.
|
||||
hold_limit_routes(Routes0, VS, Iter, St) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(Opts),
|
||||
{Routes1, Rejected} = lists:foldl(
|
||||
fun(Route, {LimitHeldRoutes, RejectedRoutes} = Acc) ->
|
||||
PaymentRoute = hg_routing:to_payment_route(Route),
|
||||
ProviderTerms = hg_routing:get_payment_terms(PaymentRoute, VS, Revision),
|
||||
TurnoverLimits = get_turnover_limits(ProviderTerms),
|
||||
case hg_limiter:check_limits(TurnoverLimits, Invoice, Payment, PaymentRoute) of
|
||||
{ok, _} ->
|
||||
{[Route | RoutesNoOverflowIn], RejectedIn};
|
||||
{error, {limit_overflow, IDs}} ->
|
||||
PRef = hg_routing:provider_ref(Route),
|
||||
TRef = hg_routing:terminal_ref(Route),
|
||||
RejectedOut = [{PRef, TRef, {'LimitOverflow', IDs}} | RejectedIn],
|
||||
{RoutesNoOverflowIn, RejectedOut}
|
||||
try
|
||||
ok = hg_limiter:hold_payment_limits(TurnoverLimits, PaymentRoute, Iter, Invoice, Payment),
|
||||
{[Route | LimitHeldRoutes], RejectedRoutes}
|
||||
catch
|
||||
error:(#limiter_InvalidOperationCurrency{} = LimiterError) ->
|
||||
do_reject_route(LimiterError, Route, TurnoverLimits, Acc);
|
||||
error:(#limiter_OperationContextNotSupported{} = LimiterError) ->
|
||||
do_reject_route(LimiterError, Route, TurnoverLimits, Acc);
|
||||
error:(#limiter_PaymentToolNotSupported{} = LimiterError) ->
|
||||
do_reject_route(LimiterError, Route, TurnoverLimits, Acc)
|
||||
end
|
||||
end,
|
||||
{[], RejectedRoutes},
|
||||
Routes
|
||||
).
|
||||
{[], []},
|
||||
Routes0
|
||||
),
|
||||
erlang:length(Rejected) > 0 andalso
|
||||
log_rejected_routes(limit_hold_reject, Rejected, VS),
|
||||
{lists:reverse(Routes1), Rejected}.
|
||||
|
||||
hold_limit_routes(Routes, VS, Iter, St) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(Opts),
|
||||
lists:foreach(
|
||||
fun(Route) ->
|
||||
PaymentRoute = hg_routing:to_payment_route(Route),
|
||||
ProviderTerms = hg_routing:get_payment_terms(PaymentRoute, VS, Revision),
|
||||
TurnoverLimits = get_turnover_limits(ProviderTerms),
|
||||
ok = hg_limiter:hold_payment_limits(TurnoverLimits, PaymentRoute, Iter, Invoice, Payment)
|
||||
end,
|
||||
Routes
|
||||
).
|
||||
do_reject_route(LimiterError, Route, TurnoverLimits, {LimitHeldRoutes, RejectedRoutes}) ->
|
||||
Reason = {'LimitHoldError', [T#domain_TurnoverLimit.id || T <- TurnoverLimits], LimiterError},
|
||||
RejectedRoute = hg_routing:to_rejected_route(Route, Reason),
|
||||
{LimitHeldRoutes, [RejectedRoute | RejectedRoutes]}.
|
||||
|
||||
rollback_payment_limits(Routes, Iter, St) ->
|
||||
rollback_payment_limits(Routes, Iter, St, []).
|
||||
|
||||
rollback_payment_limits(Routes, Iter, St, Flags) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
@ -2803,7 +2837,7 @@ rollback_payment_limits(Routes, Iter, St) ->
|
||||
fun(Route) ->
|
||||
ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
|
||||
TurnoverLimits = get_turnover_limits(ProviderTerms),
|
||||
ok = hg_limiter:rollback_payment_limits(TurnoverLimits, Route, Iter, Invoice, Payment)
|
||||
ok = hg_limiter:rollback_payment_limits(TurnoverLimits, Route, Iter, Invoice, Payment, Flags)
|
||||
end,
|
||||
Routes
|
||||
).
|
||||
@ -2812,7 +2846,7 @@ rollback_unused_payment_limits(St) ->
|
||||
Route = get_route(St),
|
||||
Routes = get_candidate_routes(St),
|
||||
UnUsedRoutes = Routes -- [Route],
|
||||
rollback_payment_limits(UnUsedRoutes, get_iter(St), St).
|
||||
rollback_payment_limits(UnUsedRoutes, get_iter(St), St, [ignore_business_error]).
|
||||
|
||||
get_turnover_limits(ProviderTerms) ->
|
||||
TurnoverLimitSelector = ProviderTerms#domain_PaymentsProvisionTerms.turnover_limits,
|
||||
|
@ -13,6 +13,7 @@
|
||||
-type route() :: hg_routing:payment_route().
|
||||
-type refund() :: hg_invoice_payment:domain_refund().
|
||||
-type cash() :: dmsl_domain_thrift:'Cash'().
|
||||
-type handling_flag() :: ignore_business_error.
|
||||
|
||||
-type change_queue() :: [hg_limiter_client:limit_change()].
|
||||
|
||||
@ -22,7 +23,7 @@
|
||||
-export([hold_refund_limits/5]).
|
||||
-export([commit_payment_limits/6]).
|
||||
-export([commit_refund_limits/5]).
|
||||
-export([rollback_payment_limits/5]).
|
||||
-export([rollback_payment_limits/6]).
|
||||
-export([rollback_refund_limits/5]).
|
||||
|
||||
-define(route(ProviderRef, TerminalRef), #domain_PaymentRoute{
|
||||
@ -104,55 +105,64 @@ commit_refund_limits(TurnoverLimits, Invoice, Payment, Refund, Route) ->
|
||||
Context = gen_limit_refund_context(Invoice, Payment, Refund, Route),
|
||||
commit(LimitChanges, get_latest_clock(), Context).
|
||||
|
||||
-spec rollback_payment_limits([turnover_limit()], route(), pos_integer(), invoice(), payment()) -> ok.
|
||||
rollback_payment_limits(TurnoverLimits, Route, Iter, Invoice, Payment) ->
|
||||
-spec rollback_payment_limits([turnover_limit()], route(), pos_integer(), invoice(), payment(), [handling_flag()]) ->
|
||||
ok.
|
||||
rollback_payment_limits(TurnoverLimits, Route, Iter, Invoice, Payment, Flags) ->
|
||||
ChangeIDs = [
|
||||
construct_payment_change_id(Route, Iter, Invoice, Payment),
|
||||
construct_payment_change_id(Route, Iter, Invoice, Payment, legacy)
|
||||
],
|
||||
LimitChanges = gen_limit_changes(TurnoverLimits, ChangeIDs),
|
||||
Context = gen_limit_context(Invoice, Payment, Route),
|
||||
rollback(LimitChanges, get_latest_clock(), Context).
|
||||
rollback(LimitChanges, get_latest_clock(), Context, Flags).
|
||||
|
||||
-spec rollback_refund_limits([turnover_limit()], invoice(), payment(), refund(), route()) -> ok.
|
||||
rollback_refund_limits(TurnoverLimits, Invoice, Payment, Refund, Route) ->
|
||||
ChangeIDs = [construct_refund_change_id(Invoice, Payment, Refund)],
|
||||
LimitChanges = gen_limit_changes(TurnoverLimits, ChangeIDs),
|
||||
Context = gen_limit_refund_context(Invoice, Payment, Refund, Route),
|
||||
rollback(LimitChanges, get_latest_clock(), Context).
|
||||
rollback(LimitChanges, get_latest_clock(), Context, []).
|
||||
|
||||
-spec hold([change_queue()], hg_limiter_client:clock(), hg_limiter_client:context()) -> ok.
|
||||
hold(LimitChanges, Clock, Context) ->
|
||||
process_changes(LimitChanges, fun hg_limiter_client:hold/3, Clock, Context).
|
||||
process_changes(LimitChanges, fun hg_limiter_client:hold/3, Clock, Context, []).
|
||||
|
||||
-spec commit([change_queue()], hg_limiter_client:clock(), hg_limiter_client:context()) -> ok.
|
||||
commit(LimitChanges, Clock, Context) ->
|
||||
process_changes(LimitChanges, fun hg_limiter_client:commit/3, Clock, Context).
|
||||
process_changes(LimitChanges, fun hg_limiter_client:commit/3, Clock, Context, []).
|
||||
|
||||
-spec rollback([change_queue()], hg_limiter_client:clock(), hg_limiter_client:context()) -> ok.
|
||||
rollback(LimitChanges, Clock, Context) ->
|
||||
process_changes(LimitChanges, fun hg_limiter_client:rollback/3, Clock, Context).
|
||||
-spec rollback([change_queue()], hg_limiter_client:clock(), hg_limiter_client:context(), [handling_flag()]) -> ok.
|
||||
rollback(LimitChanges, Clock, Context, Flags) ->
|
||||
process_changes(LimitChanges, fun hg_limiter_client:rollback/3, Clock, Context, Flags).
|
||||
|
||||
process_changes(LimitChangesQueues, WithFun, Clock, Context) ->
|
||||
process_changes(LimitChangesQueues, WithFun, Clock, Context, Flags) ->
|
||||
lists:foreach(
|
||||
fun(LimitChangesQueue) ->
|
||||
process_changes_try_wrap(LimitChangesQueue, WithFun, Clock, Context)
|
||||
process_changes_try_wrap(LimitChangesQueue, WithFun, Clock, Context, Flags)
|
||||
end,
|
||||
LimitChangesQueues
|
||||
).
|
||||
|
||||
process_changes_try_wrap([LimitChange], WithFun, Clock, Context) ->
|
||||
process_changes_try_wrap([LimitChange], WithFun, Clock, Context, _Flags) ->
|
||||
WithFun(LimitChange, Clock, Context);
|
||||
process_changes_try_wrap([LimitChange | OtherLimitChanges], WithFun, Clock, Context) ->
|
||||
process_changes_try_wrap([LimitChange | OtherLimitChanges], WithFun, Clock, Context, Flags) ->
|
||||
IgnoreBusinessError = lists:member(ignore_business_error, Flags),
|
||||
#limiter_LimitChange{change_id = ChangeID} = LimitChange,
|
||||
try
|
||||
WithFun(LimitChange, Clock, Context)
|
||||
catch
|
||||
%% Very specific error to crutch around
|
||||
error:#base_InvalidRequest{errors = [<<"Posting plan not found: ", ChangeID/binary>>]} ->
|
||||
process_changes_try_wrap(OtherLimitChanges, WithFun, Clock, Context)
|
||||
process_changes_try_wrap(OtherLimitChanges, WithFun, Clock, Context, Flags);
|
||||
Class:Reason:Stacktrace ->
|
||||
handle_caught_exception(Class, Reason, Stacktrace, IgnoreBusinessError)
|
||||
end.
|
||||
|
||||
handle_caught_exception(error, #limiter_InvalidOperationCurrency{}, _Stacktrace, true) -> ok;
|
||||
handle_caught_exception(error, #limiter_OperationContextNotSupported{}, _Stacktrace, true) -> ok;
|
||||
handle_caught_exception(error, #limiter_PaymentToolNotSupported{}, _Stacktrace, true) -> ok;
|
||||
handle_caught_exception(Class, Reason, Stacktrace, _IgnoreBusinessError) -> erlang:raise(Class, Reason, Stacktrace).
|
||||
|
||||
gen_limit_context(Invoice, Payment, Route) ->
|
||||
gen_limit_context(Invoice, Payment, Route, undefined).
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
-export([new/4]).
|
||||
-export([new/5]).
|
||||
-export([to_payment_route/1]).
|
||||
-export([to_rejected_route/2]).
|
||||
-export([provider_ref/1]).
|
||||
-export([terminal_ref/1]).
|
||||
|
||||
@ -113,6 +114,7 @@
|
||||
|
||||
-export_type([route/0]).
|
||||
-export_type([payment_route/0]).
|
||||
-export_type([rejected_route/0]).
|
||||
-export_type([route_predestination/0]).
|
||||
|
||||
%% Route accessors
|
||||
@ -171,6 +173,10 @@ from_payment_route(Route) ->
|
||||
to_payment_route(#route{} = Route) ->
|
||||
?route(provider_ref(Route), terminal_ref(Route)).
|
||||
|
||||
-spec to_rejected_route(route(), term()) -> rejected_route().
|
||||
to_rejected_route(Route, Reason) ->
|
||||
{provider_ref(Route), terminal_ref(Route), Reason}.
|
||||
|
||||
-spec set_weight(integer(), route()) -> route().
|
||||
set_weight(Weight, Route) ->
|
||||
Route#route{weight = Weight}.
|
||||
|
@ -47,6 +47,10 @@
|
||||
-export([payment_partial_capture_limit_success/1]).
|
||||
-export([switch_provider_after_limit_overflow/1]).
|
||||
-export([limit_not_found/1]).
|
||||
-export([limit_hold_currency_error/1]).
|
||||
-export([limit_hold_operation_not_supported/1]).
|
||||
-export([limit_hold_payment_tool_not_supported/1]).
|
||||
-export([limit_hold_two_routes_failure/1]).
|
||||
|
||||
-export([processing_deadline_reached_test/1]).
|
||||
-export([payment_success_empty_cvv/1]).
|
||||
@ -381,7 +385,11 @@ groups() ->
|
||||
payment_partial_capture_limit_success,
|
||||
switch_provider_after_limit_overflow,
|
||||
limit_not_found,
|
||||
refund_limit_success
|
||||
refund_limit_success,
|
||||
limit_hold_currency_error,
|
||||
limit_hold_operation_not_supported,
|
||||
limit_hold_payment_tool_not_supported,
|
||||
limit_hold_two_routes_failure
|
||||
]},
|
||||
|
||||
{refunds, [], [
|
||||
@ -686,6 +694,14 @@ init_per_testcase(payment_cascade_fail_wo_available_attempt_limit, C) ->
|
||||
override_domain_fixture(fun merchant_payments_service_terms_wo_attempt_limit/1, C);
|
||||
init_per_testcase(payment_cascade_success, C) ->
|
||||
override_domain_fixture(fun routes_ruleset_w_failing_provider_fixture/1, C);
|
||||
init_per_testcase(limit_hold_currency_error, C) ->
|
||||
override_domain_fixture(fun patch_limit_config_w_invalid_currency/1, C);
|
||||
init_per_testcase(limit_hold_operation_not_supported, C) ->
|
||||
override_domain_fixture(fun patch_limit_config_for_withdrawal/1, C);
|
||||
init_per_testcase(limit_hold_payment_tool_not_supported, C) ->
|
||||
override_domain_fixture(fun patch_with_unsupported_payment_tool/1, C);
|
||||
init_per_testcase(limit_hold_two_routes_failure, C) ->
|
||||
override_domain_fixture(fun patch_providers_limits_to_fail_and_overflow/1, C);
|
||||
init_per_testcase(_Name, C) ->
|
||||
init_per_testcase(C).
|
||||
|
||||
@ -1272,6 +1288,51 @@ payment_limit_overflow(C) ->
|
||||
fun({no_route_found, {forbidden, _}}) -> ok end
|
||||
).
|
||||
|
||||
-spec limit_hold_currency_error(config()) -> test_return().
|
||||
limit_hold_currency_error(C) ->
|
||||
payment_route_not_found(C).
|
||||
|
||||
-spec limit_hold_operation_not_supported(config()) -> test_return().
|
||||
limit_hold_operation_not_supported(C) ->
|
||||
payment_route_not_found(C).
|
||||
|
||||
-spec limit_hold_payment_tool_not_supported(config()) -> test_return().
|
||||
limit_hold_payment_tool_not_supported(C) ->
|
||||
{PaymentTool, Session} = hg_dummy_provider:make_payment_tool(crypto_currency, ?crypta(<<"bitcoin-ref">>)),
|
||||
payment_route_not_found(PaymentTool, Session, C).
|
||||
|
||||
-spec limit_hold_two_routes_failure(config()) -> test_return().
|
||||
limit_hold_two_routes_failure(C) ->
|
||||
payment_route_not_found(C).
|
||||
|
||||
payment_route_not_found(C) ->
|
||||
PmtSys = ?pmt_sys(<<"visa-ref">>),
|
||||
{PaymentTool, Session} = hg_dummy_provider:make_payment_tool(no_preauth, PmtSys),
|
||||
payment_route_not_found(PaymentTool, Session, C).
|
||||
|
||||
payment_route_not_found(PaymentTool, Session, C) ->
|
||||
RootUrl = cfg(root_url, C),
|
||||
PartyClient = cfg(party_client, C),
|
||||
#{party_id := PartyID} = cfg(limits, C),
|
||||
ShopID = hg_ct_helper:create_shop(PartyID, ?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
|
||||
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl)),
|
||||
|
||||
Cash = make_cash(10000, <<"RUB">>),
|
||||
InvoiceParams = make_invoice_params(PartyID, ShopID, <<"rubberduck">>, make_due_date(10), Cash),
|
||||
InvoiceID = create_invoice(InvoiceParams, Client),
|
||||
?invoice_created(?invoice_w_status(?invoice_unpaid())) = next_change(InvoiceID, Client),
|
||||
|
||||
PaymentParams = make_payment_params(PaymentTool, Session, instant),
|
||||
?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client),
|
||||
_ = start_payment_ev(InvoiceID, Client),
|
||||
?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})) =
|
||||
next_change(InvoiceID, Client),
|
||||
ok = payproc_errors:match(
|
||||
'PaymentFailure',
|
||||
Failure,
|
||||
fun({no_route_found, _}) -> ok end
|
||||
).
|
||||
|
||||
-spec switch_provider_after_limit_overflow(config()) -> test_return().
|
||||
switch_provider_after_limit_overflow(C) ->
|
||||
PmtSys = ?pmt_sys(<<"visa-ref">>),
|
||||
@ -1749,6 +1810,113 @@ merchant_payments_service_terms_wo_attempt_limit(Revision) ->
|
||||
routes_ruleset_w_failing_provider_fixture(Revision)
|
||||
]).
|
||||
|
||||
patch_limit_config_w_invalid_currency(Revision) ->
|
||||
NewRevision = hg_domain:update({limit_config, hg_limiter_helper:mk_config_object(?LIMIT_ID, <<"KEK">>)}),
|
||||
[
|
||||
change_terms_limit_config_version(Revision, NewRevision)
|
||||
].
|
||||
|
||||
patch_limit_config_for_withdrawal(Revision) ->
|
||||
NewRevision = hg_domain:update(
|
||||
{limit_config,
|
||||
hg_limiter_helper:mk_config_object(?LIMIT_ID, <<"RUB">>, hg_limiter_helper:mk_context_type(withdrawal))}
|
||||
),
|
||||
[
|
||||
change_terms_limit_config_version(Revision, NewRevision)
|
||||
].
|
||||
|
||||
patch_with_unsupported_payment_tool(Revision) ->
|
||||
NewRevision = hg_domain:update(
|
||||
{limit_config,
|
||||
hg_limiter_helper:mk_config_object(
|
||||
?LIMIT_ID,
|
||||
<<"RUB">>,
|
||||
hg_limiter_helper:mk_context_type(payment),
|
||||
hg_limiter_helper:mk_scopes([shop, payment_tool])
|
||||
)}
|
||||
),
|
||||
[
|
||||
change_provider_payments_provision_terms(?prv(5), Revision, fun(PaymentsProvisionTerms) ->
|
||||
PaymentsProvisionTerms#domain_PaymentsProvisionTerms{
|
||||
turnover_limits =
|
||||
{value, [
|
||||
#domain_TurnoverLimit{
|
||||
id = ?LIMIT_ID,
|
||||
upper_boundary = ?LIMIT_UPPER_BOUNDARY,
|
||||
domain_revision = NewRevision
|
||||
}
|
||||
]},
|
||||
payment_methods =
|
||||
{value,
|
||||
?ordset([
|
||||
?pmt(crypto_currency, ?crypta(<<"bitcoin-ref">>))
|
||||
])}
|
||||
}
|
||||
end)
|
||||
].
|
||||
|
||||
patch_providers_limits_to_fail_and_overflow(Revision) ->
|
||||
%% 1. Must have two routes to different providers.
|
||||
%% 2. Each provider must have different turnover limit.
|
||||
%% 3. First of those turnover limits must fail on hold operation with business error.
|
||||
%% 4. Second must get rejected due limit overflow.
|
||||
NewRevision = hg_domain:update([
|
||||
{limit_config,
|
||||
hg_limiter_helper:mk_config_object(?LIMIT_ID, <<"RUB">>, hg_limiter_helper:mk_context_type(withdrawal))}
|
||||
]),
|
||||
[
|
||||
hg_ct_fixture:construct_payment_routing_ruleset(
|
||||
?ruleset(4),
|
||||
<<"SubMain">>,
|
||||
{candidates, [
|
||||
%% Provider = ?prv(5)
|
||||
?candidate({constant, true}, ?trm(12)),
|
||||
%% Provider = ?prv(6)
|
||||
?candidate({constant, true}, ?trm(13))
|
||||
]}
|
||||
),
|
||||
change_terms_limit_config_version(Revision, ?prv(5), [
|
||||
#domain_TurnoverLimit{
|
||||
id = ?LIMIT_ID,
|
||||
upper_boundary = ?LIMIT_UPPER_BOUNDARY,
|
||||
domain_revision = NewRevision
|
||||
}
|
||||
]),
|
||||
change_terms_limit_config_version(Revision, ?prv(6), [
|
||||
#domain_TurnoverLimit{
|
||||
id = ?LIMIT_ID2,
|
||||
%% Every op will overflow!
|
||||
upper_boundary = 0,
|
||||
domain_revision = NewRevision
|
||||
}
|
||||
])
|
||||
].
|
||||
|
||||
change_terms_limit_config_version(Revision, LimitConfigRevision) ->
|
||||
change_terms_limit_config_version(Revision, ?prv(5), [
|
||||
#domain_TurnoverLimit{
|
||||
id = ?LIMIT_ID,
|
||||
upper_boundary = ?LIMIT_UPPER_BOUNDARY,
|
||||
domain_revision = LimitConfigRevision
|
||||
}
|
||||
]).
|
||||
|
||||
change_terms_limit_config_version(Revision, ProviderRef, TurnoverLimits) ->
|
||||
change_provider_payments_provision_terms(ProviderRef, Revision, fun(PaymentsProvisionTerms) ->
|
||||
PaymentsProvisionTerms#domain_PaymentsProvisionTerms{turnover_limits = {value, TurnoverLimits}}
|
||||
end).
|
||||
|
||||
change_provider_payments_provision_terms(ProviderID, Revision, Changer) when is_function(Changer, 1) ->
|
||||
Provider = #domain_Provider{terms = Terms} = hg_domain:get(Revision, {provider, ProviderID}),
|
||||
Terms1 =
|
||||
Terms#domain_ProvisionTermSet{
|
||||
payments = Changer(Terms#domain_ProvisionTermSet.payments)
|
||||
},
|
||||
{provider, #domain_ProviderObject{
|
||||
ref = ProviderID,
|
||||
data = Provider#domain_Provider{terms = Terms1}
|
||||
}}.
|
||||
|
||||
-spec payment_capture_failed(config()) -> test_return().
|
||||
payment_capture_failed(C) ->
|
||||
Client = cfg(client, C),
|
||||
|
@ -13,6 +13,9 @@
|
||||
-export([assert_payment_limit_amount/3]).
|
||||
-export([assert_payment_limit_amount/4]).
|
||||
-export([get_payment_limit_amount/4]).
|
||||
-export([mk_config_object/2, mk_config_object/3, mk_config_object/4]).
|
||||
-export([mk_context_type/1]).
|
||||
-export([mk_scopes/1]).
|
||||
|
||||
-type config() :: ct_suite:ct_config().
|
||||
|
||||
@ -23,10 +26,10 @@
|
||||
|
||||
-spec init_per_suite(config()) -> _.
|
||||
init_per_suite(_Config) ->
|
||||
_ = dmt_client:upsert({limit_config, limiter_mk_config_object(?LIMIT_ID)}),
|
||||
_ = dmt_client:upsert({limit_config, limiter_mk_config_object(?LIMIT_ID2)}),
|
||||
_ = dmt_client:upsert({limit_config, limiter_mk_config_object(?LIMIT_ID3)}),
|
||||
_ = dmt_client:upsert({limit_config, limiter_mk_config_object(?LIMIT_ID4)}).
|
||||
_ = dmt_client:upsert({limit_config, mk_config_object(?LIMIT_ID)}),
|
||||
_ = dmt_client:upsert({limit_config, mk_config_object(?LIMIT_ID2)}),
|
||||
_ = dmt_client:upsert({limit_config, mk_config_object(?LIMIT_ID3)}),
|
||||
_ = dmt_client:upsert({limit_config, mk_config_object(?LIMIT_ID4)}).
|
||||
|
||||
-spec get_amount(_) -> pos_integer().
|
||||
get_amount(#limiter_Limit{amount = Amount}) ->
|
||||
@ -59,7 +62,19 @@ get_payment_limit_amount(LimitId, Version, Payment, Invoice) ->
|
||||
},
|
||||
hg_dummy_limiter:get(LimitId, Version, Context, hg_dummy_limiter:new()).
|
||||
|
||||
limiter_mk_config_object(LimitID) ->
|
||||
mk_config_object(LimitID) ->
|
||||
mk_config_object(LimitID, <<"RUB">>).
|
||||
|
||||
-spec mk_config_object(_, _) -> _.
|
||||
mk_config_object(LimitID, Currency) ->
|
||||
mk_config_object(LimitID, Currency, mk_context_type(payment)).
|
||||
|
||||
-spec mk_config_object(_, _, _) -> _.
|
||||
mk_config_object(LimitID, Currency, ContextType) ->
|
||||
mk_config_object(LimitID, Currency, ContextType, mk_scopes([shop])).
|
||||
|
||||
-spec mk_config_object(_, _, _, _) -> _.
|
||||
mk_config_object(LimitID, Currency, ContextType, Scopes) ->
|
||||
#domain_LimitConfigObject{
|
||||
ref = #domain_LimitConfigRef{id = LimitID},
|
||||
data = #limiter_config_LimitConfig{
|
||||
@ -68,15 +83,25 @@ limiter_mk_config_object(LimitID) ->
|
||||
started_at = <<"2000-01-01T00:00:00Z">>,
|
||||
shard_size = 12,
|
||||
time_range_type = {calendar, {month, #limiter_config_TimeRangeTypeCalendarMonth{}}},
|
||||
context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}},
|
||||
context_type = ContextType,
|
||||
type =
|
||||
{turnover, #limiter_config_LimitTypeTurnover{
|
||||
metric = {amount, #limiter_config_LimitTurnoverAmount{currency = <<"RUB">>}}
|
||||
metric = {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}
|
||||
}},
|
||||
scopes = [{shop, #limiter_config_LimitScopeEmptyDetails{}}],
|
||||
scopes = Scopes,
|
||||
description = <<"description">>,
|
||||
op_behaviour = #limiter_config_OperationLimitBehaviour{
|
||||
invoice_payment_refund = {subtraction, #limiter_config_Subtraction{}}
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
-spec mk_context_type(payment | withdrawal) -> _.
|
||||
mk_context_type(withdrawal) ->
|
||||
{withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}};
|
||||
mk_context_type(payment) ->
|
||||
{payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}.
|
||||
|
||||
-spec mk_scopes(_) -> _.
|
||||
mk_scopes(ScopeTags) ->
|
||||
ordsets:from_list([{Tag, #limiter_config_LimitScopeEmptyDetails{}} || Tag <- ScopeTags]).
|
||||
|
@ -39,7 +39,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
machinegun:
|
||||
image: ghcr.io/valitydev/machinegun:sha-fb7fbf9
|
||||
image: ghcr.io/valitydev/machinegun:sha-058bada
|
||||
command: /opt/machinegun/bin/machinegun foreground
|
||||
volumes:
|
||||
- ./test/machinegun/config.yaml:/opt/machinegun/etc/config.yaml
|
||||
@ -63,7 +63,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
limiter:
|
||||
image: ghcr.io/valitydev/limiter:sha-3eff7dd
|
||||
image: ghcr.io/valitydev/limiter:sha-2b8723b
|
||||
command: /opt/limiter/bin/limiter foreground
|
||||
depends_on:
|
||||
machinegun:
|
||||
|
@ -17,7 +17,7 @@
|
||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2},
|
||||
{<<"damsel">>,
|
||||
{git,"https://github.com/valitydev/damsel.git",
|
||||
{ref,"cc95eab778addb9b4cb86b648c60dc87d2cec645"}},
|
||||
{ref,"698c7d275fbe6a8f06f209638ede093f3134fc9b"}},
|
||||
0},
|
||||
{<<"dmt_client">>,
|
||||
{git,"https://github.com/valitydev/dmt-client.git",
|
||||
@ -45,7 +45,7 @@
|
||||
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1},
|
||||
{<<"limiter_proto">>,
|
||||
{git,"https://github.com/valitydev/limiter-proto.git",
|
||||
{ref,"9b76200a957c0e91bcdf6f16dfbab90d38a3f173"}},
|
||||
{ref,"bbd2c0dce044dd5b4e424fc8e38a0023a1685a22"}},
|
||||
0},
|
||||
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2},
|
||||
{<<"mg_proto">>,
|
||||
|
Loading…
Reference in New Issue
Block a user