Implement ad-hoc repairs w/ the ability to push arbitrary changes (#179)

* Make misbehaving testcase revert domain config alterations at the end
This commit is contained in:
Andrew Mayorov 2018-02-15 12:43:14 +03:00 committed by GitHub
parent 0791b44eee
commit c658f1f4fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 124 additions and 11 deletions

View File

@ -213,7 +213,13 @@ handle_function_('ComputeTerms', [UserInfo, InvoiceID], _Opts) ->
ShopTerms = hg_invoice_utils:compute_shop_terms(UserInfo, PartyID, ShopID, Timestamp),
Revision = hg_domain:head(),
Cash = get_cost(St),
hg_party:reduce_terms(ShopTerms, #{cost => Cash}, Revision).
hg_party:reduce_terms(ShopTerms, #{cost => Cash}, Revision);
handle_function_('Repair', [UserInfo, InvoiceID, Changes], _Opts) ->
ok = assume_user_identity(UserInfo),
_ = set_invoicing_meta(InvoiceID),
_ = assert_invoice_accessible(get_initial_state(InvoiceID)),
repair(InvoiceID, {changes, Changes}).
assert_invoice_operable(St) ->
% FIXME do not lose party here
@ -304,6 +310,9 @@ start(ID, Args) ->
call(ID, Args) ->
map_error(hg_machine:call(?NS, ID, Args)).
repair(ID, Args) ->
map_repair_error(hg_machine:repair(?NS, ID, Args)).
map_error({ok, CallResult}) ->
case CallResult of
{ok, Result} ->
@ -326,6 +335,16 @@ map_start_error({ok, _}) ->
map_start_error({error, Reason}) ->
error(Reason).
map_repair_error({ok, _}) ->
ok;
map_repair_error({error, notfound}) ->
throw(#payproc_InvoiceNotFound{});
map_repair_error({error, working}) ->
% TODO
throw(#'InvalidRequest'{errors = [<<"No need to repair">>]});
map_repair_error({error, Reason}) ->
error(Reason).
%%
-type invoice() :: dmsl_domain_thrift:'Invoice'().
@ -388,8 +407,14 @@ handle_signal(timeout, St = #st{activity = invoice}) ->
% invoice is expired
handle_expiration(St);
handle_signal({repair, _}, St) ->
#{
handle_signal({repair, {changes, Changes}}, St) ->
Result = case Changes of
[_ | _] ->
#{changes => Changes};
[] ->
#{}
end,
Result#{
state => St
}.

View File

@ -17,6 +17,8 @@
%%% - think about safe clamping of timers returned by some proxy
%%% - why don't user interaction events imprint anything on the state?
%%% - adjustments look and behave very much like claims over payments
%%% - payment status transition are caused by the fact that some session
%%% finishes, which could have happened in the past, not just now
-module(hg_invoice_payment).
-include_lib("dmsl/include/dmsl_proxy_provider_thrift.hrl").

View File

@ -64,6 +64,7 @@
-export([start/3]).
-export([call/3]).
-export([repair/3]).
-export([get_history/2]).
-export([get_history/4]).
@ -108,6 +109,13 @@ call(Ns, Ref, Args) ->
Error
end.
-spec repair(ns(), ref(), term()) ->
{ok, term()} | {error, notfound | failed | working} | no_return().
repair(Ns, Ref, Args) ->
Descriptor = prepare_descriptor(Ns, Ref, #'HistoryRange'{}),
call_automaton('Repair', [Descriptor, wrap_args(Args)]).
-spec get_history(ns(), ref()) ->
{ok, history()} | {error, notfound} | no_return().
@ -140,7 +148,9 @@ call_automaton(Function, Args) ->
{exception, #'MachineNotFound'{}} ->
{error, notfound};
{exception, #'MachineFailed'{}} ->
{error, failed}
{error, failed};
{exception, #'MachineAlreadyWorking'{}} ->
{error, working}
end.
%%

View File

@ -139,6 +139,8 @@ generate_token(undefined, #prxprv_RecurrentTokenInfo{payment_tool = RecurrentPay
#prxprv_RecurrentTokenProxyResult{
intent = ?recurrent_token_finish_w_failure(#'Failure'{code = <<"forbidden">>})
};
unexpected_failure ->
error(unexpected_failure);
_ ->
token_sleep(1, <<"sleeping">>)
end;
@ -240,10 +242,17 @@ process_payment(?processed(), undefined, PaymentInfo, _) ->
sleep(1, <<"sleeping">>);
recurrent ->
%% simple workflow without 3DS
sleep(1, <<"sleeping">>)
sleep(1, <<"sleeping">>);
unexpected_failure ->
sleep(1, <<"sleeping">>, undefined, get_payment_id(PaymentInfo))
end;
process_payment(?processed(), <<"sleeping">>, PaymentInfo, _) ->
finish(?success(), get_payment_id(PaymentInfo));
case get_payment_info_scenario(PaymentInfo) of
unexpected_failure ->
error(unexpected_failure);
_ ->
finish(?success(), get_payment_id(PaymentInfo))
end;
process_payment(?processed(), <<"sleeping_with_user_interaction">>, PaymentInfo, _) ->
Key = {get_invoice_id(PaymentInfo), get_payment_id(PaymentInfo)},
case get_transaction_state(Key) of
@ -300,6 +309,13 @@ sleep(Timeout, State, UserInteraction) ->
next_state = State
}.
sleep(Timeout, State, UserInteraction, TrxID) ->
#prxprv_PaymentProxyResult{
intent = ?sleep(Timeout, UserInteraction),
trx = #domain_TransactionInfo{id = TrxID, extra = #{}},
next_state = State
}.
suspend(Tag, Timeout, State, UserInteraction) ->
#prxprv_PaymentProxyResult{
intent = ?suspend(Tag, Timeout, UserInteraction),
@ -344,6 +360,8 @@ get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"preauth_3ds_
preauth_3ds_offsite;
get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"forbidden">>}}) ->
forbidden;
get_payment_tool_scenario({'bank_card', #domain_BankCard{token = <<"unexpected_failure">>}}) ->
unexpected_failure;
get_payment_tool_scenario({'payment_terminal', #domain_PaymentTerminal{terminal_type = euroset}}) ->
terminal;
get_payment_tool_scenario({'digital_wallet', #domain_DigitalWallet{provider = qiwi}}) ->
@ -359,6 +377,8 @@ make_payment_tool(preauth_3ds_offsite) ->
make_simple_payment_tool(<<"preauth_3ds_offsite">>, jcb);
make_payment_tool(forbidden) ->
make_simple_payment_tool(<<"forbidden">>, visa);
make_payment_tool(unexpected_failure) ->
make_simple_payment_tool(<<"unexpected_failure">>, visa);
make_payment_tool(terminal) ->
{
{payment_terminal, #domain_PaymentTerminal{

View File

@ -53,6 +53,10 @@
-export([payment_with_offsite_preauth_success/1]).
-export([payment_with_offsite_preauth_failed/1]).
-export([terms_retrieval/1]).
-export([adhoc_repair_working_failed/1]).
-export([adhoc_repair_failed_succeeded/1]).
-export([consistent_history/1]).
%%
@ -120,6 +124,9 @@ all() ->
terms_retrieval,
adhoc_repair_working_failed,
adhoc_repair_failed_succeeded,
consistent_history
].
@ -1106,11 +1113,47 @@ terms_retrieval(C) ->
?pmt(payment_terminal, euroset)
]}
}} = TermSet1,
Revision = hg_domain:head(),
ok = hg_domain:update(construct_term_set_for_cost(1000, 2000)),
TermSet2 = hg_client_invoicing:compute_terms(InvoiceID, Client),
#domain_TermSet{payments = #domain_PaymentsServiceTerms{
payment_methods = {value, [?pmt(bank_card, visa)]}
}} = TermSet2.
}} = TermSet2,
ok = hg_domain:reset(Revision).
%%
-spec adhoc_repair_working_failed(config()) -> _ | no_return().
adhoc_repair_working_failed(C) ->
Client = cfg(client, C),
InvoiceID = start_invoice(<<"rubbercrack">>, make_due_date(10), 42000, C),
PaymentParams = make_payment_params(),
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
{exception, #'InvalidRequest'{}} = repair_invoice(InvoiceID, [], Client),
PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client),
PaymentID = await_payment_capture(InvoiceID, PaymentID, Client).
-spec adhoc_repair_failed_succeeded(config()) -> _ | no_return().
adhoc_repair_failed_succeeded(C) ->
Client = cfg(client, C),
InvoiceID = start_invoice(<<"rubbercrack">>, make_due_date(10), 42000, C),
{PaymentTool, Session} = hg_dummy_provider:make_payment_tool(unexpected_failure),
PaymentParams = make_payment_params(PaymentTool, Session),
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
[
?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(PaymentID))))
] = next_event(InvoiceID, Client),
% assume no more events here since machine is FUBAR already
timeout = next_event(InvoiceID, 2000, Client),
Changes = [
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))),
?payment_ev(PaymentID, ?payment_status_changed(?processed()))
],
ok = repair_invoice(InvoiceID, Changes, Client),
Changes = next_event(InvoiceID, Client),
PaymentID = await_payment_capture(InvoiceID, PaymentID, Client).
-spec payment_with_offsite_preauth_success(config()) -> test_return().
@ -1118,7 +1161,7 @@ payment_with_offsite_preauth_success(C) ->
Client = cfg(client, C),
InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C),
{PaymentTool, Session} = hg_dummy_provider:make_payment_tool(preauth_3ds_offsite),
PaymentParams = make_payment_params(PaymentTool, Session, instant),
PaymentParams = make_payment_params(PaymentTool, Session),
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
UserInteraction = await_payment_process_interaction(InvoiceID, PaymentID, Client),
timer:sleep(2000),
@ -1325,6 +1368,9 @@ make_payment_params(FlowType) ->
{PaymentTool, Session} = hg_dummy_provider:make_payment_tool(no_preauth),
make_payment_params(PaymentTool, Session, FlowType).
make_payment_params(PaymentTool, Session) ->
make_payment_params(PaymentTool, Session, instant).
make_payment_params(PaymentTool, Session, FlowType) ->
Flow = case FlowType of
instant ->
@ -1368,6 +1414,9 @@ create_invoice(InvoiceParams, Client) ->
?invoice_state(?invoice(InvoiceID)) = hg_client_invoicing:create(InvoiceParams, Client),
InvoiceID.
repair_invoice(InvoiceID, Changes, Client) ->
hg_client_invoicing:repair(InvoiceID, Changes, Client).
start_invoice(Product, Due, Amount, C) ->
start_invoice(cfg(shop_id, C), Product, Due, Amount, C).

View File

@ -11,6 +11,7 @@
-export([get/2]).
-export([fulfill/3]).
-export([rescind/3]).
-export([repair/3]).
-export([start_payment/3]).
-export([get_payment/3]).
@ -115,6 +116,12 @@ fulfill(InvoiceID, Reason, Client) ->
rescind(InvoiceID, Reason, Client) ->
map_result_error(gen_server:call(Client, {call, 'Rescind', [InvoiceID, Reason]})).
-spec repair(invoice_id(), [tuple()], pid()) ->
ok | woody_error:business_error().
repair(InvoiceID, Changes, Client) ->
map_result_error(gen_server:call(Client, {call, 'Repair', [InvoiceID, Changes]})).
-spec start_payment(invoice_id(), payment_params(), pid()) ->
payment() | woody_error:business_error().

View File

@ -39,7 +39,7 @@
{branch, "master"}
}
},
{dmsl , {git, "git@github.com:rbkmoney/damsel.git", {branch, "release/erlang/master"}}},
{dmsl , {git, "git@github.com:keynslug/damsel.git", {branch, "release"}}},
{mg_proto , {git, "git@github.com:rbkmoney/machinegun_proto.git", {branch, "master"}}},
{dmt_client , {git, "git@github.com:rbkmoney/dmt_client.git", {branch, "master"}}},
{scoper , {git, "git@github.com:rbkmoney/scoper.git", {branch, "master"}}}

View File

@ -3,8 +3,8 @@
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},1},
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},2},
{<<"dmsl">>,
{git,"git@github.com:rbkmoney/damsel.git",
{ref,"4ca2329c564b3730dbd742ae370be9919905b9de"}},
{git,"git@github.com:keynslug/damsel.git",
{ref,"cdef6d34947d03610faaa094925c1ff38892fdfe"}},
0},
{<<"dmt_client">>,
{git,"git@github.com:rbkmoney/dmt_client.git",