mirror of
https://github.com/valitydev/hellgate.git
synced 2024-11-06 02:45:20 +00:00
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
This commit is contained in:
parent
24f9f6bd63
commit
fa7cd47a79
@ -24,6 +24,7 @@
|
||||
-define(NS, <<"invoice">>).
|
||||
|
||||
-export([process_callback/2]).
|
||||
-export([process_session_change_by_tag/2]).
|
||||
|
||||
-export_type([activity/0]).
|
||||
-export_type([invoice/0]).
|
||||
@ -211,22 +212,42 @@ get_payment_state(PaymentSession) ->
|
||||
%%
|
||||
|
||||
-type tag() :: dmsl_base_thrift:'Tag'().
|
||||
-type session_change() :: hg_session:change().
|
||||
-type callback() :: {provider, dmsl_proxy_provider_thrift:'Callback'()}.
|
||||
-type callback_response() :: dmsl_proxy_provider_thrift:'CallbackResponse'().
|
||||
|
||||
-spec process_callback(tag(), callback()) ->
|
||||
{ok, callback_response()} | {error, invalid_callback | notfound | failed} | no_return().
|
||||
process_callback(Tag, Callback) ->
|
||||
process_with_tag(Tag, fun(MachineID) ->
|
||||
case hg_machine:call(?NS, MachineID, {callback, Tag, Callback}) of
|
||||
{ok, _} = Ok ->
|
||||
Ok;
|
||||
{exception, invalid_callback} ->
|
||||
{error, invalid_callback};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end
|
||||
end).
|
||||
|
||||
-spec process_session_change_by_tag(tag(), session_change()) ->
|
||||
ok | {error, notfound | failed} | no_return().
|
||||
process_session_change_by_tag(Tag, SessionChange) ->
|
||||
process_with_tag(Tag, fun(MachineID) ->
|
||||
case hg_machine:call(?NS, MachineID, {session_change, Tag, SessionChange}) of
|
||||
ok ->
|
||||
ok;
|
||||
{exception, invalid_callback} ->
|
||||
{error, notfound};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end
|
||||
end).
|
||||
|
||||
process_with_tag(Tag, F) ->
|
||||
case hg_machine_tag:get_binding(namespace(), Tag) of
|
||||
{ok, _EntityID, MachineID} ->
|
||||
case hg_machine:call(?NS, MachineID, {callback, Tag, Callback}) of
|
||||
{ok, _} = Ok ->
|
||||
Ok;
|
||||
{exception, invalid_callback} ->
|
||||
{error, invalid_callback};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end;
|
||||
F(MachineID);
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
@ -339,7 +360,8 @@ handle_expiration(St) ->
|
||||
|
||||
-type thrift_call() :: hg_machine:thrift_call().
|
||||
-type callback_call() :: {callback, tag(), callback()}.
|
||||
-type call() :: thrift_call() | callback_call().
|
||||
-type session_change_call() :: {session_change, tag(), session_change()}.
|
||||
-type call() :: thrift_call() | callback_call() | session_change_call().
|
||||
-type call_result() :: #{
|
||||
changes => [invoice_change()],
|
||||
action => hg_machine_action:t(),
|
||||
@ -455,14 +477,20 @@ handle_call({{'Invoicing', 'CreatePaymentAdjustment'}, {_InvoiceID, PaymentID, P
|
||||
hg_invoice_payment:create_adjustment(Timestamp, Params, PaymentSession, Opts),
|
||||
St
|
||||
);
|
||||
handle_call({callback, Tag, Callback}, St) ->
|
||||
dispatch_callback(Tag, Callback, St).
|
||||
handle_call({callback, _Tag, _Callback} = Call, St) ->
|
||||
dispatch_to_session(Call, St);
|
||||
handle_call({session_change, _Tag, _SessionChange} = Call, St) ->
|
||||
dispatch_to_session(Call, St).
|
||||
|
||||
-spec dispatch_callback(tag(), callback(), st()) -> call_result().
|
||||
dispatch_callback(Tag, {provider, Payload}, St = #st{activity = {payment, PaymentID}}) ->
|
||||
-spec dispatch_to_session({callback, tag(), callback()} | {session_change, tag(), session_change()}, st()) ->
|
||||
call_result().
|
||||
dispatch_to_session({callback, Tag, {provider, Payload}}, St = #st{activity = {payment, PaymentID}}) ->
|
||||
PaymentSession = get_payment_session(PaymentID, St),
|
||||
process_payment_call({callback, Tag, Payload}, PaymentID, PaymentSession, St);
|
||||
dispatch_callback(_Tag, _Callback, _St) ->
|
||||
dispatch_to_session({session_change, _Tag, _SessionChange} = Call, St = #st{activity = {payment, PaymentID}}) ->
|
||||
PaymentSession = get_payment_session(PaymentID, St),
|
||||
process_payment_call(Call, PaymentID, PaymentSession, St);
|
||||
dispatch_to_session(_Call, _St) ->
|
||||
throw(invalid_callback).
|
||||
|
||||
assert_no_pending_payment(#st{activity = {payment, PaymentID}}) ->
|
||||
|
@ -200,6 +200,7 @@
|
||||
-type trx_info() :: dmsl_domain_thrift:'TransactionInfo'().
|
||||
-type tag() :: dmsl_proxy_provider_thrift:'CallbackTag'().
|
||||
-type callback() :: dmsl_proxy_provider_thrift:'Callback'().
|
||||
-type session_change() :: hg_session:change().
|
||||
-type callback_response() :: dmsl_proxy_provider_thrift:'CallbackResponse'().
|
||||
-type make_recurrent() :: true | false.
|
||||
-type retry_strategy() :: hg_retry:strategy().
|
||||
@ -1924,12 +1925,20 @@ repair_process_timeout(Activity, Action, St = #st{repair_scenario = Scenario}) -
|
||||
process_timeout(Activity, Action, St)
|
||||
end.
|
||||
|
||||
-spec process_call({callback, tag(), callback()}, st(), opts()) -> {callback_response(), machine_result()}.
|
||||
-spec process_call
|
||||
({callback, tag(), callback()}, st(), opts()) -> {callback_response(), machine_result()};
|
||||
({session_change, tag(), session_change()}, st(), opts()) -> {ok, machine_result()}.
|
||||
process_call({callback, Tag, Payload}, St, Options) ->
|
||||
scoper:scope(
|
||||
payment,
|
||||
get_st_meta(St),
|
||||
fun() -> process_callback(Tag, Payload, St#st{opts = Options}) end
|
||||
);
|
||||
process_call({session_change, Tag, SessionChange}, St, Options) ->
|
||||
scoper:scope(
|
||||
payment,
|
||||
get_st_meta(St),
|
||||
fun() -> process_session_change(Tag, SessionChange, St#st{opts = Options}) end
|
||||
).
|
||||
|
||||
-spec process_callback(tag(), callback(), st()) -> {callback_response(), machine_result()}.
|
||||
@ -1937,6 +1946,11 @@ process_callback(Tag, Payload, St) ->
|
||||
Session = get_activity_session(St),
|
||||
process_callback(Tag, Payload, Session, St).
|
||||
|
||||
-spec process_session_change(tag(), session_change(), st()) -> {ok, machine_result()}.
|
||||
process_session_change(Tag, SessionChange, St) ->
|
||||
Session = get_activity_session(St),
|
||||
process_session_change(Tag, SessionChange, Session, St).
|
||||
|
||||
process_callback(Tag, Payload, Session, St) when Session /= undefined ->
|
||||
case {hg_session:status(Session), hg_session:tags(Session)} of
|
||||
{suspended, [Tag | _]} ->
|
||||
@ -1947,6 +1961,19 @@ process_callback(Tag, Payload, Session, St) when Session /= undefined ->
|
||||
process_callback(_Tag, _Payload, undefined, _St) ->
|
||||
throw(invalid_callback).
|
||||
|
||||
process_session_change(Tag, SessionChange, Session0, St) when Session0 /= undefined ->
|
||||
%% NOTE Change allowed only for suspended session. Not suspended
|
||||
%% session does not have registered callback with tag.
|
||||
case {hg_session:status(Session0), hg_session:tags(Session0)} of
|
||||
{suspended, [Tag | _]} ->
|
||||
{Result, Session1} = hg_session:process_change(SessionChange, Session0),
|
||||
{ok, finish_session_processing(get_activity(St), Result, Session1, St)};
|
||||
_ ->
|
||||
throw(invalid_callback)
|
||||
end;
|
||||
process_session_change(_Tag, _Payload, undefined, _St) ->
|
||||
throw(invalid_callback).
|
||||
|
||||
%%
|
||||
|
||||
-spec process_shop_limit_initialization(action(), st()) -> machine_result().
|
||||
|
@ -49,11 +49,16 @@ handle_function('GetPayment', {Tag}, _) ->
|
||||
end;
|
||||
{error, notfound} ->
|
||||
hg_woody_service_wrapper:raise(#proxy_provider_PaymentNotFound{})
|
||||
end.
|
||||
end;
|
||||
handle_function('ChangePaymentSession', {Tag, SessionChange}, _) ->
|
||||
handle_callback_result(hg_invoice:process_session_change_by_tag(Tag, SessionChange)).
|
||||
|
||||
-spec handle_callback_result
|
||||
(ok) -> ok;
|
||||
({ok, callback_response()}) -> callback_response();
|
||||
({error, any()}) -> no_return().
|
||||
handle_callback_result(ok) ->
|
||||
ok;
|
||||
handle_callback_result({ok, Response}) ->
|
||||
Response;
|
||||
handle_callback_result({error, invalid_callback}) ->
|
||||
|
@ -39,6 +39,7 @@
|
||||
-type repair_scenario() :: {result, proxy_result()}.
|
||||
|
||||
-export_type([t/0]).
|
||||
-export_type([change/0]).
|
||||
-export_type([event_context/0]).
|
||||
-export_type([process_result/0]).
|
||||
|
||||
@ -73,6 +74,7 @@
|
||||
|
||||
-export([process/1]).
|
||||
-export([process_callback/2]).
|
||||
-export([process_change/2]).
|
||||
|
||||
%% Internal types
|
||||
|
||||
@ -88,6 +90,7 @@
|
||||
-type interaction() :: dmsl_user_interaction_thrift:'UserInteraction'().
|
||||
-type payment_info() :: dmsl_proxy_provider_thrift:'PaymentInfo'().
|
||||
-type timings() :: hg_timings:t().
|
||||
-type change() :: dmsl_proxy_provider_thrift:'PaymentSessionChange'().
|
||||
|
||||
-type wrapped_event() :: dmsl_payproc_thrift:'InvoicePaymentChangePayload'().
|
||||
-type wrapped_events() :: [wrapped_event()].
|
||||
@ -201,6 +204,18 @@ process_callback(Payload, Session) ->
|
||||
{Response, Result} = handle_callback_result(CallbackResult, Session),
|
||||
{Response, apply_result(Result, Session)}.
|
||||
|
||||
-spec process_change(change(), t()) -> process_result().
|
||||
process_change(#proxy_provider_PaymentSessionChange{status = {failure, Failure}}, Session) ->
|
||||
SessionEvents = [
|
||||
?session_activated(),
|
||||
?session_finished(?session_failed({failure, Failure}))
|
||||
],
|
||||
Result = {SessionEvents, hg_machine_action:instant()},
|
||||
apply_result(Result, Session);
|
||||
process_change(_Change, _Session) ->
|
||||
%% NOTE For now there is no other applicable change defined in protocol.
|
||||
throw(unknown_change).
|
||||
|
||||
-spec deduce_activity(t()) -> activity().
|
||||
deduce_activity(#{repair_scenario := Scenario}) when Scenario =/= undefined ->
|
||||
repair;
|
||||
|
@ -431,7 +431,6 @@ create_shop_(
|
||||
ShopAccountParams = #payproc_ShopAccountParams{currency = ?cur(Currency)},
|
||||
|
||||
ContractParams = make_contract_params(TemplateRef, PaymentInstRef),
|
||||
PayoutToolParams = make_payout_tool_params(),
|
||||
|
||||
TurnoverLimits1 = genlib:define(TurnoverLimits0, ordsets:new()),
|
||||
|
||||
@ -440,14 +439,6 @@ create_shop_(
|
||||
id = ContractID,
|
||||
modification = {creation, ContractParams}
|
||||
}},
|
||||
{contract_modification, #payproc_ContractModificationUnit{
|
||||
id = ContractID,
|
||||
modification =
|
||||
{payout_tool_modification, #payproc_PayoutToolModificationUnit{
|
||||
payout_tool_id = PayoutToolID,
|
||||
modification = {creation, PayoutToolParams}
|
||||
}}
|
||||
}},
|
||||
?shop_modification(ShopID, {creation, ShopParams}),
|
||||
?shop_modification(ShopID, {shop_account_creation, ShopAccountParams}),
|
||||
?shop_modification(ShopID, {turnover_limits_modification, TurnoverLimits1})
|
||||
@ -559,19 +550,6 @@ make_contractor() ->
|
||||
russian_bank_account = BankAccount
|
||||
}}}.
|
||||
|
||||
-spec make_payout_tool_params() -> dmsl_payproc_thrift:'PayoutToolParams'().
|
||||
make_payout_tool_params() ->
|
||||
#payproc_PayoutToolParams{
|
||||
currency = ?cur(<<"RUB">>),
|
||||
tool_info =
|
||||
{russian_bank_account, #domain_RussianBankAccount{
|
||||
account = <<"4276300010908312893">>,
|
||||
bank_name = <<"SomeBank">>,
|
||||
bank_post_account = <<"123129876">>,
|
||||
bank_bik = <<"66642666">>
|
||||
}}
|
||||
}.
|
||||
|
||||
-spec make_invoice_params(party_id(), shop_id(), binary(), cash()) -> invoice_params().
|
||||
make_invoice_params(PartyID, ShopID, Product, Cost) ->
|
||||
make_invoice_params(PartyID, ShopID, Product, make_due_date(), Cost).
|
||||
|
@ -16,6 +16,8 @@
|
||||
-export([get_callback_url/0]).
|
||||
-export([construct_silent_callback/1]).
|
||||
|
||||
-export([change_payment_session/2]).
|
||||
|
||||
-export([make_payment_tool/2]).
|
||||
-export([mk_trx/1]).
|
||||
|
||||
@ -733,6 +735,9 @@ get_payment_tool_scenario(
|
||||
| {preauth_3ds, integer()}
|
||||
| {preauth_3ds_sleep, integer()}.
|
||||
|
||||
-type tag() :: dmsl_proxy_provider_thrift:'CallbackTag'().
|
||||
-type session_change() :: dmsl_proxy_provider_thrift:'PaymentSessionChange'().
|
||||
|
||||
-spec make_payment_tool(payment_tool_code(), payment_system()) -> payment_tool().
|
||||
make_payment_tool(Code, PSys) when
|
||||
Code =:= no_preauth orelse
|
||||
@ -864,6 +869,15 @@ terminate(_Reason, _Req, _State) ->
|
||||
get_callback_url() ->
|
||||
genlib:to_binary("http://127.0.0.1:" ++ integer_to_list(?COWBOY_PORT)).
|
||||
|
||||
-spec change_payment_session(tag(), session_change()) -> ok | {exception, _Reason} | {error, _Reason}.
|
||||
change_payment_session(Tag, Change) ->
|
||||
Client = hg_client_api:new(hg_ct_helper:get_hellgate_url()),
|
||||
case hg_client_api:call(proxy_host_provider, 'ChangePaymentSession', [Tag, Change], Client) of
|
||||
{{ok, ok}, _} -> ok;
|
||||
{{exception, _Reason} = Exception, _} -> Exception;
|
||||
{{error, _Reason} = Error, _} -> Error
|
||||
end.
|
||||
|
||||
handle_user_interaction_response(<<"POST">>, Req) ->
|
||||
{ok, Body, Req2} = cowboy_req:read_body(Req),
|
||||
Form = maps:from_list(cow_qs:parse_qs(Body)),
|
||||
|
@ -7,6 +7,7 @@
|
||||
-include("hg_ct_domain.hrl").
|
||||
-include("hg_ct_invoice.hrl").
|
||||
-include_lib("damsel/include/dmsl_repair_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_proxy_provider_thrift.hrl").
|
||||
-include_lib("hellgate/include/allocation.hrl").
|
||||
-include_lib("fault_detector_proto/include/fd_proto_fault_detector_thrift.hrl").
|
||||
|
||||
@ -69,6 +70,7 @@
|
||||
-export([payment_success_with_decreased_cost/1]).
|
||||
-export([refund_payment_with_decreased_cost/1]).
|
||||
-export([payment_fail_after_silent_callback/1]).
|
||||
-export([payment_session_changed_to_fail/1]).
|
||||
-export([invoice_success_on_third_payment/1]).
|
||||
-export([party_revision_check/1]).
|
||||
-export([payment_customer_risk_score_check/1]).
|
||||
@ -331,6 +333,8 @@ groups() ->
|
||||
payment_success_with_decreased_cost,
|
||||
refund_payment_with_decreased_cost,
|
||||
payment_fail_after_silent_callback,
|
||||
payment_session_changed_to_fail,
|
||||
|
||||
payment_temporary_unavailability_retry_success,
|
||||
payment_temporary_unavailability_too_many_retries,
|
||||
invoice_success_on_third_payment,
|
||||
@ -2337,6 +2341,39 @@ payment_fail_after_silent_callback(C) ->
|
||||
_ = assert_success_post_request({URL, hg_dummy_provider:construct_silent_callback(Form)}),
|
||||
PaymentID = await_payment_process_timeout(InvoiceID, PaymentID, Client).
|
||||
|
||||
-spec payment_session_changed_to_fail(config()) -> _ | no_return().
|
||||
payment_session_changed_to_fail(C) ->
|
||||
Client = cfg(client, C),
|
||||
InvoiceID = start_invoice(<<"rubberdick">>, make_due_date(20), 42000, C),
|
||||
%% Payment w/ preauth for suspend w/ user interaction occurrence.
|
||||
PaymentID = start_payment(InvoiceID, make_tds_payment_params(instant, ?pmt_sys(<<"visa-ref">>)), Client),
|
||||
UserInteraction = await_payment_process_interaction(InvoiceID, PaymentID, Client),
|
||||
|
||||
Failure = payproc_errors:construct(
|
||||
'PaymentFailure',
|
||||
{authorization_failed, {operation_blocked, ?err_gen_failure()}},
|
||||
genlib:unique()
|
||||
),
|
||||
Change = #proxy_provider_PaymentSessionChange{status = {failure, Failure}},
|
||||
|
||||
%% Unknown session callback tag
|
||||
?assertMatch(
|
||||
{exception, #base_InvalidRequest{errors = [<<"Not found">>]}},
|
||||
hg_dummy_provider:change_payment_session(<<"unknown tag">>, Change)
|
||||
),
|
||||
|
||||
%% Since we expect UI to be a redirect, then parse tag value from
|
||||
%% from request parameter.
|
||||
Tag = user_interaction_callback_tag(UserInteraction),
|
||||
ok = hg_dummy_provider:change_payment_session(Tag, Change),
|
||||
{failed, PaymentID, {failure, Failure}} = await_payment_process_failure(InvoiceID, PaymentID, Client),
|
||||
|
||||
%% Bad session callback tag must not be found again
|
||||
?assertMatch(
|
||||
{exception, #base_InvalidRequest{errors = [<<"Not found">>]}},
|
||||
hg_dummy_provider:change_payment_session(Tag, Change)
|
||||
).
|
||||
|
||||
-spec payments_w_bank_card_issuer_conditions(config()) -> test_return().
|
||||
payments_w_bank_card_issuer_conditions(C) ->
|
||||
PmtSys = ?pmt_sys(<<"visa-ref">>),
|
||||
@ -8308,6 +8345,13 @@ assert_success_post_request(Req) ->
|
||||
assert_invalid_post_request(Req) ->
|
||||
{ok, 400, _RespHeaders, _RespBody} = post_request(Req).
|
||||
|
||||
user_interaction_callback_tag(
|
||||
{redirect, {post_request, #user_interaction_BrowserPostRequest{form = #{<<"tag">> := Tag}}}}
|
||||
) ->
|
||||
Tag;
|
||||
user_interaction_callback_tag(_UserInteraction) ->
|
||||
undefined.
|
||||
|
||||
post_request({URL, Form}) ->
|
||||
Method = post,
|
||||
Headers = [],
|
||||
|
@ -27,7 +27,7 @@ services:
|
||||
command: /sbin/init
|
||||
|
||||
dominant:
|
||||
image: ghcr.io/valitydev/dominant:sha-2150eea
|
||||
image: ghcr.io/valitydev/dominant:sha-fae8726
|
||||
command: /opt/dominant/bin/dominant foreground
|
||||
depends_on:
|
||||
machinegun:
|
||||
@ -97,7 +97,7 @@ services:
|
||||
disable: true
|
||||
|
||||
party-management:
|
||||
image: ghcr.io/valitydev/party-management:sha-9af7d71
|
||||
image: ghcr.io/valitydev/party-management:sha-b78d0f5
|
||||
command: /opt/party-management/bin/party-management foreground
|
||||
depends_on:
|
||||
machinegun:
|
||||
|
@ -21,7 +21,7 @@
|
||||
{<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2},
|
||||
{<<"damsel">>,
|
||||
{git,"https://github.com/valitydev/damsel.git",
|
||||
{ref,"9d4aa513fcbc1cc7ba5eedd9f96d8bc8590a6ac2"}},
|
||||
{ref,"b149379e9f706dafcace7d36b124145d71fb1bc6"}},
|
||||
0},
|
||||
{<<"dmt_client">>,
|
||||
{git,"https://github.com/valitydev/dmt-client.git",
|
||||
|
Loading…
Reference in New Issue
Block a user