diff --git a/apps/capi/src/capi_cash_limits.erl b/apps/capi/src/capi_cash_limits.erl index d77154e..d8714a8 100644 --- a/apps/capi/src/capi_cash_limits.erl +++ b/apps/capi/src/capi_cash_limits.erl @@ -5,6 +5,11 @@ % - exclusive bounds приводятся к inclusive (границы теряют строгость) % - терминалы с cash_limit=decisions полностью игнорируются (нет fallback на provider) % - при отсутствии payment_methods ответ пустой, даже если лимит посчитан +% +% Учитываются allow и global_allow: +% - {constant, true} -> разрешено, {constant, false} -> запрещено +% - остальные предикаты (all_of, any_of и т.д.) -> по умолчанию разрешено +% - global_allow у провайдера при false запрещает все терминалы провайдера -include_lib("damsel/include/dmsl_domain_thrift.hrl"). -include_lib("damsel/include/dmsl_payproc_thrift.hrl"). @@ -80,7 +85,12 @@ get_payment_terminal_refs(PiRef, PartyID, ShopID, Context) -> {ok, #domain_PaymentInstitution{payment_routing_rules = Rules}} -> case compute_routing_ruleset(Rules, PartyID, ShopID, Context) of {ok, #domain_RoutingRuleset{decisions = {candidates, Candidates}}} -> - lists:usort([C#domain_RoutingCandidate.terminal || C <- Candidates]); + AllowedCandidates = [ + C#domain_RoutingCandidate.terminal + || C <- Candidates, + predicate_allowed(C#domain_RoutingCandidate.allowed) + ], + lists:usort(AllowedCandidates); _ -> [] end; @@ -88,6 +98,13 @@ get_payment_terminal_refs(PiRef, PartyID, ShopID, Context) -> [] end. +predicate_allowed({constant, false}) -> + false; +predicate_allowed({all_of, List}) when is_list(List) -> + lists:all(fun predicate_allowed/1, List); +predicate_allowed(_) -> + true. + compute_routing_ruleset(undefined, _PartyID, _ShopID, _Context) -> undefined; compute_routing_ruleset(#domain_RoutingRules{policies = RulesetRef}, PartyID, ShopID, Context) -> @@ -127,31 +144,45 @@ log_terminal_terms(TerminalRef, Limit) -> ). get_terminal_limit(TerminalRef, Currency, Context) -> - case get_and_check_terminal(TerminalRef, Context) of - {ok, #domain_Terminal{provider_ref = ProviderRef, terms = TerminalTerms}} -> - TerminalLimit = extract_provider_limit(TerminalTerms, Currency), - case TerminalLimit of - undefined -> - ProviderTerms = get_provider_terms(ProviderRef, Context), - extract_provider_limit(ProviderTerms, Currency); - _ -> - TerminalLimit - end; + case capi_domain:get({terminal, TerminalRef}, Context) of + {ok, #domain_TerminalObject{data = #domain_Terminal{provider_ref = ProviderRef, terms = Terms}}} -> + ProviderTerms = get_provider_terms(ProviderRef, Context), + compute_terminal_limit(Terms, ProviderTerms, Currency); _ -> undefined end. -get_and_check_terminal(TerminalRef, Context) -> - case capi_domain:get({terminal, TerminalRef}, Context) of - {ok, #domain_TerminalObject{data = #domain_Terminal{terms = Terms} = Terminal}} -> - case Terms of - #domain_ProvisionTermSet{payments = #domain_PaymentsProvisionTerms{cash_limit = {value, _}}} -> - {ok, Terminal}; - _ -> - undefined +compute_terminal_limit( + #domain_ProvisionTermSet{payments = #domain_PaymentsProvisionTerms{cash_limit = {value, _}}} = TerminalTerms, + ProviderTerms, + Currency +) -> + case terminal_and_provider_allowed(TerminalTerms#domain_ProvisionTermSet.payments, ProviderTerms) of + true -> + case extract_provider_limit(TerminalTerms, Currency) of + undefined -> + extract_provider_limit(ProviderTerms, Currency); + Limit -> + Limit end; - _ -> + false -> undefined + end; +compute_terminal_limit(_, _ProviderTerms, _Currency) -> + undefined. + +-spec terminal_and_provider_allowed(term(), term()) -> boolean(). +terminal_and_provider_allowed(TerminalPayments, ProviderTerms) -> + TerminalAllowed = predicate_allowed(TerminalPayments#domain_PaymentsProvisionTerms.allow), + TerminalGlobalAllowed = predicate_allowed(TerminalPayments#domain_PaymentsProvisionTerms.global_allow), + case ProviderTerms of + #domain_ProvisionTermSet{payments = #domain_PaymentsProvisionTerms{} = ProviderPayments} -> + %% global_allow провайдера при false запрещает все терминалы + ProviderGlobalAllowed = predicate_allowed(ProviderPayments#domain_PaymentsProvisionTerms.global_allow), + ProviderAllowed = predicate_allowed(ProviderPayments#domain_PaymentsProvisionTerms.allow), + TerminalAllowed andalso TerminalGlobalAllowed andalso ProviderGlobalAllowed andalso ProviderAllowed; + _ -> + TerminalAllowed andalso TerminalGlobalAllowed end. get_provider_terms(ProviderRef, Context) -> @@ -285,3 +316,98 @@ encode_payment_method(crypto_currency) -> #{<<"method">> => <<"CryptoWallet">>}; encode_payment_method(mobile) -> #{<<"method">> => <<"MobileCommerce">>}. + +%%% +%%% EUnit tests +%%% +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-spec test() -> _. + +-spec predicate_allowed_undefined_test() -> _. +predicate_allowed_undefined_test() -> + ?assertEqual(true, predicate_allowed(undefined)). + +-spec predicate_allowed_constant_true_test() -> _. +predicate_allowed_constant_true_test() -> + ?assertEqual(true, predicate_allowed({constant, true})). + +-spec predicate_allowed_constant_false_test() -> _. +predicate_allowed_constant_false_test() -> + ?assertEqual(false, predicate_allowed({constant, false})). + +-spec predicate_allowed_other_predicates_test() -> _. +predicate_allowed_other_predicates_test() -> + ?assertEqual(true, predicate_allowed({all_of, []})), + ?assertEqual(true, predicate_allowed({all_of, [{constant, true}, {constant, true}]})), + ?assertEqual(false, predicate_allowed({all_of, [{constant, true}, {constant, false}]})), + ?assertEqual(false, predicate_allowed({all_of, [{all_of, [{constant, true}, {constant, false}]}]})), + ?assertEqual(true, predicate_allowed({any_of, []})), + ?assertEqual(true, predicate_allowed({condition, []})). + +-spec terminal_and_provider_allowed_all_true_test() -> _. +terminal_and_provider_allowed_all_true_test() -> + TerminalPayments = #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, true} + }, + ProviderTerms = #domain_ProvisionTermSet{ + payments = #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, true} + } + }, + ?assertEqual(true, terminal_and_provider_allowed(TerminalPayments, ProviderTerms)). + +-spec terminal_and_provider_allowed_terminal_allow_false_test() -> _. +terminal_and_provider_allowed_terminal_allow_false_test() -> + TerminalPayments = #domain_PaymentsProvisionTerms{ + allow = {constant, false}, + global_allow = {constant, true} + }, + ProviderTerms = #domain_ProvisionTermSet{ + payments = #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, true} + } + }, + ?assertEqual(false, terminal_and_provider_allowed(TerminalPayments, ProviderTerms)). + +-spec terminal_and_provider_allowed_provider_global_allow_false_test() -> _. +terminal_and_provider_allowed_provider_global_allow_false_test() -> + TerminalPayments = #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, true} + }, + ProviderTerms = #domain_ProvisionTermSet{ + payments = #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, false} + } + }, + ?assertEqual(false, terminal_and_provider_allowed(TerminalPayments, ProviderTerms)). + +-spec terminal_and_provider_allowed_provider_allow_false_test() -> _. +terminal_and_provider_allowed_provider_allow_false_test() -> + TerminalPayments = #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, true} + }, + ProviderTerms = #domain_ProvisionTermSet{ + payments = #domain_PaymentsProvisionTerms{ + allow = {constant, false}, + global_allow = {constant, true} + } + }, + ?assertEqual(false, terminal_and_provider_allowed(TerminalPayments, ProviderTerms)). + +-spec terminal_and_provider_allowed_provider_undefined_test() -> _. +terminal_and_provider_allowed_provider_undefined_test() -> + TerminalPayments = #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, true} + }, + ?assertEqual(true, terminal_and_provider_allowed(TerminalPayments, undefined)). + +-endif. diff --git a/apps/capi/test/capi_base_api_token_tests_SUITE.erl b/apps/capi/test/capi_base_api_token_tests_SUITE.erl index b565983..f701a67 100644 --- a/apps/capi/test/capi_base_api_token_tests_SUITE.erl +++ b/apps/capi/test/capi_base_api_token_tests_SUITE.erl @@ -65,6 +65,8 @@ get_shop_by_id_for_party_error_test/1, get_shops_for_party_error_test/1, get_shop_limits_for_party_ok_test/1, + get_shop_limits_candidate_allowed_false_test/1, + get_shop_limits_provider_blocks_terminal_test/1, create_webhook_ok_test/1, create_webhook_limit_exceeded_test/1, get_webhooks/1, @@ -145,6 +147,8 @@ groups() -> get_shops_for_party_restricted_ok_test, get_shops_for_party_error_test, get_shop_limits_for_party_ok_test, + get_shop_limits_candidate_allowed_false_test, + get_shop_limits_provider_blocks_terminal_test, create_payment_ok_test, create_payment_with_changed_cost_ok_test, @@ -1271,6 +1275,76 @@ get_shop_limits_for_party_ok_test(Config) -> ?assertEqual(#{<<"amount">> => 10000, <<"inclusive">> => true}, maps:get(<<"lowerBound">>, Result)), ?assertEqual(#{<<"amount">> => 120000000, <<"inclusive">> => true}, maps:get(<<"upperBound">>, Result)). +-spec get_shop_limits_candidate_allowed_false_test(config()) -> ok. +get_shop_limits_candidate_allowed_false_test(Config) -> + Context = ?config(context, Config), + TestRuleset = #domain_RoutingRuleset{ + name = <<"mock-ruleset">>, + description = <<"mocked for cash limits test">>, + decisions = + {candidates, [ + #domain_RoutingCandidate{ + allowed = {constant, true}, + terminal = #domain_TerminalRef{id = ?KZT_TERMINAL_15_ID} + }, + #domain_RoutingCandidate{ + allowed = {constant, false}, + terminal = #domain_TerminalRef{id = ?KZT_TERMINAL_16_ID} + } + ]} + }, + _ = capi_ct_helper:mock_services( + [ + {party_management, fun('ComputeRoutingRuleset', _) -> + {ok, TestRuleset} + end} + ], + Config + ), + _ = capi_ct_helper_bouncer:mock_assert_shop_op_ctx( + <<"GetShopCashLimitsForParty">>, + ?KZT_PARTY_ID, + ?KZT_SHOP_ID, + Config + ), + {ok, [Result]} = capi_client_shops:get_shop_cash_limits_for_party(Context, ?KZT_PARTY_ID, ?KZT_SHOP_ID), + PaymentMethod = maps:get(<<"paymentMethod">>, Result), + ?assertEqual(<<"BankCard">>, maps:get(<<"method">>, PaymentMethod)), + ?assertEqual([], maps:get(<<"paymentSystems">>, PaymentMethod)), + ?assertEqual(<<"KZT">>, maps:get(<<"currency">>, Result)), + ?assertEqual(#{<<"amount">> => 10000, <<"inclusive">> => true}, maps:get(<<"lowerBound">>, Result)), + ?assertEqual(#{<<"amount">> => 120000000, <<"inclusive">> => true}, maps:get(<<"upperBound">>, Result)). + +-spec get_shop_limits_provider_blocks_terminal_test(config()) -> ok. +get_shop_limits_provider_blocks_terminal_test(Config) -> + Context = ?config(context, Config), + TestRuleset = #domain_RoutingRuleset{ + name = <<"mock-ruleset">>, + description = <<"mocked for cash limits test">>, + decisions = + {candidates, [ + #domain_RoutingCandidate{ + allowed = {constant, true}, + terminal = #domain_TerminalRef{id = ?KZT_TERMINAL_17_ID} + } + ]} + }, + _ = capi_ct_helper:mock_services( + [ + {party_management, fun('ComputeRoutingRuleset', _) -> + {ok, TestRuleset} + end} + ], + Config + ), + _ = capi_ct_helper_bouncer:mock_assert_shop_op_ctx( + <<"GetShopCashLimitsForParty">>, + ?KZT_PARTY_ID, + ?KZT_SHOP_ID, + Config + ), + ?assertMatch({ok, []}, capi_client_shops:get_shop_cash_limits_for_party(Context, ?KZT_PARTY_ID, ?KZT_SHOP_ID)). + -spec create_webhook_ok_test(config()) -> _. create_webhook_ok_test(Config) -> _ = capi_ct_helper:mock_services( diff --git a/apps/capi/test/capi_dummy_data.hrl b/apps/capi/test/capi_dummy_data.hrl index 8009d72..dacb6c9 100644 --- a/apps/capi/test/capi_dummy_data.hrl +++ b/apps/capi/test/capi_dummy_data.hrl @@ -39,8 +39,10 @@ -define(KZT_PROHIBITIONS_ID, 1060). -define(KZT_PROVIDER_8_ID, 8). -define(KZT_PROVIDER_9_ID, 9). +-define(KZT_PROVIDER_10_ID, 10). -define(KZT_TERMINAL_15_ID, 15). -define(KZT_TERMINAL_16_ID, 16). +-define(KZT_TERMINAL_17_ID, 17). -define(RATIONAL, #base_Rational{p = ?INTEGER, q = ?INTEGER}). @@ -968,6 +970,49 @@ } }}, + %% Провайдер с global_allow=false — для теста блокировки терминала + {provider, #domain_ProviderRef{id = ?KZT_PROVIDER_10_ID}} => + {provider, #domain_ProviderObject{ + ref = #domain_ProviderRef{id = ?KZT_PROVIDER_10_ID}, + data = #domain_Provider{ + name = ?STRING, + description = ?STRING, + proxy = #domain_Proxy{ref = #domain_ProxyRef{id = ?INTEGER}, additional = #{}}, + realm = test, + terms = + #domain_ProvisionTermSet{ + payments = + #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, false}, + payment_methods = + {value, [ + #domain_PaymentMethodRef{ + id = + {bank_card, #domain_BankCardPaymentMethod{ + payment_system = #domain_PaymentSystemRef{id = <<"VISA">>}, + is_cvv_empty = false + }} + } + ]}, + cash_limit = + {value, #domain_CashRange{ + lower = + {inclusive, #domain_Cash{ + amount = 50000, + currency = #domain_CurrencyRef{symbolic_code = ?KZT} + }}, + upper = + {inclusive, #domain_Cash{ + amount = 50000000, + currency = #domain_CurrencyRef{symbolic_code = ?KZT} + }} + }} + } + } + } + }}, + {terminal, #domain_TerminalRef{id = ?KZT_TERMINAL_15_ID}} => {terminal, #domain_TerminalObject{ ref = #domain_TerminalRef{id = ?KZT_TERMINAL_15_ID}, @@ -1069,6 +1114,48 @@ } }}, + %% Терминал с провайдером global_allow=false — для теста блокировки + {terminal, #domain_TerminalRef{id = ?KZT_TERMINAL_17_ID}} => + {terminal, #domain_TerminalObject{ + ref = #domain_TerminalRef{id = ?KZT_TERMINAL_17_ID}, + data = #domain_Terminal{ + name = ?STRING, + description = ?STRING, + provider_ref = #domain_ProviderRef{id = ?KZT_PROVIDER_10_ID}, + terms = + #domain_ProvisionTermSet{ + payments = + #domain_PaymentsProvisionTerms{ + allow = {constant, true}, + global_allow = {constant, true}, + payment_methods = + {value, [ + #domain_PaymentMethodRef{ + id = + {bank_card, #domain_BankCardPaymentMethod{ + payment_system = #domain_PaymentSystemRef{id = <<"VISA">>}, + is_cvv_empty = false + }} + } + ]}, + cash_limit = + {value, #domain_CashRange{ + lower = + {inclusive, #domain_Cash{ + amount = 50000, + currency = #domain_CurrencyRef{symbolic_code = ?KZT} + }}, + upper = + {inclusive, #domain_Cash{ + amount = 50000000, + currency = #domain_CurrencyRef{symbolic_code = ?KZT} + }} + }} + } + } + } + }}, + {term_set_hierarchy, #domain_TermSetHierarchyRef{id = ?KZT_TERMS_ID}} => {term_set_hierarchy, #domain_TermSetHierarchyObject{ ref = #domain_TermSetHierarchyRef{id = ?KZT_TERMS_ID},