ED-212: refactor routing (#594)

This commit is contained in:
Boris 2021-10-05 14:20:42 +03:00 committed by GitHub
parent c5f4735cfc
commit 0587356c41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 403 additions and 629 deletions

View File

@ -208,7 +208,6 @@
-type shop() :: dmsl_domain_thrift:'Shop'(). -type shop() :: dmsl_domain_thrift:'Shop'().
-type payment_tool() :: dmsl_domain_thrift:'PaymentTool'(). -type payment_tool() :: dmsl_domain_thrift:'PaymentTool'().
-type recurrent_paytool_service_terms() :: dmsl_domain_thrift:'RecurrentPaytoolsServiceTerms'(). -type recurrent_paytool_service_terms() :: dmsl_domain_thrift:'RecurrentPaytoolsServiceTerms'().
-type session_status() :: active | suspended | finished. -type session_status() :: active | suspended | finished.
-type session() :: #{ -type session() :: #{
@ -466,21 +465,6 @@ get_merchant_terms(Party, Shop, DomainRevision, Timestamp, VS) ->
), ),
Terms. Terms.
-spec get_provider_terminal_terms(route(), hg_varset:varset(), hg_domain:revision()) ->
dmsl_domain_thrift:'PaymentsProvisionTerms'() | undefined.
get_provider_terminal_terms(?route(ProviderRef, TerminalRef), VS, Revision) ->
PreparedVS = hg_varset:prepare_varset(VS),
{Client, Context} = get_party_client(),
{ok, TermsSet} = party_client_thrift:compute_provider_terminal_terms(
ProviderRef,
TerminalRef,
Revision,
PreparedVS,
Client,
Context
),
TermsSet#domain_ProvisionTermSet.payments.
assert_contract_active(#domain_Contract{status = {active, _}}) -> assert_contract_active(#domain_Contract{status = {active, _}}) ->
ok; ok;
assert_contract_active(#domain_Contract{status = Status}) -> assert_contract_active(#domain_Contract{status = Status}) ->
@ -774,19 +758,22 @@ gather_routes(PaymentInstitution, VS, Revision, St) ->
Payment = get_payment(St), Payment = get_payment(St),
Predestination = choose_routing_predestination(Payment), Predestination = choose_routing_predestination(Payment),
case case
hg_routing_rule:gather_routes( hg_routing:gather_routes(
Predestination, Predestination,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
) )
of of
{[], RejectContext} -> {ok, {[], RejectedRoutes}} ->
_ = log_reject_context(unknown, RejectContext), _ = log_rejected_routes(unknown, RejectedRoutes, VS),
throw({no_route_found, unknown}); throw({no_route_found, unknown});
{Routes, RejectContext} -> {ok, {Routes, RejectedRoutes}} ->
_ = log_misconfigurations(RejectContext), _ = log_rejected_routes(unknown, RejectedRoutes, VS),
Routes Routes;
{error, {misconfiguration, _Reason} = Error} ->
_ = log_misconfigurations(Error),
throw({no_route_found, misconfiguration})
end. end.
-spec check_risk_score(risk_score()) -> ok | {error, risk_score_is_too_high}. -spec check_risk_score(risk_score()) -> ok | {error, risk_score_is_too_high}.
@ -806,42 +793,25 @@ choose_routing_predestination(#domain_InvoicePayment{payer = ?payment_resource_p
log_route_choice_meta(ChoiceMeta, Revision) -> log_route_choice_meta(ChoiceMeta, Revision) ->
_ = logger:log(info, "Routing decision made", hg_routing:get_logger_metadata(ChoiceMeta, Revision)). _ = logger:log(info, "Routing decision made", hg_routing:get_logger_metadata(ChoiceMeta, Revision)).
log_misconfigurations(RejectContext) -> log_misconfigurations({misconfiguration, _} = Error) ->
RejectedProviders = maps:get(rejected_providers, RejectContext), {Format, Details} = hg_routing:prepare_log_message(Error),
RejectedRoutes = maps:get(rejected_routes, RejectContext), _ = logger:warning(Format, Details),
Rejects = RejectedProviders ++ RejectedRoutes,
_ = lists:foreach(fun maybe_log_misconfiguration/1, Rejects),
ok. ok.
maybe_log_misconfiguration({PRef, {'Misconfiguration', Reason}}) -> log_rejected_routes(RejectReason, RejectedRoutes, Varset) ->
Text = "The provider with ref ~p has been misconfigured: ~p", log_rejected_routes(warning, RejectReason, RejectedRoutes, Varset).
_ = logger:warning(Text, [PRef, Reason]);
maybe_log_misconfiguration({PRef, TRef, {'Misconfiguration', Reason}}) ->
Text = "The route with provider ref ~p and terminal ref ~p has been misconfigured: ~p",
_ = logger:warning(Text, [PRef, TRef, Reason]);
maybe_log_misconfiguration(_NotMisconfiguration) ->
ok.
log_reject_context(RejectReason, RejectContext) -> log_rejected_routes(Level, RejectReason, RejectedRoutes, Varset) ->
log_reject_context(warning, RejectReason, RejectContext).
log_reject_context(Level, RejectReason, RejectContext) ->
_ = logger:log( _ = logger:log(
Level, Level,
"No route found, reason = ~p, varset: ~p", "No route found, reason = ~p, varset: ~p",
[RejectReason, maps:get(varset, RejectContext)], [RejectReason, Varset],
logger:get_process_metadata()
),
_ = logger:log(
Level,
"No route found, reason = ~p, rejected providers: ~p",
[RejectReason, maps:get(rejected_providers, RejectContext)],
logger:get_process_metadata() logger:get_process_metadata()
), ),
_ = logger:log( _ = logger:log(
Level, Level,
"No route found, reason = ~p, rejected routes: ~p", "No route found, reason = ~p, rejected routes: ~p",
[RejectReason, maps:get(rejected_routes, RejectContext)], [RejectReason, RejectedRoutes],
logger:get_process_metadata() logger:get_process_metadata()
), ),
ok. ok.
@ -1024,7 +994,7 @@ partial_capture(St0, Reason, Cost, Cart, Opts) ->
MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS), MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS),
ok = validate_merchant_hold_terms(MerchantTerms), ok = validate_merchant_hold_terms(MerchantTerms),
Route = get_route(St), Route = get_route(St),
ProviderTerms = get_provider_terminal_terms(Route, VS, Revision), ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
ok = validate_provider_holds_terms(ProviderTerms), ok = validate_provider_holds_terms(ProviderTerms),
FinalCashflow = calculate_cashflow(Route, Payment2, MerchantTerms, ProviderTerms, VS, Revision, Opts), FinalCashflow = calculate_cashflow(Route, Payment2, MerchantTerms, ProviderTerms, VS, Revision, Opts),
Changes = start_partial_capture(Reason, Cost, Cart, FinalCashflow), Changes = start_partial_capture(Reason, Cost, Cart, FinalCashflow),
@ -1206,7 +1176,7 @@ make_refund_cashflow(Refund, Payment, Revision, CreatedAt, St, Opts) ->
VS = collect_validation_varset(St, Opts), VS = collect_validation_varset(St, Opts),
MerchantTerms = get_merchant_refunds_terms(get_merchant_payments_terms(Opts, Revision, CreatedAt, VS)), MerchantTerms = get_merchant_refunds_terms(get_merchant_payments_terms(Opts, Revision, CreatedAt, VS)),
ok = validate_refund(MerchantTerms, Refund, Payment), ok = validate_refund(MerchantTerms, Refund, Payment),
ProviderPaymentsTerms = get_provider_terminal_terms(Route, VS, Revision), ProviderPaymentsTerms = hg_routing:get_payment_terms(Route, VS, Revision),
ProviderTerms = get_provider_refunds_terms(ProviderPaymentsTerms, Refund, Payment), ProviderTerms = get_provider_refunds_terms(ProviderPaymentsTerms, Refund, Payment),
Cashflow = collect_refund_cashflow(MerchantTerms, ProviderTerms), Cashflow = collect_refund_cashflow(MerchantTerms, ProviderTerms),
PaymentInstitutionRef = get_payment_institution_ref(Opts), PaymentInstitutionRef = get_payment_institution_ref(Opts),
@ -1554,7 +1524,7 @@ get_cash_flow_for_target_status({failed, _}, _St, _Opts) ->
) -> cash_flow(). ) -> cash_flow().
calculate_cashflow(Route, Payment, Timestamp, VS, Revision, Opts) -> calculate_cashflow(Route, Payment, Timestamp, VS, Revision, Opts) ->
MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS), MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS),
ProviderTerms = get_provider_terminal_terms(Route, VS, Revision), ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS, Revision, Opts). calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS, Revision, Opts).
-spec calculate_cashflow( -spec calculate_cashflow(
@ -1829,9 +1799,8 @@ process_risk_score(Action, St) ->
Opts = get_opts(St), Opts = get_opts(St),
Revision = get_payment_revision(St), Revision = get_payment_revision(St),
Payment = get_payment(St), Payment = get_payment(St),
VS1 = get_varset(St, #{}),
PaymentInstitutionRef = get_payment_institution_ref(Opts), PaymentInstitutionRef = get_payment_institution_ref(Opts),
VS0 = reconstruct_payment_flow(Payment, #{}),
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision), PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision),
RiskScore = repair_inspect(Payment, PaymentInstitution, Opts, St), RiskScore = repair_inspect(Payment, PaymentInstitution, Opts, St),
Events = [?risk_score_changed(RiskScore)], Events = [?risk_score_changed(RiskScore)],
@ -1848,20 +1817,18 @@ process_routing(Action, St) ->
Opts = get_opts(St), Opts = get_opts(St),
Revision = get_payment_revision(St), Revision = get_payment_revision(St),
Payment = get_payment(St), Payment = get_payment(St),
#{payment_tool := PaymentTool} = VS1 = get_varset(St, #{risk_score => get_risk_score(St)}),
CreatedAt = get_payment_created_at(Payment), CreatedAt = get_payment_created_at(Payment),
PaymentInstitutionRef = get_payment_institution_ref(Opts), PaymentInstitutionRef = get_payment_institution_ref(Opts),
VS0 = #{risk_score => get_risk_score(St)}, MerchantTerms = get_merchant_payments_terms(Opts, Revision, CreatedAt, VS1),
VS1 = reconstruct_payment_flow(Payment, VS0), VS2 = collect_refund_varset(
#{payment_tool := PaymentTool} = VS2 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS1),
MerchantTerms = get_merchant_payments_terms(Opts, Revision, CreatedAt, VS2),
VS3 = collect_refund_varset(
MerchantTerms#domain_PaymentsServiceTerms.refunds, MerchantTerms#domain_PaymentsServiceTerms.refunds,
PaymentTool, PaymentTool,
VS2 VS1
), ),
VS4 = collect_chargeback_varset( VS3 = collect_chargeback_varset(
MerchantTerms#domain_PaymentsServiceTerms.chargebacks, MerchantTerms#domain_PaymentsServiceTerms.chargebacks,
VS3 VS2
), ),
PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision), PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision),
try try
@ -1871,10 +1838,10 @@ process_routing(Action, St) ->
{ok, PaymentRoute} -> {ok, PaymentRoute} ->
[hg_routing:from_payment_route(PaymentRoute)]; [hg_routing:from_payment_route(PaymentRoute)];
undefined -> undefined ->
gather_routes(PaymentInstitution, VS4, Revision, St) gather_routes(PaymentInstitution, VS3, Revision, St)
end, end,
Events = handle_gathered_route_result( Events = handle_gathered_route_result(
filter_limit_overflow_routes(Routes, VS4, St), filter_limit_overflow_routes(Routes, VS3, St),
[hg_routing:to_payment_route(R) || R <- Routes], [hg_routing:to_payment_route(R) || R <- Routes],
Revision Revision
), ),
@ -1885,8 +1852,8 @@ process_routing(Action, St) ->
end. end.
handle_gathered_route_result({ok, RoutesNoOverflow}, Routes, Revision) -> handle_gathered_route_result({ok, RoutesNoOverflow}, Routes, Revision) ->
{ChoosenRoute, ChoiceMeta} = hg_routing:choose_route(RoutesNoOverflow), {ChoosenRoute, ChoiceContext} = hg_routing:choose_route(RoutesNoOverflow),
_ = log_route_choice_meta(ChoiceMeta, Revision), _ = log_route_choice_meta(ChoiceContext, Revision),
[?route_changed(hg_routing:to_payment_route(ChoosenRoute), Routes)]; [?route_changed(hg_routing:to_payment_route(ChoosenRoute), Routes)];
handle_gathered_route_result({error, not_found}, Routes, _) -> handle_gathered_route_result({error, not_found}, Routes, _) ->
Failure = Failure =
@ -1912,17 +1879,16 @@ handle_choose_route_error(Reason, Events, St, Action) ->
-spec process_cash_flow_building(action(), st()) -> machine_result(). -spec process_cash_flow_building(action(), st()) -> machine_result().
process_cash_flow_building(Action, St) -> process_cash_flow_building(Action, St) ->
Route = get_route(St),
Opts = get_opts(St), Opts = get_opts(St),
Revision = get_payment_revision(St), Revision = get_payment_revision(St),
Payment = get_payment(St), Payment = get_payment(St),
Invoice = get_invoice(Opts), Invoice = get_invoice(Opts),
Route = get_route(St), VS = get_varset(St, #{}),
Timestamp = get_payment_created_at(Payment), CreatedAt = get_payment_created_at(Payment),
VS0 = reconstruct_payment_flow(Payment, #{}), MerchantTerms = get_merchant_payments_terms(Opts, Revision, CreatedAt, VS),
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0), ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS1), FinalCashflow = calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS, Revision, Opts),
ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision),
FinalCashflow = calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS1, Revision, Opts),
_ = rollback_unused_payment_limits(St), _ = rollback_unused_payment_limits(St),
_Clock = hg_accounting:hold( _Clock = hg_accounting:hold(
construct_payment_plan_id(Invoice, Payment), construct_payment_plan_id(Invoice, Payment),
@ -2539,7 +2505,7 @@ get_provider_terms(St, Revision) ->
Payment = get_payment(St), Payment = get_payment(St),
VS0 = reconstruct_payment_flow(Payment, #{}), VS0 = reconstruct_payment_flow(Payment, #{}),
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0), VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
get_provider_terminal_terms(Route, VS1, Revision). hg_routing:get_payment_terms(Route, VS1, Revision).
filter_limit_overflow_routes(Routes, VS, St) -> filter_limit_overflow_routes(Routes, VS, St) ->
ok = hold_limit_routes(Routes, VS, St), ok = hold_limit_routes(Routes, VS, St),
@ -2552,13 +2518,14 @@ filter_limit_overflow_routes(Routes, VS, St) ->
end. end.
get_limit_overflow_routes(Routes, VS, St, RejectedRoutes) -> get_limit_overflow_routes(Routes, VS, St, RejectedRoutes) ->
Opts = get_opts(St),
Revision = get_payment_revision(St), Revision = get_payment_revision(St),
Payment = get_payment(St), Payment = get_payment(St),
Invoice = get_invoice(get_opts(St)), Invoice = get_invoice(Opts),
lists:foldl( lists:foldl(
fun(Route, {RoutesNoOverflowIn, RejectedIn}) -> fun(Route, {RoutesNoOverflowIn, RejectedIn}) ->
PaymentRoute = hg_routing:to_payment_route(Route), PaymentRoute = hg_routing:to_payment_route(Route),
ProviderTerms = get_provider_terminal_terms(PaymentRoute, VS, Revision), ProviderTerms = hg_routing:get_payment_terms(PaymentRoute, VS, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms), TurnoverLimits = get_turnover_limits(ProviderTerms),
case hg_limiter:check_limits(TurnoverLimits, Invoice, Payment) of case hg_limiter:check_limits(TurnoverLimits, Invoice, Payment) of
{ok, _} -> {ok, _} ->
@ -2575,13 +2542,14 @@ get_limit_overflow_routes(Routes, VS, St, RejectedRoutes) ->
). ).
hold_limit_routes(Routes, VS, St) -> hold_limit_routes(Routes, VS, St) ->
Opts = get_opts(St),
Revision = get_payment_revision(St), Revision = get_payment_revision(St),
Payment = get_payment(St), Payment = get_payment(St),
Invoice = get_invoice(get_opts(St)), Invoice = get_invoice(Opts),
lists:foreach( lists:foreach(
fun(Route) -> fun(Route) ->
PaymentRoute = hg_routing:to_payment_route(Route), PaymentRoute = hg_routing:to_payment_route(Route),
ProviderTerms = get_provider_terminal_terms(PaymentRoute, VS, Revision), ProviderTerms = hg_routing:get_payment_terms(PaymentRoute, VS, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms), TurnoverLimits = get_turnover_limits(ProviderTerms),
ok = hg_limiter:hold_payment_limits(TurnoverLimits, PaymentRoute, Invoice, Payment) ok = hg_limiter:hold_payment_limits(TurnoverLimits, PaymentRoute, Invoice, Payment)
end, end,
@ -2589,15 +2557,14 @@ hold_limit_routes(Routes, VS, St) ->
). ).
rollback_payment_limits(Routes, St) -> rollback_payment_limits(Routes, St) ->
Revision = get_payment_revision(St),
Opts = get_opts(St), Opts = get_opts(St),
Revision = get_payment_revision(St),
Payment = get_payment(St), Payment = get_payment(St),
Invoice = get_invoice(get_opts(St)), Invoice = get_invoice(Opts),
VS0 = reconstruct_payment_flow(Payment, #{}), VS = get_varset(St, #{}),
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
lists:foreach( lists:foreach(
fun(Route) -> fun(Route) ->
ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision), ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms), TurnoverLimits = get_turnover_limits(ProviderTerms),
ok = hg_limiter:rollback_payment_limits(TurnoverLimits, Route, Invoice, Payment) ok = hg_limiter:rollback_payment_limits(TurnoverLimits, Route, Invoice, Payment)
end, end,
@ -2615,9 +2582,10 @@ get_turnover_limits(ProviderTerms) ->
hg_limiter:get_turnover_limits(TurnoverLimitSelector). hg_limiter:get_turnover_limits(TurnoverLimitSelector).
commit_payment_limits(#st{capture_params = CaptureParams} = St) -> commit_payment_limits(#st{capture_params = CaptureParams} = St) ->
Opts = get_opts(St),
Revision = get_payment_revision(St), Revision = get_payment_revision(St),
Invoice = get_invoice(get_opts(St)),
Payment = get_payment(St), Payment = get_payment(St),
Invoice = get_invoice(Opts),
Route = get_route(St), Route = get_route(St),
#payproc_InvoicePaymentCaptureParams{cash = CapturedCash} = CaptureParams, #payproc_InvoicePaymentCaptureParams{cash = CapturedCash} = CaptureParams,
ProviderTerms = get_provider_terms(St, Revision), ProviderTerms = get_provider_terms(St, Revision),
@ -2929,6 +2897,13 @@ get_payer_payment_tool(?recurrent_payer(PaymentTool, _, _)) ->
get_resource_payment_tool(#domain_DisposablePaymentResource{payment_tool = PaymentTool}) -> get_resource_payment_tool(#domain_DisposablePaymentResource{payment_tool = PaymentTool}) ->
PaymentTool. PaymentTool.
get_varset(St, InitialValue) ->
Opts = get_opts(St),
Payment = get_payment(St),
VS0 = reconstruct_payment_flow(Payment, InitialValue),
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
VS1.
%% %%
-spec throw_invalid_request(binary()) -> no_return(). -spec throw_invalid_request(binary()) -> no_return().

View File

@ -422,7 +422,7 @@ build_chargeback_cash_flow(State, Opts) ->
Shop = hg_party:get_shop(ShopID, Party), Shop = hg_party:get_shop(ShopID, Party),
VS = collect_validation_varset(Party, Shop, Payment, Body), VS = collect_validation_varset(Party, Shop, Payment, Body),
ServiceTerms = get_merchant_chargeback_terms(Party, Shop, VS, Revision, CreatedAt), ServiceTerms = get_merchant_chargeback_terms(Party, Shop, VS, Revision, CreatedAt),
PaymentsTerms = hg_routing:get_payments_terms(Route, Revision), PaymentsTerms = hg_routing:get_payment_terms(Route, VS, Revision),
ProviderTerms = get_provider_chargeback_terms(PaymentsTerms, Payment), ProviderTerms = get_provider_chargeback_terms(PaymentsTerms, Payment),
ServiceCashFlow = get_chargeback_service_cash_flow(ServiceTerms), ServiceCashFlow = get_chargeback_service_cash_flow(ServiceTerms),
ProviderCashFlow = get_chargeback_provider_cash_flow(ProviderTerms), ProviderCashFlow = get_chargeback_provider_cash_flow(ProviderTerms),

View File

@ -108,9 +108,7 @@ filtermap_payment_methods_to_set(ItemList) ->
) )
). ).
maybe_legacy_bank_card(#domain_BankCard{payment_system_deprecated = PS} = BC) when maybe_legacy_bank_card(#domain_BankCard{payment_system_deprecated = PS} = BC) when PS /= undefined ->
PS /= undefined
->
#domain_BankCardPaymentMethod{ #domain_BankCardPaymentMethod{
payment_system_deprecated = BC#domain_BankCard.payment_system_deprecated, payment_system_deprecated = BC#domain_BankCard.payment_system_deprecated,
is_cvv_empty = genlib:define(BC#domain_BankCard.is_cvv_empty, false), is_cvv_empty = genlib:define(BC#domain_BankCard.is_cvv_empty, false),
@ -120,9 +118,7 @@ maybe_legacy_bank_card(#domain_BankCard{payment_system_deprecated = PS} = BC) wh
maybe_legacy_bank_card(_) -> maybe_legacy_bank_card(_) ->
undefined. undefined.
maybe_bank_card(#domain_BankCard{payment_system = PS} = BC) when maybe_bank_card(#domain_BankCard{payment_system = PS} = BC) when PS /= undefined ->
PS /= undefined
->
#domain_BankCardPaymentMethod{ #domain_BankCardPaymentMethod{
payment_system = PS, payment_system = PS,
is_cvv_empty = genlib:define(BC#domain_BankCard.is_cvv_empty, false), is_cvv_empty = genlib:define(BC#domain_BankCard.is_cvv_empty, false),

View File

@ -240,9 +240,9 @@ init(EncodedParams, #{id := RecPaymentToolID}) ->
try try
check_risk_score(RiskScore), check_risk_score(RiskScore),
NonFailRatedRoutes = gather_routes(PaymentInstitution, VS1, Revision), NonFailRatedRoutes = gather_routes(PaymentInstitution, VS1, Revision),
{ChosenRoute, ChoiceMeta} = hg_routing:choose_route(NonFailRatedRoutes), {ChosenRoute, ChoiceContext} = hg_routing:choose_route(NonFailRatedRoutes),
ChosenPaymentRoute = hg_routing:to_payment_route(ChosenRoute), ChosenPaymentRoute = hg_routing:to_payment_route(ChosenRoute),
_ = logger:log(info, "Routing decision made", hg_routing:get_logger_metadata(ChoiceMeta, Revision)), _ = logger:log(info, "Routing decision made", hg_routing:get_logger_metadata(ChoiceContext, Revision)),
RecPaymentTool2 = set_minimal_payment_cost(RecPaymentTool, ChosenPaymentRoute, VS, Revision), RecPaymentTool2 = set_minimal_payment_cost(RecPaymentTool, ChosenPaymentRoute, VS, Revision),
{ok, {Changes, Action}} = start_session(), {ok, {Changes, Action}} = start_session(),
StartChanges = [ StartChanges = [
@ -258,23 +258,25 @@ init(EncodedParams, #{id := RecPaymentToolID}) ->
throw:risk_score_is_too_high = Error -> throw:risk_score_is_too_high = Error ->
error(handle_route_error(Error, RecPaymentTool)); error(handle_route_error(Error, RecPaymentTool));
throw:{no_route_found, {unknown, _}} = Error -> throw:{no_route_found, {unknown, _}} = Error ->
error(handle_route_error(Error, RecPaymentTool)) error(handle_route_error(Error, RecPaymentTool, VS1))
end. end.
gather_routes(PaymentInstitution, VS, Revision) -> gather_routes(PaymentInstitution, VS, Revision) ->
Predestination = recurrent_paytool, Predestination = recurrent_paytool,
case case
hg_routing_rule:gather_routes( hg_routing:gather_routes(
Predestination, Predestination,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
) )
of of
{[], RejectContext} -> {ok, {[], RejectedRoutes}} ->
throw({no_route_found, {unknown, RejectContext}}); throw({no_route_found, {unknown, RejectedRoutes}});
{Routes, _RejectContext} -> {ok, {Routes, _RejectContext}} ->
Routes Routes;
{error, {misconfiguration, _Reason}} ->
throw({no_route_found, misconfiguration})
end. end.
%% TODO uncomment after inspect will implement %% TODO uncomment after inspect will implement
@ -361,8 +363,8 @@ validate_risk_score(RiskScore) when RiskScore == low; RiskScore == high ->
handle_route_error(risk_score_is_too_high = Reason, RecPaymentTool) -> handle_route_error(risk_score_is_too_high = Reason, RecPaymentTool) ->
_ = logger:log(info, "No route found, reason = ~p", [Reason], logger:get_process_metadata()), _ = logger:log(info, "No route found, reason = ~p", [Reason], logger:get_process_metadata()),
{misconfiguration, {'No route found for a recurrent payment tool', RecPaymentTool}}; {misconfiguration, {'No route found for a recurrent payment tool', RecPaymentTool}}.
handle_route_error({no_route_found, {Reason, RejectContext}}, RecPaymentTool) -> handle_route_error({no_route_found, {Reason, RejectedRoutes}}, RecPaymentTool, Varset) ->
LogFun = fun(Msg, Param) -> LogFun = fun(Msg, Param) ->
_ = logger:log( _ = logger:log(
error, error,
@ -371,9 +373,8 @@ handle_route_error({no_route_found, {Reason, RejectContext}}, RecPaymentTool) ->
logger:get_process_metadata() logger:get_process_metadata()
) )
end, end,
_ = LogFun("No route found, reason = ~p, varset: ~p", maps:get(varset, RejectContext)), _ = LogFun("No route found, reason = ~p, varset: ~p", Varset),
_ = LogFun("No route found, reason = ~p, rejected providers: ~p", maps:get(rejected_providers, RejectContext)), _ = LogFun("No route found, reason = ~p, rejected routes: ~p", RejectedRoutes),
_ = LogFun("No route found, reason = ~p, rejected routes: ~p", maps:get(rejected_routes, RejectContext)),
{misconfiguration, {'No route found for a recurrent payment tool', RecPaymentTool}}. {misconfiguration, {'No route found for a recurrent payment tool', RecPaymentTool}}.
start_session() -> start_session() ->

View File

@ -6,13 +6,9 @@
-include_lib("damsel/include/dmsl_payment_processing_thrift.hrl"). -include_lib("damsel/include/dmsl_payment_processing_thrift.hrl").
-include_lib("fault_detector_proto/include/fd_proto_fault_detector_thrift.hrl"). -include_lib("fault_detector_proto/include/fd_proto_fault_detector_thrift.hrl").
-export([gather_fail_rates/1]). -export([gather_routes/4]).
-export([choose_route/1]). -export([choose_route/1]).
-export([choose_rated_route/1]). -export([get_payment_terms/3]).
-export([get_payments_terms/2]).
-export([acceptable_terminal/5]).
-export([marshal/1]). -export([marshal/1]).
-export([unmarshal/1]). -export([unmarshal/1]).
@ -20,39 +16,41 @@
-export([get_logger_metadata/2]). -export([get_logger_metadata/2]).
-export([from_payment_route/1]). -export([from_payment_route/1]).
-export([new/2]).
-export([new/4]). -export([new/4]).
-export([to_payment_route/1]). -export([to_payment_route/1]).
-export([provider_ref/1]). -export([provider_ref/1]).
-export([terminal_ref/1]). -export([terminal_ref/1]).
-export([prepare_log_message/1]).
%% Using in ct
-export([gather_fail_rates/1]).
-export([choose_rated_route/1]).
%% %%
-include("domain.hrl"). -include("domain.hrl").
-type terms() :: -record(route, {
dmsl_domain_thrift:'PaymentsProvisionTerms'() provider_ref :: dmsl_domain_thrift:'ProviderRef'(),
| dmsl_domain_thrift:'RecurrentPaytoolsProvisionTerms'() terminal_ref :: dmsl_domain_thrift:'TerminalRef'(),
| undefined. weight :: integer(),
priority :: integer()
}).
-type route() :: #route{}.
-type payment_terms() :: dmsl_domain_thrift:'PaymentsProvisionTerms'().
-type payment_institution() :: dmsl_domain_thrift:'PaymentInstitution'().
-type payment_route() :: dmsl_domain_thrift:'PaymentRoute'(). -type payment_route() :: dmsl_domain_thrift:'PaymentRoute'().
-type route_predestination() :: payment | recurrent_paytool | recurrent_payment. -type route_predestination() :: payment | recurrent_paytool | recurrent_payment.
-define(rejected(Reason), {rejected, Reason}). -define(rejected(Reason), {rejected, Reason}).
-type reject_context() :: #{
varset := varset(),
rejected_providers := list(rejected_provider()),
rejected_routes := list(rejected_route())
}.
-type rejected_provider() :: {provider_ref(), Reason :: term()}.
-type rejected_route() :: {provider_ref(), terminal_ref(), Reason :: term()}. -type rejected_route() :: {provider_ref(), terminal_ref(), Reason :: term()}.
-type provider_ref() :: dmsl_domain_thrift:'ProviderRef'(). -type provider_ref() :: dmsl_domain_thrift:'ProviderRef'().
-type terminal() :: dmsl_domain_thrift:'Terminal'().
-type terminal_ref() :: dmsl_domain_thrift:'TerminalRef'(). -type terminal_ref() :: dmsl_domain_thrift:'TerminalRef'().
-type fd_service_stats() :: fd_proto_fault_detector_thrift:'ServiceStatistics'(). -type fd_service_stats() :: fd_proto_fault_detector_thrift:'ServiceStatistics'().
-type unweighted_terminal() :: {terminal_ref(), terminal()}.
-type terminal_priority_rating() :: integer(). -type terminal_priority_rating() :: integer().
@ -71,38 +69,19 @@
-type route_groups_by_priority() :: #{{availability_condition(), terminal_priority_rating()} => [fail_rated_route()]}. -type route_groups_by_priority() :: #{{availability_condition(), terminal_priority_rating()} => [fail_rated_route()]}.
-type route() :: #{
provider_ref := dmsl_domain_thrift:'ProviderRef'(),
terminal_ref := dmsl_domain_thrift:'TerminalRef'(),
weight => integer(),
priority := integer()
}.
-type fail_rated_route() :: {route(), provider_status()}. -type fail_rated_route() :: {route(), provider_status()}.
-type scored_route() :: {route_scores(), route()}. -type scored_route() :: {route_scores(), route()}.
-type route_choice_meta() :: #{ -type route_choice_context() :: #{
chosen_route => route(), chosen_route => route(),
preferable_route => route(), preferable_route => route(),
% Contains one of the field names defined in #route_scores{} % Contains one of the field names defined in #route_scores{}
reject_reason => atom() reject_reason => atom()
}. }.
-type varset() :: #{ -type varset() :: hg_varset:varset().
category => dmsl_domain_thrift:'CategoryRef'(), -type revision() :: hg_domain:revision().
currency => dmsl_domain_thrift:'CurrencyRef'(),
cost => dmsl_domain_thrift:'Cash'(),
payment_tool => dmsl_domain_thrift:'PaymentTool'(),
party_id => dmsl_domain_thrift:'PartyID'(),
shop_id => dmsl_domain_thrift:'ShopID'(),
risk_score => dmsl_domain_thrift:'RiskScore'(),
flow => instant | {hold, dmsl_domain_thrift:'HoldLifetime'()},
payout_method => dmsl_domain_thrift:'PayoutMethodRef'(),
wallet_id => dmsl_domain_thrift:'WalletID'(),
identification_level => dmsl_domain_thrift:'ContractorIdentificationLevel'(),
p2p_tool => dmsl_domain_thrift:'P2PTool'()
}.
-record(route_scores, { -record(route_scores, {
availability_condition :: condition_score(), availability_condition :: condition_score(),
@ -114,79 +93,206 @@
}). }).
-type route_scores() :: #route_scores{}. -type route_scores() :: #route_scores{}.
-type misconfiguration_error() :: {misconfiguration, {routing_decisions, _} | {routing_candidate, _}}.
-export_type([route/0]). -export_type([route/0]).
-export_type([route_predestination/0]). -export_type([route_predestination/0]).
-export_type([reject_context/0]).
-export_type([varset/0]).
-define(DEFAULT_ROUTE_WEIGHT, 0). %% Route accessors
% Set value like in protocol
% https://github.com/rbkmoney/damsel/blob/fa979b0e7e5bcf0aff7b55927689368317e0d858/proto/domain.thrift#L2814
-define(DEFAULT_ROUTE_PRIORITY, 1000).
-spec from_payment_route(payment_route()) -> route(). -spec new(provider_ref(), terminal_ref()) -> route().
from_payment_route(Route) -> new(ProviderRef, TerminalRef) ->
?route(ProviderRef, TerminalRef) = Route, #route{
#{ provider_ref = ProviderRef,
provider_ref => ProviderRef, terminal_ref = TerminalRef,
terminal_ref => TerminalRef, weight = ?DOMAIN_CANDIDATE_WEIGHT,
weight => ?DEFAULT_ROUTE_WEIGHT, priority = ?DOMAIN_CANDIDATE_PRIORITY
priority => ?DEFAULT_ROUTE_PRIORITY
}. }.
-spec new(provider_ref(), terminal_ref(), integer() | undefined, integer()) -> route(). -spec new(provider_ref(), terminal_ref(), integer() | undefined, integer()) -> route().
new(ProviderRef, TerminalRef, undefined, Priority) -> new(ProviderRef, TerminalRef, undefined, Priority) ->
new(ProviderRef, TerminalRef, ?DEFAULT_ROUTE_WEIGHT, Priority); new(ProviderRef, TerminalRef, ?DOMAIN_CANDIDATE_WEIGHT, Priority);
new(ProviderRef, TerminalRef, Weight, Priority) -> new(ProviderRef, TerminalRef, Weight, Priority) ->
#{ #route{
provider_ref => ProviderRef, provider_ref = ProviderRef,
terminal_ref => TerminalRef, terminal_ref = TerminalRef,
weight => Weight, weight = Weight,
priority => Priority priority = Priority
}. }.
-spec to_payment_route(route()) -> payment_route().
to_payment_route(#{provider_ref := _, terminal_ref := _} = Route) ->
?route(provider_ref(Route), terminal_ref(Route)).
-spec provider_ref(route()) -> provider_ref(). -spec provider_ref(route()) -> provider_ref().
provider_ref(#{provider_ref := Ref}) -> provider_ref(#route{provider_ref = Ref}) ->
Ref. Ref.
-spec terminal_ref(route()) -> terminal_ref(). -spec terminal_ref(route()) -> terminal_ref().
terminal_ref(#{terminal_ref := Ref}) -> terminal_ref(#route{terminal_ref = Ref}) ->
Ref. Ref.
-spec priority(route()) -> integer(). -spec priority(route()) -> integer().
priority(#{priority := Priority}) -> priority(#route{priority = Priority}) ->
Priority. Priority.
-spec weight(route()) -> integer(). -spec weight(route()) -> integer().
weight(Route) -> weight(#route{weight = Weight}) ->
maps:get(weight, Route). Weight.
-spec from_payment_route(payment_route()) -> route().
from_payment_route(Route) ->
?route(ProviderRef, TerminalRef) = Route,
#route{
provider_ref = ProviderRef,
terminal_ref = TerminalRef,
weight = ?DOMAIN_CANDIDATE_WEIGHT,
priority = ?DOMAIN_CANDIDATE_PRIORITY
}.
-spec to_payment_route(route()) -> payment_route().
to_payment_route(#route{} = Route) ->
?route(provider_ref(Route), terminal_ref(Route)).
-spec set_weight(integer(), route()) -> route(). -spec set_weight(integer(), route()) -> route().
set_weight(Weight, Route) -> set_weight(Weight, Route) ->
Route#{weight => Weight}. Route#route{weight = Weight}.
-spec prepare_log_message(misconfiguration_error()) -> {io:format(), [term()]}.
prepare_log_message({misconfiguration, {routing_decisions, Details}}) ->
{"PaymentRoutingDecisions couldn't be reduced to candidates, ~p", [Details]};
prepare_log_message({misconfiguration, {routing_candidate, Candidate}}) ->
{"PaymentRoutingCandidate couldn't be reduced, ~p", [Candidate]}.
%%
-spec gather_routes(
route_predestination(),
payment_institution(),
varset(),
revision()
) ->
{ok, {[route()], [rejected_route()]}}
| {error, misconfiguration_error()}.
gather_routes(_, #domain_PaymentInstitution{payment_routing_rules = undefined}, _, _) ->
{ok, {[], []}};
gather_routes(Predestination, #domain_PaymentInstitution{payment_routing_rules = RoutingRules}, VS, Revision) ->
#domain_RoutingRules{
policies = Policies,
prohibitions = Prohibitions
} = RoutingRules,
try
Candidates = get_candidates(Policies, VS, Revision),
{Accepted, RejectedRoutes} = filter_routes(
collect_routes(Predestination, Candidates, VS, Revision),
get_table_prohibitions(Prohibitions, VS, Revision)
),
{ok, {Accepted, RejectedRoutes}}
catch
throw:{misconfiguration, _Reason} = Error ->
{error, Error}
end.
get_table_prohibitions(Prohibitions, VS, Revision) ->
RuleSetDeny = compute_rule_set(Prohibitions, VS, Revision),
lists:foldr(
fun(#domain_RoutingCandidate{terminal = K, description = V}, AccIn) ->
AccIn#{K => V}
end,
#{},
get_decisions_candidates(RuleSetDeny)
).
get_candidates(RoutingRule, VS, Revision) ->
get_decisions_candidates(
compute_rule_set(RoutingRule, VS, Revision)
).
get_decisions_candidates(#domain_RoutingRuleset{decisions = Decisions}) ->
case Decisions of
{delegates, _Delegates} ->
throw({misconfiguration, {routing_decisions, Decisions}});
{candidates, Candidates} ->
ok = validate_decisions_candidates(Candidates),
Candidates
end.
validate_decisions_candidates([]) ->
ok;
validate_decisions_candidates([#domain_RoutingCandidate{allowed = {constant, true}} | Rest]) ->
validate_decisions_candidates(Rest);
validate_decisions_candidates([Candidate | _]) ->
throw({misconfiguration, {routing_candidate, Candidate}}).
collect_routes(Predestination, Candidates, VS, Revision) ->
lists:foldr(
fun(Candidate, {Accepted, Rejected}) ->
#domain_RoutingCandidate{
terminal = TerminalRef,
priority = Priority,
weight = Weight
} = Candidate,
% Looks like overhead, we got Terminal only for provider_ref. Maybe we can remove provider_ref from route().
% https://github.com/rbkmoney/hellgate/pull/583#discussion_r682745123
#domain_Terminal{
provider_ref = ProviderRef
} = hg_domain:get(Revision, {terminal, TerminalRef}),
try
true = acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision),
Route = new(ProviderRef, TerminalRef, Weight, Priority),
{[Route | Accepted], Rejected}
catch
{rejected, Reason} ->
{Accepted, [{ProviderRef, TerminalRef, Reason} | Rejected]};
error:{misconfiguration, Reason} ->
{Accepted, [{ProviderRef, TerminalRef, {'Misconfiguration', Reason}} | Rejected]}
end
end,
{[], []},
Candidates
).
filter_routes({Routes, Rejected}, Prohibitions) ->
lists:foldr(
fun(Route, {AccIn, RejectedIn}) ->
TRef = terminal_ref(Route),
case maps:find(TRef, Prohibitions) of
error ->
{[Route | AccIn], RejectedIn};
{ok, Description} ->
PRef = provider_ref(Route),
RejectedOut = [{PRef, TRef, {'RoutingRule', Description}} | RejectedIn],
{AccIn, RejectedOut}
end
end,
{[], Rejected},
Routes
).
compute_rule_set(RuleSetRef, VS, Revision) ->
Ctx = hg_context:load(),
{ok, RuleSet} = party_client_thrift:compute_routing_ruleset(
RuleSetRef,
Revision,
hg_varset:prepare_varset(VS),
hg_context:get_party_client(Ctx),
hg_context:get_party_client_context(Ctx)
),
RuleSet.
-spec gather_fail_rates([route()]) -> [fail_rated_route()]. -spec gather_fail_rates([route()]) -> [fail_rated_route()].
gather_fail_rates(Routes) -> gather_fail_rates(Routes) ->
score_routes_with_fault_detector(Routes). score_routes_with_fault_detector(Routes).
-spec choose_route([route()]) -> {route(), route_choice_meta()}. -spec choose_route([route()]) -> {route(), route_choice_context()}.
choose_route(Routes) -> choose_route(Routes) ->
FailRatedRoutes = gather_fail_rates(Routes), FailRatedRoutes = gather_fail_rates(Routes),
choose_rated_route(FailRatedRoutes). choose_rated_route(FailRatedRoutes).
-spec choose_rated_route([fail_rated_route()]) -> {route(), route_choice_meta()}. -spec choose_rated_route([fail_rated_route()]) -> {route(), route_choice_context()}.
choose_rated_route(FailRatedRoutes) -> choose_rated_route(FailRatedRoutes) ->
BalancedRoutes = balance_routes(FailRatedRoutes), BalancedRoutes = balance_routes(FailRatedRoutes),
ScoredRoutes = score_routes(BalancedRoutes), ScoredRoutes = score_routes(BalancedRoutes),
{ChosenScoredRoute, IdealRoute} = find_best_routes(ScoredRoutes), {ChosenScoredRoute, IdealRoute} = find_best_routes(ScoredRoutes),
RouteChoiceMeta = get_route_choice_meta(ChosenScoredRoute, IdealRoute), RouteChoiceContext = get_route_choice_context(ChosenScoredRoute, IdealRoute),
{_, Route} = ChosenScoredRoute, {_, Route} = ChosenScoredRoute,
{Route, RouteChoiceMeta}. {Route, RouteChoiceContext}.
-spec find_best_routes([scored_route()]) -> {Chosen :: scored_route(), Ideal :: scored_route()}. -spec find_best_routes([scored_route()]) -> {Chosen :: scored_route(), Ideal :: scored_route()}.
find_best_routes([Route]) -> find_best_routes([Route]) ->
@ -224,28 +330,28 @@ set_ideal_score({RouteScores, PT}) ->
PT PT
}. }.
get_route_choice_meta({_, SameRoute}, {_, SameRoute}) -> get_route_choice_context({_, SameRoute}, {_, SameRoute}) ->
#{ #{
chosen_route => SameRoute chosen_route => SameRoute
}; };
get_route_choice_meta({ChosenScores, ChosenRoute}, {IdealScores, IdealRoute}) -> get_route_choice_context({ChosenScores, ChosenRoute}, {IdealScores, IdealRoute}) ->
#{ #{
chosen_route => ChosenRoute, chosen_route => ChosenRoute,
preferable_route => IdealRoute, preferable_route => IdealRoute,
reject_reason => map_route_switch_reason(ChosenScores, IdealScores) reject_reason => map_route_switch_reason(ChosenScores, IdealScores)
}. }.
-spec get_logger_metadata(route_choice_meta(), hg_domain:revision()) -> LoggerFormattedMetadata :: map(). -spec get_logger_metadata(route_choice_context(), revision()) -> LoggerFormattedMetadata :: map().
get_logger_metadata(RouteChoiceMeta, Revision) -> get_logger_metadata(RouteChoiceContext, Revision) ->
#{route_choice_metadata => format_logger_metadata(RouteChoiceMeta, Revision)}. #{route_choice_metadata => format_logger_metadata(RouteChoiceContext, Revision)}.
format_logger_metadata(RouteChoiceMeta, Revision) -> format_logger_metadata(RouteChoiceContext, Revision) ->
maps:fold( maps:fold(
fun(K, V, Acc) -> fun(K, V, Acc) ->
Acc ++ format_logger_metadata(K, V, Revision) Acc ++ format_logger_metadata(K, V, Revision)
end, end,
[], [],
RouteChoiceMeta RouteChoiceContext
). ).
format_logger_metadata(reject_reason, Reason, _) -> format_logger_metadata(reject_reason, Reason, _) ->
@ -259,9 +365,13 @@ add_route_name(Route, Revision) ->
TerminalRef = terminal_ref(Route), TerminalRef = terminal_ref(Route),
#domain_Provider{name = PName} = hg_domain:get(Revision, {provider, ProviderRef}), #domain_Provider{name = PName} = hg_domain:get(Revision, {provider, ProviderRef}),
#domain_Terminal{name = TName} = hg_domain:get(Revision, {terminal, TerminalRef}), #domain_Terminal{name = TName} = hg_domain:get(Revision, {terminal, TerminalRef}),
genlib_map:compact(Route#{ genlib_map:compact(#{
provider_name => PName, provider_name => PName,
terminal_name => TName terminal_name => TName,
provider_ref => ProviderRef,
terminal_ref => TerminalRef,
priority => priority(Route),
weight => weight(Route)
}). }).
map_route_switch_reason(SameScores, SameScores) -> map_route_switch_reason(SameScores, SameScores) ->
@ -418,20 +528,27 @@ build_fd_availability_service_id(#domain_ProviderRef{id = ID}) ->
build_fd_conversion_service_id(#domain_ProviderRef{id = ID}) -> build_fd_conversion_service_id(#domain_ProviderRef{id = ID}) ->
hg_fault_detector_client:build_service_id(provider_conversion, ID). hg_fault_detector_client:build_service_id(provider_conversion, ID).
-spec get_payments_terms(payment_route(), hg_domain:revision()) -> terms(). -spec get_payment_terms(payment_route(), varset(), revision()) -> payment_terms() | undefined.
get_payments_terms(?route(ProviderRef, TerminalRef), Revision) -> get_payment_terms(?route(ProviderRef, TerminalRef), VS, Revision) ->
#domain_Provider{terms = Terms0} = hg_domain:get(Revision, {provider, ProviderRef}), PreparedVS = hg_varset:prepare_varset(VS),
#domain_Terminal{terms = Terms1} = hg_domain:get(Revision, {terminal, TerminalRef}), {Client, Context} = get_party_client(),
Terms = merge_terms(Terms0, Terms1), {ok, TermsSet} = party_client_thrift:compute_provider_terminal_terms(
Terms#domain_ProvisionTermSet.payments. ProviderRef,
TerminalRef,
Revision,
PreparedVS,
Client,
Context
),
TermsSet#domain_ProvisionTermSet.payments.
-spec acceptable_terminal( -spec acceptable_terminal(
route_predestination(), route_predestination(),
provider_ref(), provider_ref(),
terminal_ref(), terminal_ref(),
varset(), varset(),
hg_domain:revision() revision()
) -> unweighted_terminal() | no_return(). ) -> true | no_return().
acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision) -> acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision) ->
{Client, Context} = get_party_client(), {Client, Context} = get_party_client(),
Result = party_client_thrift:compute_provider_terminal_terms( Result = party_client_thrift:compute_provider_terminal_terms(
@ -442,15 +559,12 @@ acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision) ->
Client, Client,
Context Context
), ),
ProvisionTermSet = case Result of
case Result of {ok, ProvisionTermSet} ->
{ok, Terms} -> check_terms_acceptability(Predestination, ProvisionTermSet, VS);
Terms; {error, #payproc_ProvisionTermSetUndefined{}} ->
{error, #payproc_ProvisionTermSetUndefined{}} -> throw(?rejected({'ProvisionTermSet', undefined}))
undefined end.
end,
_ = check_terms_acceptability(Predestination, ProvisionTermSet, VS),
{TerminalRef, hg_domain:get(Revision, {terminal, TerminalRef})}.
%% %%
@ -461,35 +575,13 @@ get_party_client() ->
{Client, Context}. {Client, Context}.
check_terms_acceptability(payment, Terms, VS) -> check_terms_acceptability(payment, Terms, VS) ->
_ = acceptable_provision_payment_terms(Terms, VS); acceptable_payment_terms(Terms#domain_ProvisionTermSet.payments, VS);
check_terms_acceptability(recurrent_paytool, Terms, VS) -> check_terms_acceptability(recurrent_paytool, Terms, VS) ->
_ = acceptable_provision_recurrent_terms(Terms, VS); acceptable_recurrent_paytool_terms(Terms#domain_ProvisionTermSet.recurrent_paytools, VS);
check_terms_acceptability(recurrent_payment, Terms, VS) -> check_terms_acceptability(recurrent_payment, Terms, VS) ->
% Use provider check combined from recurrent_paytool and payment check % Use provider check combined from recurrent_paytool and payment check
_ = acceptable_provision_payment_terms(Terms, VS), _ = acceptable_payment_terms(Terms#domain_ProvisionTermSet.payments, VS),
_ = acceptable_provision_recurrent_terms(Terms, VS). acceptable_recurrent_paytool_terms(Terms#domain_ProvisionTermSet.recurrent_paytools, VS).
acceptable_provision_payment_terms(
#domain_ProvisionTermSet{
payments = PaymentTerms
},
VS
) ->
_ = acceptable_payment_terms(PaymentTerms, VS),
true;
acceptable_provision_payment_terms(undefined, _VS) ->
throw(?rejected({'ProvisionTermSet', undefined})).
acceptable_provision_recurrent_terms(
#domain_ProvisionTermSet{
recurrent_paytools = RecurrentTerms
},
VS
) ->
_ = acceptable_recurrent_paytool_terms(RecurrentTerms, VS),
true;
acceptable_provision_recurrent_terms(undefined, _VS) ->
throw(?rejected({'ProvisionTermSet', undefined})).
acceptable_payment_terms( acceptable_payment_terms(
#domain_PaymentsProvisionTerms{ #domain_PaymentsProvisionTerms{
@ -568,63 +660,6 @@ acceptable_partial_refunds_terms(
acceptable_partial_refunds_terms(undefined, _RVS) -> acceptable_partial_refunds_terms(undefined, _RVS) ->
throw(?rejected({'PartialRefundsProvisionTerms', undefined})). throw(?rejected({'PartialRefundsProvisionTerms', undefined})).
merge_terms(
#domain_ProvisionTermSet{
payments = PPayments,
recurrent_paytools = PRecurrents
},
#domain_ProvisionTermSet{
payments = TPayments,
% TODO: Allow to define recurrent terms in terminal
recurrent_paytools = _TRecurrents
}
) ->
#domain_ProvisionTermSet{
payments = merge_payment_terms(PPayments, TPayments),
recurrent_paytools = PRecurrents
};
merge_terms(ProviderTerms, TerminalTerms) ->
hg_utils:select_defined(TerminalTerms, ProviderTerms).
-spec merge_payment_terms(terms(), terms()) -> terms().
merge_payment_terms(
#domain_PaymentsProvisionTerms{
currencies = PCurrencies,
categories = PCategories,
payment_methods = PPaymentMethods,
cash_limit = PCashLimit,
cash_flow = PCashflow,
holds = PHolds,
refunds = PRefunds,
chargebacks = PChargebacks,
risk_coverage = PRiskCoverage
},
#domain_PaymentsProvisionTerms{
currencies = TCurrencies,
categories = TCategories,
payment_methods = TPaymentMethods,
cash_limit = TCashLimit,
cash_flow = TCashflow,
holds = THolds,
refunds = TRefunds,
chargebacks = TChargebacks,
risk_coverage = TRiskCoverage
}
) ->
#domain_PaymentsProvisionTerms{
currencies = hg_utils:select_defined(TCurrencies, PCurrencies),
categories = hg_utils:select_defined(TCategories, PCategories),
payment_methods = hg_utils:select_defined(TPaymentMethods, PPaymentMethods),
cash_limit = hg_utils:select_defined(TCashLimit, PCashLimit),
cash_flow = hg_utils:select_defined(TCashflow, PCashflow),
holds = hg_utils:select_defined(THolds, PHolds),
refunds = hg_utils:select_defined(TRefunds, PRefunds),
chargebacks = hg_utils:select_defined(TChargebacks, PChargebacks),
risk_coverage = hg_utils:select_defined(TRiskCoverage, PRiskCoverage)
};
merge_payment_terms(ProviderTerms, TerminalTerms) ->
hg_utils:select_defined(TerminalTerms, ProviderTerms).
%% %%
acceptable_recurrent_paytool_terms( acceptable_recurrent_paytool_terms(
@ -771,33 +806,33 @@ record_comparsion_test() ->
balance_routes_test() -> balance_routes_test() ->
Status = {{alive, 0.0}, {normal, 0.0}}, Status = {{alive, 0.0}, {normal, 0.0}},
WithWeight = [ WithWeight = [
{new(?prv(1), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(1), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(2), ?trm(1), 2, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(2), ?trm(1), 2, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(4), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(4), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status} {new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
], ],
Result1 = [ Result1 = [
{new(?prv(1), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(1), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(4), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(4), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status} {new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
], ],
Result2 = [ Result2 = [
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(2), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(2), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(4), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(4), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status} {new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
], ],
Result3 = [ Result3 = [
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(4), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(4), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status} {new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
], ],
[ [
?assertEqual(Result1, lists:reverse(calc_random_condition(0.0, 0.2, WithWeight, []))), ?assertEqual(Result1, lists:reverse(calc_random_condition(0.0, 0.2, WithWeight, []))),
@ -809,12 +844,12 @@ balance_routes_test() ->
balance_routes_with_default_weight_test() -> balance_routes_with_default_weight_test() ->
Status = {{alive, 0.0}, {normal, 0.0}}, Status = {{alive, 0.0}, {normal, 0.0}},
Routes = [ Routes = [
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status} {new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
], ],
Result = [ Result = [
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}, {new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status} {new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
], ],
[ [
?assertEqual(Result, set_routes_random_condition(Routes)) ?assertEqual(Result, set_routes_random_condition(Routes))

View File

@ -1,133 +0,0 @@
-module(hg_routing_rule).
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
-export([gather_routes/4]).
%%
-type route_predestination() :: hg_routing:route_predestination().
-type payment_institution() :: dmsl_domain_thrift:'PaymentInstitution'().
-type route() :: hg_routing:route().
-type reject_context() :: hg_routing:reject_context().
-spec gather_routes(
route_predestination(),
payment_institution(),
hg_routing:varset(),
hg_domain:revision()
) -> {[route()], reject_context()}.
gather_routes(_, #domain_PaymentInstitution{payment_routing_rules = undefined} = PayInst, VS, _) ->
logger:log(
warning,
"Payment routing rules is undefined, PaymentInstitution: ~p",
[PayInst]
),
{[], #{varset => VS, rejected_providers => [], rejected_routes => []}};
gather_routes(Predestination, #domain_PaymentInstitution{payment_routing_rules = RoutingRules}, VS, Revision) ->
RejectedContext = #{
varset => VS,
rejected_providers => [],
rejected_routes => []
},
#domain_RoutingRules{
policies = Policies,
prohibitions = Prohibitions
} = RoutingRules,
try
Candidates = get_candidates(Policies, VS, Revision),
{Accepted, RejectedRoutes} = filter_routes(
collect_routes(Predestination, Candidates, VS, Revision),
get_table_prohibitions(Prohibitions, VS, Revision)
),
{Accepted, RejectedContext#{rejected_routes => RejectedRoutes}}
catch
error:{misconfiguration, Reason} ->
{[], RejectedContext#{error => Reason}}
end.
get_table_prohibitions(Prohibitions, VS, Revision) ->
RuleSetDeny = compute_rule_set(Prohibitions, VS, Revision),
lists:foldr(
fun(#domain_RoutingCandidate{terminal = K, description = V}, AccIn) ->
AccIn#{K => V}
end,
#{},
get_decisions_candidates(RuleSetDeny)
).
get_candidates(RoutingRule, VS, Revision) ->
get_decisions_candidates(
compute_rule_set(RoutingRule, VS, Revision)
).
get_decisions_candidates(#domain_RoutingRuleset{decisions = Decisions}) ->
case Decisions of
{delegates, _Delegates} ->
error({misconfiguration, {'PaymentRoutingDecisions couldn\'t be reduced to candidates', Decisions}});
{candidates, Candidates} ->
ok = validate_decisions_candidates(Candidates),
Candidates
end.
validate_decisions_candidates([]) ->
ok;
validate_decisions_candidates([#domain_RoutingCandidate{allowed = {constant, true}} | Rest]) ->
validate_decisions_candidates(Rest);
validate_decisions_candidates([Candidate | _]) ->
error({misconfiguration, {'PaymentRoutingCandidate couldn\'t be reduced', Candidate}}).
collect_routes(Predestination, Candidates, VS, Revision) ->
lists:foldr(
fun(Candidate, {Accepted, Rejected}) ->
#domain_RoutingCandidate{
terminal = TerminalRef,
priority = Priority,
weight = Weight
} = Candidate,
% Looks like overhead, we got Terminal only for provider_ref. Maybe we can remove provider_ref from route().
% https://github.com/rbkmoney/hellgate/pull/583#discussion_r682745123
#domain_Terminal{
provider_ref = ProviderRef
} = hg_domain:get(Revision, {terminal, TerminalRef}),
try
{_, _Terminal} = hg_routing:acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision),
Route = hg_routing:new(ProviderRef, TerminalRef, Weight, Priority),
{[Route | Accepted], Rejected}
catch
{rejected, Reason} ->
{Accepted, [{ProviderRef, TerminalRef, Reason} | Rejected]};
error:{misconfiguration, Reason} ->
{Accepted, [{ProviderRef, TerminalRef, {'Misconfiguration', Reason}} | Rejected]}
end
end,
{[], []},
Candidates
).
filter_routes({Routes, Rejected}, Prohibitions) ->
lists:foldr(
fun(Route, {AccIn, RejectedIn}) ->
TRef = hg_routing:terminal_ref(Route),
case maps:find(TRef, Prohibitions) of
error ->
{[Route | AccIn], RejectedIn};
{ok, Description} ->
PRef = hg_routing:provider_ref(Route),
RejectedOut = [{PRef, TRef, {'RoutingRule', Description}} | RejectedIn],
{AccIn, RejectedOut}
end
end,
{[], Rejected},
Routes
).
compute_rule_set(RuleSetRef, VS, Revision) ->
Ctx = hg_context:load(),
{ok, RuleSet} = party_client_thrift:compute_routing_ruleset(
RuleSetRef,
Revision,
hg_varset:prepare_varset(VS),
hg_context:get_party_client(Ctx),
hg_context:get_party_client_context(Ctx)
),
RuleSet.

View File

@ -41,6 +41,7 @@
-define(dummy_party_id, <<"dummy_party_id">>). -define(dummy_party_id, <<"dummy_party_id">>).
-define(dummy_shop_id, <<"dummy_shop_id">>). -define(dummy_shop_id, <<"dummy_shop_id">>).
-define(dummy_another_shop_id, <<"dummy_another_shop_id">>). -define(dummy_another_shop_id, <<"dummy_another_shop_id">>).
-define(assert_set_equal(S1, S2), ?assertEqual(lists:sort(S1), lists:sort(S2))).
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. -spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) -> init([]) ->
@ -184,27 +185,23 @@ no_route_found_for_payment(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{[], RejectContext} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision), {ok, {[], RejectedRoutes}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
#{ [
rejected_routes := [ {?prv(1), ?trm(1), {'PaymentsProvisionTerms', cost}},
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', cost}}, {?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}}, {?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}} ] = RejectedRoutes,
]
} = RejectContext,
VS1 = VS#{ VS1 = VS#{
currency => ?cur(<<"EUR">>), currency => ?cur(<<"EUR">>),
cost => ?cash(1000, <<"EUR">>) cost => ?cash(1000, <<"EUR">>)
}, },
{[], RejectContext1} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS1, Revision), {ok, {[], RejectedRoutes1}} = hg_routing:gather_routes(payment, PaymentInstitution, VS1, Revision),
#{ [
rejected_routes := [ {?prv(1), ?trm(1), {'PaymentsProvisionTerms', currency}},
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', currency}}, {?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}}, {?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}} ] = RejectedRoutes1.
]
} = RejectContext1.
-spec gather_route_success(config()) -> test_return(). -spec gather_route_success(config()) -> test_return().
gather_route_success(_C) -> gather_route_success(_C) ->
@ -220,18 +217,20 @@ gather_route_success(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{[#{terminal_ref := ?trm(1)}], RejectContext} = hg_routing_rule:gather_routes( {ok, {[Route], RejectedRoutes}} = hg_routing:gather_routes(
payment, payment,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
), ),
#{ ?assertMatch(?trm(1), hg_routing:terminal_ref(Route)),
rejected_routes := [ ?assertMatch(
[
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}}, {?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}} {?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
] ],
} = RejectContext. RejectedRoutes
).
-spec rejected_by_table_prohibitions(config()) -> test_return(). -spec rejected_by_table_prohibitions(config()) -> test_return().
rejected_by_table_prohibitions(_C) -> rejected_by_table_prohibitions(_C) ->
@ -254,15 +253,12 @@ rejected_by_table_prohibitions(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{[], RejectContext} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision), {ok, {[], RejectedRoutes}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
[
#{ {?prv(3), ?trm(3), {'RoutingRule', undefined}},
rejected_routes := [ {?prv(1), ?trm(1), {'PaymentsProvisionTerms', payment_tool}},
{?prv(3), ?trm(3), {'RoutingRule', undefined}}, {?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}}
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', payment_tool}}, ] = RejectedRoutes,
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}}
]
} = RejectContext,
ok. ok.
-spec empty_candidate_ok(config()) -> test_return(). -spec empty_candidate_ok(config()) -> test_return().
@ -284,11 +280,7 @@ empty_candidate_ok(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(2)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(2)}),
{[], #{ {ok, {[], []}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision).
varset := VS,
rejected_routes := [],
rejected_providers := []
}} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision).
-spec ruleset_misconfig(config()) -> test_return(). -spec ruleset_misconfig(config()) -> test_return().
ruleset_misconfig(_C) -> ruleset_misconfig(_C) ->
@ -300,21 +292,22 @@ ruleset_misconfig(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{[], #{ {error, {misconfiguration, {routing_decisions, {delegates, []}}}} = hg_routing:gather_routes(
varset := VS, payment,
rejected_routes := [], PaymentInstitution,
rejected_providers := [] VS,
}} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision). Revision
).
-spec routes_selected_for_low_risk_score(config()) -> test_return(). -spec routes_selected_for_low_risk_score(config()) -> test_return().
routes_selected_for_low_risk_score(C) -> routes_selected_for_low_risk_score(C) ->
routes_selected_with_risk_score(C, low, [21, 22, 23]). routes_selected_with_risk_score(C, low, [?prv(21), ?prv(22), ?prv(23)]).
-spec routes_selected_for_high_risk_score(config()) -> test_return(). -spec routes_selected_for_high_risk_score(config()) -> test_return().
routes_selected_for_high_risk_score(C) -> routes_selected_for_high_risk_score(C) ->
routes_selected_with_risk_score(C, high, [22, 23]). routes_selected_with_risk_score(C, high, [?prv(22), ?prv(23)]).
routes_selected_with_risk_score(_C, RiskScore, PrvIDList) -> routes_selected_with_risk_score(_C, RiskScore, ProviderRefs) ->
VS = #{ VS = #{
category => ?cat(1), category => ?cat(1),
currency => ?cur(<<"RUB">>), currency => ?cur(<<"RUB">>),
@ -326,12 +319,8 @@ routes_selected_with_risk_score(_C, RiskScore, PrvIDList) ->
}, },
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{SelectedProviders, _} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision), {ok, {Routes, _}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
Routes = sort_routes(SelectedProviders), ?assert_set_equal(ProviderRefs, lists:map(fun hg_routing:provider_ref/1, Routes)).
%% Ensure list of selected provider ID match to given
PrvIDList = [P || #{provider_ref := ?prv(P)} <- Routes],
ok.
-spec prefer_alive(config()) -> test_return(). -spec prefer_alive(config()) -> test_return().
prefer_alive(_C) -> prefer_alive(_C) ->
@ -347,21 +336,13 @@ prefer_alive(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{RoutesUnordered, _RejectContext} = hg_routing_rule:gather_routes( {ok, {Routes, _RejectedRoutes}} = hg_routing:gather_routes(
payment, payment,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
), ),
Routes = sort_routes(RoutesUnordered), ?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
?assertMatch(
[
#{provider_ref := ?prv(21)},
#{provider_ref := ?prv(22)},
#{provider_ref := ?prv(23)}
],
Routes
),
Alive = {alive, 0.0}, Alive = {alive, 0.0},
Dead = {dead, 1.0}, Dead = {dead, 1.0},
@ -371,23 +352,17 @@ prefer_alive(_C) ->
ProviderStatuses1 = [{Dead, Normal}, {Alive, Normal}, {Dead, Normal}], ProviderStatuses1 = [{Dead, Normal}, {Alive, Normal}, {Dead, Normal}],
ProviderStatuses2 = [{Dead, Normal}, {Dead, Normal}, {Alive, Normal}], ProviderStatuses2 = [{Dead, Normal}, {Dead, Normal}, {Alive, Normal}],
FailRatedRoutes0 = lists:zip(Routes, ProviderStatuses0), [{Route1, _}, _, _] = FailRatedRoutes0 = lists:zip(Routes, ProviderStatuses0),
FailRatedRoutes1 = lists:zip(Routes, ProviderStatuses1), [_, {Route2, _}, _] = FailRatedRoutes1 = lists:zip(Routes, ProviderStatuses1),
FailRatedRoutes2 = lists:zip(Routes, ProviderStatuses2), FailRatedRoutes2 = lists:zip(Routes, ProviderStatuses2),
Result0 = hg_routing:choose_rated_route(FailRatedRoutes0), {Route1, Meta0} = hg_routing:choose_rated_route(FailRatedRoutes0),
Result1 = hg_routing:choose_rated_route(FailRatedRoutes1), {Route2, Meta1} = hg_routing:choose_rated_route(FailRatedRoutes1),
Result2 = hg_routing:choose_rated_route(FailRatedRoutes2), {Route3, Meta2} = hg_routing:choose_rated_route(FailRatedRoutes2),
{#{provider_ref := ?prv(21), terminal_ref := ?trm(21)}, Meta0} = Result0, #{reject_reason := availability_condition, preferable_route := Route3} = Meta0,
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, Meta1} = Result1, #{reject_reason := availability_condition, preferable_route := Route3} = Meta1,
{#{provider_ref := ?prv(23), terminal_ref := ?trm(23)}, Meta2} = Result2, false = maps:is_key(reject_reason, Meta2).
#{reject_reason := availability_condition, preferable_route := #{provider_ref := ?prv(23)}} = Meta0,
#{reject_reason := availability_condition, preferable_route := #{provider_ref := ?prv(23)}} = Meta1,
false = maps:is_key(reject_reason, Meta2),
ok.
-spec prefer_normal_conversion(config()) -> test_return(). -spec prefer_normal_conversion(config()) -> test_return().
prefer_normal_conversion(_C) -> prefer_normal_conversion(_C) ->
@ -403,21 +378,13 @@ prefer_normal_conversion(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes( {ok, {Routes, _RC}} = hg_routing:gather_routes(
payment, payment,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
), ),
Routes = sort_routes(RoutesUnordered), ?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
?assertMatch(
[
#{provider_ref := ?prv(21)},
#{provider_ref := ?prv(22)},
#{provider_ref := ?prv(23)}
],
Routes
),
Alive = {alive, 0.0}, Alive = {alive, 0.0},
Normal = {normal, 0.0}, Normal = {normal, 0.0},
@ -426,23 +393,18 @@ prefer_normal_conversion(_C) ->
ProviderStatuses0 = [{Alive, Normal}, {Alive, Lacking}, {Alive, Lacking}], ProviderStatuses0 = [{Alive, Normal}, {Alive, Lacking}, {Alive, Lacking}],
ProviderStatuses1 = [{Alive, Lacking}, {Alive, Normal}, {Alive, Lacking}], ProviderStatuses1 = [{Alive, Lacking}, {Alive, Normal}, {Alive, Lacking}],
ProviderStatuses2 = [{Alive, Lacking}, {Alive, Lacking}, {Alive, Normal}], ProviderStatuses2 = [{Alive, Lacking}, {Alive, Lacking}, {Alive, Normal}],
FailRatedRoutes0 = lists:zip(Routes, ProviderStatuses0),
FailRatedRoutes1 = lists:zip(Routes, ProviderStatuses1), [{Route1, _}, _, _] = FailRatedRoutes0 = lists:zip(Routes, ProviderStatuses0),
[_, {Route2, _}, _] = FailRatedRoutes1 = lists:zip(Routes, ProviderStatuses1),
FailRatedRoutes2 = lists:zip(Routes, ProviderStatuses2), FailRatedRoutes2 = lists:zip(Routes, ProviderStatuses2),
Result0 = hg_routing:choose_rated_route(FailRatedRoutes0), {Route1, Meta0} = hg_routing:choose_rated_route(FailRatedRoutes0),
Result1 = hg_routing:choose_rated_route(FailRatedRoutes1), {Route2, Meta1} = hg_routing:choose_rated_route(FailRatedRoutes1),
Result2 = hg_routing:choose_rated_route(FailRatedRoutes2), {Route3, Meta2} = hg_routing:choose_rated_route(FailRatedRoutes2),
{#{provider_ref := ?prv(21), terminal_ref := ?trm(21)}, Meta0} = Result0, #{reject_reason := conversion_condition, preferable_route := Route3} = Meta0,
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, Meta1} = Result1, #{reject_reason := conversion_condition, preferable_route := Route3} = Meta1,
{#{provider_ref := ?prv(23), terminal_ref := ?trm(23)}, Meta2} = Result2, false = maps:is_key(reject_reason, Meta2).
#{reject_reason := conversion_condition, preferable_route := #{provider_ref := ?prv(23)}} = Meta0,
#{reject_reason := conversion_condition, preferable_route := #{provider_ref := ?prv(23)}} = Meta1,
false = maps:is_key(reject_reason, Meta2),
ok.
-spec prefer_higher_availability(config()) -> test_return(). -spec prefer_higher_availability(config()) -> test_return().
prefer_higher_availability(_C) -> prefer_higher_availability(_C) ->
@ -458,32 +420,19 @@ prefer_higher_availability(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes( {ok, {Routes, _RC}} = hg_routing:gather_routes(
payment, payment,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
), ),
Routes = sort_routes(RoutesUnordered), ?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
?assertMatch(
[
#{provider_ref := ?prv(21)},
#{provider_ref := ?prv(22)},
#{provider_ref := ?prv(23)}
],
Routes
),
ProviderStatuses = [{{alive, 0.5}, {normal, 0.5}}, {{dead, 0.8}, {lacking, 1.0}}, {{alive, 0.6}, {normal, 0.5}}], ProviderStatuses = [{{alive, 0.5}, {normal, 0.5}}, {{dead, 0.8}, {lacking, 1.0}}, {{alive, 0.6}, {normal, 0.5}}],
FailRatedRoutes = lists:zip(Routes, ProviderStatuses), [{Route1, _}, _, {Route3, _}] = FailRatedRoutes = lists:zip(Routes, ProviderStatuses),
Result = hg_routing:choose_rated_route(FailRatedRoutes), Result = hg_routing:choose_rated_route(FailRatedRoutes),
?assertMatch({Route1, #{reject_reason := availability, preferable_route := Route3}}, Result).
{#{provider_ref := ?prv(21), terminal_ref := ?trm(21)}, #{
reject_reason := availability,
preferable_route := #{provider_ref := ?prv(23)}
}} = Result,
ok.
-spec prefer_higher_conversion(config()) -> test_return(). -spec prefer_higher_conversion(config()) -> test_return().
prefer_higher_conversion(_C) -> prefer_higher_conversion(_C) ->
@ -498,31 +447,19 @@ prefer_higher_conversion(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes( {ok, {Routes, _RC}} = hg_routing:gather_routes(
payment, payment,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
), ),
Routes = sort_routes(RoutesUnordered), ?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
?assertMatch(
[
#{provider_ref := ?prv(21)},
#{provider_ref := ?prv(22)},
#{provider_ref := ?prv(23)}
],
Routes
),
ProviderStatuses = [{{dead, 0.8}, {lacking, 1.0}}, {{alive, 0.5}, {normal, 0.3}}, {{alive, 0.5}, {normal, 0.5}}], ProviderStatuses = [{{dead, 0.8}, {lacking, 1.0}}, {{alive, 0.5}, {normal, 0.3}}, {{alive, 0.5}, {normal, 0.5}}],
FailRatedRoutes = lists:zip(Routes, ProviderStatuses), [_, {Route2, _}, {Route3, _}] = FailRatedRoutes = lists:zip(Routes, ProviderStatuses),
Result = hg_routing:choose_rated_route(FailRatedRoutes), Result = hg_routing:choose_rated_route(FailRatedRoutes),
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, #{ ?assertMatch({Route2, #{reject_reason := conversion, preferable_route := Route3}}, Result).
reject_reason := conversion,
preferable_route := #{provider_ref := ?prv(23)}
}} = Result,
ok.
-spec prefer_weight_over_availability(config()) -> test_return(). -spec prefer_weight_over_availability(config()) -> test_return().
prefer_weight_over_availability(_C) -> prefer_weight_over_availability(_C) ->
@ -538,29 +475,19 @@ prefer_weight_over_availability(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes( {ok, {Routes, _RC}} = hg_routing:gather_routes(
payment, payment,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
), ),
Routes = sort_routes(RoutesUnordered), ?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
?assertMatch(
[
#{provider_ref := ?prv(21)},
#{provider_ref := ?prv(22)},
#{provider_ref := ?prv(23)}
],
Routes
),
ProviderStatuses = [{{alive, 0.3}, {normal, 0.3}}, {{alive, 0.5}, {normal, 0.3}}, {{alive, 0.3}, {normal, 0.3}}], ProviderStatuses = [{{alive, 0.3}, {normal, 0.3}}, {{alive, 0.5}, {normal, 0.3}}, {{alive, 0.3}, {normal, 0.3}}],
FailRatedRoutes = lists:zip(Routes, ProviderStatuses), FailRatedRoutes = lists:zip(Routes, ProviderStatuses),
Result = hg_routing:choose_rated_route(FailRatedRoutes), Route = hg_routing:new(?prv(22), ?trm(22), 0, 1005),
?assertMatch({Route, _}, hg_routing:choose_rated_route(FailRatedRoutes)).
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, _Meta} = Result,
ok.
-spec prefer_weight_over_conversion(config()) -> test_return(). -spec prefer_weight_over_conversion(config()) -> test_return().
prefer_weight_over_conversion(_C) -> prefer_weight_over_conversion(_C) ->
@ -574,28 +501,19 @@ prefer_weight_over_conversion(_C) ->
}, },
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes( {ok, {Routes, _RC}} = hg_routing:gather_routes(
payment, payment,
PaymentInstitution, PaymentInstitution,
VS, VS,
Revision Revision
), ),
Routes = sort_routes(RoutesUnordered), ?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
?assertMatch(
[
#{provider_ref := ?prv(21)},
#{provider_ref := ?prv(22)},
#{provider_ref := ?prv(23)}
],
Routes
),
ProviderStatuses = [{{alive, 0.3}, {normal, 0.5}}, {{alive, 0.3}, {normal, 0.3}}, {{alive, 0.3}, {normal, 0.3}}], ProviderStatuses = [{{alive, 0.3}, {normal, 0.5}}, {{alive, 0.3}, {normal, 0.3}}, {{alive, 0.3}, {normal, 0.3}}],
FailRatedRoutes = lists:zip(Routes, ProviderStatuses), FailRatedRoutes = lists:zip(Routes, ProviderStatuses),
Result = hg_routing:choose_rated_route(FailRatedRoutes), Result = hg_routing:choose_rated_route(FailRatedRoutes),
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, _Meta} = Result, {Route, _Meta} = Result,
ok. ?assertMatch({?prv(22), ?trm(22)}, {hg_routing:provider_ref(Route), hg_routing:terminal_ref(Route)}).
-spec gathers_fail_rated_routes(config()) -> test_return(). -spec gathers_fail_rated_routes(config()) -> test_return().
gathers_fail_rated_routes(_C) -> gathers_fail_rated_routes(_C) ->
@ -610,43 +528,25 @@ gathers_fail_rated_routes(_C) ->
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{Routes0, _RejectContext0} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision), {ok, {Routes0, _RejectedRoutes}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
Result = hg_routing:gather_fail_rates(Routes0), Result = hg_routing:gather_fail_rates(Routes0),
?assertMatch( ?assertEqual(
[ [
{#{provider_ref := ?prv(21)}, {{dead, 0.9}, {lacking, 0.9}}}, {hg_routing:new(?prv(21), ?trm(21)), {{dead, 0.9}, {lacking, 0.9}}},
{#{provider_ref := ?prv(22)}, {{alive, 0.1}, {normal, 0.1}}}, {hg_routing:new(?prv(22), ?trm(22)), {{alive, 0.1}, {normal, 0.1}}},
{#{provider_ref := ?prv(23)}, {{alive, 0.0}, {normal, 0.0}}} {hg_routing:new(?prv(23), ?trm(23)), {{alive, 0.0}, {normal, 0.0}}}
], ],
lists:sort(Result) lists:sort(Result)
). ).
sort_routes(Routes) ->
lists:sort(
fun(#{provider_ref := ?prv(ID1)}, #{provider_ref := ?prv(ID2)}) ->
ID1 < ID2
end,
Routes
).
%%% Terminal priority tests %%% Terminal priority tests
-spec terminal_priority_for_shop(config()) -> test_return(). -spec terminal_priority_for_shop(config()) -> test_return().
terminal_priority_for_shop(C) -> terminal_priority_for_shop(C) ->
{ Route1 = hg_routing:new(?prv(41), ?trm(41), 0, 10),
#{ Route2 = hg_routing:new(?prv(42), ?trm(42), 0, 10),
provider_ref := ?prv(41), ?assertMatch({Route1, _}, terminal_priority_for_shop(?dummy_party_id, ?dummy_shop_id, C)),
terminal_ref := ?trm(41) ?assertMatch({Route2, _}, terminal_priority_for_shop(?dummy_party_id, ?dummy_another_shop_id, C)).
},
_Meta0
} = terminal_priority_for_shop(?dummy_party_id, ?dummy_shop_id, C),
{
#{
provider_ref := ?prv(42),
terminal_ref := ?trm(42)
},
_Meta1
} = terminal_priority_for_shop(?dummy_party_id, ?dummy_another_shop_id, C).
terminal_priority_for_shop(PartyID, ShopID, _C) -> terminal_priority_for_shop(PartyID, ShopID, _C) ->
VS = #{ VS = #{
@ -660,7 +560,7 @@ terminal_priority_for_shop(PartyID, ShopID, _C) ->
}, },
Revision = hg_domain:head(), Revision = hg_domain:head(),
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
{Routes, _RejectContext} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision), {ok, {Routes, _RejectedRoutes}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
FailRatedRoutes = hg_routing:gather_fail_rates(Routes), FailRatedRoutes = hg_routing:gather_fail_rates(Routes),
hg_routing:choose_rated_route(FailRatedRoutes). hg_routing:choose_rated_route(FailRatedRoutes).