Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
aa14029
add refund clock update event
aenglisc Nov 30, 2020
b0482fc
update refund tests
aenglisc Nov 30, 2020
15b9ebf
add refund clock handling
aenglisc Nov 30, 2020
d45a7ca
fix format
aenglisc Nov 30, 2020
59c492a
Merge branch 'HG-544/ft/shumaich_payments' of github.com:rbkmoney/hel…
aenglisc Nov 30, 2020
fc27a98
type fix
aenglisc Nov 30, 2020
aa5e664
Merge branch 'HG-544/ft/shumaich_payments' of github.com:rbkmoney/hel…
aenglisc Nov 30, 2020
82fb1fe
Merge branch 'HG-544/ft/shumaich_payments' into HG-544/ft/shumaich_re…
kehitt Jul 8, 2021
210ad9e
run formatter
kehitt Jul 8, 2021
d3391f5
Merge branch 'HG-544/ft/shumaich_payments' into HG-544/ft/shumaich_re…
kehitt Jul 12, 2021
746a15e
uncomment test
kehitt Jul 12, 2021
247250b
Merge branch 'HG-544/ft/shumaich_payments' into HG-544/ft/shumaich_re…
kehitt Jul 13, 2021
7d2a525
Merge branch 'HG-544/ft/shumaich_payments' into HG-544/ft/shumaich_re…
kehitt Jul 14, 2021
0b25808
update payment clock when refund completes
kehitt Jul 22, 2021
a3f59a0
Merge branch 'HG-544/ft/shumaich_payments' into HG-544/ft/shumaich_re…
kehitt Jul 29, 2021
363efed
merge clocks
kehitt Aug 5, 2021
5ac26f2
formatting, argument order change
kehitt Aug 6, 2021
d9eaeb9
Merge remote-tracking branch 'origin/HG-544/ft/shumaich_payments' int…
kehitt Sep 23, 2021
6350f87
refunds with mg retries 1
kehitt Sep 23, 2021
087ad75
Merge remote-tracking branch 'origin/HG-544/ft/shumaich_payments' int…
kehitt Sep 23, 2021
31e3751
avoid resource_unavailable when possible
kehitt Sep 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/hellgate/include/payment_events.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@
}}
).

-define(refund_clock_update(Clock),
{invoice_payment_refund_clock_update, #payproc_InvoicePaymentClockUpdate{clock = Clock}}
).

-define(refund_rollback_started(Failure),
{invoice_payment_refund_rollback_started, #payproc_InvoicePaymentRefundRollbackStarted{reason = Failure}}
).
Expand Down
178 changes: 128 additions & 50 deletions apps/hellgate/src/hg_invoice_payment.erl
Original file line number Diff line number Diff line change
Expand Up @@ -959,11 +959,14 @@ collect_cash_flow_context(
}.

get_available_amount(AccountID, Clock) ->
#{
min_available_amount := AvailableAmount
} =
hg_accounting:get_balance(AccountID, Clock),
AvailableAmount.
case hg_accounting_new:get_balance(AccountID, Clock) of
{ok, #{
min_available_amount := AvailableAmount
}} ->
{ok, AvailableAmount};
{error, _} = Error ->
throw(Error)
end.

construct_payment_plan_id(St) ->
construct_payment_plan_id(get_invoice(get_opts(St)), get_payment(St)).
Expand Down Expand Up @@ -1378,14 +1381,39 @@ collect_refund_cashflow(
ProviderCashflow = get_selector_value(provider_refund_cash_flow, ProviderCashflowSelector),
MerchantCashflow ++ ProviderCashflow.

prepare_refund_cashflow(RefundSt, St) ->
hg_accounting:hold(construct_refund_plan_id(RefundSt, St), get_refund_cashflow_plan(RefundSt)).
prepare_refund_cashflow(RefundSt, St = #st{clock = Clock}) ->
#{timestamp := Timestamp} = get_opts(St),
case
hg_accounting_new:hold(
construct_refund_plan_id(RefundSt, St),
get_refund_cashflow_plan(RefundSt),
Timestamp,
Clock
)
of
{ok, _} = Result ->
Result;
{error, _} = Error ->
throw(Error)
end.

commit_refund_cashflow(RefundSt, St) ->
hg_accounting:commit(construct_refund_plan_id(RefundSt, St), [get_refund_cashflow_plan(RefundSt)]).
commit_refund_cashflow(RefundSt, St = #st{clock = Clock}) ->
#{timestamp := Timestamp} = get_opts(St),
hg_accounting_new:commit(
construct_refund_plan_id(RefundSt, St),
[get_refund_cashflow_plan(RefundSt)],
Timestamp,
Clock
).

rollback_refund_cashflow(RefundSt, St) ->
hg_accounting:rollback(construct_refund_plan_id(RefundSt, St), [get_refund_cashflow_plan(RefundSt)]).
rollback_refund_cashflow(RefundSt, St = #st{clock = Clock}) ->
#{timestamp := Timestamp} = get_opts(St),
hg_accounting_new:rollback(
construct_refund_plan_id(RefundSt, St),
[get_refund_cashflow_plan(RefundSt)],
Timestamp,
Clock
).

construct_refund_plan_id(RefundSt, St) ->
hg_utils:construct_complex_id([
Expand Down Expand Up @@ -1932,27 +1960,36 @@ process_refund_cashflow(ID, Action, St) ->
Opts = get_opts(St),
Shop = get_shop(Opts),
RefundSt = try_get_refund_state(ID, St),
hold_refund_limits(RefundSt, St),

#{{merchant, settlement} := SettlementID} = hg_accounting:collect_merchant_account_map(Shop, #{}),
Clock = prepare_refund_cashflow(RefundSt, St),
% NOTE we assume that posting involving merchant settlement account MUST be present in the cashflow
case get_available_amount(SettlementID, Clock) of
% TODO we must pull this rule out of refund terms
Available when Available >= 0 ->
Events = [?session_ev(?refunded(), ?session_started()) | get_manual_refund_events(RefundSt)],
{next, {
[?refund_ev(ID, C) || C <- Events],
hg_machine_action:set_timeout(0, Action)
}};
_ ->
Failure =
{failure,
payproc_errors:construct(
'RefundFailure',
{terms_violated, {insufficient_merchant_funds, #payprocerr_GeneralFailure{}}}
)},
process_failure(get_activity(St), [], Action, Failure, St, RefundSt)
try
{ok, Clock} = prepare_refund_cashflow(RefundSt, St),
% NOTE we assume that posting involving merchant settlement account MUST be present in the cashflow
case get_available_amount(SettlementID, Clock) of
% TODO we must pull this rule out of refund terms
{ok, Available} when Available >= 0 ->
_ = hold_refund_limits(RefundSt, St),
Events = [
?refund_clock_update(Clock),
?session_ev(?refunded(), ?session_started())
| get_manual_refund_events(RefundSt)
],
{next, {
[?refund_ev(ID, C) || C <- Events],
hg_machine_action:set_timeout(0, Action)
}};
_ ->
Failure =
{failure,
payproc_errors:construct(
'RefundFailure',
{terms_violated, {insufficient_merchant_funds, #payprocerr_GeneralFailure{}}}
)},
process_failure(get_activity(St), [], Action, Failure, St, RefundSt)
end
catch
throw:{error, not_ready} ->
_ = logger:warning("Accounter was not ready, retrying"),
{next, {[], hg_machine_action:set_timeout(0, Action)}}
end.

get_manual_refund_events(#refund_st{transaction_info = undefined}) ->
Expand Down Expand Up @@ -2161,26 +2198,43 @@ process_result({payment, finalizing_accounter}, Action, St) ->
process_result({refund_failure, ID}, Action, St) ->
RefundSt = try_get_refund_state(ID, St),
Failure = RefundSt#refund_st.failure,
_ = rollback_refund_limits(RefundSt, St),
_Clocks = rollback_refund_cashflow(RefundSt, St),
Events = [
?refund_ev(ID, ?refund_status_changed(?refund_failed(Failure)))
],
{done, {Events, Action}};
case rollback_refund_cashflow(RefundSt, St) of
{ok, Clock} ->
_ = rollback_refund_limits(RefundSt, St),
Events = [
?refund_ev(ID, ?refund_clock_update(Clock)),
?refund_ev(ID, ?refund_status_changed(?refund_failed(Failure)))
],
{done, {Events, Action}};
{error, not_ready} ->
_ = logger:warning("Accounter was not ready, retrying"),
{next, {[], hg_machine_action:set_timeout(0, Action)}}
end;
process_result({refund_accounter, ID}, Action, St) ->
RefundSt = try_get_refund_state(ID, St),
_ = commit_refund_limits(RefundSt, St),
_Clocks = commit_refund_cashflow(RefundSt, St),
Events =
case get_remaining_payment_amount(get_refund_cash(get_refund(RefundSt)), St) of
?cash(0, _) ->
[
?payment_status_changed(?refunded())
];
?cash(Amount, _) when Amount > 0 ->
[]
end,
{done, {[?refund_ev(ID, ?refund_status_changed(?refund_succeeded())) | Events], Action}}.
case commit_refund_cashflow(RefundSt, St) of
{ok, Clock} ->
_ = commit_refund_limits(RefundSt, St),
Events =
case get_remaining_payment_amount(get_refund_cash(get_refund(RefundSt)), St) of
?cash(0, _) ->
[
?payment_status_changed(?refunded())
];
?cash(Amount, _) when Amount > 0 ->
[]
end,
{done,
{[
?refund_ev(ID, ?refund_clock_update(Clock)),
?refund_ev(ID, ?refund_status_changed(?refund_succeeded()))
| Events
],
Action}};
{error, not_ready} ->
_ = logger:warning("Accounter was not ready, retrying"),
{next, {[], hg_machine_action:set_timeout(0, Action)}}
end.

process_failure(Activity, Events, Action, Failure, St) ->
process_failure(Activity, Events, Action, Failure, St, undefined).
Expand Down Expand Up @@ -3055,6 +3109,18 @@ merge_change(Change = ?refund_ev(ID, Event), St, Opts) ->
?refund_created(_, _, _) ->
_ = validate_transition(idle, Change, St, Opts),
St#st{activity = {refund_new, ID}};
?refund_clock_update(_Clock) ->
_ = validate_transition(
[
{refund_new, ID},
{refund_accounter, ID},
{refund_failure, ID}
],
Change,
St,
Opts
),
St;
?session_ev(?refunded(), ?session_started()) ->
_ = validate_transition([{refund_new, ID}, {refund_session, ID}], Change, St, Opts),
St#st{activity = {refund_session, ID}};
Expand All @@ -3075,7 +3141,7 @@ merge_change(Change = ?refund_ev(ID, Event), St, Opts) ->
St
end,
RefundSt = merge_refund_change(Event, try_get_refund_state(ID, St1)),
St2 = set_refund_state(ID, RefundSt, St1),
St2 = set_refund_clock(Event, set_refund_state(ID, RefundSt, St1)),
case get_refund_status(get_refund(RefundSt)) of
{S, _} when S == succeeded; S == failed ->
St2#st{activity = idle};
Expand Down Expand Up @@ -3181,11 +3247,23 @@ merge_refund_change(?refund_status_changed(Status), RefundSt) ->
set_refund(set_refund_status(Status, get_refund(RefundSt)), RefundSt);
merge_refund_change(?refund_rollback_started(Failure), RefundSt) ->
RefundSt#refund_st{failure = Failure};
merge_refund_change(?refund_clock_update(_), RefundSt) ->
RefundSt;
merge_refund_change(?session_ev(?refunded(), ?session_started()), St) ->
add_refund_session(create_session(?refunded(), undefined), St);
merge_refund_change(?session_ev(?refunded(), Change), St) ->
update_refund_session(merge_session_change(Change, get_refund_session(St), #{}), St).

set_refund_clock(?refund_clock_update(Clock), St = #st{activity = {Activity, _}}) when
Activity =:= refund_new;
Activity =:= refund_session;
Activity =:= refund_accounter;
Activity =:= refund_failure
->
St#st{clock = Clock};
set_refund_clock(_, St = #st{}) ->
St.

merge_adjustment_change(?adjustment_created(Adjustment), undefined) ->
Adjustment;
merge_adjustment_change(?adjustment_status_changed(Status), Adjustment) ->
Expand Down
12 changes: 12 additions & 0 deletions apps/hellgate/test/hg_invoice_tests_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3672,6 +3672,7 @@ payment_refund_success(C) ->
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_rollback_started(Failure)))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_status_changed(?refund_failed(Failure))))
] = next_event(InvoiceID, Client),
% top up merchant account
Expand Down Expand Up @@ -3712,6 +3713,7 @@ payment_refund_failure(C) ->
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_rollback_started(NoFunds)))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_status_changed(?refund_failed(NoFunds))))
] = next_event(InvoiceID, Client),
% top up merchant account
Expand All @@ -3727,6 +3729,7 @@ payment_refund_failure(C) ->
?payment_ev(PaymentID, ?refund_ev(ID, ?refund_rollback_started(Failure)))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(ID, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(ID, ?refund_status_changed(?refund_failed(Failure))))
] = next_event(InvoiceID, Client),
#domain_InvoicePaymentRefund{status = ?refund_failed(Failure)} =
Expand Down Expand Up @@ -3762,6 +3765,7 @@ deadline_doesnt_affect_payment_refund(C) ->
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_rollback_started(NoFunds)))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_status_changed(?refund_failed(NoFunds))))
] = next_event(InvoiceID, Client),
% top up merchant account
Expand Down Expand Up @@ -3802,6 +3806,7 @@ payment_manual_refund(C) ->
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_rollback_started(NoFunds)))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_status_changed(?refund_failed(NoFunds))))
] = next_event(InvoiceID, Client),
% top up merchant account
Expand All @@ -3817,6 +3822,7 @@ payment_manual_refund(C) ->
?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_created(_Refund, _, TrxInfo)))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?session_ev(?refunded(), ?session_started()))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?session_ev(?refunded(), ?trx_bound(TrxInfo)))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?session_ev(?refunded(), ?session_finished(?session_succeeded()))))
Expand Down Expand Up @@ -5278,23 +5284,27 @@ await_partial_manual_refund_succeeded(InvoiceID, PaymentID, RefundID, TrxInfo, C
?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_created(_Refund, _, TrxInfo)))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?session_ev(?refunded(), ?session_started()))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?session_ev(?refunded(), ?trx_bound(TrxInfo)))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?session_ev(?refunded(), ?session_finished(?session_succeeded()))))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_status_changed(?refund_succeeded())))
] = next_event(InvoiceID, Client),
PaymentID.

await_refund_session_started(InvoiceID, PaymentID, RefundID, Client) ->
[
?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(RefundID, ?session_ev(?refunded(), ?session_started())))
] = next_event(InvoiceID, Client),
PaymentID.

await_refund_succeeded(InvoiceID, PaymentID, Client) ->
[
?payment_ev(PaymentID, ?refund_ev(_, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(_, ?refund_status_changed(?refund_succeeded()))),
?payment_ev(PaymentID, ?payment_status_changed(?refunded()))
] = next_event(InvoiceID, Client),
Expand All @@ -5310,6 +5320,7 @@ await_refund_payment_process_finish(InvoiceID, PaymentID, Client, Restarts) ->
?payment_ev(PaymentID, ?refund_ev(_, ?session_ev(?refunded(), ?session_finished(?session_succeeded()))))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(_, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(_, ?refund_status_changed(?refund_succeeded())))
] = next_event(InvoiceID, Client),
PaymentID.
Expand All @@ -5321,6 +5332,7 @@ await_refund_payment_complete(InvoiceID, PaymentID, Client) ->
?payment_ev(PaymentID, ?refund_ev(_, ?session_ev(?refunded(), ?session_finished(?session_succeeded()))))
] = next_event(InvoiceID, Client),
[
?payment_ev(PaymentID, ?refund_ev(_, ?refund_clock_update(_))),
?payment_ev(PaymentID, ?refund_ev(_, ?refund_status_changed(?refund_succeeded()))),
?payment_ev(PaymentID, ?payment_status_changed(?refunded()))
] = next_event(InvoiceID, Client),
Expand Down