mirror of
https://github.com/valitydev/hellgate.git
synced 2024-11-06 02:45:20 +00:00
ED-212: refactor routing (#594)
This commit is contained in:
parent
c5f4735cfc
commit
0587356c41
@ -208,7 +208,6 @@
|
||||
-type shop() :: dmsl_domain_thrift:'Shop'().
|
||||
-type payment_tool() :: dmsl_domain_thrift:'PaymentTool'().
|
||||
-type recurrent_paytool_service_terms() :: dmsl_domain_thrift:'RecurrentPaytoolsServiceTerms'().
|
||||
|
||||
-type session_status() :: active | suspended | finished.
|
||||
|
||||
-type session() :: #{
|
||||
@ -466,21 +465,6 @@ get_merchant_terms(Party, Shop, DomainRevision, Timestamp, VS) ->
|
||||
),
|
||||
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, _}}) ->
|
||||
ok;
|
||||
assert_contract_active(#domain_Contract{status = Status}) ->
|
||||
@ -774,19 +758,22 @@ gather_routes(PaymentInstitution, VS, Revision, St) ->
|
||||
Payment = get_payment(St),
|
||||
Predestination = choose_routing_predestination(Payment),
|
||||
case
|
||||
hg_routing_rule:gather_routes(
|
||||
hg_routing:gather_routes(
|
||||
Predestination,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
)
|
||||
of
|
||||
{[], RejectContext} ->
|
||||
_ = log_reject_context(unknown, RejectContext),
|
||||
{ok, {[], RejectedRoutes}} ->
|
||||
_ = log_rejected_routes(unknown, RejectedRoutes, VS),
|
||||
throw({no_route_found, unknown});
|
||||
{Routes, RejectContext} ->
|
||||
_ = log_misconfigurations(RejectContext),
|
||||
Routes
|
||||
{ok, {Routes, RejectedRoutes}} ->
|
||||
_ = log_rejected_routes(unknown, RejectedRoutes, VS),
|
||||
Routes;
|
||||
{error, {misconfiguration, _Reason} = Error} ->
|
||||
_ = log_misconfigurations(Error),
|
||||
throw({no_route_found, misconfiguration})
|
||||
end.
|
||||
|
||||
-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) ->
|
||||
_ = logger:log(info, "Routing decision made", hg_routing:get_logger_metadata(ChoiceMeta, Revision)).
|
||||
|
||||
log_misconfigurations(RejectContext) ->
|
||||
RejectedProviders = maps:get(rejected_providers, RejectContext),
|
||||
RejectedRoutes = maps:get(rejected_routes, RejectContext),
|
||||
Rejects = RejectedProviders ++ RejectedRoutes,
|
||||
_ = lists:foreach(fun maybe_log_misconfiguration/1, Rejects),
|
||||
log_misconfigurations({misconfiguration, _} = Error) ->
|
||||
{Format, Details} = hg_routing:prepare_log_message(Error),
|
||||
_ = logger:warning(Format, Details),
|
||||
ok.
|
||||
|
||||
maybe_log_misconfiguration({PRef, {'Misconfiguration', Reason}}) ->
|
||||
Text = "The provider with ref ~p has been misconfigured: ~p",
|
||||
_ = 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_rejected_routes(RejectReason, RejectedRoutes, Varset) ->
|
||||
log_rejected_routes(warning, RejectReason, RejectedRoutes, Varset).
|
||||
|
||||
log_reject_context(RejectReason, RejectContext) ->
|
||||
log_reject_context(warning, RejectReason, RejectContext).
|
||||
|
||||
log_reject_context(Level, RejectReason, RejectContext) ->
|
||||
log_rejected_routes(Level, RejectReason, RejectedRoutes, Varset) ->
|
||||
_ = logger:log(
|
||||
Level,
|
||||
"No route found, reason = ~p, varset: ~p",
|
||||
[RejectReason, maps:get(varset, RejectContext)],
|
||||
logger:get_process_metadata()
|
||||
),
|
||||
_ = logger:log(
|
||||
Level,
|
||||
"No route found, reason = ~p, rejected providers: ~p",
|
||||
[RejectReason, maps:get(rejected_providers, RejectContext)],
|
||||
[RejectReason, Varset],
|
||||
logger:get_process_metadata()
|
||||
),
|
||||
_ = logger:log(
|
||||
Level,
|
||||
"No route found, reason = ~p, rejected routes: ~p",
|
||||
[RejectReason, maps:get(rejected_routes, RejectContext)],
|
||||
[RejectReason, RejectedRoutes],
|
||||
logger:get_process_metadata()
|
||||
),
|
||||
ok.
|
||||
@ -1024,7 +994,7 @@ partial_capture(St0, Reason, Cost, Cart, Opts) ->
|
||||
MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS),
|
||||
ok = validate_merchant_hold_terms(MerchantTerms),
|
||||
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),
|
||||
FinalCashflow = calculate_cashflow(Route, Payment2, MerchantTerms, ProviderTerms, VS, Revision, Opts),
|
||||
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),
|
||||
MerchantTerms = get_merchant_refunds_terms(get_merchant_payments_terms(Opts, Revision, CreatedAt, VS)),
|
||||
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),
|
||||
Cashflow = collect_refund_cashflow(MerchantTerms, ProviderTerms),
|
||||
PaymentInstitutionRef = get_payment_institution_ref(Opts),
|
||||
@ -1554,7 +1524,7 @@ get_cash_flow_for_target_status({failed, _}, _St, _Opts) ->
|
||||
) -> cash_flow().
|
||||
calculate_cashflow(Route, Payment, Timestamp, VS, Revision, Opts) ->
|
||||
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).
|
||||
|
||||
-spec calculate_cashflow(
|
||||
@ -1829,9 +1799,8 @@ process_risk_score(Action, St) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
VS1 = get_varset(St, #{}),
|
||||
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),
|
||||
RiskScore = repair_inspect(Payment, PaymentInstitution, Opts, St),
|
||||
Events = [?risk_score_changed(RiskScore)],
|
||||
@ -1848,20 +1817,18 @@ process_routing(Action, St) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
#{payment_tool := PaymentTool} = VS1 = get_varset(St, #{risk_score => get_risk_score(St)}),
|
||||
CreatedAt = get_payment_created_at(Payment),
|
||||
PaymentInstitutionRef = get_payment_institution_ref(Opts),
|
||||
VS0 = #{risk_score => get_risk_score(St)},
|
||||
VS1 = reconstruct_payment_flow(Payment, VS0),
|
||||
#{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 = get_merchant_payments_terms(Opts, Revision, CreatedAt, VS1),
|
||||
VS2 = collect_refund_varset(
|
||||
MerchantTerms#domain_PaymentsServiceTerms.refunds,
|
||||
PaymentTool,
|
||||
VS2
|
||||
VS1
|
||||
),
|
||||
VS4 = collect_chargeback_varset(
|
||||
VS3 = collect_chargeback_varset(
|
||||
MerchantTerms#domain_PaymentsServiceTerms.chargebacks,
|
||||
VS3
|
||||
VS2
|
||||
),
|
||||
PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision),
|
||||
try
|
||||
@ -1871,10 +1838,10 @@ process_routing(Action, St) ->
|
||||
{ok, PaymentRoute} ->
|
||||
[hg_routing:from_payment_route(PaymentRoute)];
|
||||
undefined ->
|
||||
gather_routes(PaymentInstitution, VS4, Revision, St)
|
||||
gather_routes(PaymentInstitution, VS3, Revision, St)
|
||||
end,
|
||||
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],
|
||||
Revision
|
||||
),
|
||||
@ -1885,8 +1852,8 @@ process_routing(Action, St) ->
|
||||
end.
|
||||
|
||||
handle_gathered_route_result({ok, RoutesNoOverflow}, Routes, Revision) ->
|
||||
{ChoosenRoute, ChoiceMeta} = hg_routing:choose_route(RoutesNoOverflow),
|
||||
_ = log_route_choice_meta(ChoiceMeta, Revision),
|
||||
{ChoosenRoute, ChoiceContext} = hg_routing:choose_route(RoutesNoOverflow),
|
||||
_ = log_route_choice_meta(ChoiceContext, Revision),
|
||||
[?route_changed(hg_routing:to_payment_route(ChoosenRoute), Routes)];
|
||||
handle_gathered_route_result({error, not_found}, Routes, _) ->
|
||||
Failure =
|
||||
@ -1912,17 +1879,16 @@ handle_choose_route_error(Reason, Events, St, Action) ->
|
||||
|
||||
-spec process_cash_flow_building(action(), st()) -> machine_result().
|
||||
process_cash_flow_building(Action, St) ->
|
||||
Route = get_route(St),
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(Opts),
|
||||
Route = get_route(St),
|
||||
Timestamp = get_payment_created_at(Payment),
|
||||
VS0 = reconstruct_payment_flow(Payment, #{}),
|
||||
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
|
||||
MerchantTerms = get_merchant_payments_terms(Opts, Revision, Timestamp, VS1),
|
||||
ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision),
|
||||
FinalCashflow = calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS1, Revision, Opts),
|
||||
VS = get_varset(St, #{}),
|
||||
CreatedAt = get_payment_created_at(Payment),
|
||||
MerchantTerms = get_merchant_payments_terms(Opts, Revision, CreatedAt, VS),
|
||||
ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
|
||||
FinalCashflow = calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS, Revision, Opts),
|
||||
_ = rollback_unused_payment_limits(St),
|
||||
_Clock = hg_accounting:hold(
|
||||
construct_payment_plan_id(Invoice, Payment),
|
||||
@ -2539,7 +2505,7 @@ get_provider_terms(St, Revision) ->
|
||||
Payment = get_payment(St),
|
||||
VS0 = reconstruct_payment_flow(Payment, #{}),
|
||||
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
|
||||
get_provider_terminal_terms(Route, VS1, Revision).
|
||||
hg_routing:get_payment_terms(Route, VS1, Revision).
|
||||
|
||||
filter_limit_overflow_routes(Routes, VS, St) ->
|
||||
ok = hold_limit_routes(Routes, VS, St),
|
||||
@ -2552,13 +2518,14 @@ filter_limit_overflow_routes(Routes, VS, St) ->
|
||||
end.
|
||||
|
||||
get_limit_overflow_routes(Routes, VS, St, RejectedRoutes) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(get_opts(St)),
|
||||
Invoice = get_invoice(Opts),
|
||||
lists:foldl(
|
||||
fun(Route, {RoutesNoOverflowIn, RejectedIn}) ->
|
||||
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),
|
||||
case hg_limiter:check_limits(TurnoverLimits, Invoice, Payment) of
|
||||
{ok, _} ->
|
||||
@ -2575,13 +2542,14 @@ get_limit_overflow_routes(Routes, VS, St, RejectedRoutes) ->
|
||||
).
|
||||
|
||||
hold_limit_routes(Routes, VS, St) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(get_opts(St)),
|
||||
Invoice = get_invoice(Opts),
|
||||
lists:foreach(
|
||||
fun(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),
|
||||
ok = hg_limiter:hold_payment_limits(TurnoverLimits, PaymentRoute, Invoice, Payment)
|
||||
end,
|
||||
@ -2589,15 +2557,14 @@ hold_limit_routes(Routes, VS, St) ->
|
||||
).
|
||||
|
||||
rollback_payment_limits(Routes, St) ->
|
||||
Revision = get_payment_revision(St),
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(get_opts(St)),
|
||||
VS0 = reconstruct_payment_flow(Payment, #{}),
|
||||
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
|
||||
Invoice = get_invoice(Opts),
|
||||
VS = get_varset(St, #{}),
|
||||
lists:foreach(
|
||||
fun(Route) ->
|
||||
ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision),
|
||||
ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
|
||||
TurnoverLimits = get_turnover_limits(ProviderTerms),
|
||||
ok = hg_limiter:rollback_payment_limits(TurnoverLimits, Route, Invoice, Payment)
|
||||
end,
|
||||
@ -2615,9 +2582,10 @@ get_turnover_limits(ProviderTerms) ->
|
||||
hg_limiter:get_turnover_limits(TurnoverLimitSelector).
|
||||
|
||||
commit_payment_limits(#st{capture_params = CaptureParams} = St) ->
|
||||
Opts = get_opts(St),
|
||||
Revision = get_payment_revision(St),
|
||||
Invoice = get_invoice(get_opts(St)),
|
||||
Payment = get_payment(St),
|
||||
Invoice = get_invoice(Opts),
|
||||
Route = get_route(St),
|
||||
#payproc_InvoicePaymentCaptureParams{cash = CapturedCash} = CaptureParams,
|
||||
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}) ->
|
||||
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().
|
||||
|
@ -422,7 +422,7 @@ build_chargeback_cash_flow(State, Opts) ->
|
||||
Shop = hg_party:get_shop(ShopID, Party),
|
||||
VS = collect_validation_varset(Party, Shop, Payment, Body),
|
||||
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),
|
||||
ServiceCashFlow = get_chargeback_service_cash_flow(ServiceTerms),
|
||||
ProviderCashFlow = get_chargeback_provider_cash_flow(ProviderTerms),
|
||||
|
@ -108,9 +108,7 @@ filtermap_payment_methods_to_set(ItemList) ->
|
||||
)
|
||||
).
|
||||
|
||||
maybe_legacy_bank_card(#domain_BankCard{payment_system_deprecated = PS} = BC) when
|
||||
PS /= undefined
|
||||
->
|
||||
maybe_legacy_bank_card(#domain_BankCard{payment_system_deprecated = PS} = BC) when PS /= undefined ->
|
||||
#domain_BankCardPaymentMethod{
|
||||
payment_system_deprecated = BC#domain_BankCard.payment_system_deprecated,
|
||||
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(_) ->
|
||||
undefined.
|
||||
|
||||
maybe_bank_card(#domain_BankCard{payment_system = PS} = BC) when
|
||||
PS /= undefined
|
||||
->
|
||||
maybe_bank_card(#domain_BankCard{payment_system = PS} = BC) when PS /= undefined ->
|
||||
#domain_BankCardPaymentMethod{
|
||||
payment_system = PS,
|
||||
is_cvv_empty = genlib:define(BC#domain_BankCard.is_cvv_empty, false),
|
||||
|
@ -240,9 +240,9 @@ init(EncodedParams, #{id := RecPaymentToolID}) ->
|
||||
try
|
||||
check_risk_score(RiskScore),
|
||||
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),
|
||||
_ = 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),
|
||||
{ok, {Changes, Action}} = start_session(),
|
||||
StartChanges = [
|
||||
@ -258,23 +258,25 @@ init(EncodedParams, #{id := RecPaymentToolID}) ->
|
||||
throw:risk_score_is_too_high = Error ->
|
||||
error(handle_route_error(Error, RecPaymentTool));
|
||||
throw:{no_route_found, {unknown, _}} = Error ->
|
||||
error(handle_route_error(Error, RecPaymentTool))
|
||||
error(handle_route_error(Error, RecPaymentTool, VS1))
|
||||
end.
|
||||
|
||||
gather_routes(PaymentInstitution, VS, Revision) ->
|
||||
Predestination = recurrent_paytool,
|
||||
case
|
||||
hg_routing_rule:gather_routes(
|
||||
hg_routing:gather_routes(
|
||||
Predestination,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
)
|
||||
of
|
||||
{[], RejectContext} ->
|
||||
throw({no_route_found, {unknown, RejectContext}});
|
||||
{Routes, _RejectContext} ->
|
||||
Routes
|
||||
{ok, {[], RejectedRoutes}} ->
|
||||
throw({no_route_found, {unknown, RejectedRoutes}});
|
||||
{ok, {Routes, _RejectContext}} ->
|
||||
Routes;
|
||||
{error, {misconfiguration, _Reason}} ->
|
||||
throw({no_route_found, misconfiguration})
|
||||
end.
|
||||
|
||||
%% 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) ->
|
||||
_ = logger:log(info, "No route found, reason = ~p", [Reason], logger:get_process_metadata()),
|
||||
{misconfiguration, {'No route found for a recurrent payment tool', RecPaymentTool}};
|
||||
handle_route_error({no_route_found, {Reason, RejectContext}}, RecPaymentTool) ->
|
||||
{misconfiguration, {'No route found for a recurrent payment tool', RecPaymentTool}}.
|
||||
handle_route_error({no_route_found, {Reason, RejectedRoutes}}, RecPaymentTool, Varset) ->
|
||||
LogFun = fun(Msg, Param) ->
|
||||
_ = logger:log(
|
||||
error,
|
||||
@ -371,9 +373,8 @@ handle_route_error({no_route_found, {Reason, RejectContext}}, RecPaymentTool) ->
|
||||
logger:get_process_metadata()
|
||||
)
|
||||
end,
|
||||
_ = LogFun("No route found, reason = ~p, varset: ~p", maps:get(varset, RejectContext)),
|
||||
_ = LogFun("No route found, reason = ~p, rejected providers: ~p", maps:get(rejected_providers, RejectContext)),
|
||||
_ = LogFun("No route found, reason = ~p, rejected routes: ~p", maps:get(rejected_routes, RejectContext)),
|
||||
_ = LogFun("No route found, reason = ~p, varset: ~p", Varset),
|
||||
_ = LogFun("No route found, reason = ~p, rejected routes: ~p", RejectedRoutes),
|
||||
{misconfiguration, {'No route found for a recurrent payment tool', RecPaymentTool}}.
|
||||
|
||||
start_session() ->
|
||||
|
@ -6,13 +6,9 @@
|
||||
-include_lib("damsel/include/dmsl_payment_processing_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_rated_route/1]).
|
||||
|
||||
-export([get_payments_terms/2]).
|
||||
|
||||
-export([acceptable_terminal/5]).
|
||||
-export([get_payment_terms/3]).
|
||||
|
||||
-export([marshal/1]).
|
||||
-export([unmarshal/1]).
|
||||
@ -20,39 +16,41 @@
|
||||
-export([get_logger_metadata/2]).
|
||||
|
||||
-export([from_payment_route/1]).
|
||||
-export([new/2]).
|
||||
-export([new/4]).
|
||||
-export([to_payment_route/1]).
|
||||
-export([provider_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").
|
||||
|
||||
-type terms() ::
|
||||
dmsl_domain_thrift:'PaymentsProvisionTerms'()
|
||||
| dmsl_domain_thrift:'RecurrentPaytoolsProvisionTerms'()
|
||||
| undefined.
|
||||
-record(route, {
|
||||
provider_ref :: dmsl_domain_thrift:'ProviderRef'(),
|
||||
terminal_ref :: dmsl_domain_thrift:'TerminalRef'(),
|
||||
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 route_predestination() :: payment | recurrent_paytool | recurrent_payment.
|
||||
|
||||
-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 provider_ref() :: dmsl_domain_thrift:'ProviderRef'().
|
||||
-type terminal() :: dmsl_domain_thrift:'Terminal'().
|
||||
-type terminal_ref() :: dmsl_domain_thrift:'TerminalRef'().
|
||||
|
||||
-type fd_service_stats() :: fd_proto_fault_detector_thrift:'ServiceStatistics'().
|
||||
-type unweighted_terminal() :: {terminal_ref(), terminal()}.
|
||||
|
||||
-type terminal_priority_rating() :: integer().
|
||||
|
||||
@ -71,38 +69,19 @@
|
||||
|
||||
-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 scored_route() :: {route_scores(), route()}.
|
||||
|
||||
-type route_choice_meta() :: #{
|
||||
-type route_choice_context() :: #{
|
||||
chosen_route => route(),
|
||||
preferable_route => route(),
|
||||
% Contains one of the field names defined in #route_scores{}
|
||||
reject_reason => atom()
|
||||
}.
|
||||
|
||||
-type varset() :: #{
|
||||
category => dmsl_domain_thrift:'CategoryRef'(),
|
||||
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'()
|
||||
}.
|
||||
-type varset() :: hg_varset:varset().
|
||||
-type revision() :: hg_domain:revision().
|
||||
|
||||
-record(route_scores, {
|
||||
availability_condition :: condition_score(),
|
||||
@ -114,79 +93,206 @@
|
||||
}).
|
||||
|
||||
-type route_scores() :: #route_scores{}.
|
||||
-type misconfiguration_error() :: {misconfiguration, {routing_decisions, _} | {routing_candidate, _}}.
|
||||
|
||||
-export_type([route/0]).
|
||||
-export_type([route_predestination/0]).
|
||||
-export_type([reject_context/0]).
|
||||
-export_type([varset/0]).
|
||||
|
||||
-define(DEFAULT_ROUTE_WEIGHT, 0).
|
||||
% Set value like in protocol
|
||||
% https://github.com/rbkmoney/damsel/blob/fa979b0e7e5bcf0aff7b55927689368317e0d858/proto/domain.thrift#L2814
|
||||
-define(DEFAULT_ROUTE_PRIORITY, 1000).
|
||||
%% Route accessors
|
||||
|
||||
-spec from_payment_route(payment_route()) -> route().
|
||||
from_payment_route(Route) ->
|
||||
?route(ProviderRef, TerminalRef) = Route,
|
||||
#{
|
||||
provider_ref => ProviderRef,
|
||||
terminal_ref => TerminalRef,
|
||||
weight => ?DEFAULT_ROUTE_WEIGHT,
|
||||
priority => ?DEFAULT_ROUTE_PRIORITY
|
||||
-spec new(provider_ref(), terminal_ref()) -> route().
|
||||
new(ProviderRef, TerminalRef) ->
|
||||
#route{
|
||||
provider_ref = ProviderRef,
|
||||
terminal_ref = TerminalRef,
|
||||
weight = ?DOMAIN_CANDIDATE_WEIGHT,
|
||||
priority = ?DOMAIN_CANDIDATE_PRIORITY
|
||||
}.
|
||||
|
||||
-spec new(provider_ref(), terminal_ref(), integer() | undefined, integer()) -> route().
|
||||
new(ProviderRef, TerminalRef, undefined, Priority) ->
|
||||
new(ProviderRef, TerminalRef, ?DEFAULT_ROUTE_WEIGHT, Priority);
|
||||
new(ProviderRef, TerminalRef, ?DOMAIN_CANDIDATE_WEIGHT, Priority);
|
||||
new(ProviderRef, TerminalRef, Weight, Priority) ->
|
||||
#{
|
||||
provider_ref => ProviderRef,
|
||||
terminal_ref => TerminalRef,
|
||||
weight => Weight,
|
||||
priority => Priority
|
||||
#route{
|
||||
provider_ref = ProviderRef,
|
||||
terminal_ref = TerminalRef,
|
||||
weight = Weight,
|
||||
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().
|
||||
provider_ref(#{provider_ref := Ref}) ->
|
||||
provider_ref(#route{provider_ref = Ref}) ->
|
||||
Ref.
|
||||
|
||||
-spec terminal_ref(route()) -> terminal_ref().
|
||||
terminal_ref(#{terminal_ref := Ref}) ->
|
||||
terminal_ref(#route{terminal_ref = Ref}) ->
|
||||
Ref.
|
||||
|
||||
-spec priority(route()) -> integer().
|
||||
priority(#{priority := Priority}) ->
|
||||
priority(#route{priority = Priority}) ->
|
||||
Priority.
|
||||
|
||||
-spec weight(route()) -> integer().
|
||||
weight(Route) ->
|
||||
maps:get(weight, Route).
|
||||
weight(#route{weight = Weight}) ->
|
||||
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().
|
||||
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()].
|
||||
gather_fail_rates(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) ->
|
||||
FailRatedRoutes = gather_fail_rates(Routes),
|
||||
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) ->
|
||||
BalancedRoutes = balance_routes(FailRatedRoutes),
|
||||
ScoredRoutes = score_routes(BalancedRoutes),
|
||||
{ChosenScoredRoute, IdealRoute} = find_best_routes(ScoredRoutes),
|
||||
RouteChoiceMeta = get_route_choice_meta(ChosenScoredRoute, IdealRoute),
|
||||
RouteChoiceContext = get_route_choice_context(ChosenScoredRoute, IdealRoute),
|
||||
{_, Route} = ChosenScoredRoute,
|
||||
{Route, RouteChoiceMeta}.
|
||||
{Route, RouteChoiceContext}.
|
||||
|
||||
-spec find_best_routes([scored_route()]) -> {Chosen :: scored_route(), Ideal :: scored_route()}.
|
||||
find_best_routes([Route]) ->
|
||||
@ -224,28 +330,28 @@ set_ideal_score({RouteScores, PT}) ->
|
||||
PT
|
||||
}.
|
||||
|
||||
get_route_choice_meta({_, SameRoute}, {_, SameRoute}) ->
|
||||
get_route_choice_context({_, SameRoute}, {_, SameRoute}) ->
|
||||
#{
|
||||
chosen_route => SameRoute
|
||||
};
|
||||
get_route_choice_meta({ChosenScores, ChosenRoute}, {IdealScores, IdealRoute}) ->
|
||||
get_route_choice_context({ChosenScores, ChosenRoute}, {IdealScores, IdealRoute}) ->
|
||||
#{
|
||||
chosen_route => ChosenRoute,
|
||||
preferable_route => IdealRoute,
|
||||
reject_reason => map_route_switch_reason(ChosenScores, IdealScores)
|
||||
}.
|
||||
|
||||
-spec get_logger_metadata(route_choice_meta(), hg_domain:revision()) -> LoggerFormattedMetadata :: map().
|
||||
get_logger_metadata(RouteChoiceMeta, Revision) ->
|
||||
#{route_choice_metadata => format_logger_metadata(RouteChoiceMeta, Revision)}.
|
||||
-spec get_logger_metadata(route_choice_context(), revision()) -> LoggerFormattedMetadata :: map().
|
||||
get_logger_metadata(RouteChoiceContext, Revision) ->
|
||||
#{route_choice_metadata => format_logger_metadata(RouteChoiceContext, Revision)}.
|
||||
|
||||
format_logger_metadata(RouteChoiceMeta, Revision) ->
|
||||
format_logger_metadata(RouteChoiceContext, Revision) ->
|
||||
maps:fold(
|
||||
fun(K, V, Acc) ->
|
||||
Acc ++ format_logger_metadata(K, V, Revision)
|
||||
end,
|
||||
[],
|
||||
RouteChoiceMeta
|
||||
RouteChoiceContext
|
||||
).
|
||||
|
||||
format_logger_metadata(reject_reason, Reason, _) ->
|
||||
@ -259,9 +365,13 @@ add_route_name(Route, Revision) ->
|
||||
TerminalRef = terminal_ref(Route),
|
||||
#domain_Provider{name = PName} = hg_domain:get(Revision, {provider, ProviderRef}),
|
||||
#domain_Terminal{name = TName} = hg_domain:get(Revision, {terminal, TerminalRef}),
|
||||
genlib_map:compact(Route#{
|
||||
genlib_map:compact(#{
|
||||
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) ->
|
||||
@ -418,20 +528,27 @@ build_fd_availability_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).
|
||||
|
||||
-spec get_payments_terms(payment_route(), hg_domain:revision()) -> terms().
|
||||
get_payments_terms(?route(ProviderRef, TerminalRef), Revision) ->
|
||||
#domain_Provider{terms = Terms0} = hg_domain:get(Revision, {provider, ProviderRef}),
|
||||
#domain_Terminal{terms = Terms1} = hg_domain:get(Revision, {terminal, TerminalRef}),
|
||||
Terms = merge_terms(Terms0, Terms1),
|
||||
Terms#domain_ProvisionTermSet.payments.
|
||||
-spec get_payment_terms(payment_route(), varset(), revision()) -> payment_terms() | undefined.
|
||||
get_payment_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.
|
||||
|
||||
-spec acceptable_terminal(
|
||||
route_predestination(),
|
||||
provider_ref(),
|
||||
terminal_ref(),
|
||||
varset(),
|
||||
hg_domain:revision()
|
||||
) -> unweighted_terminal() | no_return().
|
||||
revision()
|
||||
) -> true | no_return().
|
||||
acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision) ->
|
||||
{Client, Context} = get_party_client(),
|
||||
Result = party_client_thrift:compute_provider_terminal_terms(
|
||||
@ -442,15 +559,12 @@ acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision) ->
|
||||
Client,
|
||||
Context
|
||||
),
|
||||
ProvisionTermSet =
|
||||
case Result of
|
||||
{ok, Terms} ->
|
||||
Terms;
|
||||
{error, #payproc_ProvisionTermSetUndefined{}} ->
|
||||
undefined
|
||||
end,
|
||||
_ = check_terms_acceptability(Predestination, ProvisionTermSet, VS),
|
||||
{TerminalRef, hg_domain:get(Revision, {terminal, TerminalRef})}.
|
||||
case Result of
|
||||
{ok, ProvisionTermSet} ->
|
||||
check_terms_acceptability(Predestination, ProvisionTermSet, VS);
|
||||
{error, #payproc_ProvisionTermSetUndefined{}} ->
|
||||
throw(?rejected({'ProvisionTermSet', undefined}))
|
||||
end.
|
||||
|
||||
%%
|
||||
|
||||
@ -461,35 +575,13 @@ get_party_client() ->
|
||||
{Client, Context}.
|
||||
|
||||
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) ->
|
||||
_ = acceptable_provision_recurrent_terms(Terms, VS);
|
||||
acceptable_recurrent_paytool_terms(Terms#domain_ProvisionTermSet.recurrent_paytools, VS);
|
||||
check_terms_acceptability(recurrent_payment, Terms, VS) ->
|
||||
% Use provider check combined from recurrent_paytool and payment check
|
||||
_ = acceptable_provision_payment_terms(Terms, VS),
|
||||
_ = acceptable_provision_recurrent_terms(Terms, 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(Terms#domain_ProvisionTermSet.payments, VS),
|
||||
acceptable_recurrent_paytool_terms(Terms#domain_ProvisionTermSet.recurrent_paytools, VS).
|
||||
|
||||
acceptable_payment_terms(
|
||||
#domain_PaymentsProvisionTerms{
|
||||
@ -568,63 +660,6 @@ acceptable_partial_refunds_terms(
|
||||
acceptable_partial_refunds_terms(undefined, _RVS) ->
|
||||
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(
|
||||
@ -771,33 +806,33 @@ record_comparsion_test() ->
|
||||
balance_routes_test() ->
|
||||
Status = {{alive, 0.0}, {normal, 0.0}},
|
||||
WithWeight = [
|
||||
{new(?prv(1), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 2, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}
|
||||
{new(?prv(1), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 2, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
|
||||
],
|
||||
|
||||
Result1 = [
|
||||
{new(?prv(1), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}
|
||||
{new(?prv(1), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
|
||||
],
|
||||
Result2 = [
|
||||
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 1, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}
|
||||
{new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 1, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
|
||||
],
|
||||
Result3 = [
|
||||
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}
|
||||
{new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(3), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(4), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(5), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
|
||||
],
|
||||
[
|
||||
?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() ->
|
||||
Status = {{alive, 0.0}, {normal, 0.0}},
|
||||
Routes = [
|
||||
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}
|
||||
{new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
|
||||
],
|
||||
Result = [
|
||||
{new(?prv(1), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DEFAULT_ROUTE_PRIORITY), Status}
|
||||
{new(?prv(1), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status},
|
||||
{new(?prv(2), ?trm(1), 0, ?DOMAIN_CANDIDATE_PRIORITY), Status}
|
||||
],
|
||||
[
|
||||
?assertEqual(Result, set_routes_random_condition(Routes))
|
||||
|
@ -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.
|
@ -41,6 +41,7 @@
|
||||
-define(dummy_party_id, <<"dummy_party_id">>).
|
||||
-define(dummy_shop_id, <<"dummy_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()]}}.
|
||||
init([]) ->
|
||||
@ -184,27 +185,23 @@ no_route_found_for_payment(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
|
||||
{[], RejectContext} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision),
|
||||
#{
|
||||
rejected_routes := [
|
||||
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', cost}},
|
||||
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
|
||||
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
|
||||
]
|
||||
} = RejectContext,
|
||||
{ok, {[], RejectedRoutes}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
|
||||
[
|
||||
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', cost}},
|
||||
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
|
||||
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
|
||||
] = RejectedRoutes,
|
||||
|
||||
VS1 = VS#{
|
||||
currency => ?cur(<<"EUR">>),
|
||||
cost => ?cash(1000, <<"EUR">>)
|
||||
},
|
||||
{[], RejectContext1} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS1, Revision),
|
||||
#{
|
||||
rejected_routes := [
|
||||
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', currency}},
|
||||
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
|
||||
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
|
||||
]
|
||||
} = RejectContext1.
|
||||
{ok, {[], RejectedRoutes1}} = hg_routing:gather_routes(payment, PaymentInstitution, VS1, Revision),
|
||||
[
|
||||
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', currency}},
|
||||
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
|
||||
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
|
||||
] = RejectedRoutes1.
|
||||
|
||||
-spec gather_route_success(config()) -> test_return().
|
||||
gather_route_success(_C) ->
|
||||
@ -220,18 +217,20 @@ gather_route_success(_C) ->
|
||||
|
||||
Revision = hg_domain:head(),
|
||||
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,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
),
|
||||
#{
|
||||
rejected_routes := [
|
||||
?assertMatch(?trm(1), hg_routing:terminal_ref(Route)),
|
||||
?assertMatch(
|
||||
[
|
||||
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}},
|
||||
{?prv(3), ?trm(3), {'PaymentsProvisionTerms', payment_tool}}
|
||||
]
|
||||
} = RejectContext.
|
||||
],
|
||||
RejectedRoutes
|
||||
).
|
||||
|
||||
-spec rejected_by_table_prohibitions(config()) -> test_return().
|
||||
rejected_by_table_prohibitions(_C) ->
|
||||
@ -254,15 +253,12 @@ rejected_by_table_prohibitions(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
|
||||
{[], RejectContext} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision),
|
||||
|
||||
#{
|
||||
rejected_routes := [
|
||||
{?prv(3), ?trm(3), {'RoutingRule', undefined}},
|
||||
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', payment_tool}},
|
||||
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}}
|
||||
]
|
||||
} = RejectContext,
|
||||
{ok, {[], RejectedRoutes}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
|
||||
[
|
||||
{?prv(3), ?trm(3), {'RoutingRule', undefined}},
|
||||
{?prv(1), ?trm(1), {'PaymentsProvisionTerms', payment_tool}},
|
||||
{?prv(2), ?trm(2), {'PaymentsProvisionTerms', category}}
|
||||
] = RejectedRoutes,
|
||||
ok.
|
||||
|
||||
-spec empty_candidate_ok(config()) -> test_return().
|
||||
@ -284,11 +280,7 @@ empty_candidate_ok(_C) ->
|
||||
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(2)}),
|
||||
{[], #{
|
||||
varset := VS,
|
||||
rejected_routes := [],
|
||||
rejected_providers := []
|
||||
}} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision).
|
||||
{ok, {[], []}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision).
|
||||
|
||||
-spec ruleset_misconfig(config()) -> test_return().
|
||||
ruleset_misconfig(_C) ->
|
||||
@ -300,21 +292,22 @@ ruleset_misconfig(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
|
||||
{[], #{
|
||||
varset := VS,
|
||||
rejected_routes := [],
|
||||
rejected_providers := []
|
||||
}} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision).
|
||||
{error, {misconfiguration, {routing_decisions, {delegates, []}}}} = hg_routing:gather_routes(
|
||||
payment,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
).
|
||||
|
||||
-spec routes_selected_for_low_risk_score(config()) -> test_return().
|
||||
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().
|
||||
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 = #{
|
||||
category => ?cat(1),
|
||||
currency => ?cur(<<"RUB">>),
|
||||
@ -326,12 +319,8 @@ routes_selected_with_risk_score(_C, RiskScore, PrvIDList) ->
|
||||
},
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
{SelectedProviders, _} = hg_routing_rule:gather_routes(payment, PaymentInstitution, VS, Revision),
|
||||
Routes = sort_routes(SelectedProviders),
|
||||
|
||||
%% Ensure list of selected provider ID match to given
|
||||
PrvIDList = [P || #{provider_ref := ?prv(P)} <- Routes],
|
||||
ok.
|
||||
{ok, {Routes, _}} = hg_routing:gather_routes(payment, PaymentInstitution, VS, Revision),
|
||||
?assert_set_equal(ProviderRefs, lists:map(fun hg_routing:provider_ref/1, Routes)).
|
||||
|
||||
-spec prefer_alive(config()) -> test_return().
|
||||
prefer_alive(_C) ->
|
||||
@ -347,21 +336,13 @@ prefer_alive(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
|
||||
{RoutesUnordered, _RejectContext} = hg_routing_rule:gather_routes(
|
||||
{ok, {Routes, _RejectedRoutes}} = hg_routing:gather_routes(
|
||||
payment,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
),
|
||||
Routes = sort_routes(RoutesUnordered),
|
||||
?assertMatch(
|
||||
[
|
||||
#{provider_ref := ?prv(21)},
|
||||
#{provider_ref := ?prv(22)},
|
||||
#{provider_ref := ?prv(23)}
|
||||
],
|
||||
Routes
|
||||
),
|
||||
?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
|
||||
|
||||
Alive = {alive, 0.0},
|
||||
Dead = {dead, 1.0},
|
||||
@ -371,23 +352,17 @@ prefer_alive(_C) ->
|
||||
ProviderStatuses1 = [{Dead, Normal}, {Alive, Normal}, {Dead, Normal}],
|
||||
ProviderStatuses2 = [{Dead, Normal}, {Dead, Normal}, {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),
|
||||
|
||||
Result0 = hg_routing:choose_rated_route(FailRatedRoutes0),
|
||||
Result1 = hg_routing:choose_rated_route(FailRatedRoutes1),
|
||||
Result2 = hg_routing:choose_rated_route(FailRatedRoutes2),
|
||||
{Route1, Meta0} = hg_routing:choose_rated_route(FailRatedRoutes0),
|
||||
{Route2, Meta1} = hg_routing:choose_rated_route(FailRatedRoutes1),
|
||||
{Route3, Meta2} = hg_routing:choose_rated_route(FailRatedRoutes2),
|
||||
|
||||
{#{provider_ref := ?prv(21), terminal_ref := ?trm(21)}, Meta0} = Result0,
|
||||
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, Meta1} = Result1,
|
||||
{#{provider_ref := ?prv(23), terminal_ref := ?trm(23)}, Meta2} = Result2,
|
||||
|
||||
#{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.
|
||||
#{reject_reason := availability_condition, preferable_route := Route3} = Meta0,
|
||||
#{reject_reason := availability_condition, preferable_route := Route3} = Meta1,
|
||||
false = maps:is_key(reject_reason, Meta2).
|
||||
|
||||
-spec prefer_normal_conversion(config()) -> test_return().
|
||||
prefer_normal_conversion(_C) ->
|
||||
@ -403,21 +378,13 @@ prefer_normal_conversion(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
|
||||
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes(
|
||||
{ok, {Routes, _RC}} = hg_routing:gather_routes(
|
||||
payment,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
),
|
||||
Routes = sort_routes(RoutesUnordered),
|
||||
?assertMatch(
|
||||
[
|
||||
#{provider_ref := ?prv(21)},
|
||||
#{provider_ref := ?prv(22)},
|
||||
#{provider_ref := ?prv(23)}
|
||||
],
|
||||
Routes
|
||||
),
|
||||
?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
|
||||
|
||||
Alive = {alive, 0.0},
|
||||
Normal = {normal, 0.0},
|
||||
@ -426,23 +393,18 @@ prefer_normal_conversion(_C) ->
|
||||
ProviderStatuses0 = [{Alive, Normal}, {Alive, Lacking}, {Alive, Lacking}],
|
||||
ProviderStatuses1 = [{Alive, Lacking}, {Alive, Normal}, {Alive, Lacking}],
|
||||
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),
|
||||
|
||||
Result0 = hg_routing:choose_rated_route(FailRatedRoutes0),
|
||||
Result1 = hg_routing:choose_rated_route(FailRatedRoutes1),
|
||||
Result2 = hg_routing:choose_rated_route(FailRatedRoutes2),
|
||||
{Route1, Meta0} = hg_routing:choose_rated_route(FailRatedRoutes0),
|
||||
{Route2, Meta1} = hg_routing:choose_rated_route(FailRatedRoutes1),
|
||||
{Route3, Meta2} = hg_routing:choose_rated_route(FailRatedRoutes2),
|
||||
|
||||
{#{provider_ref := ?prv(21), terminal_ref := ?trm(21)}, Meta0} = Result0,
|
||||
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, Meta1} = Result1,
|
||||
{#{provider_ref := ?prv(23), terminal_ref := ?trm(23)}, Meta2} = Result2,
|
||||
|
||||
#{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.
|
||||
#{reject_reason := conversion_condition, preferable_route := Route3} = Meta0,
|
||||
#{reject_reason := conversion_condition, preferable_route := Route3} = Meta1,
|
||||
false = maps:is_key(reject_reason, Meta2).
|
||||
|
||||
-spec prefer_higher_availability(config()) -> test_return().
|
||||
prefer_higher_availability(_C) ->
|
||||
@ -458,32 +420,19 @@ prefer_higher_availability(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
|
||||
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes(
|
||||
{ok, {Routes, _RC}} = hg_routing:gather_routes(
|
||||
payment,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
),
|
||||
Routes = sort_routes(RoutesUnordered),
|
||||
?assertMatch(
|
||||
[
|
||||
#{provider_ref := ?prv(21)},
|
||||
#{provider_ref := ?prv(22)},
|
||||
#{provider_ref := ?prv(23)}
|
||||
],
|
||||
Routes
|
||||
),
|
||||
?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
|
||||
|
||||
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),
|
||||
|
||||
{#{provider_ref := ?prv(21), terminal_ref := ?trm(21)}, #{
|
||||
reject_reason := availability,
|
||||
preferable_route := #{provider_ref := ?prv(23)}
|
||||
}} = Result,
|
||||
|
||||
ok.
|
||||
?assertMatch({Route1, #{reject_reason := availability, preferable_route := Route3}}, Result).
|
||||
|
||||
-spec prefer_higher_conversion(config()) -> test_return().
|
||||
prefer_higher_conversion(_C) ->
|
||||
@ -498,31 +447,19 @@ prefer_higher_conversion(_C) ->
|
||||
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes(
|
||||
{ok, {Routes, _RC}} = hg_routing:gather_routes(
|
||||
payment,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
),
|
||||
Routes = sort_routes(RoutesUnordered),
|
||||
?assertMatch(
|
||||
[
|
||||
#{provider_ref := ?prv(21)},
|
||||
#{provider_ref := ?prv(22)},
|
||||
#{provider_ref := ?prv(23)}
|
||||
],
|
||||
Routes
|
||||
),
|
||||
?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
|
||||
|
||||
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),
|
||||
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, #{
|
||||
reject_reason := conversion,
|
||||
preferable_route := #{provider_ref := ?prv(23)}
|
||||
}} = Result,
|
||||
ok.
|
||||
?assertMatch({Route2, #{reject_reason := conversion, preferable_route := Route3}}, Result).
|
||||
|
||||
-spec prefer_weight_over_availability(config()) -> test_return().
|
||||
prefer_weight_over_availability(_C) ->
|
||||
@ -538,29 +475,19 @@ prefer_weight_over_availability(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
|
||||
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes(
|
||||
{ok, {Routes, _RC}} = hg_routing:gather_routes(
|
||||
payment,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
),
|
||||
Routes = sort_routes(RoutesUnordered),
|
||||
?assertMatch(
|
||||
[
|
||||
#{provider_ref := ?prv(21)},
|
||||
#{provider_ref := ?prv(22)},
|
||||
#{provider_ref := ?prv(23)}
|
||||
],
|
||||
Routes
|
||||
),
|
||||
?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
|
||||
|
||||
ProviderStatuses = [{{alive, 0.3}, {normal, 0.3}}, {{alive, 0.5}, {normal, 0.3}}, {{alive, 0.3}, {normal, 0.3}}],
|
||||
FailRatedRoutes = lists:zip(Routes, ProviderStatuses),
|
||||
|
||||
Result = hg_routing:choose_rated_route(FailRatedRoutes),
|
||||
|
||||
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, _Meta} = Result,
|
||||
ok.
|
||||
Route = hg_routing:new(?prv(22), ?trm(22), 0, 1005),
|
||||
?assertMatch({Route, _}, hg_routing:choose_rated_route(FailRatedRoutes)).
|
||||
|
||||
-spec prefer_weight_over_conversion(config()) -> test_return().
|
||||
prefer_weight_over_conversion(_C) ->
|
||||
@ -574,28 +501,19 @@ prefer_weight_over_conversion(_C) ->
|
||||
},
|
||||
Revision = hg_domain:head(),
|
||||
PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}),
|
||||
{RoutesUnordered, _RC} = hg_routing_rule:gather_routes(
|
||||
{ok, {Routes, _RC}} = hg_routing:gather_routes(
|
||||
payment,
|
||||
PaymentInstitution,
|
||||
VS,
|
||||
Revision
|
||||
),
|
||||
Routes = sort_routes(RoutesUnordered),
|
||||
?assertMatch(
|
||||
[
|
||||
#{provider_ref := ?prv(21)},
|
||||
#{provider_ref := ?prv(22)},
|
||||
#{provider_ref := ?prv(23)}
|
||||
],
|
||||
Routes
|
||||
),
|
||||
?assert_set_equal([?prv(21), ?prv(22), ?prv(23)], [hg_routing:provider_ref(R) || R <- Routes]),
|
||||
|
||||
ProviderStatuses = [{{alive, 0.3}, {normal, 0.5}}, {{alive, 0.3}, {normal, 0.3}}, {{alive, 0.3}, {normal, 0.3}}],
|
||||
FailRatedRoutes = lists:zip(Routes, ProviderStatuses),
|
||||
|
||||
Result = hg_routing:choose_rated_route(FailRatedRoutes),
|
||||
{#{provider_ref := ?prv(22), terminal_ref := ?trm(22)}, _Meta} = Result,
|
||||
ok.
|
||||
{Route, _Meta} = Result,
|
||||
?assertMatch({?prv(22), ?trm(22)}, {hg_routing:provider_ref(Route), hg_routing:terminal_ref(Route)}).
|
||||
|
||||
-spec gathers_fail_rated_routes(config()) -> test_return().
|
||||
gathers_fail_rated_routes(_C) ->
|
||||
@ -610,43 +528,25 @@ gathers_fail_rated_routes(_C) ->
|
||||
Revision = hg_domain:head(),
|
||||
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),
|
||||
?assertMatch(
|
||||
?assertEqual(
|
||||
[
|
||||
{#{provider_ref := ?prv(21)}, {{dead, 0.9}, {lacking, 0.9}}},
|
||||
{#{provider_ref := ?prv(22)}, {{alive, 0.1}, {normal, 0.1}}},
|
||||
{#{provider_ref := ?prv(23)}, {{alive, 0.0}, {normal, 0.0}}}
|
||||
{hg_routing:new(?prv(21), ?trm(21)), {{dead, 0.9}, {lacking, 0.9}}},
|
||||
{hg_routing:new(?prv(22), ?trm(22)), {{alive, 0.1}, {normal, 0.1}}},
|
||||
{hg_routing:new(?prv(23), ?trm(23)), {{alive, 0.0}, {normal, 0.0}}}
|
||||
],
|
||||
lists:sort(Result)
|
||||
).
|
||||
|
||||
sort_routes(Routes) ->
|
||||
lists:sort(
|
||||
fun(#{provider_ref := ?prv(ID1)}, #{provider_ref := ?prv(ID2)}) ->
|
||||
ID1 < ID2
|
||||
end,
|
||||
Routes
|
||||
).
|
||||
|
||||
%%% Terminal priority tests
|
||||
|
||||
-spec terminal_priority_for_shop(config()) -> test_return().
|
||||
terminal_priority_for_shop(C) ->
|
||||
{
|
||||
#{
|
||||
provider_ref := ?prv(41),
|
||||
terminal_ref := ?trm(41)
|
||||
},
|
||||
_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).
|
||||
Route1 = hg_routing:new(?prv(41), ?trm(41), 0, 10),
|
||||
Route2 = hg_routing:new(?prv(42), ?trm(42), 0, 10),
|
||||
?assertMatch({Route1, _}, terminal_priority_for_shop(?dummy_party_id, ?dummy_shop_id, C)),
|
||||
?assertMatch({Route2, _}, terminal_priority_for_shop(?dummy_party_id, ?dummy_another_shop_id, C)).
|
||||
|
||||
terminal_priority_for_shop(PartyID, ShopID, _C) ->
|
||||
VS = #{
|
||||
@ -660,7 +560,7 @@ terminal_priority_for_shop(PartyID, ShopID, _C) ->
|
||||
},
|
||||
Revision = hg_domain:head(),
|
||||
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),
|
||||
hg_routing:choose_rated_route(FailRatedRoutes).
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user