diff --git a/apps/hellgate/include/payment_events.hrl b/apps/hellgate/include/payment_events.hrl index 2d696ea75..8adc8cb1b 100644 --- a/apps/hellgate/include/payment_events.hrl +++ b/apps/hellgate/include/payment_events.hrl @@ -33,6 +33,10 @@ {invoice_payment_cash_flow_changed, #payproc_InvoicePaymentCashFlowChanged{cash_flow = CashFlow}} ). +-define(payment_clock_update(Clock), + {invoice_payment_clock_update, #payproc_InvoicePaymentClockUpdate{clock = Clock}} +). + -define(payment_status_changed(Status), {invoice_payment_status_changed, #payproc_InvoicePaymentStatusChanged{status = Status}} ). diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index 398866f3f..e74c98757 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -148,7 +148,8 @@ capture_params :: undefined | capture_params(), failure :: undefined | failure(), timings :: undefined | hg_timings:t(), - latest_change_at :: undefined | hg_datetime:timestamp() + latest_change_at :: undefined | hg_datetime:timestamp(), + clock :: undefined | hg_accounting_new:clock() }). -record(refund_st, { @@ -1879,11 +1880,13 @@ process_cash_flow_building(Action, St) -> TurnoverLimits = get_turnover_limits(ProviderTerms), ok = hg_limiter:hold_payment_limits(TurnoverLimits, Invoice, Payment), FinalCashflow = calculate_cashflow(Route, Payment, MerchantTerms, ProviderTerms, VS1, Revision, Opts), - _Clock = hg_accounting:hold( + {ok, Clock} = hg_accounting_new:hold( construct_payment_plan_id(Invoice, Payment), - {1, FinalCashflow} + {1, FinalCashflow}, + Timestamp, + undefined ), - Events = [?cash_flow_changed(FinalCashflow)], + Events = [?cash_flow_changed(FinalCashflow), ?payment_clock_update(Clock)], case hg_limiter:check_limits(TurnoverLimits, Invoice, Payment) of {ok, _} -> {next, {Events, hg_machine_action:set_timeout(0, Action)}}; @@ -1972,7 +1975,7 @@ process_adjustment_cashflow(ID, _Action, St) -> {done, {Events, hg_machine_action:new()}}. process_accounter_update(Action, St = #st{partial_cash_flow = FinalCashflow, capture_params = CaptureParams}) -> - Opts = get_opts(St), + #{timestamp := Timestamp} = Opts = get_opts(St), #payproc_InvoicePaymentCaptureParams{ reason = Reason, cash = Cost, @@ -1981,15 +1984,24 @@ process_accounter_update(Action, St = #st{partial_cash_flow = FinalCashflow, cap Invoice = get_invoice(Opts), Payment = get_payment(St), Payment2 = Payment#domain_InvoicePayment{cost = Cost}, - _Clock = hg_accounting:plan( - construct_payment_plan_id(Invoice, Payment2), - [ - {2, hg_cashflow:revert(get_cashflow(St))}, - {3, FinalCashflow} - ] - ), - Events = start_session(?captured(Reason, Cost, Cart)), - {next, {Events, hg_machine_action:set_timeout(0, Action)}}. + case + hg_accounting_new:plan( + construct_payment_plan_id(Invoice, Payment2), + [ + {2, hg_cashflow:revert(get_cashflow(St))}, + {3, FinalCashflow} + ], + Timestamp, + St#st.clock + ) + of + {ok, NewClock} -> + Events = start_session(?captured(Reason, Cost, Cart)), + {next, {[?payment_clock_update(NewClock) | Events], hg_machine_action:set_timeout(0, Action)}}; + {error, not_ready} -> + _ = logger:warning("Accounter was not ready, retrying"), + {next, {[], hg_machine_action:set_timeout(0, Action)}} + end. %% @@ -2126,24 +2138,27 @@ process_result({payment, processing_accounter}, Action, St) -> NewAction = get_action(Target, Action, St), {done, {[?payment_status_changed(Target)], NewAction}}; process_result({payment, processing_failure}, Action, St = #st{failure = Failure}) -> - NewAction = hg_machine_action:set_timeout(0, Action), - _ = rollback_payment_limits(St), - _Clocks = rollback_payment_cashflow(St), - {done, {[?payment_status_changed(?failed(Failure))], NewAction}}; + case rollback_payment_cashflow(St) of + {ok, AccounterClock} -> + _ = rollback_payment_limits(St), + NewAction = hg_machine_action:set_timeout(0, Action), + {done, {[?payment_clock_update(AccounterClock), ?payment_status_changed(?failed(Failure))], NewAction}}; + {error, not_ready} -> + _ = logger:warning("Accounter was not ready, retrying"), + {next, {[], hg_machine_action:set_timeout(0, Action)}} + end; process_result({payment, finalizing_accounter}, Action, St) -> Target = get_target(St), - _Clocks = - case Target of - ?captured() -> - commit_payment_limits(St), - commit_payment_cashflow(St); - ?cancelled() -> - rollback_payment_limits(St), - rollback_payment_cashflow(St) - end, - check_recurrent_token(St), - NewAction = get_action(Target, Action, St), - {done, {[?payment_status_changed(Target)], NewAction}}; + case finalize_payment_accounter(Target, St) of + {ok, AccounterClock} -> + _ = finalize_payment_limiter(Target, St), + _ = check_recurrent_token(St), + NewAction = get_action(Target, Action, St), + {done, {[?payment_clock_update(AccounterClock), ?payment_status_changed(Target)], NewAction}}; + {error, not_ready} -> + _ = logger:warning("Accounter was not ready, retrying"), + {next, {[], hg_machine_action:set_timeout(0, Action)}} + end; process_result({refund_failure, ID}, Action, St) -> RefundSt = try_get_refund_state(ID, St), Failure = RefundSt#refund_st.failure, @@ -2238,6 +2253,16 @@ process_payment_session_callback(Payload, State) -> erlang:raise(error, Reason, StackTrace) end. +finalize_payment_accounter(?captured(), St) -> + commit_payment_cashflow(St); +finalize_payment_accounter(?cancelled(), St) -> + rollback_payment_cashflow(St). + +finalize_payment_limiter(?captured(), St) -> + commit_payment_limits(St); +finalize_payment_limiter(?cancelled(), St) -> + rollback_payment_limits(St). + check_recurrent_token(#st{ payment = #domain_InvoicePayment{id = ID, make_recurrent = true}, recurrent_token = undefined @@ -2545,11 +2570,13 @@ rollback_refund_limits(RefundSt, St) -> TurnoverLimits = get_turnover_limits(ProviderTerms), hg_limiter:rollback_refund_limits(TurnoverLimits, Invoice, Payment, Refund). -commit_payment_cashflow(St) -> - hg_accounting:commit(construct_payment_plan_id(St), get_cashflow_plan(St)). +commit_payment_cashflow(St = #st{clock = Clock}) -> + #{timestamp := Timestamp} = get_opts(St), + hg_accounting_new:commit(construct_payment_plan_id(St), get_cashflow_plan(St), Timestamp, Clock). -rollback_payment_cashflow(St) -> - hg_accounting:rollback(construct_payment_plan_id(St), get_cashflow_plan(St)). +rollback_payment_cashflow(St = #st{clock = Clock}) -> + #{timestamp := Timestamp} = get_opts(St), + hg_accounting_new:rollback(construct_payment_plan_id(St), get_cashflow_plan(St), Timestamp, Clock). get_cashflow_plan(St = #st{partial_cash_flow = PartialCashFlow}) when PartialCashFlow =/= undefined -> [ @@ -2902,6 +2929,22 @@ merge_change(Change = ?cash_flow_changed(Cashflow), #st{activity = Activity} = S _ -> St end; +merge_change(Change = ?payment_clock_update(Clock), #st{} = St, Opts) -> + _ = validate_transition( + [ + {payment, S} + || S <- [ + processing_session, + updating_accounter, + processing_failure, + finalizing_accounter + ] + ], + Change, + St, + Opts + ), + St#st{clock = Clock}; merge_change(Change = ?rec_token_acquired(Token), #st{} = St, Opts) -> _ = validate_transition([{payment, processing_session}, {payment, finalizing_session}], Change, St, Opts), St#st{recurrent_token = Token}; diff --git a/apps/hellgate/test/hg_invoice_adjustment_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_adjustment_tests_SUITE.erl index 927ce042a..7b798ba08 100644 --- a/apps/hellgate/test/hg_invoice_adjustment_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_adjustment_tests_SUITE.erl @@ -885,7 +885,8 @@ start_payment(InvoiceID, PaymentParams, Client) -> ?payment_ev(PaymentID, ?route_changed(_)) ] = next_event(InvoiceID, Client), [ - ?payment_ev(PaymentID, ?cash_flow_changed(_)) + ?payment_ev(PaymentID, ?cash_flow_changed(_)), + ?payment_ev(PaymentID, ?payment_clock_update(_)) ] = next_event(InvoiceID, Client), PaymentID. @@ -946,6 +947,7 @@ await_payment_partial_capture(InvoiceID, PaymentID, Reason, Cash, Client, Restar ?payment_ev(PaymentID, ?cash_flow_changed(_)) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cash), ?session_started())) ] = next_event(InvoiceID, Client), await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, Restarts, Cash). @@ -964,6 +966,7 @@ await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, Restarts, Cos ?payment_ev(PaymentID, ?session_ev(Target, ?session_finished(?session_succeeded()))) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(Target)), ?invoice_status_changed(?invoice_paid()) ] = next_event(InvoiceID, Client), diff --git a/apps/hellgate/test/hg_invoice_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_tests_SUITE.erl index dce109c4e..8ff528896 100644 --- a/apps/hellgate/test/hg_invoice_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_tests_SUITE.erl @@ -1228,7 +1228,10 @@ processing_deadline_reached_test(C) -> PaymentID = start_payment(InvoiceID, PaymentParams, Client), PaymentID = await_sessions_restarts(PaymentID, ?processed(), InvoiceID, Client, 0), [?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure}))] = next_event(InvoiceID, Client), - [?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure})))] = next_event(InvoiceID, Client), + [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), + ?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure}))) + ] = next_event(InvoiceID, Client), ok = payproc_errors:match( 'PaymentFailure', Failure, @@ -1393,13 +1396,19 @@ payment_error_in_cancel_session_does_not_cause_payment_failure(C) -> InvoiceID = start_invoice(ShopID, <<"rubberduck">>, make_due_date(1000), Amount, C), PaymentParams = make_scenario_payment_params([good, fail, good], {hold, capture}), PaymentID = process_payment(InvoiceID, PaymentParams, Client), - ?assertMatch(#{max_available_amount := 40110}, hg_ct_helper:get_balance(SettlementID)), + ?assertMatch( + {ok, #{min_available_amount := -1890, max_available_amount := 42000}}, + hg_accounting_new:get_balance(SettlementID) + ), ok = hg_client_invoicing:cancel_payment(InvoiceID, PaymentID, <<"cancel">>, Client), [ ?payment_ev(PaymentID, ?session_ev(?cancelled_with_reason(Reason), ?session_started())) ] = next_event(InvoiceID, Client), timeout = next_event(InvoiceID, Client), - ?assertMatch(#{min_available_amount := 0, max_available_amount := 40110}, hg_ct_helper:get_balance(SettlementID)), + ?assertMatch( + {ok, #{min_available_amount := -1890, max_available_amount := 42000}}, + hg_accounting_new:get_balance(SettlementID) + ), ?assertException( error, {{woody_error, _}, _}, @@ -1421,14 +1430,20 @@ payment_error_in_capture_session_does_not_cause_payment_failure(C) -> InvoiceID = start_invoice(ShopID, <<"rubberduck">>, make_due_date(1000), Amount, C), PaymentParams = make_scenario_payment_params([good, fail, good], {hold, cancel}), PaymentID = process_payment(InvoiceID, PaymentParams, Client), - ?assertMatch(#{min_available_amount := 0, max_available_amount := 40110}, hg_ct_helper:get_balance(SettlementID)), + ?assertMatch( + {ok, #{min_available_amount := -1890, max_available_amount := 42000}}, + hg_accounting_new:get_balance(SettlementID) + ), ok = hg_client_invoicing:capture_payment(InvoiceID, PaymentID, <<"capture">>, Client), [ ?payment_ev(PaymentID, ?payment_capture_started(Reason, Cost, _)), ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cost), ?session_started())) ] = next_event(InvoiceID, Client), timeout = next_event(InvoiceID, Client), - ?assertMatch(#{min_available_amount := 0, max_available_amount := 40110}, hg_ct_helper:get_balance(SettlementID)), + ?assertMatch( + {ok, #{min_available_amount := -1890, max_available_amount := 42000}}, + hg_accounting_new:get_balance(SettlementID) + ), ?assertException( error, {{woody_error, _}, _}, @@ -1454,6 +1469,7 @@ repair_failed_cancel(InvoiceID, PaymentID, Reason, Client) -> ?payment_ev(PaymentID, ?session_ev(?cancelled_with_reason(Reason), ?session_finished(?session_succeeded()))) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(?cancelled_with_reason(Reason))) ] = next_event(InvoiceID, Client), PaymentID. @@ -1546,6 +1562,7 @@ payment_suspend_timeout_failure(C) -> ?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure}))) ] = next_event(InvoiceID, Client). @@ -3542,6 +3559,7 @@ start_chargeback_partial_capture(C, Cost, Partial, CBParams) -> ?payment_ev(PaymentID, ?cash_flow_changed(_)) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cash), ?session_started())) ] = next_event(InvoiceID, Client), PaymentID = await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, 0, Cash), @@ -4175,6 +4193,7 @@ payment_hold_partial_capturing(C) -> ?payment_ev(PaymentID, ?cash_flow_changed(_)) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cash), ?session_started())) ] = next_event(InvoiceID, Client), PaymentID = await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, 0, Cash). @@ -4194,6 +4213,7 @@ payment_hold_partial_capturing_with_cart(C) -> ?payment_ev(PaymentID, ?cash_flow_changed(_)) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cash, Cart), ?session_started())) ] = next_event(InvoiceID, Client), PaymentID = await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, 0, Cash, Cart). @@ -4213,6 +4233,7 @@ payment_hold_partial_capturing_with_cart_missing_cash(C) -> ?payment_ev(PaymentID, ?cash_flow_changed(_)) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cash, Cart), ?session_started())) ] = next_event(InvoiceID, Client), PaymentID = await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, 0, Cash, Cart). @@ -4503,6 +4524,7 @@ payment_with_offsite_preauth_failed(C) -> ?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})) ] = next_event(InvoiceID, 8000, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure}))) ] = next_event(InvoiceID, 8000, Client), ok = payproc_errors:match('PaymentFailure', Failure, fun({authorization_failed, _}) -> ok end), @@ -4582,6 +4604,7 @@ repair_fail_session_succeeded(C) -> ?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure}))) ] = next_event(InvoiceID, Client). @@ -4623,7 +4646,6 @@ repair_complex_succeeded_first(C) -> timeout = next_event(InvoiceID, 2000, Client), ok = repair_invoice_with_scenario(InvoiceID, complex, Client), - _ = await_payment_cash_flow(low, ?route(?prv(2), ?trm(7)), InvoiceID, PaymentID, Client), [ ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())) @@ -4653,6 +4675,7 @@ repair_complex_succeeded_second(C) -> ?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure}))) ] = next_event(InvoiceID, Client). @@ -5073,7 +5096,10 @@ start_payment(InvoiceID, PaymentParams, Client) -> ?payment_ev(PaymentID, ?risk_score_changed(_)), ?payment_ev(PaymentID, ?route_changed(_)) ] = next_event(InvoiceID, Client), - [?payment_ev(PaymentID, ?cash_flow_changed(_))] = next_event(InvoiceID, Client), + [ + ?payment_ev(PaymentID, ?cash_flow_changed(_)), + ?payment_ev(PaymentID, ?payment_clock_update(_)) + ] = next_event(InvoiceID, Client), PaymentID. process_payment(InvoiceID, PaymentParams, Client) -> @@ -5096,7 +5122,8 @@ await_payment_cash_flow(InvoiceID, PaymentID, Client) -> ?payment_ev(PaymentID, ?route_changed(_)) ] = next_event(InvoiceID, Client), [ - ?payment_ev(PaymentID, ?cash_flow_changed(CashFlow)) + ?payment_ev(PaymentID, ?cash_flow_changed(CashFlow)), + ?payment_ev(PaymentID, ?payment_clock_update(_)) ] = next_event(InvoiceID, Client), CashFlow. @@ -5106,7 +5133,8 @@ await_payment_cash_flow(RS, Route, InvoiceID, PaymentID, Client) -> ?payment_ev(PaymentID, ?route_changed(Route)) ] = next_event(InvoiceID, Client), [ - ?payment_ev(PaymentID, ?cash_flow_changed(CashFlow)) + ?payment_ev(PaymentID, ?cash_flow_changed(CashFlow)), + ?payment_ev(PaymentID, ?payment_clock_update(_)) ] = next_event(InvoiceID, Client), CashFlow. @@ -5117,6 +5145,7 @@ await_payment_rollback(InvoiceID, PaymentID, Client) -> ] = next_event(InvoiceID, Client), [ ?payment_ev(PaymentID, ?cash_flow_changed(_)), + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})) ] = next_event(InvoiceID, Client), Failure. @@ -5175,6 +5204,7 @@ await_payment_partial_capture(InvoiceID, PaymentID, Reason, Cash, Client, Restar ?payment_ev(PaymentID, ?cash_flow_changed(_)) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cash), ?session_started())) ] = next_event(InvoiceID, Client), await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, Restarts, Cash). @@ -5193,6 +5223,7 @@ await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client, Restarts, Cos ?payment_ev(PaymentID, ?session_ev(Target, ?session_finished(?session_succeeded()))) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(Target)), ?invoice_status_changed(?invoice_paid()) ] = next_event(InvoiceID, Client), @@ -5206,6 +5237,7 @@ await_payment_cancel(InvoiceID, PaymentID, Reason, Client) -> ?payment_ev(PaymentID, ?session_ev(?cancelled_with_reason(Reason), ?session_finished(?session_succeeded()))) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(?cancelled_with_reason(Reason))) ] = next_event(InvoiceID, Client), PaymentID. @@ -5230,6 +5262,7 @@ await_payment_process_failure(InvoiceID, PaymentID, Client, Restarts, Target) -> ?payment_ev(PaymentID, ?payment_rollback_started(Failure)) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?payment_clock_update(_)), ?payment_ev(PaymentID, ?payment_status_changed(?failed(Failure))) ] = next_event(InvoiceID, Client), {failed, PaymentID, Failure}. diff --git a/docker-compose.sh b/docker-compose.sh index 16187d518..76f986d50 100755 --- a/docker-compose.sh +++ b/docker-compose.sh @@ -77,7 +77,7 @@ services: - shumaich healthcheck: # FIXME: dirty trick, hangs in "health: staring" otherwise - # used to be fine + # used to be fine test: "exit 0" # test: "curl http://localhost:8022/" # interval: 5s