From aa140299fdb8784876bfc41c376f2332ce4301e0 Mon Sep 17 00:00:00 2001 From: Roman Pushkov Date: Mon, 30 Nov 2020 11:15:44 +0300 Subject: [PATCH 01/12] add refund clock update event --- apps/hellgate/include/payment_events.hrl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/hellgate/include/payment_events.hrl b/apps/hellgate/include/payment_events.hrl index 8adc8cb1b..38509d8e9 100644 --- a/apps/hellgate/include/payment_events.hrl +++ b/apps/hellgate/include/payment_events.hrl @@ -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}} ). From b0482fcf587cc2789aa43c3f5a67e1cc1c183589 Mon Sep 17 00:00:00 2001 From: Roman Pushkov Date: Mon, 30 Nov 2020 11:16:00 +0300 Subject: [PATCH 02/12] update refund tests --- apps/hellgate/test/hg_invoice_tests_SUITE.erl | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/apps/hellgate/test/hg_invoice_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_tests_SUITE.erl index 4b77ef282..4f9bdd144 100644 --- a/apps/hellgate/test/hg_invoice_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_tests_SUITE.erl @@ -184,22 +184,22 @@ cfg(Key, C) -> -spec all() -> [test_case_name() | {group, group_name()}]. all() -> [ - invalid_party_status, - invalid_shop_status, + % invalid_party_status, + % invalid_shop_status, - % With constant domain config - {group, all_non_destructive_tests}, + % % With constant domain config + % {group, all_non_destructive_tests}, - payments_w_bank_card_issuer_conditions, - payments_w_bank_conditions, + % payments_w_bank_card_issuer_conditions, + % payments_w_bank_conditions, - % With variable domain config - {group, adjustments}, - {group, holds_management_with_custom_config}, + % % With variable domain config + % {group, adjustments}, + % {group, holds_management_with_custom_config}, {group, refunds}, - {group, chargebacks}, - rounding_cashflow_volume, - terms_retrieval, + % {group, chargebacks}, + % rounding_cashflow_volume, + % terms_retrieval, consistent_account_balances, consistent_history @@ -3563,6 +3563,7 @@ payment_refund_idempotency(C) -> ?payment_ev(PaymentID, ?refund_ev(ID, ?session_ev(?refunded(), ?session_finished(?session_succeeded())))) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?refund_ev(ID, ?refund_clock_update(_))), ?payment_ev(PaymentID, ?refund_ev(ID, ?refund_status_changed(?refund_succeeded()))), ?payment_ev(PaymentID, ?payment_status_changed(?refunded())) ] = next_event(InvoiceID, Client), @@ -3599,6 +3600,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 @@ -3618,6 +3620,7 @@ payment_refund_success(C) -> ?payment_ev(PaymentID, ?refund_ev(ID, ?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(ID, ?refund_status_changed(?refund_succeeded()))), ?payment_ev(PaymentID, ?payment_status_changed(?refunded())) ] = next_event(InvoiceID, Client), @@ -3655,6 +3658,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 @@ -3674,6 +3678,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)} = @@ -3710,6 +3715,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 @@ -3729,6 +3735,7 @@ deadline_doesnt_affect_payment_refund(C) -> ?payment_ev(PaymentID, ?refund_ev(ID, ?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(ID, ?refund_status_changed(?refund_succeeded()))), ?payment_ev(PaymentID, ?payment_status_changed(?refunded())) ] = next_event(InvoiceID, Client), @@ -3765,6 +3772,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 @@ -3785,11 +3793,13 @@ 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())))) ] = next_event(InvoiceID, Client), [ + ?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_clock_update(_))), ?payment_ev(PaymentID, ?refund_ev(RefundID, ?refund_status_changed(?refund_succeeded()))), ?payment_ev(PaymentID, ?payment_status_changed(?refunded())) ] = next_event(InvoiceID, Client), @@ -3866,6 +3876,7 @@ payment_partial_refunds_success(C) -> ?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), @@ -5409,17 +5420,20 @@ await_partial_manual_refund_succeeded(Refund, TrxInfo, InvoiceID, PaymentID, Ref ?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. @@ -5434,6 +5448,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. From 15b9ebfd82836b52c6db48608db55933ccb5dcd7 Mon Sep 17 00:00:00 2001 From: Roman Pushkov Date: Mon, 30 Nov 2020 11:16:24 +0300 Subject: [PATCH 03/12] add refund clock handling --- apps/hellgate/src/hg_invoice_payment.erl | 89 +++++++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index da497ed77..df0804345 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -157,7 +157,8 @@ cash_flow :: undefined | cash_flow(), sessions = [] :: [session()], transaction_info :: undefined | trx_info(), - failure :: undefined | failure() + failure :: undefined | failure(), + clock :: hg_accounting_new:clock() }). -type chargeback_state() :: hg_invoice_payment_chargeback:state(). @@ -970,7 +971,7 @@ get_available_amount(AccountID, Clock) -> #{ min_available_amount := AvailableAmount } = - hg_accounting:get_balance(AccountID, Clock), + hg_accounting_new:get_balance(AccountID, Clock), AvailableAmount. construct_payment_plan_id(St) -> @@ -1392,14 +1393,42 @@ 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), + hg_accounting_new:hold( + construct_refund_plan_id(RefundSt, St), + get_refund_cashflow_plan(RefundSt), + Timestamp, + Clock + ). commit_refund_cashflow(RefundSt, St) -> - hg_accounting:commit(construct_refund_plan_id(RefundSt, St), [get_refund_cashflow_plan(RefundSt)]). + Clock = + case RefundSt#refund_st.clock of + undefined -> St#st.clock; + RefundClock -> RefundClock + end, + #{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)]). + Clock = + case RefundSt#refund_st.clock of + undefined -> St#st.clock; + RefundClock -> RefundClock + end, + #{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([ @@ -1926,6 +1955,31 @@ maybe_set_charged_back_status(_ChargebackStatus, _ChargebackBody, _St) -> %% -spec process_refund_cashflow(refund_id(), action(), st()) -> machine_result(). +% process_refund_cashflow(ID, Action, St) -> +% Opts = get_opts(St), +% Shop = get_shop(Opts), +% #{{merchant, settlement} := SettlementID} = hg_accounting:collect_merchant_account_map(Shop, #{}), +% RefundSt = try_get_refund_state(ID, St), +% 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 -> +% Events0 = [?session_ev(?refunded(), ?session_started())], +% Events1 = get_manual_refund_events(RefundSt), +% {next, { +% [?refund_ev(ID, C) || C <- Events0 ++ Events1], +% hg_machine_action:set_timeout(0, Action) +% }}; +% Available when Available < 0 -> +% Failure = +% {failure, +% payproc_errors:construct( +% 'RefundFailure', +% {terms_violated, {insufficient_merchant_funds, #payprocerr_GeneralFailure{}}} +% )}, +% process_failure(get_activity(St), [], Action, Failure, St, RefundSt) +% end. process_refund_cashflow(ID, Action, St) -> Opts = get_opts(St), Shop = get_shop(Opts), @@ -1936,7 +1990,7 @@ process_refund_cashflow(ID, Action, St) -> case get_available_amount(SettlementID, Clock) of % TODO we must pull this rule out of refund terms Available when Available >= 0 -> - Events0 = [?session_ev(?refunded(), ?session_started())], + Events0 = [?refund_clock_update(Clock), ?session_ev(?refunded(), ?session_started())], Events1 = get_manual_refund_events(RefundSt), {next, { [?refund_ev(ID, C) || C <- Events0 ++ Events1], @@ -2147,15 +2201,17 @@ 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, - _Clocks = rollback_refund_cashflow(RefundSt, St), + Clock = rollback_refund_cashflow(RefundSt, St), Events = [ + ?refund_ev(ID, ?refund_clock_update(Clock)), ?refund_ev(ID, ?refund_status_changed(?refund_failed(Failure))) ], {done, {Events, Action}}; process_result({refund_accounter, ID}, Action, St) -> RefundSt = try_get_refund_state(ID, St), - _Clocks = commit_refund_cashflow(RefundSt, St), + Clock = commit_refund_cashflow(RefundSt, St), Events2 = [ + ?refund_ev(ID, ?refund_clock_update(Clock)), ?refund_ev(ID, ?refund_status_changed(?refund_succeeded())) ], Events3 = @@ -2853,7 +2909,6 @@ merge_change(Change = ?cash_flow_changed(Cashflow), #st{activity = Activity} = S St end; merge_change(Change = ?payment_clock_update(Clock), #st{activity = Activity} = St, Opts) -> - % erlang:display(Activity), _ = validate_transition( [ {payment, S} @@ -2995,6 +3050,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}}; @@ -3121,6 +3188,8 @@ 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(Clock), RefundSt) -> + RefundSt#refund_st{clock = Clock}; 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) -> From d45a7ca4239fa42fa1d47b123afba2d6911ea675 Mon Sep 17 00:00:00 2001 From: Roman Pushkov Date: Mon, 30 Nov 2020 11:28:32 +0300 Subject: [PATCH 04/12] fix format --- apps/hellgate/test/hg_invoice_tests_SUITE.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/hellgate/test/hg_invoice_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_tests_SUITE.erl index 4f9bdd144..adbde1e01 100644 --- a/apps/hellgate/test/hg_invoice_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_tests_SUITE.erl @@ -4843,10 +4843,10 @@ consistent_account_balances(C) -> Party = hg_client_party:get(PartyClient), Shops = maps:values(Party#domain_Party.shops), _ = [ - { + { consistent_account_balance(AccountID, Shop), consistent_account_balance_new(AccountID, Shop) - } + } || #domain_Shop{account = ShopAccount} = Shop <- Shops, #domain_ShopAccount{settlement = AccountID1, guarantee = AccountID2} <- [ShopAccount], AccountID <- [AccountID1, AccountID2] From fc27a98a17ada806d9c91a497fa9bbc997564a8d Mon Sep 17 00:00:00 2001 From: Roman Pushkov Date: Mon, 30 Nov 2020 12:46:04 +0300 Subject: [PATCH 05/12] type fix --- apps/hellgate/src/hg_invoice_payment.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index bb6f814ef..8c1fd02b4 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -158,7 +158,7 @@ sessions = [] :: [session()], transaction_info :: undefined | trx_info(), failure :: undefined | failure(), - clock :: hg_accounting_new:clock() + clock :: undefined | hg_accounting_new:clock() }). -type chargeback_state() :: hg_invoice_payment_chargeback:state(). From 210ad9e83372dcb7c3bc35e0761e1deb1af0aaf2 Mon Sep 17 00:00:00 2001 From: Kehitt Date: Thu, 8 Jul 2021 15:23:07 +0300 Subject: [PATCH 06/12] run formatter --- apps/hellgate/src/hg_invoice_payment.erl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index dc0688d24..04f254422 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -1994,7 +1994,11 @@ process_refund_cashflow(ID, Action, St) -> case get_available_amount(SettlementID, Clock) of % TODO we must pull this rule out of refund terms Available when Available >= 0 -> - Events = [?refund_clock_update(Clock), ?session_ev(?refunded(), ?session_started()) | get_manual_refund_events(RefundSt)], + 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) @@ -2225,7 +2229,13 @@ process_result({refund_accounter, ID}, Action, St) -> ?cash(Amount, _) when Amount > 0 -> [] end, - {done, {[?refund_ev(ID, ?refund_clock_update(Clock)), ?refund_ev(ID, ?refund_status_changed(?refund_succeeded())) | Events], Action}}. + {done, + {[ + ?refund_ev(ID, ?refund_clock_update(Clock)), + ?refund_ev(ID, ?refund_status_changed(?refund_succeeded())) + | Events + ], + Action}}. process_failure(Activity, Events, Action, Failure, St) -> process_failure(Activity, Events, Action, Failure, St, undefined). From 746a15efbe14b4ba328f6ce4c571ff38c57f08bd Mon Sep 17 00:00:00 2001 From: Kehitt Date: Mon, 12 Jul 2021 15:18:21 +0300 Subject: [PATCH 07/12] uncomment test --- apps/hellgate/src/hg_invoice_payment.erl | 25 ------------------- apps/hellgate/test/hg_invoice_tests_SUITE.erl | 24 +++++++++--------- 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index 4f5d688c1..a72a0a8a9 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -1957,31 +1957,6 @@ maybe_set_charged_back_status(_ChargebackStatus, _ChargebackBody, _St) -> %% -spec process_refund_cashflow(refund_id(), action(), st()) -> machine_result(). -% process_refund_cashflow(ID, Action, St) -> -% Opts = get_opts(St), -% Shop = get_shop(Opts), -% #{{merchant, settlement} := SettlementID} = hg_accounting:collect_merchant_account_map(Shop, #{}), -% RefundSt = try_get_refund_state(ID, St), -% 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 -> -% Events0 = [?session_ev(?refunded(), ?session_started())], -% Events1 = get_manual_refund_events(RefundSt), -% {next, { -% [?refund_ev(ID, C) || C <- Events0 ++ Events1], -% hg_machine_action:set_timeout(0, Action) -% }}; -% Available when Available < 0 -> -% Failure = -% {failure, -% payproc_errors:construct( -% 'RefundFailure', -% {terms_violated, {insufficient_merchant_funds, #payprocerr_GeneralFailure{}}} -% )}, -% process_failure(get_activity(St), [], Action, Failure, St, RefundSt) -% end. process_refund_cashflow(ID, Action, St) -> Opts = get_opts(St), Shop = get_shop(Opts), diff --git a/apps/hellgate/test/hg_invoice_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_tests_SUITE.erl index ccca53937..372228c93 100644 --- a/apps/hellgate/test/hg_invoice_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_tests_SUITE.erl @@ -202,22 +202,22 @@ cfg(Key, C) -> -spec all() -> [test_case_name() | {group, group_name()}]. all() -> [ - % invalid_party_status, - % invalid_shop_status, + invalid_party_status, + invalid_shop_status, - % % With constant domain config - % {group, all_non_destructive_tests}, + % With constant domain config + {group, all_non_destructive_tests}, - % payments_w_bank_card_issuer_conditions, - % payments_w_bank_conditions, + payments_w_bank_card_issuer_conditions, + payments_w_bank_conditions, - % % With variable domain config - % {group, adjustments}, - % {group, holds_management_with_custom_config}, + % With variable domain config + {group, adjustments}, + {group, holds_management_with_custom_config}, {group, refunds}, - % {group, chargebacks}, - % rounding_cashflow_volume, - % terms_retrieval, + {group, chargebacks}, + rounding_cashflow_volume, + terms_retrieval, consistent_account_balances, consistent_history From 0b25808144824a7464b838d2afd6cc7922136356 Mon Sep 17 00:00:00 2001 From: Kehitt Date: Thu, 22 Jul 2021 17:11:41 +0300 Subject: [PATCH 08/12] update payment clock when refund completes --- apps/hellgate/src/hg_invoice_payment.erl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index 735e07e07..9a1700a64 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -3120,7 +3120,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(set_refund_state(ID, RefundSt, St1), Event), case get_refund_status(get_refund(RefundSt)) of {S, _} when S == succeeded; S == failed -> St2#st{activity = idle}; @@ -3233,6 +3233,14 @@ merge_refund_change(?session_ev(?refunded(), ?session_started()), St) -> merge_refund_change(?session_ev(?refunded(), Change), St) -> update_refund_session(merge_session_change(Change, get_refund_session(St), #{}), St). +set_refund_clock(St = #st{activity = {Activity, _}}, ?refund_clock_update(Clock)) when + 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) -> From 363efed187f6bcd358a55cbc29b9e0c3d59d1407 Mon Sep 17 00:00:00 2001 From: Kehitt Date: Thu, 5 Aug 2021 17:21:21 +0300 Subject: [PATCH 09/12] merge clocks --- apps/hellgate/src/hg_invoice_payment.erl | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index da1880318..9e1f096e9 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -157,8 +157,7 @@ cash_flow :: undefined | cash_flow(), sessions = [] :: [session()], transaction_info :: undefined | trx_info(), - failure :: undefined | failure(), - clock :: undefined | hg_accounting_new:clock() + failure :: undefined | failure() }). -type chargeback_state() :: hg_invoice_payment_chargeback:state(). @@ -1388,12 +1387,7 @@ prepare_refund_cashflow(RefundSt, St = #st{clock = Clock}) -> Clock ). -commit_refund_cashflow(RefundSt, St) -> - Clock = - case RefundSt#refund_st.clock of - undefined -> St#st.clock; - RefundClock -> RefundClock - end, +commit_refund_cashflow(RefundSt, St = #st{clock = Clock}) -> #{timestamp := Timestamp} = get_opts(St), hg_accounting_new:commit( construct_refund_plan_id(RefundSt, St), @@ -1402,12 +1396,7 @@ commit_refund_cashflow(RefundSt, St) -> Clock ). -rollback_refund_cashflow(RefundSt, St) -> - Clock = - case RefundSt#refund_st.clock of - undefined -> St#st.clock; - RefundClock -> RefundClock - end, +rollback_refund_cashflow(RefundSt, St = #st{clock = Clock}) -> #{timestamp := Timestamp} = get_opts(St), hg_accounting_new:rollback( construct_refund_plan_id(RefundSt, St), @@ -3213,14 +3202,16 @@ 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(Clock), RefundSt) -> - RefundSt#refund_st{clock = Clock}; +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(St = #st{activity = {Activity, _}}, ?refund_clock_update(Clock)) when + Activity =:= refund_new; + Activity =:= refund_session; Activity =:= refund_accounter; Activity =:= refund_failure -> From 5ac26f2d74204763f11beebdd75358351d3f6407 Mon Sep 17 00:00:00 2001 From: Kehitt Date: Fri, 6 Aug 2021 16:48:00 +0300 Subject: [PATCH 10/12] formatting, argument order change --- apps/hellgate/src/hg_invoice_payment.erl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index 9e1f096e9..0bcbac32b 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -961,8 +961,7 @@ collect_cash_flow_context( get_available_amount(AccountID, Clock) -> #{ min_available_amount := AvailableAmount - } = - hg_accounting_new:get_balance(AccountID, Clock), + } = hg_accounting_new:get_balance(AccountID, Clock), AvailableAmount. construct_payment_plan_id(St) -> @@ -3096,7 +3095,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_clock(set_refund_state(ID, RefundSt, St1), Event), + 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}; @@ -3209,14 +3208,14 @@ merge_refund_change(?session_ev(?refunded(), ?session_started()), St) -> merge_refund_change(?session_ev(?refunded(), Change), St) -> update_refund_session(merge_session_change(Change, get_refund_session(St), #{}), St). -set_refund_clock(St = #st{activity = {Activity, _}}, ?refund_clock_update(Clock)) when +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{}, _) -> +set_refund_clock(_, St = #st{}) -> St. merge_adjustment_change(?adjustment_created(Adjustment), undefined) -> From 6350f873c7acf68b8ff2db730f2f95bf611d6f7e Mon Sep 17 00:00:00 2001 From: Kehitt Date: Thu, 23 Sep 2021 14:24:46 +0300 Subject: [PATCH 11/12] refunds with mg retries 1 --- apps/hellgate/src/hg_invoice_payment.erl | 70 ++++++++++++++---------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index dea889131..a3c6692d3 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -959,10 +959,14 @@ collect_cash_flow_context( }. get_available_amount(AccountID, Clock) -> - #{ - min_available_amount := AvailableAmount - } = hg_accounting_new:get_balance(AccountID, Clock), - AvailableAmount. + case hg_accounting_new:get_balance(AccountID, Clock) of + {ok, #{ + min_available_amount := AvailableAmount + }} -> + {ok, AvailableAmount}; + {error, _} = Error -> + Error + end. construct_payment_plan_id(St) -> construct_payment_plan_id(get_invoice(get_opts(St)), get_payment(St)). @@ -2179,33 +2183,41 @@ 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), - Clock = rollback_refund_cashflow(RefundSt, St), - Events = [ - ?refund_ev(ID, ?refund_clock_update(Clock)), - ?refund_ev(ID, ?refund_status_changed(?refund_failed(Failure))) - ], - {done, {Events, Action}}; -process_result({refund_accounter, ID}, Action, St) -> - RefundSt = try_get_refund_state(ID, St), - _ = commit_refund_limits(RefundSt, St), - Clock = 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, - {[ + 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_succeeded())) - | Events + ?refund_ev(ID, ?refund_status_changed(?refund_failed(Failure))) ], - Action}}. + {done, {Events, Action}}; + {error, not_ready} -> + woody_error:raise(system, {external, resource_unavailable, <<"Accounter was not ready">>}) + end; +process_result({refund_accounter, ID}, Action, St) -> + RefundSt = try_get_refund_state(ID, St), + 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} -> + woody_error:raise(system, {external, resource_unavailable, <<"Accounter was not ready">>}) + end. process_failure(Activity, Events, Action, Failure, St) -> process_failure(Activity, Events, Action, Failure, St, undefined). From 31e37514b7ecf59a9d82fcea4f8f9ebf368863f4 Mon Sep 17 00:00:00 2001 From: Kehitt Date: Thu, 23 Sep 2021 17:04:10 +0300 Subject: [PATCH 12/12] avoid resource_unavailable when possible --- apps/hellgate/src/hg_invoice_payment.erl | 80 ++++++++++++++---------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/apps/hellgate/src/hg_invoice_payment.erl b/apps/hellgate/src/hg_invoice_payment.erl index 7d4c08d3c..121101481 100644 --- a/apps/hellgate/src/hg_invoice_payment.erl +++ b/apps/hellgate/src/hg_invoice_payment.erl @@ -965,7 +965,7 @@ get_available_amount(AccountID, Clock) -> }} -> {ok, AvailableAmount}; {error, _} = Error -> - Error + throw(Error) end. construct_payment_plan_id(St) -> @@ -1383,12 +1383,19 @@ collect_refund_cashflow( prepare_refund_cashflow(RefundSt, St = #st{clock = Clock}) -> #{timestamp := Timestamp} = get_opts(St), - hg_accounting_new:hold( - construct_refund_plan_id(RefundSt, St), - get_refund_cashflow_plan(RefundSt), - Timestamp, - Clock - ). + 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 = #st{clock = Clock}) -> #{timestamp := Timestamp} = get_opts(St), @@ -1953,31 +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 = [ - ?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) + 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}) -> @@ -2195,7 +2207,8 @@ process_result({refund_failure, ID}, Action, St) -> ], {done, {Events, Action}}; {error, not_ready} -> - woody_error:raise(system, {external, resource_unavailable, <<"Accounter was 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), @@ -2219,7 +2232,8 @@ process_result({refund_accounter, ID}, Action, St) -> ], Action}}; {error, not_ready} -> - woody_error:raise(system, {external, resource_unavailable, <<"Accounter was 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) ->