From bfccd439a33d18ef4867734edc0ae4cfb1b64c36 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Mon, 17 Apr 2023 11:30:24 +0300 Subject: [PATCH] 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 --- apps/hellgate/src/hg_invoice_payment.erl | 124 ++++++++----- apps/hellgate/src/hg_limiter.erl | 40 +++-- apps/hellgate/src/hg_routing.erl | 6 + apps/hellgate/test/hg_invoice_tests_SUITE.erl | 170 +++++++++++++++++- apps/hellgate/test/hg_limiter_helper.erl | 41 ++++- compose.yaml | 4 +- rebar.lock | 4 +- 7 files changed, 316 insertions(+), 73 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index 78ae930..d41cea9 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -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, diff --git a/apps/hellgate/src/hg_limiter.erl b/apps/hellgate/src/hg_limiter.erl index 556ab6b..f245f89 100644 --- a/apps/hellgate/src/hg_limiter.erl +++ b/apps/hellgate/src/hg_limiter.erl @@ -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). diff --git a/apps/hellgate/src/hg_routing.erl b/apps/hellgate/src/hg_routing.erl index 81f9f11..2bd321c 100644 --- a/apps/hellgate/src/hg_routing.erl +++ b/apps/hellgate/src/hg_routing.erl @@ -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}. diff --git a/apps/hellgate/test/hg_invoice_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_tests_SUITE.erl index da2797c..0107aab 100644 --- a/apps/hellgate/test/hg_invoice_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_tests_SUITE.erl @@ -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), diff --git a/apps/hellgate/test/hg_limiter_helper.erl b/apps/hellgate/test/hg_limiter_helper.erl index b9e2ff3..bd127fb 100644 --- a/apps/hellgate/test/hg_limiter_helper.erl +++ b/apps/hellgate/test/hg_limiter_helper.erl @@ -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]). diff --git a/compose.yaml b/compose.yaml index c6a31e5..13b23e1 100644 --- a/compose.yaml +++ b/compose.yaml @@ -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: diff --git a/rebar.lock b/rebar.lock index f5f904b..9098312 100644 --- a/rebar.lock +++ b/rebar.lock @@ -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">>,