Skip to content

Commit fa7cd47

Browse files
authored
IMP-331: Implements provider host's session change handler (#145)
* IMP-331: Implements provider host's session change handler * Implements session' failure change * Adds session change testcase * Fixes helper' spec * Refactors testcase w/ more asserts and fixes session finalizing on status change * Bumps damsel
1 parent 24f9f6b commit fa7cd47

File tree

9 files changed

+153
-42
lines changed

9 files changed

+153
-42
lines changed

apps/hellgate/src/hg_invoice.erl

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
-define(NS, <<"invoice">>).
2525

2626
-export([process_callback/2]).
27+
-export([process_session_change_by_tag/2]).
2728

2829
-export_type([activity/0]).
2930
-export_type([invoice/0]).
@@ -211,22 +212,42 @@ get_payment_state(PaymentSession) ->
211212
%%
212213

213214
-type tag() :: dmsl_base_thrift:'Tag'().
215+
-type session_change() :: hg_session:change().
214216
-type callback() :: {provider, dmsl_proxy_provider_thrift:'Callback'()}.
215217
-type callback_response() :: dmsl_proxy_provider_thrift:'CallbackResponse'().
216218

217219
-spec process_callback(tag(), callback()) ->
218220
{ok, callback_response()} | {error, invalid_callback | notfound | failed} | no_return().
219221
process_callback(Tag, Callback) ->
222+
process_with_tag(Tag, fun(MachineID) ->
223+
case hg_machine:call(?NS, MachineID, {callback, Tag, Callback}) of
224+
{ok, _} = Ok ->
225+
Ok;
226+
{exception, invalid_callback} ->
227+
{error, invalid_callback};
228+
{error, _} = Error ->
229+
Error
230+
end
231+
end).
232+
233+
-spec process_session_change_by_tag(tag(), session_change()) ->
234+
ok | {error, notfound | failed} | no_return().
235+
process_session_change_by_tag(Tag, SessionChange) ->
236+
process_with_tag(Tag, fun(MachineID) ->
237+
case hg_machine:call(?NS, MachineID, {session_change, Tag, SessionChange}) of
238+
ok ->
239+
ok;
240+
{exception, invalid_callback} ->
241+
{error, notfound};
242+
{error, _} = Error ->
243+
Error
244+
end
245+
end).
246+
247+
process_with_tag(Tag, F) ->
220248
case hg_machine_tag:get_binding(namespace(), Tag) of
221249
{ok, _EntityID, MachineID} ->
222-
case hg_machine:call(?NS, MachineID, {callback, Tag, Callback}) of
223-
{ok, _} = Ok ->
224-
Ok;
225-
{exception, invalid_callback} ->
226-
{error, invalid_callback};
227-
{error, _} = Error ->
228-
Error
229-
end;
250+
F(MachineID);
230251
{error, _} = Error ->
231252
Error
232253
end.
@@ -339,7 +360,8 @@ handle_expiration(St) ->
339360

340361
-type thrift_call() :: hg_machine:thrift_call().
341362
-type callback_call() :: {callback, tag(), callback()}.
342-
-type call() :: thrift_call() | callback_call().
363+
-type session_change_call() :: {session_change, tag(), session_change()}.
364+
-type call() :: thrift_call() | callback_call() | session_change_call().
343365
-type call_result() :: #{
344366
changes => [invoice_change()],
345367
action => hg_machine_action:t(),
@@ -455,14 +477,20 @@ handle_call({{'Invoicing', 'CreatePaymentAdjustment'}, {_InvoiceID, PaymentID, P
455477
hg_invoice_payment:create_adjustment(Timestamp, Params, PaymentSession, Opts),
456478
St
457479
);
458-
handle_call({callback, Tag, Callback}, St) ->
459-
dispatch_callback(Tag, Callback, St).
460-
461-
-spec dispatch_callback(tag(), callback(), st()) -> call_result().
462-
dispatch_callback(Tag, {provider, Payload}, St = #st{activity = {payment, PaymentID}}) ->
480+
handle_call({callback, _Tag, _Callback} = Call, St) ->
481+
dispatch_to_session(Call, St);
482+
handle_call({session_change, _Tag, _SessionChange} = Call, St) ->
483+
dispatch_to_session(Call, St).
484+
485+
-spec dispatch_to_session({callback, tag(), callback()} | {session_change, tag(), session_change()}, st()) ->
486+
call_result().
487+
dispatch_to_session({callback, Tag, {provider, Payload}}, St = #st{activity = {payment, PaymentID}}) ->
463488
PaymentSession = get_payment_session(PaymentID, St),
464489
process_payment_call({callback, Tag, Payload}, PaymentID, PaymentSession, St);
465-
dispatch_callback(_Tag, _Callback, _St) ->
490+
dispatch_to_session({session_change, _Tag, _SessionChange} = Call, St = #st{activity = {payment, PaymentID}}) ->
491+
PaymentSession = get_payment_session(PaymentID, St),
492+
process_payment_call(Call, PaymentID, PaymentSession, St);
493+
dispatch_to_session(_Call, _St) ->
466494
throw(invalid_callback).
467495

468496
assert_no_pending_payment(#st{activity = {payment, PaymentID}}) ->

apps/hellgate/src/hg_invoice_payment.erl

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@
200200
-type trx_info() :: dmsl_domain_thrift:'TransactionInfo'().
201201
-type tag() :: dmsl_proxy_provider_thrift:'CallbackTag'().
202202
-type callback() :: dmsl_proxy_provider_thrift:'Callback'().
203+
-type session_change() :: hg_session:change().
203204
-type callback_response() :: dmsl_proxy_provider_thrift:'CallbackResponse'().
204205
-type make_recurrent() :: true | false.
205206
-type retry_strategy() :: hg_retry:strategy().
@@ -1924,19 +1925,32 @@ repair_process_timeout(Activity, Action, St = #st{repair_scenario = Scenario}) -
19241925
process_timeout(Activity, Action, St)
19251926
end.
19261927

1927-
-spec process_call({callback, tag(), callback()}, st(), opts()) -> {callback_response(), machine_result()}.
1928+
-spec process_call
1929+
({callback, tag(), callback()}, st(), opts()) -> {callback_response(), machine_result()};
1930+
({session_change, tag(), session_change()}, st(), opts()) -> {ok, machine_result()}.
19281931
process_call({callback, Tag, Payload}, St, Options) ->
19291932
scoper:scope(
19301933
payment,
19311934
get_st_meta(St),
19321935
fun() -> process_callback(Tag, Payload, St#st{opts = Options}) end
1936+
);
1937+
process_call({session_change, Tag, SessionChange}, St, Options) ->
1938+
scoper:scope(
1939+
payment,
1940+
get_st_meta(St),
1941+
fun() -> process_session_change(Tag, SessionChange, St#st{opts = Options}) end
19331942
).
19341943

19351944
-spec process_callback(tag(), callback(), st()) -> {callback_response(), machine_result()}.
19361945
process_callback(Tag, Payload, St) ->
19371946
Session = get_activity_session(St),
19381947
process_callback(Tag, Payload, Session, St).
19391948

1949+
-spec process_session_change(tag(), session_change(), st()) -> {ok, machine_result()}.
1950+
process_session_change(Tag, SessionChange, St) ->
1951+
Session = get_activity_session(St),
1952+
process_session_change(Tag, SessionChange, Session, St).
1953+
19401954
process_callback(Tag, Payload, Session, St) when Session /= undefined ->
19411955
case {hg_session:status(Session), hg_session:tags(Session)} of
19421956
{suspended, [Tag | _]} ->
@@ -1947,6 +1961,19 @@ process_callback(Tag, Payload, Session, St) when Session /= undefined ->
19471961
process_callback(_Tag, _Payload, undefined, _St) ->
19481962
throw(invalid_callback).
19491963

1964+
process_session_change(Tag, SessionChange, Session0, St) when Session0 /= undefined ->
1965+
%% NOTE Change allowed only for suspended session. Not suspended
1966+
%% session does not have registered callback with tag.
1967+
case {hg_session:status(Session0), hg_session:tags(Session0)} of
1968+
{suspended, [Tag | _]} ->
1969+
{Result, Session1} = hg_session:process_change(SessionChange, Session0),
1970+
{ok, finish_session_processing(get_activity(St), Result, Session1, St)};
1971+
_ ->
1972+
throw(invalid_callback)
1973+
end;
1974+
process_session_change(_Tag, _Payload, undefined, _St) ->
1975+
throw(invalid_callback).
1976+
19501977
%%
19511978

19521979
-spec process_shop_limit_initialization(action(), st()) -> machine_result().

apps/hellgate/src/hg_proxy_host_provider.erl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,16 @@ handle_function('GetPayment', {Tag}, _) ->
4949
end;
5050
{error, notfound} ->
5151
hg_woody_service_wrapper:raise(#proxy_provider_PaymentNotFound{})
52-
end.
52+
end;
53+
handle_function('ChangePaymentSession', {Tag, SessionChange}, _) ->
54+
handle_callback_result(hg_invoice:process_session_change_by_tag(Tag, SessionChange)).
5355

5456
-spec handle_callback_result
57+
(ok) -> ok;
5558
({ok, callback_response()}) -> callback_response();
5659
({error, any()}) -> no_return().
60+
handle_callback_result(ok) ->
61+
ok;
5762
handle_callback_result({ok, Response}) ->
5863
Response;
5964
handle_callback_result({error, invalid_callback}) ->

apps/hellgate/src/hg_session.erl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
-type repair_scenario() :: {result, proxy_result()}.
4040

4141
-export_type([t/0]).
42+
-export_type([change/0]).
4243
-export_type([event_context/0]).
4344
-export_type([process_result/0]).
4445

@@ -73,6 +74,7 @@
7374

7475
-export([process/1]).
7576
-export([process_callback/2]).
77+
-export([process_change/2]).
7678

7779
%% Internal types
7880

@@ -88,6 +90,7 @@
8890
-type interaction() :: dmsl_user_interaction_thrift:'UserInteraction'().
8991
-type payment_info() :: dmsl_proxy_provider_thrift:'PaymentInfo'().
9092
-type timings() :: hg_timings:t().
93+
-type change() :: dmsl_proxy_provider_thrift:'PaymentSessionChange'().
9194

9295
-type wrapped_event() :: dmsl_payproc_thrift:'InvoicePaymentChangePayload'().
9396
-type wrapped_events() :: [wrapped_event()].
@@ -201,6 +204,18 @@ process_callback(Payload, Session) ->
201204
{Response, Result} = handle_callback_result(CallbackResult, Session),
202205
{Response, apply_result(Result, Session)}.
203206

207+
-spec process_change(change(), t()) -> process_result().
208+
process_change(#proxy_provider_PaymentSessionChange{status = {failure, Failure}}, Session) ->
209+
SessionEvents = [
210+
?session_activated(),
211+
?session_finished(?session_failed({failure, Failure}))
212+
],
213+
Result = {SessionEvents, hg_machine_action:instant()},
214+
apply_result(Result, Session);
215+
process_change(_Change, _Session) ->
216+
%% NOTE For now there is no other applicable change defined in protocol.
217+
throw(unknown_change).
218+
204219
-spec deduce_activity(t()) -> activity().
205220
deduce_activity(#{repair_scenario := Scenario}) when Scenario =/= undefined ->
206221
repair;

apps/hellgate/test/hg_ct_helper.erl

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,6 @@ create_shop_(
431431
ShopAccountParams = #payproc_ShopAccountParams{currency = ?cur(Currency)},
432432

433433
ContractParams = make_contract_params(TemplateRef, PaymentInstRef),
434-
PayoutToolParams = make_payout_tool_params(),
435434

436435
TurnoverLimits1 = genlib:define(TurnoverLimits0, ordsets:new()),
437436

@@ -440,14 +439,6 @@ create_shop_(
440439
id = ContractID,
441440
modification = {creation, ContractParams}
442441
}},
443-
{contract_modification, #payproc_ContractModificationUnit{
444-
id = ContractID,
445-
modification =
446-
{payout_tool_modification, #payproc_PayoutToolModificationUnit{
447-
payout_tool_id = PayoutToolID,
448-
modification = {creation, PayoutToolParams}
449-
}}
450-
}},
451442
?shop_modification(ShopID, {creation, ShopParams}),
452443
?shop_modification(ShopID, {shop_account_creation, ShopAccountParams}),
453444
?shop_modification(ShopID, {turnover_limits_modification, TurnoverLimits1})
@@ -559,19 +550,6 @@ make_contractor() ->
559550
russian_bank_account = BankAccount
560551
}}}.
561552

562-
-spec make_payout_tool_params() -> dmsl_payproc_thrift:'PayoutToolParams'().
563-
make_payout_tool_params() ->
564-
#payproc_PayoutToolParams{
565-
currency = ?cur(<<"RUB">>),
566-
tool_info =
567-
{russian_bank_account, #domain_RussianBankAccount{
568-
account = <<"4276300010908312893">>,
569-
bank_name = <<"SomeBank">>,
570-
bank_post_account = <<"123129876">>,
571-
bank_bik = <<"66642666">>
572-
}}
573-
}.
574-
575553
-spec make_invoice_params(party_id(), shop_id(), binary(), cash()) -> invoice_params().
576554
make_invoice_params(PartyID, ShopID, Product, Cost) ->
577555
make_invoice_params(PartyID, ShopID, Product, make_due_date(), Cost).

apps/hellgate/test/hg_dummy_provider.erl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
-export([get_callback_url/0]).
1717
-export([construct_silent_callback/1]).
1818

19+
-export([change_payment_session/2]).
20+
1921
-export([make_payment_tool/2]).
2022
-export([mk_trx/1]).
2123

@@ -733,6 +735,9 @@ get_payment_tool_scenario(
733735
| {preauth_3ds, integer()}
734736
| {preauth_3ds_sleep, integer()}.
735737

738+
-type tag() :: dmsl_proxy_provider_thrift:'CallbackTag'().
739+
-type session_change() :: dmsl_proxy_provider_thrift:'PaymentSessionChange'().
740+
736741
-spec make_payment_tool(payment_tool_code(), payment_system()) -> payment_tool().
737742
make_payment_tool(Code, PSys) when
738743
Code =:= no_preauth orelse
@@ -864,6 +869,15 @@ terminate(_Reason, _Req, _State) ->
864869
get_callback_url() ->
865870
genlib:to_binary("http://127.0.0.1:" ++ integer_to_list(?COWBOY_PORT)).
866871

872+
-spec change_payment_session(tag(), session_change()) -> ok | {exception, _Reason} | {error, _Reason}.
873+
change_payment_session(Tag, Change) ->
874+
Client = hg_client_api:new(hg_ct_helper:get_hellgate_url()),
875+
case hg_client_api:call(proxy_host_provider, 'ChangePaymentSession', [Tag, Change], Client) of
876+
{{ok, ok}, _} -> ok;
877+
{{exception, _Reason} = Exception, _} -> Exception;
878+
{{error, _Reason} = Error, _} -> Error
879+
end.
880+
867881
handle_user_interaction_response(<<"POST">>, Req) ->
868882
{ok, Body, Req2} = cowboy_req:read_body(Req),
869883
Form = maps:from_list(cow_qs:parse_qs(Body)),

apps/hellgate/test/hg_invoice_tests_SUITE.erl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
-include("hg_ct_domain.hrl").
88
-include("hg_ct_invoice.hrl").
99
-include_lib("damsel/include/dmsl_repair_thrift.hrl").
10+
-include_lib("damsel/include/dmsl_proxy_provider_thrift.hrl").
1011
-include_lib("hellgate/include/allocation.hrl").
1112
-include_lib("fault_detector_proto/include/fd_proto_fault_detector_thrift.hrl").
1213

@@ -69,6 +70,7 @@
6970
-export([payment_success_with_decreased_cost/1]).
7071
-export([refund_payment_with_decreased_cost/1]).
7172
-export([payment_fail_after_silent_callback/1]).
73+
-export([payment_session_changed_to_fail/1]).
7274
-export([invoice_success_on_third_payment/1]).
7375
-export([party_revision_check/1]).
7476
-export([payment_customer_risk_score_check/1]).
@@ -331,6 +333,8 @@ groups() ->
331333
payment_success_with_decreased_cost,
332334
refund_payment_with_decreased_cost,
333335
payment_fail_after_silent_callback,
336+
payment_session_changed_to_fail,
337+
334338
payment_temporary_unavailability_retry_success,
335339
payment_temporary_unavailability_too_many_retries,
336340
invoice_success_on_third_payment,
@@ -2337,6 +2341,39 @@ payment_fail_after_silent_callback(C) ->
23372341
_ = assert_success_post_request({URL, hg_dummy_provider:construct_silent_callback(Form)}),
23382342
PaymentID = await_payment_process_timeout(InvoiceID, PaymentID, Client).
23392343

2344+
-spec payment_session_changed_to_fail(config()) -> _ | no_return().
2345+
payment_session_changed_to_fail(C) ->
2346+
Client = cfg(client, C),
2347+
InvoiceID = start_invoice(<<"rubberdick">>, make_due_date(20), 42000, C),
2348+
%% Payment w/ preauth for suspend w/ user interaction occurrence.
2349+
PaymentID = start_payment(InvoiceID, make_tds_payment_params(instant, ?pmt_sys(<<"visa-ref">>)), Client),
2350+
UserInteraction = await_payment_process_interaction(InvoiceID, PaymentID, Client),
2351+
2352+
Failure = payproc_errors:construct(
2353+
'PaymentFailure',
2354+
{authorization_failed, {operation_blocked, ?err_gen_failure()}},
2355+
genlib:unique()
2356+
),
2357+
Change = #proxy_provider_PaymentSessionChange{status = {failure, Failure}},
2358+
2359+
%% Unknown session callback tag
2360+
?assertMatch(
2361+
{exception, #base_InvalidRequest{errors = [<<"Not found">>]}},
2362+
hg_dummy_provider:change_payment_session(<<"unknown tag">>, Change)
2363+
),
2364+
2365+
%% Since we expect UI to be a redirect, then parse tag value from
2366+
%% from request parameter.
2367+
Tag = user_interaction_callback_tag(UserInteraction),
2368+
ok = hg_dummy_provider:change_payment_session(Tag, Change),
2369+
{failed, PaymentID, {failure, Failure}} = await_payment_process_failure(InvoiceID, PaymentID, Client),
2370+
2371+
%% Bad session callback tag must not be found again
2372+
?assertMatch(
2373+
{exception, #base_InvalidRequest{errors = [<<"Not found">>]}},
2374+
hg_dummy_provider:change_payment_session(Tag, Change)
2375+
).
2376+
23402377
-spec payments_w_bank_card_issuer_conditions(config()) -> test_return().
23412378
payments_w_bank_card_issuer_conditions(C) ->
23422379
PmtSys = ?pmt_sys(<<"visa-ref">>),
@@ -8308,6 +8345,13 @@ assert_success_post_request(Req) ->
83088345
assert_invalid_post_request(Req) ->
83098346
{ok, 400, _RespHeaders, _RespBody} = post_request(Req).
83108347

8348+
user_interaction_callback_tag(
8349+
{redirect, {post_request, #user_interaction_BrowserPostRequest{form = #{<<"tag">> := Tag}}}}
8350+
) ->
8351+
Tag;
8352+
user_interaction_callback_tag(_UserInteraction) ->
8353+
undefined.
8354+
83118355
post_request({URL, Form}) ->
83128356
Method = post,
83138357
Headers = [],

compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ services:
2727
command: /sbin/init
2828

2929
dominant:
30-
image: ghcr.io/valitydev/dominant:sha-2150eea
30+
image: ghcr.io/valitydev/dominant:sha-fae8726
3131
command: /opt/dominant/bin/dominant foreground
3232
depends_on:
3333
machinegun:
@@ -97,7 +97,7 @@ services:
9797
disable: true
9898

9999
party-management:
100-
image: ghcr.io/valitydev/party-management:sha-9af7d71
100+
image: ghcr.io/valitydev/party-management:sha-b78d0f5
101101
command: /opt/party-management/bin/party-management foreground
102102
depends_on:
103103
machinegun:

0 commit comments

Comments
 (0)