HG-250 Multistage payment start (#229)

This commit is contained in:
Andrey Fadeev 2018-08-09 11:32:18 +03:00 committed by GitHub
parent 2e145ceae8
commit 77d2fd4827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 539 additions and 252 deletions

View File

@ -5,7 +5,12 @@
-define(payment_started(Payment),
{invoice_payment_started,
#payproc_InvoicePaymentStarted{payment = Payment}
#payproc_InvoicePaymentStarted{
payment = Payment,
risk_score = undefined,
route = undefined,
cash_flow = undefined
}
}
).
-define(payment_started(Payment, RiskScore, Route, CashFlow),
@ -18,6 +23,21 @@
}
}
).
-define(risk_score_changed(RiskScore),
{invoice_payment_risk_score_changed,
#payproc_InvoicePaymentRiskScoreChanged{risk_score = RiskScore}
}
).
-define(route_changed(Route),
{invoice_payment_route_changed,
#payproc_InvoicePaymentRouteChanged{route = Route}
}
).
-define(cash_flow_changed(CashFlow),
{invoice_payment_cash_flow_changed,
#payproc_InvoicePaymentCashFlowChanged{cash_flow = CashFlow}
}
).
-define(payment_status_changed(Status),
{invoice_payment_status_changed,
#payproc_InvoicePaymentStatusChanged{status = Status}

View File

@ -583,11 +583,10 @@ start_payment(PaymentParams, St) ->
PaymentID = create_payment_id(St),
Opts = get_payment_opts(St),
% TODO make timer reset explicit here
{PaymentSession, {Changes1, _}} = hg_invoice_payment:init(PaymentID, PaymentParams, Opts),
{ok, {Changes2, Action}} = hg_invoice_payment:start_session(?processed()),
{PaymentSession, {Changes, Action}} = hg_invoice_payment:init(PaymentID, PaymentParams, Opts),
#{
response => PaymentSession,
changes => wrap_payment_changes(PaymentID, Changes1 ++ Changes2),
changes => wrap_payment_changes(PaymentID, Changes),
action => Action,
state => St
}.
@ -725,10 +724,10 @@ merge_change(?payment_ev(PaymentID, Event), St) ->
PaymentSession1 = hg_invoice_payment:merge_change(Event, PaymentSession),
St1 = set_payment_session(PaymentID, PaymentSession1, St),
case hg_invoice_payment:get_activity(PaymentSession1) of
A when A /= undefined ->
A when A =/= idle ->
% TODO Shouldn't we have here some kind of stack instead?
St1#st{activity = {payment, PaymentID}};
undefined ->
idle ->
St1#st{activity = invoice}
end.
@ -1059,13 +1058,13 @@ unmarshal({ID, Dt, Payload}) ->
%% Version > 1
unmarshal({list, changes}, Changes) when is_list(Changes) ->
[unmarshal(change, Change) || Change <- Changes];
lists:flatten([unmarshal(change, Change) || Change <- Changes]);
%% Version 1
unmarshal({list, changes}, {bin, Bin}) when is_binary(Bin) ->
Changes = binary_to_term(Bin),
[unmarshal(change, [1, Change]) || Change <- Changes];
lists:flatten([unmarshal(change, [1, Change]) || Change <- Changes]);
%% Changes
@ -1084,20 +1083,16 @@ unmarshal(change, [2, #{
<<"id">> := PaymentID,
<<"payload">> := Payload
}]) ->
?payment_ev(
unmarshal(str, PaymentID),
hg_invoice_payment:unmarshal(Payload)
);
PaymentEvents = hg_invoice_payment:unmarshal(Payload),
[?payment_ev(unmarshal(str, PaymentID), Event) || Event <- PaymentEvents];
unmarshal(change, [1, ?legacy_invoice_created(Invoice)]) ->
?invoice_created(unmarshal(invoice, Invoice));
unmarshal(change, [1, ?legacy_invoice_status_changed(Status)]) ->
?invoice_status_changed(unmarshal(status, Status));
unmarshal(change, [1, ?legacy_payment_ev(PaymentID, Payload)]) ->
?payment_ev(
unmarshal(str, PaymentID),
hg_invoice_payment:unmarshal([1, Payload])
);
PaymentEvents = hg_invoice_payment:unmarshal([1, Payload]),
[?payment_ev(unmarshal(str, PaymentID), Event) || Event <- PaymentEvents];
%% Change components

View File

@ -2,7 +2,7 @@
%%%
%%% TODO
%%% - make proper submachine interface
%%% - `init` / `start_session` should provide `next` or `done` to the caller
%%% - `init` should provide `next` or `done` to the caller
%%% - handle idempotent callbacks uniformly
%%% - get rid of matches against session status
%%% - tag machine with the provider trx
@ -37,8 +37,6 @@
%% Business logic
-export([start_session/1]).
-export([capture/2]).
-export([cancel/2]).
-export([refund/3]).
@ -65,7 +63,17 @@
%%
-type activity() :: undefined | payment | {refund, refund_id()}.
-type activity() :: payment_activity() | refund_activity() | idle.
-type payment_activity() :: {payment, payment_step()}.
-type refund_activity() :: {refund, refund_id()}.
-type payment_step() ::
new |
risk_scoring |
routing |
cash_flow_building |
processing |
flow_waiting |
finalizing.
-record(st, {
activity :: activity(),
@ -114,9 +122,10 @@
-type tag() :: dmsl_proxy_provider_thrift:'CallbackTag'().
-type retry_strategy() :: hg_retry:strategy().
-type session_status() :: active | suspended | finished.
-type session() :: #{
target := target(),
status := active | suspended | finished,
status := session_status(),
trx := trx_info(),
tags := [tag()],
result => session_result(),
@ -195,7 +204,11 @@ get_tags(#st{sessions = Sessions, refunds = Refunds}) ->
%%
-type result() :: {[_], hg_machine_action:t()}. % FIXME
-type event() :: any(). % FIXME
-type action() :: hg_machine_action:t().
-type events() :: [event()].
-type result() :: {events(), action()}.
-type machine_result() :: {next | done, result()}.
-spec init(payment_id(), _, opts()) ->
{payment(), result()}.
@ -217,36 +230,18 @@ init_(PaymentID, Params, Opts) ->
Party = get_party(Opts),
Shop = get_shop(Opts),
Invoice = get_invoice(Opts),
PaymentInstitution = get_payment_institution(Opts, Revision),
Cost = get_invoice_cost(Invoice),
Payer = construct_payer(get_payer_params(Params), Shop),
Flow = get_flow_params(Params),
CreatedAt = hg_datetime:format_now(),
MerchantTerms = get_merchant_payments_terms(Opts, Revision),
VS0 = collect_varset(Party, Shop, #{}),
{Payment, VS1} = construct_payment(
VS0 = collect_validation_varset(Party, Shop, #{}),
Payment = construct_payment(
PaymentID, CreatedAt, Cost, Payer, Flow, MerchantTerms, Party, Shop, VS0, Revision
),
{RiskScore, VS2} = validate_risk_score(inspect(Payment, PaymentInstitution, VS1, Opts), VS1),
Route = case get_predefined_route(Payer) of
{ok, R} ->
R;
undefined ->
validate_route(
hg_routing:choose(payment, PaymentInstitution, VS2, Revision),
Payment
)
end,
ProviderTerms = get_provider_payments_terms(Route, Revision),
Provider = get_route_provider(Route, Revision),
Cashflow = collect_cashflow(MerchantTerms, ProviderTerms, VS2, Revision),
FinalCashflow = construct_final_cashflow(Payment, Shop, PaymentInstitution, Provider, Cashflow, VS2, Revision),
_AffectedAccounts = hg_accounting:plan(
construct_payment_plan_id(Invoice, Payment),
{1, FinalCashflow}
),
Events = [?payment_started(Payment, RiskScore, Route, FinalCashflow)],
{collapse_changes(Events), {Events, hg_machine_action:new()}}.
Events = [?payment_started(Payment)],
{collapse_changes(Events), {Events, hg_machine_action:instant()}}.
get_merchant_payments_terms(Opts, Revision) ->
get_merchant_payments_terms(Opts, Revision, get_invoice_created_at(get_invoice(Opts))).
@ -326,46 +321,38 @@ construct_payment(PaymentID, CreatedAt, Cost, Payer, FlowParams, Terms, Party, S
VS1,
Revision
),
{Flow, VS3} = construct_payment_flow(
Flow = construct_payment_flow(
FlowParams,
CreatedAt,
Terms#domain_PaymentsServiceTerms.holds,
VS2,
Revision
),
VS4 = collect_refund_varset(
Terms#domain_PaymentsServiceTerms.refunds,
VS3,
Revision
),
{
#domain_InvoicePayment{
id = PaymentID,
created_at = CreatedAt,
owner_id = Party#domain_Party.id,
shop_id = Shop#domain_Shop.id,
domain_revision = Revision,
status = ?pending(),
cost = Cost,
payer = Payer,
flow = Flow
},
VS4
#domain_InvoicePayment{
id = PaymentID,
created_at = CreatedAt,
owner_id = Party#domain_Party.id,
shop_id = Shop#domain_Shop.id,
domain_revision = Revision,
status = ?pending(),
cost = Cost,
payer = Payer,
flow = Flow
}.
construct_payment_flow({instant, _}, _CreatedAt, _Terms, VS, _Revision) ->
{
?invoice_payment_flow_instant(),
VS#{flow => instant}
};
construct_payment_flow({instant, _}, _CreatedAt, _Terms, _VS, _Revision) ->
?invoice_payment_flow_instant();
construct_payment_flow({hold, Params}, CreatedAt, Terms, VS, Revision) ->
OnHoldExpiration = Params#payproc_InvoicePaymentParamsFlowHold.on_hold_expiration,
Lifetime = ?hold_lifetime(Seconds) = validate_hold_lifetime(Terms, VS, Revision),
?hold_lifetime(Seconds) = validate_hold_lifetime(Terms, VS, Revision),
HeldUntil = hg_datetime:format_ts(hg_datetime:parse_ts(CreatedAt) + Seconds),
{
?invoice_payment_flow_hold(OnHoldExpiration, HeldUntil),
VS#{flow => {hold, Lifetime}}
}.
?invoice_payment_flow_hold(OnHoldExpiration, HeldUntil).
reconstruct_payment_flow(?invoice_payment_flow_instant(), _CreatedAt, VS) ->
VS#{flow => instant};
reconstruct_payment_flow(?invoice_payment_flow_hold(_OnHoldExpiration, HeldUntil), CreatedAt, VS) ->
Seconds = hg_datetime:parse_ts(HeldUntil) - hg_datetime:parse_ts(CreatedAt),
VS#{flow => {hold, ?hold_lifetime(Seconds)}}.
get_predefined_route(?customer_payer(_, _, RecPaymentToolID, _, _) = Payer) ->
case get_rec_payment_tool(RecPaymentToolID) of
@ -422,26 +409,25 @@ validate_limit(Cash, CashRange) ->
throw_invalid_request(<<"Invalid amount, more than allowed maximum">>)
end.
validate_risk_score(RiskScore, VS) when RiskScore == low; RiskScore == high ->
{RiskScore, VS#{risk_score => RiskScore}};
validate_risk_score(fatal, _VS) ->
throw_invalid_request(<<"Fatal error">>).
choose_route(Payer, PaymentInstitution, VS, Revision) ->
case get_predefined_route(Payer) of
{ok, _Route} = Result ->
Result;
undefined ->
case hg_routing:choose(payment, PaymentInstitution, VS, Revision) of
{ok, _Route} = Result ->
Result;
{error, {no_route_found, RejectContext}} = Error ->
_ = log_reject_context(RejectContext),
Error
end
end.
validate_route({ok, Route}, _Payment) ->
Route;
validate_route({error, {no_route_found, RejectContext}}, Payment) ->
LogFun = fun(Msg, Param) ->
_ = lager:log(
error,
lager:md(),
Msg,
[Param]
)
end,
_ = LogFun("No route found, varset: ~p", maps:get(varset, RejectContext)),
_ = LogFun("No route found, rejected providers: ~p", maps:get(rejected_providers, RejectContext)),
_ = LogFun("No route found, rejected terminals: ~p", maps:get(rejected_terminals, RejectContext)),
error({misconfiguration, {'No route found for a payment', Payment}}).
log_reject_context(RejectContext) ->
_ = lager:warning("No route found, varset: ~p", maps:get(varset, RejectContext)),
_ = lager:warning("No route found, rejected providers: ~p", maps:get(rejected_providers, RejectContext)),
_ = lager:warning("No route found, rejected terminals: ~p", maps:get(rejected_terminals, RejectContext)),
ok.
validate_refund_time(RefundCreatedAt, PaymentCreatedAt, TimeSpanSelector, VS, Revision) ->
EligibilityTime = reduce_selector(eligibility_time, TimeSpanSelector, VS, Revision),
@ -485,18 +471,16 @@ collect_partial_refund_varset(
collect_partial_refund_varset(undefined, _, _) ->
#{}.
collect_varset(St, Opts) ->
collect_varset(get_party(Opts), get_shop(Opts), get_payment(St), #{}).
collect_validation_varset(St, Opts) ->
collect_validation_varset(get_party(Opts), get_shop(Opts), get_payment(St), #{}).
collect_varset(
#domain_Party{id = PartyID},
collect_validation_varset(Party, Shop, VS) ->
#domain_Party{id = PartyID} = Party,
#domain_Shop{
id = ShopID,
category = Category,
account = #domain_ShopAccount{currency = Currency}
},
VS
) ->
} = Shop,
VS#{
party_id => PartyID,
shop_id => ShopID,
@ -504,13 +488,28 @@ collect_varset(
currency => Currency
}.
collect_varset(Party, Shop, Payment, VS) ->
VS0 = collect_varset(Party, Shop, VS),
collect_validation_varset(Party, Shop, Payment, VS) ->
VS0 = collect_validation_varset(Party, Shop, VS),
VS0#{
cost => get_payment_cost(Payment),
payment_tool => get_payment_tool(Payment)
}.
collect_routing_varset(Payment, Opts, VS0) ->
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
#domain_InvoicePayment{
created_at = CreatedAt,
domain_revision = Revision,
flow = DomainFlow
} = Payment,
MerchantTerms = get_merchant_payments_terms(Opts, Revision),
VS2 = reconstruct_payment_flow(DomainFlow, CreatedAt, VS1),
collect_refund_varset(
MerchantTerms#domain_PaymentsServiceTerms.refunds,
VS2,
Revision
).
%%
collect_cashflow(
@ -629,12 +628,10 @@ reduce_selector(Name, Selector, VS, Revision) ->
%%
-spec start_session(target()) ->
{ok, result()}.
events().
start_session(Target) ->
Events = [?session_ev(Target, ?session_started())],
Action = hg_machine_action:instant(),
{ok, {Events, Action}}.
[?session_ev(Target, ?session_started())].
-spec capture(st(), atom()) -> {ok, result()}.
@ -648,9 +645,9 @@ cancel(St, Reason) ->
do_payment(St, Target) ->
Payment = get_payment(St),
_ = assert_payment_status(processed, Payment),
_ = assert_activity({payment, flow_waiting}, St),
_ = assert_payment_flow(hold, Payment),
start_session(Target).
{ok, {start_session(Target), hg_machine_action:instant()}}.
-spec refund(refund_params(), st(), opts()) ->
{refund(), result()}.
@ -665,7 +662,7 @@ refund(Params, St0, Opts) ->
Provider = get_route_provider(Route, Revision),
_ = assert_payment_status(captured, Payment),
_ = assert_previous_refunds_finished(St),
VS0 = collect_varset(St, Opts),
VS0 = collect_validation_varset(St, Opts),
Cash = define_refund_cash(Params#payproc_InvoicePaymentRefundParams.cash, Payment),
_ = assert_refund_cash(Cash, St),
ID = construct_refund_id(St),
@ -899,7 +896,7 @@ create_adjustment(Timestamp, Params, St, Opts) ->
Route = get_route(St),
Provider = get_route_provider(Route, Revision),
ProviderTerms = get_provider_payments_terms(Route, Revision),
VS = collect_varset(St, Opts),
VS = collect_validation_varset(St, Opts),
Cashflow = collect_cashflow(MerchantTerms, ProviderTerms, VS, Revision),
FinalCashflow = construct_final_cashflow(Payment, Shop, PaymentInstitution, Provider, Cashflow, VS, Revision),
ID = construct_adjustment_id(St),
@ -925,6 +922,14 @@ get_adjustment_revision(Params) ->
construct_adjustment_id(#st{adjustments = As}) ->
integer_to_binary(length(As) + 1).
-spec assert_activity(activity(), st()) -> ok | no_return().
assert_activity(Activity, #st{activity = Activity}) ->
ok;
assert_activity(_Activity, St) ->
%% TODO: Create dedicated error like "Payment is capturing already"
#domain_InvoicePayment{status = Status} = get_payment(St),
throw(#payproc_InvalidPaymentStatus{status = Status}).
assert_payment_status(Status, #domain_InvoicePayment{status = {Status, _}}) ->
ok;
assert_payment_status(_, #domain_InvoicePayment{status = Status}) ->
@ -1016,8 +1021,7 @@ get_adjustment_cashflow(#domain_InvoicePaymentAdjustment{new_cash_flow = Cashflo
%%
-spec process_signal(timeout, st(), opts()) ->
{next | done, result()}.
machine_result().
process_signal(timeout, St, Options) ->
scoper:scope(
payment,
@ -1027,21 +1031,24 @@ process_signal(timeout, St, Options) ->
process_timeout(St) ->
Action = hg_machine_action:new(),
case get_active_session(St) of
Session when Session /= undefined ->
case get_session_status(Session) of
active ->
process(Action, St);
suspended ->
process_callback_timeout(Action, St)
end;
undefined ->
process_finished_session(St)
end.
process_timeout(get_activity(St), Action, St).
-spec process_timeout(activity(), action(), st()) -> machine_result().
process_timeout({payment, risk_scoring}, Action, St) ->
%% There are three processing steps here (scoring, routing and cash flow building)
process_routing(Action, St);
process_timeout({payment, Step}, Action, St) when
Step =:= processing orelse
Step =:= finalizing
->
process_session(Action, St);
process_timeout({refund, _ID}, Action, St) ->
process_session(Action, St);
process_timeout({payment, flow_waiting}, Action, St) ->
finalize_payment(Action, St).
-spec process_call({callback, tag(), _}, st(), opts()) ->
{_, {next | done, result()}}. % FIXME
{_, machine_result()}. % FIXME
process_call({callback, Tag, Payload}, St, Options) ->
scoper:scope(
payment,
@ -1051,7 +1058,7 @@ process_call({callback, Tag, Payload}, St, Options) ->
process_callback(Tag, Payload, St) ->
Action = hg_machine_action:new(),
Session = get_active_session(St),
Session = get_activity_session(St),
process_callback(Tag, Payload, Action, Session, St).
process_callback(Tag, Payload, Action, Session, St) when Session /= undefined ->
@ -1061,22 +1068,81 @@ process_callback(Tag, Payload, Action, Session, St) when Session /= undefined ->
_ ->
throw(invalid_callback)
end;
process_callback(_Tag, _Payload, _Action, undefined, _St) ->
throw(invalid_callback).
process_callback_timeout(Action, St) ->
Session = get_active_session(St),
Result = handle_proxy_callback_timeout(Action, Session),
finish_processing(Result, St).
%%
process(Action, St) ->
-spec process_routing(action(), st()) -> machine_result().
process_routing(Action, St) ->
Opts = get_opts(St),
Revision = get_payment_revision(St),
PaymentInstitution = get_payment_institution(Opts, Revision),
Payment = get_payment(St),
VS0 = collect_routing_varset(Payment, Opts, #{}),
RiskScore = inspect(Payment, PaymentInstitution, VS0, Opts),
Events0 = [?risk_score_changed(RiskScore)],
Payer = get_payment_payer(St),
VS1 = VS0#{risk_score => RiskScore},
case choose_route(Payer, PaymentInstitution, VS1, Revision) of
{ok, Route} ->
process_cash_flow_building(Route, VS1, Payment, PaymentInstitution, Revision, Opts, Events0, Action);
{error, {no_route_found, _Details}} ->
Failure = {failure, payproc_errors:construct('PaymentFailure',
{no_route_found, #payprocerr_GeneralFailure{}}
)},
process_failure(get_activity(St), Events0, Action, Failure, St)
end.
process_cash_flow_building(Route, VS, Payment, PaymentInstitution, Revision, Opts, Events0, Action) ->
MerchantTerms = get_merchant_payments_terms(Opts, Revision),
ProviderTerms = get_provider_payments_terms(Route, Revision),
Provider = get_route_provider(Route, Revision),
Cashflow = collect_cashflow(MerchantTerms, ProviderTerms, VS, Revision),
Shop = get_shop(Opts),
FinalCashflow = construct_final_cashflow(Payment, Shop, PaymentInstitution, Provider, Cashflow, VS, Revision),
Invoice = get_invoice(Opts),
_AffectedAccounts = hg_accounting:plan(
construct_payment_plan_id(Invoice, Payment),
{1, FinalCashflow}
),
Events1 = Events0 ++ [?route_changed(Route), ?cash_flow_changed(FinalCashflow)],
{next, {Events1, hg_machine_action:set_timeout(0, Action)}}.
%%
-spec process_session(action(), st()) -> machine_result().
process_session(Action, St) ->
Session = get_activity_session(St),
process_session(Session, Action, St).
process_session(undefined, Action, St0) ->
Events = start_session(get_target(St0)),
St1 = collapse_changes(Events, St0),
Session = get_activity_session(St1),
process_session(Session, Action, Events, St1);
process_session(Session, Action, St) ->
process_session(Session, Action, [], St).
process_session(Session, Action, Events, St) ->
Status = get_session_status(Session),
process_session(Status, Session, Action, Events, St).
-spec process_session(session_status(), session(), action(), events(), st()) -> machine_result().
process_session(active, Session, Action, Events, St) ->
process_active_session(Action, Session, Events, St);
process_session(suspended, Session, Action, Events, St) ->
process_callback_timeout(Action, Session, Events, St).
-spec process_active_session(action(), session(), events(), st()) -> machine_result().
process_active_session(Action, Session, Events, St) ->
ProxyContext = construct_proxy_context(St),
{ok, ProxyResult} = issue_process_call(ProxyContext, St),
Result = handle_proxy_result(ProxyResult, Action, get_active_session(St)),
finish_processing(Result, St).
Result = handle_proxy_result(ProxyResult, Action, Events, Session),
finish_session_processing(Result, St).
process_finished_session(St) ->
-spec finalize_payment(action(), st()) -> machine_result().
finalize_payment(Action, St) ->
Target = case get_payment_flow(get_payment(St)) of
?invoice_payment_flow_instant() ->
?captured();
@ -1088,19 +1154,28 @@ process_finished_session(St) ->
?captured()
end
end,
{ok, Result} = start_session(Target),
{done, Result}.
StartEvents = start_session(Target),
{done, {StartEvents, hg_machine_action:set_timeout(0, Action)}}.
-spec process_callback_timeout(action(), session(), events(), st()) -> machine_result().
process_callback_timeout(Action, Session, Events, St) ->
Result = handle_proxy_callback_timeout(Action, Events, Session),
finish_session_processing(Result, St).
handle_callback(Payload, Action, St) ->
ProxyContext = construct_proxy_context(St),
{ok, CallbackResult} = issue_callback_call(Payload, ProxyContext, St),
{Response, Result} = handle_callback_result(CallbackResult, Action, get_active_session(St)),
{Response, finish_processing(Result, St)}.
{Response, Result} = handle_callback_result(CallbackResult, Action, get_activity_session(St)),
{Response, finish_session_processing(Result, St)}.
finish_processing(Result, St) ->
finish_processing(get_activity(St), Result, St).
-spec finish_session_processing(result(), st()) -> machine_result().
finish_session_processing(Result, St) ->
finish_session_processing(get_activity(St), Result, St).
finish_processing(payment = Activity, {Events, Action}, St) ->
finish_session_processing({payment, Step} = Activity, {Events, Action}, St) when
Step =:= processing orelse
Step =:= finalizing
->
Target = get_target(St),
St1 = collapse_changes(Events, St),
case get_session(Target, St1) of
@ -1121,7 +1196,7 @@ finish_processing(payment = Activity, {Events, Action}, St) ->
{next, {Events, Action}}
end;
finish_processing({refund, ID} = Activity, {Events, Action}, St) ->
finish_session_processing({refund, ID} = Activity, {Events, Action}, St) ->
Events1 = [?refund_ev(ID, Ev) || Ev <- Events],
St1 = collapse_changes(Events1, St),
RefundSt1 = try_get_refund_state(ID, St1),
@ -1149,7 +1224,15 @@ finish_processing({refund, ID} = Activity, {Events, Action}, St) ->
process_failure(Activity, Events, Action, Failure, St) ->
process_failure(Activity, Events, Action, Failure, St, undefined).
process_failure(payment, Events, Action, Failure, St, _RefundSt) ->
process_failure({payment, Step}, Events, Action, Failure, _St, _RefundSt) when
Step =:= risk_scoring orelse
Step =:= routing
->
{done, {Events ++ [?payment_status_changed(?failed(Failure))], Action}};
process_failure({payment, Step}, Events, Action, Failure, St, _RefundSt) when
Step =:= processing orelse
Step =:= finalizing
->
Target = get_target(St),
case check_retry_possibility(Target, Failure, St) of
{retry, Timeout} ->
@ -1177,7 +1260,7 @@ process_failure({refund, ID}, Events, Action, Failure, St, RefundSt) ->
end.
retry_session(Action, Target, Timeout) ->
{ok, {NewEvents, _Action}} = start_session(Target),
NewEvents = start_session(Target),
NewAction = set_timer({timeout, Timeout}, Action),
{NewEvents, NewAction}.
@ -1207,7 +1290,7 @@ check_retry_possibility(Target, Failure, St) ->
fatal
end;
fatal ->
_ = lager:debug("Failure is fatal"),
_ = lager:debug("Failure ~p is not transient", [Failure]),
fatal
end.
@ -1222,25 +1305,26 @@ do_check_failure_type({authorization_failed, {temporarily_unavailable, _}}) ->
do_check_failure_type(_Failure) ->
fatal.
get_action({processed, _}, Action, St) ->
get_action(?processed(), Action, St) ->
case get_payment_flow(get_payment(St)) of
?invoice_payment_flow_instant() ->
hg_machine_action:set_timeout(0, Action);
?invoice_payment_flow_hold(_, HeldUntil) ->
hg_machine_action:set_deadline(HeldUntil, Action)
end;
get_action(_, Action, _) ->
get_action(_Target, Action, _St) ->
Action.
handle_proxy_result(
#prxprv_PaymentProxyResult{intent = {_Type, Intent}, trx = Trx, next_state = ProxyState},
Action0,
Events0,
Session
) ->
Events1 = hg_proxy_provider:bind_transaction(Trx, Session),
Events2 = update_proxy_state(ProxyState, Session),
{Events3, Action} = handle_proxy_intent(Intent, Action0),
{wrap_session_events(Events1 ++ Events2 ++ Events3, Session), Action}.
{Events0 ++ wrap_session_events(Events1 ++ Events2 ++ Events3, Session), Action}.
handle_callback_result(
#prxprv_PaymentCallbackResult{result = ProxyResult, response = Response},
@ -1267,9 +1351,9 @@ handle_proxy_callback_result(
Events2 = update_proxy_state(ProxyState, Session),
{wrap_session_events(Events1 ++ Events2, Session), Action0}.
handle_proxy_callback_timeout(Action, Session) ->
Events = [?session_finished(?session_failed(?operation_timeout()))],
{wrap_session_events(Events, Session), Action}.
handle_proxy_callback_timeout(Action, Events, Session) ->
SessionEvents = [?session_finished(?session_failed(?operation_timeout()))],
{Events ++ wrap_session_events(SessionEvents, Session), Action}.
wrap_session_events(SessionEvents, #{target := Target}) ->
[?session_ev(Target, Ev) || Ev <- SessionEvents].
@ -1340,7 +1424,7 @@ construct_payment_info(St, Opts) ->
construct_proxy_context(St) ->
#prxprv_PaymentContext{
session = construct_session(get_active_session(St)),
session = construct_session(get_activity_session(St)),
payment_info = construct_payment_info(St, get_opts(St)),
options = collect_proxy_options(St)
}.
@ -1351,7 +1435,7 @@ construct_session(Session = #{target := Target}) ->
state = get_session_proxy_state(Session)
}.
construct_payment_info(payment, _St, PaymentInfo) ->
construct_payment_info({payment, _Step}, _St, PaymentInfo) ->
PaymentInfo;
construct_payment_info({refund, ID}, St, PaymentInfo) ->
PaymentInfo#prxprv_PaymentInfo{
@ -1552,31 +1636,70 @@ throw_invalid_request(Why) ->
-spec merge_change(change(), st() | undefined) -> st().
merge_change(Event, undefined) ->
merge_change(Event, #st{});
merge_change(Event, #st{activity = {payment, new}});
merge_change(?payment_started(Payment, RiskScore, Route, Cashflow), St) ->
merge_change(?payment_started(Payment), #st{activity = {payment, new}} = St) ->
St#st{
activity = payment,
target = ?processed(),
payment = Payment,
risk_score = RiskScore,
route = Route,
cash_flow = Cashflow
activity = {payment, risk_scoring}
};
merge_change(?risk_score_changed(RiskScore), #st{activity = {payment, risk_scoring}} = St) ->
St#st{
risk_score = RiskScore,
activity = {payment, routing}
};
merge_change(?route_changed(Route), #st{activity = {payment, routing}} = St) ->
St#st{
route = Route,
activity = {payment, cash_flow_building}
};
merge_change(?cash_flow_changed(Cashflow), #st{activity = {payment, cash_flow_building}} = St) ->
St#st{
cash_flow = Cashflow,
activity = {payment, processing}
};
merge_change(
?payment_status_changed({failed, _} = Status),
#st{payment = Payment, activity = {payment, _Step}} = St
) ->
St#st{
payment = Payment#domain_InvoicePayment{status = Status},
activity = idle
};
merge_change(
?payment_status_changed({StatusTag, _} = Status),
#st{payment = Payment, activity = {payment, finalizing}} = St
) when
StatusTag =:= cancelled orelse
StatusTag =:= captured
->
St#st{
payment = Payment#domain_InvoicePayment{status = Status},
activity = idle
};
merge_change(
?payment_status_changed({processed, _} = Status),
#st{payment = Payment, activity = {payment, processing}} = St
) ->
St#st{
payment = Payment#domain_InvoicePayment{status = Status},
activity = {payment, flow_waiting}
};
merge_change(
?payment_status_changed({refunded, _} = Status),
#st{payment = Payment, activity = idle} = St
) ->
St#st{
payment = Payment#domain_InvoicePayment{status = Status}
};
merge_change(?payment_status_changed(Status), St = #st{payment = Payment}) ->
St1 = St#st{payment = Payment#domain_InvoicePayment{status = Status}},
case Status of
{S, _} when S == captured; S == cancelled; S == failed ->
St1#st{activity = undefined};
_ ->
St1
end;
merge_change(?refund_ev(ID, Event), St) ->
St1 = St#st{activity = {refund, ID}},
RefundSt = merge_refund_change(Event, try_get_refund_state(ID, St1)),
St2 = set_refund_state(ID, RefundSt, St1),
case get_refund_status(get_refund(RefundSt)) of
{S, _} when S == succeeded; S == failed ->
St2#st{activity = undefined};
St2#st{activity = idle};
_ ->
St2
end;
@ -1590,21 +1713,29 @@ merge_change(?adjustment_ev(ID, Event), St) ->
_ ->
St1
end;
merge_change(?session_ev(Target, ?session_started()), St) ->
merge_change(?session_ev(Target, ?session_started()), #st{activity = {payment, Step}} = St) when
Step =:= processing orelse
Step =:= flow_waiting orelse
Step =:= finalizing
->
% FIXME why the hell dedicated handling
St1 = set_session(Target, create_session(Target, get_trx(St)), St#st{target = Target}),
save_retry_attempt(Target, St1);
St2 = save_retry_attempt(Target, St1),
NextStep = case Step of
processing ->
processing;
flow_waiting ->
finalizing;
finalizing ->
%% session retrying
finalizing
end,
St2#st{activity = {payment, NextStep}};
merge_change(?session_ev(Target, Event), St) ->
Session = merge_session_change(Event, get_session(Target, St)),
St1 = set_session(Target, Session, St),
% FIXME leaky transactions
St2 = set_trx(get_session_trx(Session), St1),
case get_session_status(Session) of
finished ->
St2#st{target = undefined};
_ ->
St2
end.
set_trx(get_session_trx(Session), St1).
save_retry_attempt(Target, #st{retry_attempts = Attempts} = St) ->
St#st{retry_attempts = maps:update_with(Target, fun(N) -> N + 1 end, 0, Attempts)}.
@ -1748,14 +1879,22 @@ get_target(#st{target = Target}) ->
get_opts(#st{opts = Opts}) ->
Opts.
get_payment_revision(#st{payment = #domain_InvoicePayment{domain_revision = Revision}}) ->
Revision.
get_payment_payer(#st{payment = #domain_InvoicePayment{payer = Payer}}) ->
Payer.
%%
get_active_session(St) ->
get_active_session(get_activity(St), St).
get_activity_session(St) ->
get_activity_session(get_activity(St), St).
get_active_session(payment, St) ->
-spec get_activity_session(activity(), st()) -> session() | undefined.
get_activity_session({payment, _Step}, St) ->
get_session(get_target(St), St);
get_active_session({refund, ID}, St) ->
get_activity_session({refund, ID}, St) ->
RefundSt = try_get_refund_state(ID, St),
RefundSt#refund_st.session.
@ -1828,10 +1967,36 @@ issue_customer_call(Func, Args) ->
-spec get_log_params(change(), st()) ->
{ok, #{type := invoice_payment_event, params := list(), message := string()}} | undefined.
get_log_params(?payment_started(Payment, _, _, Cashflow), _) ->
get_log_params(?payment_started(Payment), _) ->
Params = #{
payment => Payment,
event_type => invoice_payment_started
},
make_log_params(Params);
get_log_params(?risk_score_changed(RiskScore), _) ->
Params = #{
risk_score => RiskScore,
event_type => invoice_payment_risk_score_changed
},
make_log_params(Params);
get_log_params(?route_changed(Route), _) ->
Params = #{
route => Route,
event_type => invoice_payment_route_changed
},
make_log_params(Params);
get_log_params(?cash_flow_changed(Cashflow), _) ->
Params = #{
cashflow => Cashflow,
event_type => invoice_payment_cash_flow_changed
},
make_log_params(Params);
get_log_params(?payment_started(Payment, RiskScore, Route, Cashflow), _) ->
Params = #{
payment => Payment,
cashflow => Cashflow,
risk_score => RiskScore,
route => Route,
event_type => invoice_payment_started
},
make_log_params(Params);
@ -1877,6 +2042,8 @@ make_log_params(flow, ?invoice_payment_flow_instant()) ->
[{type, instant}];
make_log_params(flow, ?invoice_payment_flow_hold(OnHoldExpiration, _)) ->
[{type, hold}, {on_hold_expiration, OnHoldExpiration}];
make_log_params(cashflow, undefined) ->
[];
make_log_params(cashflow, CashFlow) ->
Reminders = maps:to_list(hg_cashflow:get_partial_remainders(CashFlow)),
Accounts = lists:map(
@ -1887,6 +2054,10 @@ make_log_params(cashflow, CashFlow) ->
Reminders
),
[{accounts, Accounts}];
make_log_params(risk_score, Score) ->
[{risk_score, Score}];
make_log_params(route, _Route) ->
[];
make_log_params(status, {StatusTag, StatusDetails}) ->
[{status, StatusTag}] ++ format_status_details(StatusDetails);
make_log_params(event_type, EventType) ->
@ -1910,6 +2081,12 @@ get_account_key({AccountParty, AccountType}) ->
get_message(invoice_payment_started) ->
"Invoice payment is started";
get_message(invoice_payment_risk_score_changed) ->
"Invoice payment risk score changed";
get_message(invoice_payment_route_changed) ->
"Invoice payment route changed";
get_message(invoice_payment_cash_flow_changed) ->
"Invoice payment cash flow changed";
get_message(invoice_payment_status_changed) ->
"Invoice payment status is changed".
@ -1925,12 +2102,24 @@ marshal(Change) ->
%% Changes
marshal(change, ?payment_started(Payment, RiskScore, Route, Cashflow)) ->
marshal(change, ?payment_started(Payment)) ->
[2, #{
<<"change">> => <<"started">>,
<<"payment">> => marshal(payment, Payment),
<<"risk_score">> => marshal(risk_score, RiskScore),
<<"route">> => hg_routing:marshal(Route),
<<"change">> => <<"payment_created">>,
<<"payment">> => marshal(payment, Payment)
}];
marshal(change, ?risk_score_changed(RiskScore)) ->
[2, #{
<<"change">> => <<"risk_score_changed">>,
<<"risk_score">> => marshal(risk_score, RiskScore)
}];
marshal(change, ?route_changed(Route)) ->
[2, #{
<<"change">> => <<"route_changed">>,
<<"route">> => hg_routing:marshal(Route)
}];
marshal(change, ?cash_flow_changed(Cashflow)) ->
[2, #{
<<"change">> => <<"cash_flow_changed">>,
<<"cash_flow">> => hg_cashflow:marshal(Cashflow)
}];
marshal(change, ?payment_status_changed(Status)) ->
@ -1964,6 +2153,8 @@ marshal(payment, #domain_InvoicePayment{} = Payment) ->
<<"id">> => marshal(str, Payment#domain_InvoicePayment.id),
<<"created_at">> => marshal(str, Payment#domain_InvoicePayment.created_at),
<<"domain_revision">> => marshal(str, Payment#domain_InvoicePayment.domain_revision),
<<"owner_id">> => marshal(str, Payment#domain_InvoicePayment.owner_id),
<<"shop_id">> => marshal(str, Payment#domain_InvoicePayment.shop_id),
<<"cost">> => hg_cash:marshal(Payment#domain_InvoicePayment.cost),
<<"payer">> => marshal(payer, Payment#domain_InvoicePayment.payer),
<<"flow">> => marshal(flow, Payment#domain_InvoicePayment.flow),
@ -2196,13 +2387,58 @@ marshal(_, Other) ->
%% Unmarshalling
-spec unmarshal(hg_msgpack_marshalling:value()) -> change().
-spec unmarshal(hg_msgpack_marshalling:value()) -> [change()].
unmarshal(Change) ->
unmarshal(change, Change).
%% Changes
unmarshal(change, [2, #{
<<"change">> := <<"payment_created">>,
<<"payment">> := Payment
}]) ->
[?payment_started(unmarshal(payment, Payment))];
unmarshal(change, [2, #{
<<"change">> := <<"status_changed">>,
<<"status">> := Status
}]) ->
[?payment_status_changed(unmarshal(status, Status))];
unmarshal(change, [2, #{
<<"change">> := <<"risk_score_changed">>,
<<"risk_score">>:= RiskScore
}]) ->
[?risk_score_changed(unmarshal(risk_score, RiskScore))];
unmarshal(change, [2, #{
<<"change">> := <<"route_changed">>,
<<"route">> := Route
}]) ->
[?route_changed(hg_routing:unmarshal(Route))];
unmarshal(change, [2, #{
<<"change">> := <<"cash_flow_changed">>,
<<"cash_flow">> := Cashflow
}]) ->
[?cash_flow_changed(hg_cashflow:unmarshal(Cashflow))];
unmarshal(change, [2, #{
<<"change">> := <<"session_change">>,
<<"payload">> := Payload,
<<"target">> := Target
}]) ->
[?session_ev(unmarshal(status, Target), unmarshal(session_change, Payload))];
unmarshal(change, [2, #{
<<"change">> := <<"adjustment_change">>,
<<"id">> := AdjustmentID,
<<"payload">> := Payload
}]) ->
[?adjustment_ev(unmarshal(str, AdjustmentID), unmarshal(adjustment_change, Payload))];
unmarshal(change, [2, #{
<<"change">> := <<"refund">>,
<<"id">> := RefundID,
<<"payload">> := Payload
}]) ->
[?refund_ev(unmarshal(str, RefundID), unmarshal(refund_change, Payload))];
%% deprecated v2 changes
unmarshal(change, [2, #{
<<"change">> := <<"started">>,
<<"payment">> := Payment,
@ -2210,49 +2446,27 @@ unmarshal(change, [2, #{
<<"route">> := Route,
<<"cash_flow">> := Cashflow
}]) ->
?payment_started(
unmarshal(payment, Payment),
unmarshal(risk_score, RiskScore),
hg_routing:unmarshal(Route),
hg_cashflow:unmarshal(Cashflow)
);
unmarshal(change, [2, #{
<<"change">> := <<"status_changed">>,
<<"status">> := Status
}]) ->
?payment_status_changed(unmarshal(status, Status));
unmarshal(change, [2, #{
<<"change">> := <<"session_change">>,
<<"payload">> := Payload,
<<"target">> := Target
}]) ->
?session_ev(unmarshal(status, Target), unmarshal(session_change, Payload));
unmarshal(change, [2, #{
<<"change">> := <<"adjustment_change">>,
<<"id">> := AdjustmentID,
<<"payload">> := Payload
}]) ->
?adjustment_ev(unmarshal(str, AdjustmentID), unmarshal(adjustment_change, Payload));
unmarshal(change, [2, #{
<<"change">> := <<"refund">>,
<<"id">> := RefundID,
<<"payload">> := Payload
}]) ->
?refund_ev(unmarshal(str, RefundID), unmarshal(refund_change, Payload));
[
?payment_started(unmarshal(payment, Payment)),
?risk_score_changed(unmarshal(risk_score, RiskScore)),
?route_changed(hg_routing:unmarshal(Route)),
?cash_flow_changed(hg_cashflow:unmarshal(Cashflow))
];
%% deprecated v1 changes
unmarshal(change, [1, ?legacy_payment_started(Payment, RiskScore, Route, Cashflow)]) ->
?payment_started(
unmarshal(payment, Payment),
unmarshal(risk_score, RiskScore),
hg_routing:unmarshal([1, Route]),
hg_cashflow:unmarshal([1, Cashflow])
);
[
?payment_started(unmarshal(payment, Payment)),
?risk_score_changed(unmarshal(risk_score, RiskScore)),
?route_changed(hg_routing:unmarshal([1, Route])),
?cash_flow_changed(hg_cashflow:unmarshal([1, Cashflow]))
];
unmarshal(change, [1, ?legacy_payment_status_changed(Status)]) ->
?payment_status_changed(unmarshal(status, Status));
[?payment_status_changed(unmarshal(status, Status))];
unmarshal(change, [1, ?legacy_session_ev(Target, Payload)]) ->
?session_ev(unmarshal(status, Target), unmarshal(session_change, [1, Payload]));
[?session_ev(unmarshal(status, Target), unmarshal(session_change, [1, Payload]))];
unmarshal(change, [1, ?legacy_adjustment_ev(AdjustmentID, Payload)]) ->
?adjustment_ev(unmarshal(str, AdjustmentID), unmarshal(adjustment_change, [1, Payload]));
[?adjustment_ev(unmarshal(str, AdjustmentID), unmarshal(adjustment_change, [1, Payload]))];
%% Payment
@ -2265,10 +2479,14 @@ unmarshal(payment, #{
<<"flow">> := Flow
} = Payment) ->
Context = maps:get(<<"context">>, Payment, undefined),
OwnerID = maps:get(<<"owner_id">>, Payment, undefined),
ShopID = maps:get(<<"shop_id">>, Payment, undefined),
#domain_InvoicePayment{
id = unmarshal(str, ID),
created_at = unmarshal(str, CreatedAt),
domain_revision = unmarshal(int, Revision),
shop_id = unmarshal(str, ShopID),
owner_id = unmarshal(str, OwnerID),
cost = hg_cash:unmarshal(Cash),
payer = unmarshal(payer, MarshalledPayer),
status = ?pending(),

View File

@ -740,12 +740,15 @@ payment_risk_score_check(C) ->
PaymentParams = make_payment_params(),
?payment_state(?payment(PaymentID1)) = hg_client_invoicing:start_payment(InvoiceID1, PaymentParams, Client),
[
?payment_ev(PaymentID1, ?payment_started(
?payment_w_status(?pending()),
low, % low risk score...
?route(?prv(1), ?trm(1)), % ...covered with high risk coverage terminal
_
)),
?payment_ev(PaymentID1, ?payment_started(?payment_w_status(?pending())))
] = next_event(InvoiceID1, Client),
[
?payment_ev(PaymentID1, ?risk_score_changed(low)), % low risk score...
% ...covered with high risk coverage terminal
?payment_ev(PaymentID1, ?route_changed(?route(?prv(1), ?trm(1)))),
?payment_ev(PaymentID1, ?cash_flow_changed(_))
] = next_event(InvoiceID1, Client),
[
?payment_ev(PaymentID1, ?session_ev(?processed(), ?session_started()))
] = next_event(InvoiceID1, Client),
PaymentID1 = await_payment_process_finish(InvoiceID1, PaymentID1, Client),
@ -754,21 +757,35 @@ payment_risk_score_check(C) ->
InvoiceID2 = start_invoice(<<"rubberbucks">>, make_due_date(10), 31337000, C),
?payment_state(?payment(PaymentID2)) = hg_client_invoicing:start_payment(InvoiceID2, PaymentParams, Client),
[
?payment_ev(PaymentID2, ?payment_started(
?payment_w_status(?pending()),
high, % high risk score...
?route(?prv(1), ?trm(1)), % ...covered with the same terminal
_
)),
?payment_ev(PaymentID1, ?session_ev(?processed(), ?session_started()))
?payment_ev(PaymentID2, ?payment_started(?payment_w_status(?pending())))
] = next_event(InvoiceID2, Client),
[
?payment_ev(PaymentID2, ?risk_score_changed(high)), % high risk score...
% ...covered with the same terminal
?payment_ev(PaymentID2, ?route_changed(?route(?prv(1), ?trm(1)))),
?payment_ev(PaymentID2, ?cash_flow_changed(_))
] = next_event(InvoiceID2, Client),
[
?payment_ev(PaymentID2, ?session_ev(?processed(), ?session_started()))
] = next_event(InvoiceID2, Client),
PaymentID2 = await_payment_process_finish(InvoiceID2, PaymentID2, Client),
PaymentID2 = await_payment_capture(InvoiceID2, PaymentID2, Client),
% Invoice w/ 100000000 =< cost
InvoiceID3 = start_invoice(<<"rubbersocks">>, make_due_date(10), 100000000, C),
Exception = hg_client_invoicing:start_payment(InvoiceID3, PaymentParams, Client),
% fatal risk score is not going to be covered
{exception, #'InvalidRequest'{errors = [<<"Fatal error">>]}} = Exception.
?payment_state(?payment(PaymentID3)) = hg_client_invoicing:start_payment(InvoiceID3, PaymentParams, Client),
[
?payment_ev(PaymentID3, ?payment_started(?payment_w_status(?pending())))
] = next_event(InvoiceID3, Client),
[
% fatal risk score is not going to be covered
?payment_ev(PaymentID3, ?risk_score_changed(fatal)),
?payment_ev(PaymentID3, ?payment_status_changed(?failed({failure, Failure})))
] = next_event(InvoiceID3, Client),
ok = payproc_errors:match(
'PaymentFailure',
Failure,
fun({no_route_found, _}) -> ok end
).
-spec invalid_payment_adjustment(config()) -> test_return().
@ -796,7 +813,14 @@ payment_adjustment_success(C) ->
PaymentParams = make_payment_params(),
?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client),
[
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()), _, _, CF1)),
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending())))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?risk_score_changed(low)),
?payment_ev(PaymentID, ?route_changed(_)),
?payment_ev(PaymentID, ?cash_flow_changed(CF1))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
] = next_event(InvoiceID, Client),
PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client),
@ -875,6 +899,7 @@ payment_temporary_unavailability_too_many_retries(C) ->
InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C),
PaymentParams = make_temporary_unavailability_payment_params([fail, fail, fail, fail]),
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()),
{failed, PaymentID, {failure, Failure}} =
await_payment_process_failure(InvoiceID, PaymentID, Client, 3),
ok = payproc_errors:match(
@ -1041,7 +1066,14 @@ external_account_posting(C) ->
?payment(PaymentID)
) = hg_client_invoicing:start_payment(InvoiceID, make_payment_params(), InvoicingClient),
[
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()), low, _, CF)),
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending())))
] = next_event(InvoiceID, InvoicingClient),
[
?payment_ev(PaymentID, ?risk_score_changed(low)),
?payment_ev(PaymentID, ?route_changed(_)),
?payment_ev(PaymentID, ?cash_flow_changed(CF))
] = next_event(InvoiceID, InvoicingClient),
[
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
] = next_event(InvoiceID, InvoicingClient),
PaymentID = await_payment_process_finish(InvoiceID, PaymentID, InvoicingClient),
@ -1347,7 +1379,14 @@ rounding_cashflow_volume(C) ->
PaymentParams = make_payment_params(),
?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client),
[
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()), _, _, CF)),
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending())))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?risk_score_changed(_)),
?payment_ev(PaymentID, ?route_changed(_)),
?payment_ev(PaymentID, ?cash_flow_changed(CF))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
] = next_event(InvoiceID, Client),
?cash(0, <<"RUB">>) = get_cashflow_volume({provider, settlement}, {merchant, settlement}, CF),
@ -1476,6 +1515,7 @@ adhoc_repair_working_failed(C) ->
InvoiceID = start_invoice(<<"rubbercrack">>, make_due_date(10), 42000, C),
PaymentParams = make_payment_params(),
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()),
{exception, #'InvalidRequest'{}} = repair_invoice(InvoiceID, [], Client),
PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client),
PaymentID = await_payment_capture(InvoiceID, PaymentID, Client).
@ -1489,6 +1529,7 @@ adhoc_repair_failed_succeeded(C) ->
PaymentParams = make_payment_params(PaymentTool, Session),
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
[
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())),
?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
@ -1555,7 +1596,8 @@ payment_with_tokenized_bank_card(C) ->
%%
next_event(InvoiceID, Client) ->
next_event(InvoiceID, 5000, Client).
%% timeout should be at least as large as hold expiration in construct_domain_fixture/0
next_event(InvoiceID, 12000, Client).
next_event(InvoiceID, Timeout, Client) ->
case hg_client_invoicing:pull_event(InvoiceID, Timeout, Client) of
@ -1806,8 +1848,12 @@ start_invoice(ShopID, Product, Due, Amount, C) ->
start_payment(InvoiceID, PaymentParams, Client) ->
?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client),
[
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))),
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending())))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?risk_score_changed(_)),
?payment_ev(PaymentID, ?route_changed(_)),
?payment_ev(PaymentID, ?cash_flow_changed(_))
] = next_event(InvoiceID, Client),
PaymentID.
@ -1816,10 +1862,18 @@ process_payment(InvoiceID, PaymentParams, Client) ->
process_payment(InvoiceID, PaymentParams, Client, Restarts) ->
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()),
PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client, Restarts).
await_payment_session_started(InvoiceID, PaymentID, Client, Target) ->
[
?payment_ev(PaymentID, ?session_ev(Target, ?session_started()))
] = next_event(InvoiceID, Client),
PaymentID.
await_payment_process_interaction(InvoiceID, PaymentID, Client) ->
[
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())),
?payment_ev(PaymentID, ?session_ev(?processed(), ?interaction_requested(UserInteraction)))
] = next_event(InvoiceID, Client),
UserInteraction.
@ -2032,7 +2086,7 @@ construct_domain_fixture() ->
lifetime = {decisions, [
#domain_HoldLifetimeDecision{
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
then_ = {value, #domain_HoldLifetime{seconds = 3}}
then_ = {value, #domain_HoldLifetime{seconds = 10}}
}
]}
},
@ -2436,7 +2490,7 @@ construct_domain_fixture() ->
if_ = {condition, {payment_tool, {bank_card, #domain_BankCardCondition{
definition = {payment_system_is, visa}
}}}},
then_ = {value, ?hold_lifetime(5)}
then_ = {value, ?hold_lifetime(12)}
}
]}
},

View File

@ -8,7 +8,7 @@
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},2},
{<<"dmsl">>,
{git,"git@github.com:rbkmoney/damsel.git",
{ref,"f5f48eb2c5a8a950d29e943f0d86bf4cf12f7065"}},
{ref,"bf9ca1773fc5997eb9d4d1c3031b655257ae066b"}},
0},
{<<"dmt_client">>,
{git,"git@github.com:rbkmoney/dmt_client.git",