mirror of
https://github.com/valitydev/hellgate.git
synced 2024-11-06 10:55:22 +00:00
HG-250 Multistage payment start (#229)
This commit is contained in:
parent
2e145ceae8
commit
77d2fd4827
@ -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}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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)}
|
||||
}
|
||||
]}
|
||||
},
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user