Skip to content

Commit 872e076

Browse files
authored
IMP-171: Add route blacklist check (#119)
* added without tests * fixed * added tests * bumped, fixed, added explanation * fixed explanations * bumped cache * fixed explanation * fixed * fixed
1 parent 578bbc8 commit 872e076

File tree

10 files changed

+244
-18
lines changed

10 files changed

+244
-18
lines changed

.github/workflows/erlang-checks.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ jobs:
3838
thrift-version: ${{ needs.setup.outputs.thrift-version }}
3939
run-ct-with-compose: true
4040
use-coveralls: true
41-
cache-version: v5
41+
cache-version: v6

apps/hellgate/src/hg_inspector.erl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
-module(hg_inspector).
22

3+
-export([check_blacklist/1]).
34
-export([inspect/4]).
45

56
-export([compare_risk_score/2]).
67

78
-export_type([risk_score/0]).
9+
-export_type([blacklist_context/0]).
810

911
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
1012
-include_lib("damsel/include/dmsl_proxy_inspector_thrift.hrl").
@@ -15,6 +17,48 @@
1517
-type inspector() :: dmsl_domain_thrift:'Inspector'().
1618
-type risk_score() :: dmsl_domain_thrift:'RiskScore'().
1719
-type risk_magnitude() :: integer().
20+
-type domain_revision() :: dmsl_domain_thrift:'DataRevision'().
21+
22+
-type blacklist_context() :: #{
23+
route => hg_route:t(),
24+
revision := domain_revision(),
25+
token => binary(),
26+
inspector := inspector()
27+
}.
28+
29+
-spec check_blacklist(blacklist_context()) -> boolean().
30+
check_blacklist(#{
31+
route := Route,
32+
revision := Revision,
33+
token := Token,
34+
inspector := #domain_Inspector{
35+
proxy = Proxy
36+
}
37+
}) when Token =/= undefined ->
38+
#domain_ProviderRef{id = ProviderID} = hg_route:provider_ref(Route),
39+
#domain_TerminalRef{id = TerminalID} = hg_route:terminal_ref(Route),
40+
Context = #proxy_inspector_BlackListContext{
41+
first_id = genlib:to_binary(ProviderID),
42+
second_id = genlib:to_binary(TerminalID),
43+
field_name = <<"CARD_TOKEN">>,
44+
value = Token
45+
},
46+
Result = issue_call(
47+
'IsBlacklisted',
48+
{Context},
49+
hg_proxy:get_call_options(
50+
Proxy,
51+
Revision
52+
)
53+
),
54+
case Result of
55+
{ok, Check} when is_atom(Check) ->
56+
Check;
57+
{exception, Error} ->
58+
error(Error)
59+
end;
60+
check_blacklist(_Ctx) ->
61+
false.
1862

1963
-spec inspect(shop(), invoice(), payment(), inspector()) -> risk_score() | no_return().
2064
inspect(
@@ -113,6 +157,9 @@ get_payment_info(
113157
payment = ProxyPayment
114158
}.
115159

160+
issue_call(Func, Args, CallOpts) ->
161+
issue_call(Func, Args, CallOpts, undefined, undefined).
162+
116163
issue_call(Func, Args, CallOpts, undefined, _DeadLine) ->
117164
% Do not set custom deadline without fallback risk score
118165
hg_woody_wrapper:call(proxy_inspector, Func, Args, CallOpts);

apps/hellgate/src/hg_invoice_payment.erl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,8 @@ log_rejected_routes(limit_misconfiguration, Routes, _VS) ->
813813
?LOG_MD(warning, "Limiter hold error caused route candidates to be rejected: ~p", [Routes]);
814814
log_rejected_routes(limit_overflow, Routes, _VS) ->
815815
?LOG_MD(notice, "Limit overflow caused route candidates to be rejected: ~p", [Routes]);
816+
log_rejected_routes(in_blacklist, Routes, _VS) ->
817+
?LOG_MD(notice, "Route candidates are blacklisted: ~p", [Routes]);
816818
log_rejected_routes(adapter_unavailable, Routes, _VS) ->
817819
?LOG_MD(notice, "Adapter unavailability caused route candidates to be rejected: ~p", [Routes]);
818820
log_rejected_routes(provider_conversion_is_too_low, Routes, _VS) ->
@@ -1968,6 +1970,7 @@ run_routing_decision_pipeline(Ctx0, VS, St) ->
19681970
%% accounted for in `St`.
19691971
fun(Ctx) -> filter_routes_with_limit_hold(Ctx, VS, get_iter(St) + 1, St) end,
19701972
fun(Ctx) -> filter_routes_by_limit_overflow(Ctx, VS, St) end,
1973+
fun(Ctx) -> hg_routing:filter_by_blacklist(Ctx, build_blacklist_context(St)) end,
19711974
fun hg_routing:filter_by_critical_provider_status/1,
19721975
fun hg_routing:choose_route_with_ctx/1
19731976
]
@@ -2023,6 +2026,28 @@ build_routing_context(PaymentInstitution, VS, Revision, St) ->
20232026
gather_routes(PaymentInstitution, VS, Revision, St)
20242027
end.
20252028

2029+
build_blacklist_context(St) ->
2030+
Revision = get_payment_revision(St),
2031+
#domain_InvoicePayment{payer = Payer} = get_payment(St),
2032+
Token =
2033+
case get_payer_payment_tool(Payer) of
2034+
{bank_card, #domain_BankCard{token = CardToken}} ->
2035+
CardToken;
2036+
_ ->
2037+
undefined
2038+
end,
2039+
Opts = get_opts(St),
2040+
VS1 = get_varset(St, #{}),
2041+
PaymentInstitutionRef = get_payment_institution_ref(Opts),
2042+
PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision),
2043+
InspectorRef = get_selector_value(inspector, PaymentInstitution#domain_PaymentInstitution.inspector),
2044+
Inspector = hg_domain:get(Revision, {inspector, InspectorRef}),
2045+
#{
2046+
revision => Revision,
2047+
token => Token,
2048+
inspector => Inspector
2049+
}.
2050+
20262051
filter_attempted_routes(Ctx, #st{routes = AttemptedRoutes}) ->
20272052
lists:foldr(
20282053
fun(R, C) ->

apps/hellgate/test/hg_dummy_inspector.erl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,31 @@ get_service_spec() ->
1616
{"/test/proxy/inspector/dummy", {dmsl_proxy_inspector_thrift, 'InspectorProxy'}}.
1717

1818
-spec handle_function(woody:func(), woody:args(), hg_woody_service_wrapper:handler_opts()) -> term() | no_return().
19+
handle_function(
20+
'IsBlacklisted',
21+
{#proxy_inspector_BlackListContext{
22+
value = <<"inspector_fail_first">>,
23+
second_id = <<"1">>
24+
}},
25+
_Options
26+
) ->
27+
true;
28+
handle_function(
29+
'IsBlacklisted',
30+
{#proxy_inspector_BlackListContext{
31+
value = <<"inspector_fail_all">>
32+
}},
33+
_Options
34+
) ->
35+
true;
36+
handle_function(
37+
'IsBlacklisted',
38+
{#proxy_inspector_BlackListContext{
39+
value = _Token
40+
}},
41+
_Options
42+
) ->
43+
false;
1944
handle_function(
2045
'InspectPayment',
2146
{#proxy_inspector_Context{

apps/hellgate/test/hg_dummy_provider.erl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@ process_payment(?processed(), undefined, PaymentInfo, CtxOpts, _) ->
282282
no_preauth ->
283283
%% simple workflow without 3DS
284284
maybe_fail(PaymentInfo, CtxOpts, result(?sleep(0), <<"sleeping">>));
285+
inspector_fail_first ->
286+
%% simple workflow without 3DS
287+
result(?sleep(0), <<"sleeping">>);
288+
inspector_fail_all ->
289+
%% simple workflow without 3DS
290+
result(?sleep(0), <<"sleeping">>);
285291
empty_cvv ->
286292
%% simple workflow without 3DS
287293
result(?sleep(0), <<"sleeping">>);
@@ -335,6 +341,8 @@ process_payment(?processed(), undefined, PaymentInfo, CtxOpts, _) ->
335341
process_payment(?processed(), <<"sleeping">>, PaymentInfo, CtxOpts, _) ->
336342
TrxID = hg_utils:construct_complex_id([get_payment_id(PaymentInfo), get_ctx_opts_override(CtxOpts)]),
337343
case get_payment_info_scenario(PaymentInfo) of
344+
inspector_fail_first ->
345+
finish(success(PaymentInfo), mk_trx(TrxID, PaymentInfo));
338346
change_cash_increase ->
339347
finish(success(PaymentInfo, get_payment_increased_cost(PaymentInfo)), mk_trx(TrxID, PaymentInfo));
340348
change_cash_decrease ->
@@ -651,6 +659,10 @@ get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"no_preauth_t
651659
no_preauth_timeout_failure;
652660
get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"no_preauth_suspend_default">>}}) ->
653661
no_preauth_suspend_default;
662+
get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"inspector_fail_first">>}}) ->
663+
inspector_fail_first;
664+
get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"inspector_fail_all">>}}) ->
665+
inspector_fail_all;
654666
get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"empty_cvv">>}}) ->
655667
empty_cvv;
656668
get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"preauth_3ds:timeout=", Timeout/binary>>}}) ->
@@ -736,6 +748,10 @@ make_payment_tool(Code, PSys) when
736748
Code =:= unexpected_failure_no_trx
737749
->
738750
?SESSION42(make_bank_card_payment_tool(atom_to_binary(Code, utf8), PSys));
751+
make_payment_tool(inspector_fail_first, PSys) ->
752+
?SESSION42(make_bank_card_payment_tool(<<"inspector_fail_first">>, PSys));
753+
make_payment_tool(inspector_fail_all, PSys) ->
754+
?SESSION42(make_bank_card_payment_tool(<<"inspector_fail_all">>, PSys));
739755
make_payment_tool(empty_cvv, PSys) ->
740756
{_, BCard} = make_bank_card_payment_tool(<<"empty_cvv">>, PSys),
741757
?SESSION42({bank_card, BCard#domain_BankCard{is_cvv_empty = true}});

apps/hellgate/test/hg_invoice_lite_tests_SUITE.erl

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
-export([payment_start_idempotency/1]).
1515
-export([payment_success/1]).
16+
-export([payment_w_first_blacklisted_success/1]).
17+
-export([payment_w_all_blacklisted/1]).
1618
-export([register_payment_success/1]).
1719
-export([register_payment_customer_payer_success/1]).
1820
-export([payment_success_additional_info/1]).
@@ -51,6 +53,8 @@ groups() ->
5153
{payments, [parallel], [
5254
payment_start_idempotency,
5355
payment_success,
56+
payment_w_first_blacklisted_success,
57+
payment_w_all_blacklisted,
5458
register_payment_success,
5559
register_payment_customer_payer_success,
5660
payment_success_additional_info,
@@ -189,6 +193,54 @@ payment_success(C) ->
189193
Trx
190194
).
191195

196+
-spec payment_w_first_blacklisted_success(config()) -> test_return().
197+
payment_w_first_blacklisted_success(C) ->
198+
Client = cfg(client, C),
199+
InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C),
200+
{PaymentTool, Session} = hg_dummy_provider:make_payment_tool(inspector_fail_first, ?pmt_sys(<<"visa-ref">>)),
201+
PaymentParams = make_payment_params(PaymentTool, Session, instant),
202+
PaymentID = process_payment(InvoiceID, PaymentParams, Client),
203+
PaymentID = await_payment_capture(InvoiceID, PaymentID, Client),
204+
?invoice_state(
205+
?invoice_w_status(?invoice_paid()),
206+
[_PaymentSt]
207+
) = hg_client_invoicing:get(InvoiceID, Client),
208+
_Explanation =
209+
#payproc_InvoicePaymentExplanation{
210+
explained_routes = [
211+
#payproc_InvoicePaymentRouteExplanation{
212+
route = ?route(?prv(1), ?trm(2)),
213+
is_chosen = true
214+
},
215+
#payproc_InvoicePaymentRouteExplanation{
216+
route = ?route(?prv(1), ?trm(1)),
217+
is_chosen = false,
218+
rejection_description = Desc
219+
}
220+
]
221+
} = hg_client_invoicing:explain_route(InvoiceID, PaymentID, Client),
222+
?assertEqual(
223+
<<"Route was blacklisted {domain_PaymentRoute,{domain_ProviderRef,1},{domain_TerminalRef,1}}.">>, Desc
224+
).
225+
226+
-spec payment_w_all_blacklisted(config()) -> test_return().
227+
payment_w_all_blacklisted(C) ->
228+
Client = cfg(client, C),
229+
InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C),
230+
{PaymentTool, Session} = hg_dummy_provider:make_payment_tool(inspector_fail_all, ?pmt_sys(<<"visa-ref">>)),
231+
PaymentParams = make_payment_params(PaymentTool, Session, instant),
232+
?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client),
233+
[
234+
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))),
235+
?payment_ev(PaymentID, ?risk_score_changed(_RiskScore)),
236+
?payment_ev(PaymentID, ?route_changed(_Route)),
237+
?payment_ev(PaymentID, ?payment_rollback_started({failure, _Failure}))
238+
] = next_changes(InvoiceID, 4, Client),
239+
?invoice_state(
240+
?invoice_w_status(?invoice_unpaid()),
241+
[_PaymentSt]
242+
) = hg_client_invoicing:get(InvoiceID, Client).
243+
192244
-spec register_payment_success(config()) -> test_return().
193245
register_payment_success(C) ->
194246
Client = cfg(client, C),
@@ -580,7 +632,7 @@ construct_domain_fixture() ->
580632
allocations = #domain_PaymentAllocationServiceTerms{
581633
allow = {constant, true}
582634
},
583-
attempt_limit = {value, #domain_AttemptLimit{attempts = 2}}
635+
attempt_limit = {value, #domain_AttemptLimit{attempts = 1}}
584636
},
585637
recurrent_paytools = #domain_RecurrentPaytoolsServiceTerms{
586638
payment_methods =
@@ -623,7 +675,8 @@ construct_domain_fixture() ->
623675
?ruleset(1),
624676
<<"Policies">>,
625677
{candidates, [
626-
?candidate({constant, true}, ?trm(1))
678+
?candidate({constant, true}, ?trm(1)),
679+
?candidate({constant, true}, ?trm(2))
627680
]}
628681
),
629682
hg_ct_fixture:construct_payment_routing_ruleset(
@@ -809,6 +862,14 @@ construct_domain_fixture() ->
809862
provider_ref = ?prv(1)
810863
}
811864
}},
865+
{terminal, #domain_TerminalObject{
866+
ref = ?trm(2),
867+
data = #domain_Terminal{
868+
name = <<"Brominal 2">>,
869+
description = <<"Brominal 2">>,
870+
provider_ref = ?prv(1)
871+
}
872+
}},
812873

813874
hg_ct_fixture:construct_mobile_operator(?mob(<<"mts-ref">>), <<"mts mobile operator">>),
814875
hg_ct_fixture:construct_payment_service(?pmt_srv(<<"qiwi-ref">>), <<"qiwi payment service">>),

0 commit comments

Comments
 (0)