From db39bf920d26172757a4f35c9319f78daf56f1d0 Mon Sep 17 00:00:00 2001 From: ttt161 Date: Mon, 4 Sep 2023 10:19:30 +0300 Subject: [PATCH] TD-291: test refactoring/split suite (#82) * TD-291: test refactoring/split suite * TD-291: fix format/dialyzer checks * TD-291: update style config * TD-291: fix typo --------- Co-authored-by: anatoliy.losev --- apps/hellgate/test/hg_ct_invoice.hrl | 29 + apps/hellgate/test/hg_invoice_helper.erl | 455 ++++++++++ .../test/hg_invoice_lite_tests_SUITE.erl | 817 ++++++++++++++++++ apps/hellgate/test/hg_invoice_tests_SUITE.erl | 666 +------------- elvis.config | 6 +- 5 files changed, 1341 insertions(+), 632 deletions(-) create mode 100644 apps/hellgate/test/hg_ct_invoice.hrl create mode 100644 apps/hellgate/test/hg_invoice_helper.erl create mode 100644 apps/hellgate/test/hg_invoice_lite_tests_SUITE.erl diff --git a/apps/hellgate/test/hg_ct_invoice.hrl b/apps/hellgate/test/hg_ct_invoice.hrl new file mode 100644 index 0000000..abeb347 --- /dev/null +++ b/apps/hellgate/test/hg_ct_invoice.hrl @@ -0,0 +1,29 @@ +-include("invoice_events.hrl"). +-include("payment_events.hrl"). +-include("customer_events.hrl"). + +-define(invoice(ID), #domain_Invoice{id = ID}). +-define(payment(ID), #domain_InvoicePayment{id = ID}). +-define(payment(ID, Revision), #domain_InvoicePayment{id = ID, party_revision = Revision}). +-define(adjustment(ID), #domain_InvoicePaymentAdjustment{id = ID}). +-define(adjustment(ID, Status), #domain_InvoicePaymentAdjustment{id = ID, status = Status}). +-define(adjustment_revision(Revision), #domain_InvoicePaymentAdjustment{party_revision = Revision}). +-define(adjustment_reason(Reason), #domain_InvoicePaymentAdjustment{reason = Reason}). +-define(invoice_state(Invoice), #payproc_Invoice{invoice = Invoice}). +-define(invoice_state(Invoice, Payments), #payproc_Invoice{invoice = Invoice, payments = Payments}). +-define(payment_state(Payment), #payproc_InvoicePayment{payment = Payment}). +-define(payment_state(Payment, Refunds), #payproc_InvoicePayment{payment = Payment, refunds = Refunds}). +-define(payment_route(Route), #payproc_InvoicePayment{route = Route}). +-define(refund_state(Refund), #payproc_InvoicePaymentRefund{refund = Refund}). +-define(payment_cashflow(CashFlow), #payproc_InvoicePayment{cash_flow = CashFlow}). +-define(payment_last_trx(Trx), #payproc_InvoicePayment{last_transaction_info = Trx}). +-define(invoice_w_status(Status), #domain_Invoice{status = Status}). +-define(invoice_w_revision(Revision), #domain_Invoice{party_revision = Revision}). +-define(payment_w_status(Status), #domain_InvoicePayment{status = Status}). +-define(payment_w_status(ID, Status), #domain_InvoicePayment{id = ID, status = Status}). +-define(invoice_payment_refund(Cash, Status), #domain_InvoicePaymentRefund{cash = Cash, status = Status}). +-define(trx_info(ID), #domain_TransactionInfo{id = ID}). +-define(trx_info(ID, Extra), #domain_TransactionInfo{id = ID, extra = Extra}). +-define(refund_id(RefundID), #domain_InvoicePaymentRefund{id = RefundID}). +-define(refund_id(RefundID, ExternalID), #domain_InvoicePaymentRefund{id = RefundID, external_id = ExternalID}). +-define(contact_info(), ?contact_info(undefined)). diff --git a/apps/hellgate/test/hg_invoice_helper.erl b/apps/hellgate/test/hg_invoice_helper.erl new file mode 100644 index 0000000..b1f82a9 --- /dev/null +++ b/apps/hellgate/test/hg_invoice_helper.erl @@ -0,0 +1,455 @@ +-module(hg_invoice_helper). + +-include("hg_ct_invoice.hrl"). +-include("hg_ct_domain.hrl"). + +-define(DEFAULT_NEXT_CHANGE_TIMEOUT, 12000). + +%% +-export([ + start_kv_store/1, + stop_kv_store/1, + start_proxies/1 +]). + +-export([ + make_due_date/1, + start_invoice/4, + start_invoice/5, + start_invoice/6, + next_changes/3, + next_change/2, + next_change/3, + make_payment_params/1, + make_payment_params/2, + make_payment_params/3, + register_payment/4, + start_payment/3, + start_payment_ev/2, + start_payment_ev_no_risk_scoring/2, + await_payment_session_started/4, + await_payment_capture/3, + await_payment_capture/4, + await_payment_capture/5, + await_payment_capture_finish/4, + await_payment_capture_finish/5, + await_payment_capture_finish/6, + await_payment_cash_flow/3, + await_payment_process_finish/3, + construct_ta_context/3, + convert_transaction_account/2, + get_cashflow_volume/4, + make_wallet_payment_params/1, + execute_payment/3, + process_payment/3, + make_cash/1, + make_cash/2, + make_customer_w_rec_tool/4 +]). + +cfg(Key, C) -> + hg_ct_helper:cfg(Key, C). + +%% init helpers +-spec start_kv_store(_) -> _. +start_kv_store(SupPid) -> + ChildSpec = #{ + id => hg_kv_store, + start => {hg_kv_store, start_link, [[]]}, + restart => permanent, + shutdown => 2000, + type => worker, + modules => [hg_kv_store] + }, + {ok, _} = supervisor:start_child(SupPid, ChildSpec), + ok. + +-spec stop_kv_store(_) -> _. +stop_kv_store(SupPid) -> + _ = supervisor:terminate_child(SupPid, hg_kv_store), + ok. + +-spec start_proxies(_) -> _. +start_proxies(Proxies) -> + setup_proxies( + lists:map( + fun + Mapper({Module, ProxyID, Context}) -> + Mapper({Module, ProxyID, #{}, Context}); + Mapper({Module, ProxyID, ProxyOpts, Context}) -> + construct_proxy(ProxyID, start_service_handler(Module, Context, #{}), ProxyOpts) + end, + Proxies + ) + ). + +start_service_handler(Module, C, HandlerOpts) -> + start_service_handler(Module, Module, C, HandlerOpts). + +start_service_handler(Name, Module, C, HandlerOpts) -> + IP = "127.0.0.1", + Port = get_random_port(), + Opts = maps:merge(HandlerOpts, #{hellgate_root_url => hg_ct_helper:cfg(root_url, C)}), + ChildSpec = hg_test_proxy:get_child_spec(Name, Module, IP, Port, Opts), + {ok, _} = supervisor:start_child(hg_ct_helper:cfg(test_sup, C), ChildSpec), + hg_test_proxy:get_url(Module, IP, Port). + +setup_proxies(Proxies) -> + _ = hg_domain:upsert(Proxies), + ok. + +construct_proxy(ID, Url, Options) -> + {proxy, #domain_ProxyObject{ + ref = ?prx(ID), + data = #domain_ProxyDefinition{ + name = Url, + description = Url, + url = Url, + options = Options + } + }}. + +get_random_port() -> + rand:uniform(32768) + 32767. + +%% Test helpers +-spec make_due_date(pos_integer()) -> pos_integer(). +make_due_date(LifetimeSeconds) -> + genlib_time:unow() + LifetimeSeconds. + +-spec make_cash(_) -> _. +make_cash(Amount) -> + make_cash(Amount, <<"RUB">>). + +-spec make_cash(_, _) -> _. +make_cash(Amount, Currency) -> + hg_ct_helper:make_cash(Amount, Currency). + +make_invoice_params(PartyID, ShopID, Product, Due, Cost) -> + hg_ct_helper:make_invoice_params(PartyID, ShopID, Product, Due, Cost). + +-spec start_invoice(_, _, _, _) -> _. +start_invoice(Product, Due, Amount, C) -> + start_invoice(cfg(shop_id, C), Product, Due, Amount, C). + +-spec start_invoice(_, _, _, _, _) -> _. +start_invoice(ShopID, Product, Due, Amount, C) -> + Client = cfg(client, C), + PartyID = cfg(party_id, C), + start_invoice(PartyID, ShopID, Product, Due, Amount, Client). + +-spec start_invoice(_, _, _, _, _, _) -> _. +start_invoice(PartyID, ShopID, Product, Due, Amount, Client) -> + InvoiceParams = make_invoice_params(PartyID, ShopID, Product, Due, make_cash(Amount)), + InvoiceID = create_invoice(InvoiceParams, Client), + ?invoice_created(?invoice_w_status(?invoice_unpaid())) = next_change(InvoiceID, Client), + InvoiceID. + +create_invoice(InvoiceParams, Client) -> + ?invoice_state(?invoice(InvoiceID)) = hg_client_invoicing:create(InvoiceParams, Client), + InvoiceID. + +-spec next_change(_, _) -> _. +next_change(InvoiceID, Client) -> + %% timeout should be at least as large as hold expiration in construct_domain_fixture/0 + next_change(InvoiceID, ?DEFAULT_NEXT_CHANGE_TIMEOUT, Client). + +-spec next_change(_, _, _) -> _. +next_change(InvoiceID, Timeout, Client) -> + hg_client_invoicing:pull_change(InvoiceID, fun filter_change/1, Timeout, Client). + +filter_change(?payment_ev(_, C)) -> + filter_change(C); +filter_change(?chargeback_ev(_, C)) -> + filter_change(C); +filter_change(?refund_ev(_, C)) -> + filter_change(C); +filter_change(?session_ev(_, ?proxy_st_changed(_))) -> + false; +filter_change(?session_ev(_, ?session_suspended(_, _))) -> + false; +filter_change(?session_ev(_, ?session_activated())) -> + false; +filter_change(_) -> + true. + +-spec next_changes(_, _, _) -> _. +next_changes(InvoiceID, Amount, Client) -> + next_changes(InvoiceID, Amount, ?DEFAULT_NEXT_CHANGE_TIMEOUT, Client). + +next_changes(InvoiceID, Amount, Timeout, Client) -> + TimeoutTime = erlang:monotonic_time(millisecond) + Timeout, + next_changes_(InvoiceID, Amount, TimeoutTime, Client). + +next_changes_(InvoiceID, Amount, Timeout, Client) -> + Result = lists:foldl( + fun(_N, Acc) -> + case erlang:monotonic_time(millisecond) of + Time when Time < Timeout -> + [next_change(InvoiceID, Client) | Acc]; + _ -> + [{error, timeout} | Acc] + end + end, + [], + lists:seq(1, Amount) + ), + lists:reverse(Result). + +-spec make_payment_params(_) -> _. +make_payment_params(PmtSys) -> + make_payment_params(PmtSys, instant). + +-spec make_payment_params(_, _) -> _. +make_payment_params(PmtSys, FlowType) -> + {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(no_preauth, PmtSys), + make_payment_params(PaymentTool, Session, FlowType). + +-spec make_payment_params(_, _, _) -> _. +make_payment_params(PaymentTool, Session, FlowType) -> + Flow = + case FlowType of + instant -> + {instant, #payproc_InvoicePaymentParamsFlowInstant{}}; + {hold, OnHoldExpiration} -> + {hold, #payproc_InvoicePaymentParamsFlowHold{on_hold_expiration = OnHoldExpiration}} + end, + #payproc_InvoicePaymentParams{ + payer = + {payment_resource, #payproc_PaymentResourcePayerParams{ + resource = #domain_DisposablePaymentResource{ + payment_tool = PaymentTool, + payment_session_id = Session, + client_info = #domain_ClientInfo{} + }, + contact_info = ?contact_info() + }}, + flow = Flow + }. + +-spec register_payment(_, _, _, _) -> _. +register_payment(InvoiceID, RegisterPaymentParams, WithRiskScoring, Client) -> + ?payment_state(?payment(PaymentID)) = + hg_client_invoicing:register_payment(InvoiceID, RegisterPaymentParams, Client), + _ = + case WithRiskScoring of + true -> + start_payment_ev(InvoiceID, Client); + false -> + start_payment_ev_no_risk_scoring(InvoiceID, Client) + end, + ?payment_ev(PaymentID, ?cash_flow_changed(_)) = + next_change(InvoiceID, Client), + PaymentID. + +-spec start_payment(_, _, _) -> _. +start_payment(InvoiceID, PaymentParams, Client) -> + ?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client), + _ = start_payment_ev(InvoiceID, Client), + ?payment_ev(PaymentID, ?cash_flow_changed(_)) = + next_change(InvoiceID, Client), + PaymentID. + +-spec start_payment_ev(_, _) -> _. +start_payment_ev(InvoiceID, Client) -> + [ + ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))), + ?payment_ev(PaymentID, ?risk_score_changed(_RiskScore)), + ?payment_ev(PaymentID, ?route_changed(Route)) + ] = next_changes(InvoiceID, 3, Client), + Route. + +-spec start_payment_ev_no_risk_scoring(_, _) -> _. +start_payment_ev_no_risk_scoring(InvoiceID, Client) -> + [ + ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))), + ?payment_ev(PaymentID, ?route_changed(Route)) + ] = next_changes(InvoiceID, 2, Client), + Route. + +-spec await_payment_session_started(_, _, _, _) -> _. +await_payment_session_started(InvoiceID, PaymentID, Client, Target) -> + ?payment_ev(PaymentID, ?session_ev(Target, ?session_started())) = + next_change(InvoiceID, Client), + PaymentID. + +-spec await_payment_capture(_, _, _) -> _. +await_payment_capture(InvoiceID, PaymentID, Client) -> + await_payment_capture(InvoiceID, PaymentID, ?timeout_reason(), Client). + +-spec await_payment_capture(_, _, _, _) -> _. +await_payment_capture(InvoiceID, PaymentID, Reason, Client) -> + await_payment_capture(InvoiceID, PaymentID, Reason, undefined, Client). + +-spec await_payment_capture(_, _, _, _, _) -> _. +await_payment_capture(InvoiceID, PaymentID, Reason, TrxID, Client) -> + Cost = get_payment_cost(InvoiceID, PaymentID, Client), + [ + ?payment_ev(PaymentID, ?payment_capture_started(Reason, Cost, _, _)), + ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cost), ?session_started())) + ] = next_changes(InvoiceID, 2, Client), + TrxID =/= undefined andalso + (?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cost, _Cart, _), ?trx_bound(?trx_info(TrxID)))) = + next_change(InvoiceID, Client)), + await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client). + +get_payment_cost(InvoiceID, PaymentID, Client) -> + #payproc_InvoicePayment{ + payment = #domain_InvoicePayment{cost = Cost} + } = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client), + Cost. + +-spec await_payment_capture_finish(_, _, _, _) -> _. +await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client) -> + Cost = get_payment_cost(InvoiceID, PaymentID, Client), + await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Client). + +-spec await_payment_capture_finish(_, _, _, _, _) -> _. +await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Client) -> + await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, undefined, Client). + +-spec await_payment_capture_finish(_, _, _, _, _, _) -> _. +await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Cart, Client) -> + [ + ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cost, Cart, _), ?session_finished(?session_succeeded()))), + ?payment_ev(PaymentID, ?payment_status_changed(?captured(Reason, Cost, Cart, _))), + ?invoice_status_changed(?invoice_paid()) + ] = next_changes(InvoiceID, 3, Client), + PaymentID. + +-spec await_payment_cash_flow(_, _, _) -> _. +await_payment_cash_flow(InvoiceID, PaymentID, Client) -> + [ + ?payment_ev(PaymentID, ?risk_score_changed(_)), + ?payment_ev(PaymentID, ?route_changed(Route)), + ?payment_ev(PaymentID, ?cash_flow_changed(CashFlow)) + ] = next_changes(InvoiceID, 3, Client), + {CashFlow, Route}. + +-spec construct_ta_context(_, _, _) -> _. +construct_ta_context(Party, Shop, Route) -> + #{ + party => Party, + shop => Shop, + route => Route + }. + +-spec get_cashflow_volume(_, _, _, _) -> _. +get_cashflow_volume(Source, Destination, CF, CFContext) -> + TAS = convert_transaction_account(Source, CFContext), + TAD = convert_transaction_account(Destination, CFContext), + [Volume] = [ + V + || #domain_FinalCashFlowPosting{ + source = #domain_FinalCashFlowAccount{ + account_type = ST, + transaction_account = SA + }, + destination = #domain_FinalCashFlowAccount{ + account_type = DT, + transaction_account = DA + }, + volume = V + } <- CF, + ST == Source, + DT == Destination, + SA == TAS, + DA == TAD + ], + Volume. + +-spec convert_transaction_account(_, _) -> _. +convert_transaction_account({merchant, Type}, #{party := Party, shop := Shop}) -> + {merchant, #domain_MerchantTransactionAccount{ + type = Type, + owner = #domain_MerchantTransactionAccountOwner{ + party_id = Party, + shop_id = Shop + } + }}; +convert_transaction_account({provider, Type}, #{route := Route}) -> + #domain_PaymentRoute{ + provider = ProviderRef, + terminal = TerminalRef + } = Route, + {provider, #domain_ProviderTransactionAccount{ + type = Type, + owner = #domain_ProviderTransactionAccountOwner{ + provider_ref = ProviderRef, + terminal_ref = TerminalRef + } + }}; +convert_transaction_account({system, Type}, _Context) -> + {system, #domain_SystemTransactionAccount{ + type = Type + }}; +convert_transaction_account({external, Type}, _Context) -> + {external, #domain_ExternalTransactionAccount{ + type = Type + }}. + +-spec make_wallet_payment_params(_) -> _. +make_wallet_payment_params(PmtSrv) -> + {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(digital_wallet, PmtSrv), + make_payment_params(PaymentTool, Session, instant). + +-spec execute_payment(_, _, _) -> _. +execute_payment(InvoiceID, Params, Client) -> + PaymentID = process_payment(InvoiceID, Params, Client), + PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), + PaymentID. + +-spec process_payment(_, _, _) -> _. +process_payment(InvoiceID, PaymentParams, Client) -> + PaymentID = start_payment(InvoiceID, PaymentParams, Client), + PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), + PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client). + +-spec await_payment_process_finish(_, _, _) -> _. +await_payment_process_finish(InvoiceID, PaymentID, Client) -> + [ + ?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(_)))), + ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))), + ?payment_ev(PaymentID, ?payment_status_changed(?processed())) + ] = next_changes(InvoiceID, 3, Client), + PaymentID. + +-spec make_customer_w_rec_tool(_, _, _, _) -> _. +make_customer_w_rec_tool(PartyID, ShopID, Client, PmtSys) -> + CustomerParams = hg_ct_helper:make_customer_params(PartyID, ShopID, <<"InvoicingTests">>), + #payproc_Customer{id = CustomerID} = + hg_client_customer:create(CustomerParams, Client), + #payproc_CustomerBinding{id = BindingID} = + hg_client_customer:start_binding( + CustomerID, + hg_ct_helper:make_customer_binding_params(hg_dummy_provider:make_payment_tool(no_preauth, PmtSys)), + Client + ), + ok = wait_for_binding_success(CustomerID, BindingID, Client), + CustomerID. + +wait_for_binding_success(CustomerID, BindingID, Client) -> + wait_for_binding_success(CustomerID, BindingID, 20000, Client). + +wait_for_binding_success(CustomerID, BindingID, TimeLeft, Client) when TimeLeft > 0 -> + Target = ?customer_binding_changed(BindingID, ?customer_binding_status_changed(?customer_binding_succeeded())), + Started = genlib_time:ticks(), + Event = hg_client_customer:pull_event(CustomerID, Client), + R = + case Event of + {ok, ?customer_event(Changes)} -> + lists:member(Target, Changes); + _ -> + false + end, + case R of + true -> + ok; + false -> + timer:sleep(200), + Now = genlib_time:ticks(), + TimeLeftNext = TimeLeft - (Now - Started) div 1000, + wait_for_binding_success(CustomerID, BindingID, TimeLeftNext, Client) + end; +wait_for_binding_success(_, _, _, _) -> + timeout. diff --git a/apps/hellgate/test/hg_invoice_lite_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_lite_tests_SUITE.erl new file mode 100644 index 0000000..23cd3f8 --- /dev/null +++ b/apps/hellgate/test/hg_invoice_lite_tests_SUITE.erl @@ -0,0 +1,817 @@ +-module(hg_invoice_lite_tests_SUITE). + +-include("hg_ct_invoice.hrl"). +-include("hg_ct_domain.hrl"). +-include_lib("stdlib/include/assert.hrl"). + +-export([all/0]). +-export([groups/0]). +-export([init_per_suite/1]). +-export([end_per_suite/1]). +-export([init_per_testcase/2]). +-export([end_per_testcase/2]). + +-export([payment_start_idempotency/1]). +-export([payment_success/1]). +-export([register_payment_success/1]). +-export([register_payment_customer_payer_success/1]). +-export([payment_success_additional_info/1]). +-export([payment_w_mobile_commerce/1]). +-export([payment_suspend_timeout_failure/1]). +-export([payment_w_crypto_currency_success/1]). +-export([payment_w_wallet_success/1]). +-export([payment_success_empty_cvv/1]). +-export([payment_has_optional_fields/1]). +-export([payment_last_trx_correct/1]). + +-type config() :: hg_ct_helper:config(). +-type test_case_name() :: hg_ct_helper:test_case_name(). +-type group_name() :: hg_ct_helper:group_name(). +-type test_return() :: _ | no_return(). + +%% Supervisor +-behaviour(supervisor). + +-export([init/1]). + +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +init([]) -> + {ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}. + +%% Tests +-spec all() -> [test_case_name() | {group, group_name()}]. +all() -> + [ + {group, payments} + ]. + +-spec groups() -> [{group_name(), list(), [test_case_name()]}]. +groups() -> + [ + {payments, [parallel], [ + payment_start_idempotency, + payment_success, + register_payment_success, + register_payment_customer_payer_success, + payment_success_additional_info, + payment_w_mobile_commerce, + payment_suspend_timeout_failure, + payment_w_crypto_currency_success, + payment_w_wallet_success, + payment_success_empty_cvv, + payment_has_optional_fields, + payment_last_trx_correct + ]} + ]. + +-spec init_per_suite(config()) -> config(). +init_per_suite(C) -> + CowboySpec = hg_dummy_provider:get_http_cowboy_spec(), + {Apps, Ret} = hg_ct_helper:start_apps([ + woody, + scoper, + dmt_client, + bender_client, + party_client, + hg_proto, + hellgate, + {cowboy, CowboySpec}, + snowflake + ]), + RootUrl = maps:get(hellgate_root_url, Ret), + _ = hg_limiter_helper:init_per_suite(C), + _ = hg_domain:insert(construct_domain_fixture()), + PartyID = hg_utils:unique_id(), + PartyClient = {party_client:create_client(), party_client:create_context()}, + CustomerClient = hg_client_customer:start(hg_ct_helper:create_client(RootUrl)), + ShopID = hg_ct_helper:create_party_and_shop(PartyID, ?cat(1), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient), + {ok, SupPid} = supervisor:start_link(?MODULE, []), + _ = unlink(SupPid), + ok = hg_invoice_helper:start_kv_store(SupPid), + NewC = [ + {party_id, PartyID}, + {shop_id, ShopID}, + {customer_client, CustomerClient}, + {root_url, RootUrl}, + {test_sup, SupPid}, + {apps, Apps} + | C + ], + ok = hg_invoice_helper:start_proxies([{hg_dummy_provider, 1, NewC}, {hg_dummy_inspector, 2, NewC}]), + NewC. + +-spec end_per_suite(config()) -> _. +end_per_suite(C) -> + _ = hg_domain:cleanup(), + _ = [application:stop(App) || App <- cfg(apps, C)], + hg_invoice_helper:stop_kv_store(cfg(test_sup, C)), + exit(cfg(test_sup, C), shutdown). + +-spec init_per_testcase(test_case_name(), config()) -> config(). +init_per_testcase(_, C) -> + ApiClient = hg_ct_helper:create_client(hg_ct_helper:cfg(root_url, C)), + Client = hg_client_invoicing:start_link(ApiClient), + ok = hg_context:save(hg_context:create()), + [ + {client, Client} + | C + ]. + +-spec end_per_testcase(test_case_name(), config()) -> config(). +end_per_testcase(_, C) -> + C. + +%% TESTS +-spec payment_start_idempotency(config()) -> test_return(). +payment_start_idempotency(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + PaymentParams0 = make_payment_params(?pmt_sys(<<"visa-ref">>)), + PaymentID1 = <<"1">>, + ExternalID = <<"42">>, + PaymentParams1 = PaymentParams0#payproc_InvoicePaymentParams{ + id = PaymentID1, + external_id = ExternalID + }, + ?payment_state(#domain_InvoicePayment{ + id = PaymentID1, + external_id = ExternalID + }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams1, Client), + ?payment_state(#domain_InvoicePayment{ + id = PaymentID1, + external_id = ExternalID + }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams1, Client), + PaymentParams2 = PaymentParams0#payproc_InvoicePaymentParams{id = <<"2">>}, + {exception, #payproc_InvoicePaymentPending{id = PaymentID1}} = + hg_client_invoicing:start_payment(InvoiceID, PaymentParams2, Client), + PaymentID1 = execute_payment(InvoiceID, PaymentParams1, Client), + ?payment_state(#domain_InvoicePayment{ + id = PaymentID1, + external_id = ExternalID + }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams1, Client). + +-spec payment_success(config()) -> test_return(). +payment_success(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + Context = #base_Content{ + type = <<"application/x-erlang-binary">>, + data = erlang:term_to_binary({you, 643, "not", [<<"welcome">>, here]}) + }, + PayerSessionInfo = #domain_PayerSessionInfo{ + redirect_url = RedirectURL = <<"https://redirectly.io/merchant">> + }, + PaymentParams = (make_payment_params(?pmt_sys(<<"visa-ref">>)))#payproc_InvoicePaymentParams{ + payer_session_info = PayerSessionInfo, + context = Context + }, + PaymentID = process_payment(InvoiceID, PaymentParams, Client), + PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), + ?invoice_state( + ?invoice_w_status(?invoice_paid()), + [PaymentSt = ?payment_state(Payment)] + ) = hg_client_invoicing:get(InvoiceID, Client), + ?payment_w_status(PaymentID, ?captured()) = Payment, + ?payment_last_trx(Trx) = PaymentSt, + ?assertMatch( + #domain_InvoicePayment{ + payer_session_info = PayerSessionInfo, + context = Context + }, + Payment + ), + ?assertMatch( + #domain_TransactionInfo{ + extra = #{ + <<"payment.payer_session_info.redirect_url">> := RedirectURL + } + }, + Trx + ). + +-spec register_payment_success(config()) -> test_return(). +register_payment_success(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + Context = #base_Content{ + type = <<"application/x-erlang-binary">>, + data = erlang:term_to_binary({you, 643, "not", [<<"welcome">>, here]}) + }, + PayerSessionInfo = #domain_PayerSessionInfo{}, + {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(no_preauth, ?pmt_sys(<<"visa-ref">>)), + Route = ?route(?prv(1), ?trm(1)), + Cost = ?cash(41999, <<"RUB">>), + ID = hg_utils:unique_id(), + ExternalID = hg_utils:unique_id(), + TransactionInfo = ?trx_info(<<"1">>, #{}), + OccurredAt = hg_datetime:format_now(), + PaymentParams = #payproc_RegisterInvoicePaymentParams{ + payer_params = + {payment_resource, #payproc_PaymentResourcePayerParams{ + resource = #domain_DisposablePaymentResource{ + payment_tool = PaymentTool, + payment_session_id = Session, + client_info = #domain_ClientInfo{} + }, + contact_info = ?contact_info() + }}, + route = Route, + payer_session_info = PayerSessionInfo, + context = Context, + cost = Cost, + id = ID, + external_id = ExternalID, + transaction_info = TransactionInfo, + risk_score = high, + occurred_at = OccurredAt + }, + PaymentID = register_payment(InvoiceID, PaymentParams, true, Client), + PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), + PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client), + PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), + ?invoice_state(?invoice_w_status(?invoice_paid())) = + hg_client_invoicing:get(InvoiceID, Client), + PaymentSt = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client), + + ?payment_route(Route) = PaymentSt, + ?payment_last_trx(TransactionInfo) = PaymentSt, + ?payment_state(Payment) = PaymentSt, + ?payment_w_status(PaymentID, ?captured()) = Payment, + ?assertMatch( + #domain_InvoicePayment{ + id = ID, + payer_session_info = PayerSessionInfo, + context = Context, + flow = ?invoice_payment_flow_instant(), + cost = Cost, + external_id = ExternalID + }, + Payment + ). + +-spec register_payment_customer_payer_success(config()) -> test_return(). +register_payment_customer_payer_success(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + Route = ?route(?prv(1), ?trm(1)), + CustomerID = make_customer_w_rec_tool( + cfg(party_id, C), cfg(shop_id, C), cfg(customer_client, C), ?pmt_sys(<<"visa-ref">>) + ), + PaymentParams = #payproc_RegisterInvoicePaymentParams{ + payer_params = + {customer, #payproc_CustomerPayerParams{ + customer_id = CustomerID + }}, + route = Route, + transaction_info = ?trx_info(<<"1">>, #{}) + }, + PaymentID = register_payment(InvoiceID, PaymentParams, false, Client), + PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), + PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client), + PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), + ?invoice_state(?invoice_w_status(?invoice_paid())) = + hg_client_invoicing:get(InvoiceID, Client). + +-spec payment_success_additional_info(config()) -> test_return(). +payment_success_additional_info(C) -> + Client = hg_ct_helper:cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(empty_cvv, ?pmt_sys(<<"visa-ref">>)), + PaymentParams = make_payment_params(PaymentTool, Session, instant), + PaymentID = start_payment(InvoiceID, PaymentParams, Client), + PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), + + [ + ?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(Trx))), + ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))) + ] = next_changes(InvoiceID, 2, Client), + %% Check additional info + AdditionalInfo = hg_ct_fixture:construct_dummy_additional_info(), + #domain_TransactionInfo{additional_info = AdditionalInfo} = Trx, + + ?payment_ev(PaymentID, ?payment_status_changed(?processed())) = + next_change(InvoiceID, Client), + PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), + ?invoice_state( + ?invoice_w_status(?invoice_paid()), + [?payment_state(?payment_w_status(PaymentID, ?captured()))] + ) = hg_client_invoicing:get(InvoiceID, Client). + +-spec payment_w_mobile_commerce(config()) -> _ | no_return(). +payment_w_mobile_commerce(C) -> + payment_w_mobile_commerce(C, success). + +-spec payment_suspend_timeout_failure(config()) -> _ | no_return(). +payment_suspend_timeout_failure(C) -> + payment_w_mobile_commerce(C, failure). + +payment_w_mobile_commerce(C, Expectation) -> + Client = cfg(client, C), + PayCash = 1001, + InvoiceID = start_invoice(<<"oatmeal">>, make_due_date(10), PayCash, C), + {PaymentTool, Session} = hg_dummy_provider:make_payment_tool({mobile_commerce, Expectation}, ?mob(<<"mts-ref">>)), + PaymentParams = make_payment_params(PaymentTool, Session, instant), + hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client), + ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))) = + next_change(InvoiceID, Client), + _ = await_payment_cash_flow(InvoiceID, PaymentID, Client), + PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), + case Expectation of + success -> + [ + ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))), + ?payment_ev(PaymentID, ?payment_status_changed(?processed())) + ] = + next_changes(InvoiceID, 2, Client); + failure -> + [ + ?payment_ev( + PaymentID, + ?session_ev(?processed(), ?session_finished(?session_failed({failure, Failure}))) + ), + ?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})), + ?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure}))) + ] = + next_changes(InvoiceID, 3, Client) + end. + +-spec payment_w_crypto_currency_success(config()) -> _ | no_return(). +payment_w_crypto_currency_success(C) -> + Client = cfg(client, C), + PayCash = 2000, + InvoiceID = start_invoice(<<"cryptoduck">>, make_due_date(10), PayCash, C), + {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(crypto_currency, ?crypta(<<"bitcoin-ref">>)), + PaymentParams = make_payment_params(PaymentTool, Session, instant), + ?payment_state(#domain_InvoicePayment{ + id = PaymentID, + owner_id = PartyID, + shop_id = ShopID + }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client), + ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))) = + next_change(InvoiceID, Client), + {CF, Route} = await_payment_cash_flow(InvoiceID, PaymentID, Client), + CFContext = construct_ta_context(PartyID, ShopID, Route), + ?cash(PayCash, <<"RUB">>) = get_cashflow_volume({provider, settlement}, {merchant, settlement}, CF, CFContext), + ?cash(36, <<"RUB">>) = get_cashflow_volume({system, settlement}, {provider, settlement}, CF, CFContext), + ?cash(90, <<"RUB">>) = get_cashflow_volume({merchant, settlement}, {system, settlement}, CF, CFContext). + +-spec payment_w_wallet_success(config()) -> _ | no_return(). +payment_w_wallet_success(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"bubbleblob">>, make_due_date(10), 42000, C), + PaymentParams = make_wallet_payment_params(?pmt_srv(<<"qiwi-ref">>)), + PaymentID = execute_payment(InvoiceID, PaymentParams, Client), + ?invoice_state( + ?invoice_w_status(?invoice_paid()), + [?payment_state(?payment_w_status(PaymentID, ?captured()))] + ) = hg_client_invoicing:get(InvoiceID, Client). + +-spec payment_success_empty_cvv(config()) -> test_return(). +payment_success_empty_cvv(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(empty_cvv, ?pmt_sys(<<"visa-ref">>)), + PaymentParams = make_payment_params(PaymentTool, Session, instant), + PaymentID = execute_payment(InvoiceID, PaymentParams, Client), + ?invoice_state( + ?invoice_w_status(?invoice_paid()), + [?payment_state(?payment_w_status(PaymentID, ?captured()))] + ) = hg_client_invoicing:get(InvoiceID, Client). + +-spec payment_has_optional_fields(config()) -> test_return(). +payment_has_optional_fields(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + PaymentParams = make_payment_params(?pmt_sys(<<"visa-ref">>)), + PaymentID = execute_payment(InvoiceID, PaymentParams, Client), + InvoicePayment = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client), + ?payment_state(Payment) = InvoicePayment, + ?payment_route(Route) = InvoicePayment, + ?payment_cashflow(CashFlow) = InvoicePayment, + ?payment_last_trx(TrxInfo) = InvoicePayment, + PartyID = cfg(party_id, C), + ShopID = cfg(shop_id, C), + #domain_InvoicePayment{owner_id = PartyID, shop_id = ShopID} = Payment, + false = Route =:= undefined, + false = CashFlow =:= undefined, + false = TrxInfo =:= undefined. + +-spec payment_last_trx_correct(config()) -> _ | no_return(). +payment_last_trx_correct(C) -> + Client = cfg(client, C), + InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), + PaymentID = start_payment(InvoiceID, make_payment_params(?pmt_sys(<<"visa-ref">>)), Client), + PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), + [ + ?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(TrxInfo0))), + ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))), + ?payment_ev(PaymentID, ?payment_status_changed(?processed())) + ] = next_changes(InvoiceID, 3, Client), + PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), + ?payment_last_trx(TrxInfo0) = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Internals +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +cfg(Key, Config) -> + hg_ct_helper:cfg(Key, Config). + +make_due_date(LifetimeSeconds) -> + hg_invoice_helper:make_due_date(LifetimeSeconds). + +start_invoice(Product, Due, Amount, Client) -> + hg_invoice_helper:start_invoice(Product, Due, Amount, Client). + +make_payment_params(PmtSys) -> + hg_invoice_helper:make_payment_params(PmtSys). + +make_payment_params(PaymentTool, Session, FlowType) -> + hg_invoice_helper:make_payment_params(PaymentTool, Session, FlowType). + +register_payment(InvoiceID, RegisterPaymentParams, WithRiskScoring, Client) -> + hg_invoice_helper:register_payment(InvoiceID, RegisterPaymentParams, WithRiskScoring, Client). + +start_payment(InvoiceID, PaymentParams, Client) -> + hg_invoice_helper:start_payment(InvoiceID, PaymentParams, Client). + +process_payment(InvoiceID, PaymentParams, Client) -> + hg_invoice_helper:process_payment(InvoiceID, PaymentParams, Client). + +await_payment_session_started(InvoiceID, PaymentID, Client, Target) -> + hg_invoice_helper:await_payment_session_started(InvoiceID, PaymentID, Client, Target). + +await_payment_process_finish(InvoiceID, PaymentID, Client) -> + hg_invoice_helper:await_payment_process_finish(InvoiceID, PaymentID, Client). + +next_changes(InvoiceID, Amount, Client) -> + hg_invoice_helper:next_changes(InvoiceID, Amount, Client). + +next_change(InvoiceID, Client) -> + hg_invoice_helper:next_change(InvoiceID, Client). + +await_payment_capture(InvoiceID, PaymentID, Client) -> + hg_invoice_helper:await_payment_capture(InvoiceID, PaymentID, Client). + +await_payment_cash_flow(InvoiceID, PaymentID, Client) -> + hg_invoice_helper:await_payment_cash_flow(InvoiceID, PaymentID, Client). + +construct_ta_context(PartyID, ShopID, Route) -> + hg_invoice_helper:construct_ta_context(PartyID, ShopID, Route). + +get_cashflow_volume(Source, Destination, CF, CFContext) -> + hg_invoice_helper:get_cashflow_volume(Source, Destination, CF, CFContext). + +make_wallet_payment_params(PmtSrv) -> + hg_invoice_helper:make_wallet_payment_params(PmtSrv). + +execute_payment(InvoiceID, PaymentParams, Client) -> + hg_invoice_helper:execute_payment(InvoiceID, PaymentParams, Client). + +make_customer_w_rec_tool(PartyID, ShopID, Client, PmtSys) -> + hg_invoice_helper:make_customer_w_rec_tool(PartyID, ShopID, Client, PmtSys). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% CONFIG +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(merchant_to_system_share_1, ?share(45, 1000, operation_amount)). + +-spec construct_domain_fixture() -> _. +construct_domain_fixture() -> + TestTermSet = #domain_TermSet{ + payments = #domain_PaymentsServiceTerms{ + currencies = + {value, + ?ordset([ + ?cur(<<"RUB">>) + ])}, + categories = + {value, + ?ordset([ + ?cat(1), + ?cat(2) + ])}, + payment_methods = + {decisions, [ + #domain_PaymentMethodDecision{ + if_ = {constant, true}, + then_ = + {value, + ?ordset([ + ?pmt(mobile, ?mob(<<"mts-ref">>)), + ?pmt(digital_wallet, ?pmt_srv(<<"qiwi-ref">>)), + ?pmt(crypto_currency, ?crypta(<<"bitcoin-ref">>)), + ?pmt(bank_card, ?bank_card_no_cvv(<<"visa-ref">>)), + ?pmt(bank_card, ?bank_card(<<"visa-ref">>)) + ])} + } + ]}, + cash_limit = + {decisions, [ + #domain_CashLimitDecision{ + if_ = {condition, {currency_is, ?cur(<<"RUB">>)}}, + then_ = + {value, + ?cashrng( + {inclusive, ?cash(10, <<"RUB">>)}, + {exclusive, ?cash(420000000, <<"RUB">>)} + )} + } + ]}, + fees = + {decisions, [ + #domain_CashFlowDecision{ + if_ = {condition, {currency_is, ?cur(<<"RUB">>)}}, + then_ = + {value, [ + ?cfpost( + {merchant, settlement}, + {system, settlement}, + ?merchant_to_system_share_1 + ) + ]} + } + ]}, + holds = #domain_PaymentHoldsServiceTerms{ + payment_methods = + {value, + ?ordset([ + ?pmt(bank_card, ?bank_card(<<"visa-ref">>)) + ])}, + lifetime = + {decisions, [ + #domain_HoldLifetimeDecision{ + if_ = {condition, {currency_is, ?cur(<<"RUB">>)}}, + then_ = {value, #domain_HoldLifetime{seconds = 10}} + } + ]} + }, + refunds = #domain_PaymentRefundsServiceTerms{ + payment_methods = + {value, + ?ordset([ + ?pmt(bank_card, ?bank_card(<<"visa-ref">>)) + ])}, + fees = + {value, [ + ?cfpost( + {merchant, settlement}, + {system, settlement}, + ?fixed(100, <<"RUB">>) + ) + ]}, + eligibility_time = {value, #base_TimeSpan{minutes = 1}}, + partial_refunds = #domain_PartialRefundsServiceTerms{ + cash_limit = + {decisions, [ + #domain_CashLimitDecision{ + if_ = {condition, {currency_is, ?cur(<<"RUB">>)}}, + then_ = + {value, + ?cashrng( + {inclusive, ?cash(1000, <<"RUB">>)}, + {exclusive, ?cash(1000000000, <<"RUB">>)} + )} + } + ]} + } + }, + allocations = #domain_PaymentAllocationServiceTerms{ + allow = {constant, true} + }, + attempt_limit = {value, #domain_AttemptLimit{attempts = 2}} + }, + recurrent_paytools = #domain_RecurrentPaytoolsServiceTerms{ + payment_methods = + {value, + ordsets:from_list([ + ?pmt(bank_card, ?bank_card(<<"visa-ref">>)) + ])} + } + }, + [ + hg_ct_fixture:construct_bank_card_category( + ?bc_cat(1), + <<"Bank card category">>, + <<"Corporative">>, + [<<"*CORPORAT*">>] + ), + + hg_ct_fixture:construct_currency(?cur(<<"RUB">>)), + + hg_ct_fixture:construct_category(?cat(1), <<"Test category">>, test), + hg_ct_fixture:construct_category(?cat(2), <<"Generic Store">>, live), + + hg_ct_fixture:construct_payment_method(?pmt(mobile, ?mob(<<"mts-ref">>))), + hg_ct_fixture:construct_payment_method(?pmt(bank_card, ?bank_card(<<"visa-ref">>))), + hg_ct_fixture:construct_payment_method(?pmt(bank_card, ?bank_card_no_cvv(<<"visa-ref">>))), + hg_ct_fixture:construct_payment_method(?pmt(crypto_currency, ?crypta(<<"bitcoin-ref">>))), + hg_ct_fixture:construct_payment_method(?pmt(digital_wallet, ?pmt_srv(<<"qiwi-ref">>))), + + hg_ct_fixture:construct_proxy(?prx(1), <<"Dummy proxy">>), + hg_ct_fixture:construct_proxy(?prx(2), <<"Inspector proxy">>), + + hg_ct_fixture:construct_inspector(?insp(1), <<"Rejector">>, ?prx(2), #{<<"risk_score">> => <<"low">>}), + + hg_ct_fixture:construct_contract_template(?tmpl(1), ?trms(1)), + + hg_ct_fixture:construct_system_account_set(?sas(1)), + hg_ct_fixture:construct_external_account_set(?eas(1)), + + hg_ct_fixture:construct_payment_routing_ruleset( + ?ruleset(1), + <<"Policies">>, + {candidates, [ + ?candidate({constant, true}, ?trm(1)) + ]} + ), + hg_ct_fixture:construct_payment_routing_ruleset( + ?ruleset(3), + <<"Prohibitions">>, + {candidates, []} + ), + + {payment_institution, #domain_PaymentInstitutionObject{ + ref = ?pinst(1), + data = #domain_PaymentInstitution{ + name = <<"Test Inc.">>, + system_account_set = {value, ?sas(1)}, + default_contract_template = {value, ?tmpl(1)}, + providers = + {value, + ?ordset([ + ?prv(1) + ])}, + payment_routing_rules = #domain_RoutingRules{ + policies = ?ruleset(1), + prohibitions = ?ruleset(3) + }, + inspector = + {decisions, [ + #domain_InspectorDecision{ + if_ = {condition, {category_is, ?cat(1)}}, + then_ = {value, ?insp(1)} + }, + #domain_InspectorDecision{ + if_ = {condition, {category_is, ?cat(2)}}, + then_ = {value, ?insp(1)} + } + ]}, + residences = [], + realm = test + } + }}, + + {globals, #domain_GlobalsObject{ + ref = #domain_GlobalsRef{}, + data = #domain_Globals{ + external_account_set = + {decisions, [ + #domain_ExternalAccountSetDecision{ + if_ = {constant, true}, + then_ = {value, ?eas(1)} + } + ]}, + payment_institutions = ?ordset([?pinst(1)]) + } + }}, + + {term_set_hierarchy, #domain_TermSetHierarchyObject{ + ref = ?trms(1), + data = #domain_TermSetHierarchy{ + term_sets = [ + #domain_TimedTermSet{ + action_time = #base_TimestampInterval{}, + terms = TestTermSet + } + ] + } + }}, + + {provider, #domain_ProviderObject{ + ref = ?prv(1), + data = #domain_Provider{ + name = <<"Brovider">>, + description = <<"A provider but bro">>, + proxy = #domain_Proxy{ + ref = ?prx(1), + additional = #{ + <<"override">> => <<"brovider">> + } + }, + abs_account = <<"1234567890">>, + accounts = hg_ct_fixture:construct_provider_account_set([?cur(<<"RUB">>)]), + terms = #domain_ProvisionTermSet{ + payments = #domain_PaymentsProvisionTerms{ + currencies = + {value, + ?ordset([ + ?cur(<<"RUB">>) + ])}, + categories = + {value, + ?ordset([ + ?cat(1), + ?cat(2) + ])}, + payment_methods = + {value, + ?ordset([ + ?pmt(mobile, ?mob(<<"mts-ref">>)), + ?pmt(digital_wallet, ?pmt_srv(<<"qiwi-ref">>)), + ?pmt(bank_card, ?bank_card_no_cvv(<<"visa-ref">>)), + ?pmt(bank_card, ?bank_card(<<"visa-ref">>)), + ?pmt(crypto_currency, ?crypta(<<"bitcoin-ref">>)) + ])}, + cash_limit = + {value, + ?cashrng( + {inclusive, ?cash(1000, <<"RUB">>)}, + {exclusive, ?cash(1000000000, <<"RUB">>)} + )}, + cash_flow = + {value, [ + ?cfpost( + {provider, settlement}, + {merchant, settlement}, + ?share(1, 1, operation_amount) + ), + ?cfpost( + {system, settlement}, + {provider, settlement}, + ?share(18, 1000, operation_amount) + ) + ]}, + holds = #domain_PaymentHoldsProvisionTerms{ + lifetime = + {decisions, [ + #domain_HoldLifetimeDecision{ + if_ = + {condition, + {payment_tool, + {bank_card, #domain_BankCardCondition{ + definition = + {payment_system, #domain_PaymentSystemCondition{ + payment_system_is = ?pmt_sys(<<"visa-ref">>) + }} + }}}}, + then_ = {value, ?hold_lifetime(12)} + } + ]} + }, + refunds = #domain_PaymentRefundsProvisionTerms{ + cash_flow = + {value, [ + ?cfpost( + {merchant, settlement}, + {provider, settlement}, + ?share(1, 1, operation_amount) + ) + ]}, + partial_refunds = #domain_PartialRefundsProvisionTerms{ + cash_limit = + {value, + ?cashrng( + {inclusive, ?cash(10, <<"RUB">>)}, + {exclusive, ?cash(1000000000, <<"RUB">>)} + )} + } + }, + chargebacks = #domain_PaymentChargebackProvisionTerms{ + cash_flow = + {value, [ + ?cfpost( + {merchant, settlement}, + {provider, settlement}, + ?share(1, 1, operation_amount) + ) + ]} + } + }, + recurrent_paytools = #domain_RecurrentPaytoolsProvisionTerms{ + categories = {value, ?ordset([?cat(1), ?cat(2)])}, + payment_methods = + {value, + ?ordset([ + ?pmt(bank_card, ?bank_card(<<"visa-ref">>)) + ])}, + cash_value = {value, ?cash(1000, <<"RUB">>)} + } + } + } + }}, + {terminal, #domain_TerminalObject{ + ref = ?trm(1), + data = #domain_Terminal{ + name = <<"Brominal 1">>, + description = <<"Brominal 1">>, + provider_ref = ?prv(1) + } + }}, + + hg_ct_fixture:construct_mobile_operator(?mob(<<"mts-ref">>), <<"mts mobile operator">>), + hg_ct_fixture:construct_payment_service(?pmt_srv(<<"qiwi-ref">>), <<"qiwi payment service">>), + hg_ct_fixture:construct_payment_system(?pmt_sys(<<"visa-ref">>), <<"visa payment system">>), + hg_ct_fixture:construct_crypto_currency(?crypta(<<"bitcoin-ref">>), <<"bitcoin currency">>) + ]. diff --git a/apps/hellgate/test/hg_invoice_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_tests_SUITE.erl index 9a643c7..1aa4a13 100644 --- a/apps/hellgate/test/hg_invoice_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_tests_SUITE.erl @@ -5,6 +5,7 @@ -module(hg_invoice_tests_SUITE). -include("hg_ct_domain.hrl"). +-include("hg_ct_invoice.hrl"). -include_lib("damsel/include/dmsl_repair_thrift.hrl"). -include_lib("hellgate/include/allocation.hrl"). @@ -34,11 +35,6 @@ -export([invoice_cancellation_after_payment_timeout/1]). -export([invalid_payment_amount/1]). --export([payment_start_idempotency/1]). --export([payment_success/1]). --export([register_payment_success/1]). --export([register_payment_customer_payer_success/1]). - -export([payment_limit_success/1]). -export([payment_routes_limit_values/1]). -export([register_payment_limit_success/1]). @@ -54,18 +50,12 @@ -export([limit_hold_two_routes_failure/1]). -export([processing_deadline_reached_test/1]). --export([payment_success_empty_cvv/1]). --export([payment_success_additional_info/1]). -export([payment_w_terminal_w_payment_service_success/1]). --export([payment_w_crypto_currency_success/1]). -export([payment_bank_card_category_condition/1]). --export([payment_w_wallet_success/1]). -export([payment_w_customer_success/1]). -export([payment_w_another_shop_customer/1]). -export([payment_w_another_party_customer/1]). -export([payment_w_deleted_customer/1]). --export([payment_w_mobile_commerce/1]). --export([payment_suspend_timeout_failure/1]). -export([payments_w_bank_card_issuer_conditions/1]). -export([payments_w_bank_conditions/1]). -export([payment_success_on_second_try/1]). @@ -158,8 +148,6 @@ -export([payment_with_offsite_preauth_failed/1]). -export([payment_with_tokenized_bank_card/1]). -export([terms_retrieval/1]). --export([payment_has_optional_fields/1]). --export([payment_last_trx_correct/1]). -export([payment_w_misconfigured_routing_failed/1]). -export([payment_capture_failed/1]). -export([payment_capture_retries_exceeded/1]). @@ -310,30 +298,18 @@ groups() -> invoice_cancellation_after_payment_timeout, invalid_payment_amount, - payment_start_idempotency, - payment_success, - register_payment_success, - register_payment_customer_payer_success, payment_success_ruleset, processing_deadline_reached_test, - payment_success_empty_cvv, - payment_success_additional_info, payment_bank_card_category_condition, payment_w_terminal_w_payment_service_success, - payment_w_crypto_currency_success, - payment_w_wallet_success, payment_w_customer_success, payment_w_another_shop_customer, payment_w_another_party_customer, payment_w_deleted_customer, - payment_w_mobile_commerce, - payment_suspend_timeout_failure, payment_success_on_second_try, payment_fail_after_silent_callback, payment_temporary_unavailability_retry_success, payment_temporary_unavailability_too_many_retries, - payment_has_optional_fields, - payment_last_trx_correct, invoice_success_on_third_payment, payment_w_misconfigured_routing_failed, payment_capture_failed, @@ -560,40 +536,11 @@ init_per_suite(C) -> end_per_suite(C) -> _ = hg_domain:cleanup(), _ = [application:stop(App) || App <- cfg(apps, C)], + _ = hg_invoice_helper:stop_kv_store(cfg(test_sup, C)), exit(cfg(test_sup, C), shutdown). %% tests --include("invoice_events.hrl"). --include("payment_events.hrl"). --include("customer_events.hrl"). - --define(invoice(ID), #domain_Invoice{id = ID}). --define(payment(ID), #domain_InvoicePayment{id = ID}). --define(payment(ID, Revision), #domain_InvoicePayment{id = ID, party_revision = Revision}). --define(adjustment(ID), #domain_InvoicePaymentAdjustment{id = ID}). --define(adjustment(ID, Status), #domain_InvoicePaymentAdjustment{id = ID, status = Status}). --define(adjustment_revision(Revision), #domain_InvoicePaymentAdjustment{party_revision = Revision}). --define(adjustment_reason(Reason), #domain_InvoicePaymentAdjustment{reason = Reason}). --define(invoice_state(Invoice), #payproc_Invoice{invoice = Invoice}). --define(invoice_state(Invoice, Payments), #payproc_Invoice{invoice = Invoice, payments = Payments}). --define(payment_state(Payment), #payproc_InvoicePayment{payment = Payment}). --define(payment_state(Payment, Refunds), #payproc_InvoicePayment{payment = Payment, refunds = Refunds}). --define(payment_route(Route), #payproc_InvoicePayment{route = Route}). --define(refund_state(Refund), #payproc_InvoicePaymentRefund{refund = Refund}). --define(payment_cashflow(CashFlow), #payproc_InvoicePayment{cash_flow = CashFlow}). --define(payment_last_trx(Trx), #payproc_InvoicePayment{last_transaction_info = Trx}). --define(invoice_w_status(Status), #domain_Invoice{status = Status}). --define(invoice_w_revision(Revision), #domain_Invoice{party_revision = Revision}). --define(payment_w_status(Status), #domain_InvoicePayment{status = Status}). --define(payment_w_status(ID, Status), #domain_InvoicePayment{id = ID, status = Status}). --define(invoice_payment_refund(Cash, Status), #domain_InvoicePaymentRefund{cash = Cash, status = Status}). --define(trx_info(ID), #domain_TransactionInfo{id = ID}). --define(trx_info(ID, Extra), #domain_TransactionInfo{id = ID, extra = Extra}). --define(refund_id(RefundID), #domain_InvoicePaymentRefund{id = RefundID}). --define(refund_id(RefundID, ExternalID), #domain_InvoicePaymentRefund{id = RefundID, external_id = ExternalID}). --define(contact_info(), ?contact_info(undefined)). - -define(invalid_invoice_status(Status), {exception, #payproc_InvalidInvoiceStatus{status = Status}} ). @@ -1089,156 +1036,6 @@ invalid_payment_amount(C) -> errors = [<<"Invalid amount, more", _/binary>>] }} = hg_client_invoicing:start_payment(InvoiceID2, PaymentParams, Client). --spec payment_start_idempotency(config()) -> test_return(). -payment_start_idempotency(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - PaymentParams0 = make_payment_params(?pmt_sys(<<"visa-ref">>)), - PaymentID1 = <<"1">>, - ExternalID = <<"42">>, - PaymentParams1 = PaymentParams0#payproc_InvoicePaymentParams{ - id = PaymentID1, - external_id = ExternalID - }, - ?payment_state(#domain_InvoicePayment{ - id = PaymentID1, - external_id = ExternalID - }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams1, Client), - ?payment_state(#domain_InvoicePayment{ - id = PaymentID1, - external_id = ExternalID - }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams1, Client), - PaymentParams2 = PaymentParams0#payproc_InvoicePaymentParams{id = <<"2">>}, - {exception, #payproc_InvoicePaymentPending{id = PaymentID1}} = - hg_client_invoicing:start_payment(InvoiceID, PaymentParams2, Client), - PaymentID1 = execute_payment(InvoiceID, PaymentParams1, Client), - ?payment_state(#domain_InvoicePayment{ - id = PaymentID1, - external_id = ExternalID - }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams1, Client). - --spec payment_success(config()) -> test_return(). -payment_success(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - Context = #base_Content{ - type = <<"application/x-erlang-binary">>, - data = erlang:term_to_binary({you, 643, "not", [<<"welcome">>, here]}) - }, - PayerSessionInfo = #domain_PayerSessionInfo{ - redirect_url = RedirectURL = <<"https://redirectly.io/merchant">> - }, - PaymentParams = (make_payment_params(?pmt_sys(<<"visa-ref">>)))#payproc_InvoicePaymentParams{ - payer_session_info = PayerSessionInfo, - context = Context - }, - PaymentID = process_payment(InvoiceID, PaymentParams, Client), - PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), - ?invoice_state( - ?invoice_w_status(?invoice_paid()), - [PaymentSt = ?payment_state(Payment)] - ) = hg_client_invoicing:get(InvoiceID, Client), - ?payment_w_status(PaymentID, ?captured()) = Payment, - ?payment_last_trx(Trx) = PaymentSt, - ?assertMatch( - #domain_InvoicePayment{ - payer_session_info = PayerSessionInfo, - context = Context - }, - Payment - ), - ?assertMatch( - #domain_TransactionInfo{ - extra = #{ - <<"payment.payer_session_info.redirect_url">> := RedirectURL - } - }, - Trx - ). - --spec register_payment_success(config()) -> test_return(). -register_payment_success(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - Context = #base_Content{ - type = <<"application/x-erlang-binary">>, - data = erlang:term_to_binary({you, 643, "not", [<<"welcome">>, here]}) - }, - PayerSessionInfo = #domain_PayerSessionInfo{}, - {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(no_preauth, ?pmt_sys(<<"visa-ref">>)), - Route = ?route(?prv(1), ?trm(1)), - Cost = ?cash(41999, <<"RUB">>), - ID = hg_utils:unique_id(), - ExternalID = hg_utils:unique_id(), - TransactionInfo = ?trx_info(<<"1">>, #{}), - OccurredAt = hg_datetime:format_now(), - PaymentParams = #payproc_RegisterInvoicePaymentParams{ - payer_params = - {payment_resource, #payproc_PaymentResourcePayerParams{ - resource = #domain_DisposablePaymentResource{ - payment_tool = PaymentTool, - payment_session_id = Session, - client_info = #domain_ClientInfo{} - }, - contact_info = ?contact_info() - }}, - route = Route, - payer_session_info = PayerSessionInfo, - context = Context, - cost = Cost, - id = ID, - external_id = ExternalID, - transaction_info = TransactionInfo, - risk_score = high, - occurred_at = OccurredAt - }, - PaymentID = register_payment(InvoiceID, PaymentParams, true, Client), - PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), - PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client), - PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), - ?invoice_state(?invoice_w_status(?invoice_paid())) = - hg_client_invoicing:get(InvoiceID, Client), - PaymentSt = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client), - - ?payment_route(Route) = PaymentSt, - ?payment_last_trx(TransactionInfo) = PaymentSt, - ?payment_state(Payment) = PaymentSt, - ?payment_w_status(PaymentID, ?captured()) = Payment, - ?assertMatch( - #domain_InvoicePayment{ - id = ID, - payer_session_info = PayerSessionInfo, - context = Context, - flow = ?invoice_payment_flow_instant(), - cost = Cost, - external_id = ExternalID - }, - Payment - ). - --spec register_payment_customer_payer_success(config()) -> test_return(). -register_payment_customer_payer_success(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - Route = ?route(?prv(1), ?trm(1)), - CustomerID = make_customer_w_rec_tool( - cfg(party_id, C), cfg(shop_id, C), cfg(customer_client, C), ?pmt_sys(<<"visa-ref">>) - ), - PaymentParams = #payproc_RegisterInvoicePaymentParams{ - payer_params = - {customer, #payproc_CustomerPayerParams{ - customer_id = CustomerID - }}, - route = Route, - transaction_info = ?trx_info(<<"1">>, #{}) - }, - PaymentID = register_payment(InvoiceID, PaymentParams, false, Client), - PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), - PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client), - PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), - ?invoice_state(?invoice_w_status(?invoice_paid())) = - hg_client_invoicing:get(InvoiceID, Client). - %%============================================================================= %% register_* cases helpers @@ -1599,74 +1396,6 @@ processing_deadline_reached_test(C) -> fun({authorization_failed, {processing_deadline_reached, _}}) -> ok end ). --spec payment_success_empty_cvv(config()) -> test_return(). -payment_success_empty_cvv(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(empty_cvv, ?pmt_sys(<<"visa-ref">>)), - PaymentParams = make_payment_params(PaymentTool, Session, instant), - PaymentID = execute_payment(InvoiceID, PaymentParams, Client), - ?invoice_state( - ?invoice_w_status(?invoice_paid()), - [?payment_state(?payment_w_status(PaymentID, ?captured()))] - ) = hg_client_invoicing:get(InvoiceID, Client). - --spec payment_success_additional_info(config()) -> test_return(). -payment_success_additional_info(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(empty_cvv, ?pmt_sys(<<"visa-ref">>)), - PaymentParams = make_payment_params(PaymentTool, Session, instant), - PaymentID = start_payment(InvoiceID, PaymentParams, Client), - PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), - - [ - ?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(Trx))), - ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))) - ] = next_changes(InvoiceID, 2, Client), - #domain_TransactionInfo{additional_info = AdditionalInfo} = Trx, - AdditionalInfo = hg_ct_fixture:construct_dummy_additional_info(), - ?payment_ev(PaymentID, ?payment_status_changed(?processed())) = - next_change(InvoiceID, Client), - - PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), - ?invoice_state( - ?invoice_w_status(?invoice_paid()), - [?payment_state(?payment_w_status(PaymentID, ?captured()))] - ) = hg_client_invoicing:get(InvoiceID, Client). - --spec payment_has_optional_fields(config()) -> test_return(). -payment_has_optional_fields(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - PaymentParams = make_payment_params(?pmt_sys(<<"visa-ref">>)), - PaymentID = execute_payment(InvoiceID, PaymentParams, Client), - InvoicePayment = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client), - ?payment_state(Payment) = InvoicePayment, - ?payment_route(Route) = InvoicePayment, - ?payment_cashflow(CashFlow) = InvoicePayment, - ?payment_last_trx(TrxInfo) = InvoicePayment, - PartyID = cfg(party_id, C), - ShopID = cfg(shop_id, C), - #domain_InvoicePayment{owner_id = PartyID, shop_id = ShopID} = Payment, - false = Route =:= undefined, - false = CashFlow =:= undefined, - false = TrxInfo =:= undefined. - --spec payment_last_trx_correct(config()) -> _ | no_return(). -payment_last_trx_correct(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C), - PaymentID = start_payment(InvoiceID, make_payment_params(?pmt_sys(<<"visa-ref">>)), Client), - PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), - [ - ?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(TrxInfo0))), - ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))), - ?payment_ev(PaymentID, ?payment_status_changed(?processed())) - ] = next_changes(InvoiceID, 3, Client), - PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), - ?payment_last_trx(TrxInfo0) = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client). - -spec payment_w_misconfigured_routing_failed(config()) -> test_return(). payment_w_misconfigured_routing_failed(C) -> Client = cfg(client, C), @@ -2297,26 +2026,6 @@ payment_w_terminal_w_payment_service_success(C) -> PaymentSt ). --spec payment_w_crypto_currency_success(config()) -> _ | no_return(). -payment_w_crypto_currency_success(C) -> - Client = cfg(client, C), - PayCash = 2000, - InvoiceID = start_invoice(<<"cryptoduck">>, make_due_date(10), PayCash, C), - {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(crypto_currency, ?crypta(<<"bitcoin-ref">>)), - PaymentParams = make_payment_params(PaymentTool, Session, instant), - ?payment_state(#domain_InvoicePayment{ - id = PaymentID, - owner_id = PartyID, - shop_id = ShopID - }) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client), - ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))) = - next_change(InvoiceID, Client), - {CF, Route} = await_payment_cash_flow(InvoiceID, PaymentID, Client), - CFContext = construct_ta_context(PartyID, ShopID, Route), - ?cash(PayCash, <<"RUB">>) = get_cashflow_volume({provider, settlement}, {merchant, settlement}, CF, CFContext), - ?cash(40, <<"RUB">>) = get_cashflow_volume({system, settlement}, {provider, settlement}, CF, CFContext), - ?cash(90, <<"RUB">>) = get_cashflow_volume({merchant, settlement}, {system, settlement}, CF, CFContext). - -spec payment_bank_card_category_condition(config()) -> _ | no_return(). payment_bank_card_category_condition(C) -> Client = cfg(client, C), @@ -2335,55 +2044,6 @@ payment_bank_card_category_condition(C) -> CFContext = construct_ta_context(cfg(party_id, C), cfg(shop_id, C), Route), ?cash(200, <<"RUB">>) = get_cashflow_volume({merchant, settlement}, {system, settlement}, CF, CFContext). --spec payment_w_mobile_commerce(config()) -> _ | no_return(). -payment_w_mobile_commerce(C) -> - payment_w_mobile_commerce(C, success). - --spec payment_suspend_timeout_failure(config()) -> _ | no_return(). -payment_suspend_timeout_failure(C) -> - payment_w_mobile_commerce(C, failure). - -payment_w_mobile_commerce(C, Expectation) -> - Client = cfg(client, C), - PayCash = 1001, - InvoiceID = start_invoice(<<"oatmeal">>, make_due_date(10), PayCash, C), - {PaymentTool, Session} = hg_dummy_provider:make_payment_tool({mobile_commerce, Expectation}, ?mob(<<"mts-ref">>)), - PaymentParams = make_payment_params(PaymentTool, Session, instant), - hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client), - ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))) = - next_change(InvoiceID, Client), - _ = await_payment_cash_flow(InvoiceID, PaymentID, Client), - PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), - case Expectation of - success -> - [ - ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))), - ?payment_ev(PaymentID, ?payment_status_changed(?processed())) - ] = - next_changes(InvoiceID, 2, Client); - failure -> - [ - ?payment_ev( - PaymentID, - ?session_ev(?processed(), ?session_finished(?session_failed({failure, Failure}))) - ), - ?payment_ev(PaymentID, ?payment_rollback_started({failure, Failure})), - ?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure}))) - ] = - next_changes(InvoiceID, 3, Client) - end. - --spec payment_w_wallet_success(config()) -> _ | no_return(). -payment_w_wallet_success(C) -> - Client = cfg(client, C), - InvoiceID = start_invoice(<<"bubbleblob">>, make_due_date(10), 42000, C), - PaymentParams = make_wallet_payment_params(?pmt_srv(<<"qiwi-ref">>)), - PaymentID = execute_payment(InvoiceID, PaymentParams, Client), - ?invoice_state( - ?invoice_w_status(?invoice_paid()), - [?payment_state(?payment_w_status(PaymentID, ?captured()))] - ) = hg_client_invoicing:get(InvoiceID, Client). - -spec payment_w_customer_success(config()) -> test_return(). payment_w_customer_success(C) -> Client = cfg(client, C), @@ -3174,11 +2834,7 @@ update_payment_terms_cashflow(ProviderRef, CashFlow) -> ok. construct_ta_context(Party, Shop, Route) -> - #{ - party => Party, - shop => Shop, - route => Route - }. + hg_invoice_helper:construct_ta_context(Party, Shop, Route). get_deprecated_cashflow_account(Type, CF, CFContext) -> ID = get_deprecated_cashflow_account_id(Type, CF, CFContext), @@ -5209,56 +4865,10 @@ rounding_cashflow_volume(C) -> PaymentID = await_payment_capture(InvoiceID, PaymentID, Client). get_cashflow_volume(Source, Destination, CF, CFContext) -> - TAS = convert_transaction_account(Source, CFContext), - TAD = convert_transaction_account(Destination, CFContext), - [Volume] = [ - V - || #domain_FinalCashFlowPosting{ - source = #domain_FinalCashFlowAccount{ - account_type = ST, - transaction_account = SA - }, - destination = #domain_FinalCashFlowAccount{ - account_type = DT, - transaction_account = DA - }, - volume = V - } <- CF, - ST == Source, - DT == Destination, - SA == TAS, - DA == TAD - ], - Volume. + hg_invoice_helper:get_cashflow_volume(Source, Destination, CF, CFContext). -convert_transaction_account({merchant, Type}, #{party := Party, shop := Shop}) -> - {merchant, #domain_MerchantTransactionAccount{ - type = Type, - owner = #domain_MerchantTransactionAccountOwner{ - party_id = Party, - shop_id = Shop - } - }}; -convert_transaction_account({provider, Type}, #{route := Route}) -> - #domain_PaymentRoute{ - provider = ProviderRef, - terminal = TerminalRef - } = Route, - {provider, #domain_ProviderTransactionAccount{ - type = Type, - owner = #domain_ProviderTransactionAccountOwner{ - provider_ref = ProviderRef, - terminal_ref = TerminalRef - } - }}; -convert_transaction_account({system, Type}, _Context) -> - {system, #domain_SystemTransactionAccount{ - type = Type - }}; -convert_transaction_account({external, Type}, _Context) -> - {external, #domain_ExternalTransactionAccount{ - type = Type - }}. +convert_transaction_account(Entity, Context) -> + hg_invoice_helper:convert_transaction_account(Entity, Context). %% @@ -6583,104 +6193,21 @@ await_cascade_triggering(InvoiceID, PaymentID, Client) -> {Route, CashFlow, TrxID, Failure}. next_changes(InvoiceID, Amount, Client) -> - next_changes(InvoiceID, Amount, ?DEFAULT_NEXT_CHANGE_TIMEOUT, Client). - -next_changes(InvoiceID, Amount, Timeout, Client) -> - TimeoutTime = erlang:monotonic_time(millisecond) + Timeout, - next_changes_(InvoiceID, Amount, TimeoutTime, Client). - -next_changes_(InvoiceID, Amount, Timeout, Client) -> - Result = lists:foldl( - fun(_N, Acc) -> - case erlang:monotonic_time(millisecond) of - Time when Time < Timeout -> - [next_change(InvoiceID, Client) | Acc]; - _ -> - [{error, timeout} | Acc] - end - end, - [], - lists:seq(1, Amount) - ), - lists:reverse(Result). + hg_invoice_helper:next_changes(InvoiceID, Amount, Client). next_change(InvoiceID, Client) -> - %% timeout should be at least as large as hold expiration in construct_domain_fixture/0 - next_change(InvoiceID, ?DEFAULT_NEXT_CHANGE_TIMEOUT, Client). + hg_invoice_helper:next_change(InvoiceID, Client). next_change(InvoiceID, Timeout, Client) -> - hg_client_invoicing:pull_change(InvoiceID, fun filter_change/1, Timeout, Client). - -filter_change(?payment_ev(_, C)) -> - filter_change(C); -filter_change(?chargeback_ev(_, C)) -> - filter_change(C); -filter_change(?refund_ev(_, C)) -> - filter_change(C); -filter_change(?session_ev(_, ?proxy_st_changed(_))) -> - false; -filter_change(?session_ev(_, ?session_suspended(_, _))) -> - false; -filter_change(?session_ev(_, ?session_activated())) -> - false; -filter_change(_) -> - true. + hg_invoice_helper:next_change(InvoiceID, Timeout, Client). %% -start_service_handler(Module, C, HandlerOpts) -> - start_service_handler(Module, Module, C, HandlerOpts). - -start_service_handler(Name, Module, C, HandlerOpts) -> - IP = "127.0.0.1", - Port = get_random_port(), - Opts = maps:merge(HandlerOpts, #{hellgate_root_url => cfg(root_url, C)}), - ChildSpec = hg_test_proxy:get_child_spec(Name, Module, IP, Port, Opts), - {ok, _} = supervisor:start_child(cfg(test_sup, C), ChildSpec), - hg_test_proxy:get_url(Module, IP, Port). - start_proxies(Proxies) -> - setup_proxies( - lists:map( - fun - Mapper({Module, ProxyID, Context}) -> - Mapper({Module, ProxyID, #{}, Context}); - Mapper({Module, ProxyID, ProxyOpts, Context}) -> - construct_proxy(ProxyID, start_service_handler(Module, Context, #{}), ProxyOpts) - end, - Proxies - ) - ). - -setup_proxies(Proxies) -> - _ = hg_domain:upsert(Proxies), - ok. + hg_invoice_helper:start_proxies(Proxies). start_kv_store(SupPid) -> - ChildSpec = #{ - id => hg_kv_store, - start => {hg_kv_store, start_link, [[]]}, - restart => permanent, - shutdown => 2000, - type => worker, - modules => [hg_kv_store] - }, - {ok, _} = supervisor:start_child(SupPid, ChildSpec), - ok. - -get_random_port() -> - rand:uniform(32768) + 32767. - -construct_proxy(ID, Url, Options) -> - {proxy, #domain_ProxyObject{ - ref = ?prx(ID), - data = #domain_ProxyDefinition{ - name = Url, - description = Url, - url = Url, - options = Options - } - }}. + hg_invoice_helper:start_kv_store(SupPid). %% make_invoice_params(PartyID, ShopID, Product, Cost) -> @@ -6694,7 +6221,7 @@ make_invoice_params(PartyID, ShopID, Product, Due, Cost, AllocationPrototype) -> hg_ct_helper:make_invoice_params(InvoiceID, PartyID, ShopID, Product, Due, Cost, AllocationPrototype). make_cash(Amount) -> - make_cash(Amount, <<"RUB">>). + hg_invoice_helper:make_cash(Amount). make_cash(Amount, Currency) -> hg_ct_helper:make_cash(Amount, Currency). @@ -6732,8 +6259,7 @@ delete_invoice_tpl(TplID, Config) -> hg_client_invoice_templating:delete(TplID, cfg(client_tpl, Config)). make_wallet_payment_params(PmtSrv) -> - {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(digital_wallet, PmtSrv), - make_payment_params(PaymentTool, Session, instant). + hg_invoice_helper:make_wallet_payment_params(PmtSrv). make_tds_payment_params(FlowType, PmtSys) -> {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(preauth_3ds, PmtSys), @@ -6757,32 +6283,13 @@ make_scenario_payment_params(Scenario, FlowType, PmtSys) -> make_payment_params(PaymentTool, Session, FlowType). make_payment_params(PmtSys) -> - make_payment_params(PmtSys, instant). + hg_invoice_helper:make_payment_params(PmtSys). make_payment_params(PmtSys, FlowType) -> - {PaymentTool, Session} = hg_dummy_provider:make_payment_tool(no_preauth, PmtSys), - make_payment_params(PaymentTool, Session, FlowType). + hg_invoice_helper:make_payment_params(PmtSys, FlowType). make_payment_params(PaymentTool, Session, FlowType) -> - Flow = - case FlowType of - instant -> - {instant, #payproc_InvoicePaymentParamsFlowInstant{}}; - {hold, OnHoldExpiration} -> - {hold, #payproc_InvoicePaymentParamsFlowHold{on_hold_expiration = OnHoldExpiration}} - end, - #payproc_InvoicePaymentParams{ - payer = - {payment_resource, #payproc_PaymentResourcePayerParams{ - resource = #domain_DisposablePaymentResource{ - payment_tool = PaymentTool, - payment_session_id = Session, - client_info = #domain_ClientInfo{} - }, - contact_info = ?contact_info() - }}, - flow = Flow - }. + hg_invoice_helper:make_payment_params(PaymentTool, Session, FlowType). make_chargeback_cancel_params() -> #payproc_InvoicePaymentChargebackCancelParams{}. @@ -6904,7 +6411,7 @@ make_status_adjustment_params(Status, Reason) -> }. make_due_date(LifetimeSeconds) -> - genlib_time:unow() + LifetimeSeconds. + hg_invoice_helper:make_due_date(LifetimeSeconds). create_invoice(InvoiceParams, Client) -> ?invoice_state(?invoice(InvoiceID)) = hg_client_invoicing:create(InvoiceParams, Client), @@ -6934,59 +6441,28 @@ repair_invoice_with_scenario(InvoiceID, Scenario, Client) -> hg_client_invoicing:repair_scenario(InvoiceID, create_repair_scenario(Scenario), Client). start_invoice(Product, Due, Amount, C) -> - start_invoice(cfg(shop_id, C), Product, Due, Amount, C). + hg_invoice_helper:start_invoice(Product, Due, Amount, C). start_invoice(ShopID, Product, Due, Amount, C) -> - Client = cfg(client, C), - PartyID = cfg(party_id, C), - start_invoice(PartyID, ShopID, Product, Due, Amount, Client). + hg_invoice_helper:start_invoice(ShopID, Product, Due, Amount, C). start_invoice(PartyID, ShopID, Product, Due, Amount, Client) -> - InvoiceParams = make_invoice_params(PartyID, ShopID, Product, Due, make_cash(Amount)), - InvoiceID = create_invoice(InvoiceParams, Client), - ?invoice_created(?invoice_w_status(?invoice_unpaid())) = next_change(InvoiceID, Client), - InvoiceID. + hg_invoice_helper:start_invoice(PartyID, ShopID, Product, Due, Amount, Client). start_payment(InvoiceID, PaymentParams, Client) -> - ?payment_state(?payment(PaymentID)) = hg_client_invoicing:start_payment(InvoiceID, PaymentParams, Client), - _ = start_payment_ev(InvoiceID, Client), - ?payment_ev(PaymentID, ?cash_flow_changed(_)) = - next_change(InvoiceID, Client), - PaymentID. + hg_invoice_helper:start_payment(InvoiceID, PaymentParams, Client). register_payment(InvoiceID, RegisterPaymentParams, WithRiskScoring, Client) -> - ?payment_state(?payment(PaymentID)) = - hg_client_invoicing:register_payment(InvoiceID, RegisterPaymentParams, Client), - _ = - case WithRiskScoring of - true -> - start_payment_ev(InvoiceID, Client); - false -> - start_payment_ev_no_risk_scoring(InvoiceID, Client) - end, - ?payment_ev(PaymentID, ?cash_flow_changed(_)) = - next_change(InvoiceID, Client), - PaymentID. + hg_invoice_helper:register_payment(InvoiceID, RegisterPaymentParams, WithRiskScoring, Client). start_payment_ev(InvoiceID, Client) -> - [ - ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))), - ?payment_ev(PaymentID, ?risk_score_changed(_RiskScore)), - ?payment_ev(PaymentID, ?route_changed(Route)) - ] = next_changes(InvoiceID, 3, Client), - Route. + hg_invoice_helper:start_payment_ev(InvoiceID, Client). start_payment_ev_no_risk_scoring(InvoiceID, Client) -> - [ - ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))), - ?payment_ev(PaymentID, ?route_changed(Route)) - ] = next_changes(InvoiceID, 2, Client), - Route. + hg_invoice_helper:start_payment_ev_no_risk_scoring(InvoiceID, Client). process_payment(InvoiceID, PaymentParams, Client) -> - PaymentID = start_payment(InvoiceID, PaymentParams, Client), - PaymentID = await_payment_session_started(InvoiceID, PaymentID, Client, ?processed()), - PaymentID = await_payment_process_finish(InvoiceID, PaymentID, Client). + hg_invoice_helper:process_payment(InvoiceID, PaymentParams, Client). await_payment_started(InvoiceID, PaymentID, Client) -> ?payment_ev(PaymentID, ?payment_started(?payment_w_status(?pending()))) = @@ -6994,12 +6470,7 @@ await_payment_started(InvoiceID, PaymentID, Client) -> PaymentID. await_payment_cash_flow(InvoiceID, PaymentID, Client) -> - [ - ?payment_ev(PaymentID, ?risk_score_changed(_)), - ?payment_ev(PaymentID, ?route_changed(Route)), - ?payment_ev(PaymentID, ?cash_flow_changed(CashFlow)) - ] = next_changes(InvoiceID, 3, Client), - {CashFlow, Route}. + hg_invoice_helper:await_payment_cash_flow(InvoiceID, PaymentID, Client). await_payment_cash_flow(RS, Route, InvoiceID, PaymentID, Client) -> [ @@ -7018,9 +6489,7 @@ await_payment_rollback(InvoiceID, PaymentID, Client) -> Failure. await_payment_session_started(InvoiceID, PaymentID, Client, Target) -> - ?payment_ev(PaymentID, ?session_ev(Target, ?session_started())) = - next_change(InvoiceID, Client), - PaymentID. + hg_invoice_helper:await_payment_session_started(InvoiceID, PaymentID, Client, Target). await_payment_process_interaction(InvoiceID, PaymentID, Client) -> ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())) = @@ -7033,12 +6502,7 @@ await_payment_process_interaction(InvoiceID, PaymentID, Client) -> UserInteraction. await_payment_process_finish(InvoiceID, PaymentID, Client) -> - [ - ?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(_)))), - ?payment_ev(PaymentID, ?session_ev(?processed(), ?session_finished(?session_succeeded()))), - ?payment_ev(PaymentID, ?payment_status_changed(?processed())) - ] = next_changes(InvoiceID, 3, Client), - PaymentID. + hg_invoice_helper:await_payment_process_finish(InvoiceID, PaymentID, Client). await_payment_process_interaction_completion(InvoiceID, PaymentID, UserInteraction, Client) -> ?payment_ev( @@ -7051,21 +6515,13 @@ await_payment_process_interaction_completion(InvoiceID, PaymentID, UserInteracti ok. await_payment_capture(InvoiceID, PaymentID, Client) -> - await_payment_capture(InvoiceID, PaymentID, ?timeout_reason(), Client). + hg_invoice_helper:await_payment_capture(InvoiceID, PaymentID, Client). await_payment_capture(InvoiceID, PaymentID, Reason, Client) -> - await_payment_capture(InvoiceID, PaymentID, Reason, undefined, Client). + hg_invoice_helper:await_payment_capture(InvoiceID, PaymentID, Reason, Client). await_payment_capture(InvoiceID, PaymentID, Reason, TrxID, Client) -> - Cost = get_payment_cost(InvoiceID, PaymentID, Client), - [ - ?payment_ev(PaymentID, ?payment_capture_started(Reason, Cost, _, _)), - ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cost), ?session_started())) - ] = next_changes(InvoiceID, 2, Client), - TrxID =/= undefined andalso - (?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cost, _Cart, _), ?trx_bound(?trx_info(TrxID)))) = - next_change(InvoiceID, Client)), - await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client). + hg_invoice_helper:await_payment_capture(InvoiceID, PaymentID, Reason, TrxID, Client). await_payment_partial_capture(InvoiceID, PaymentID, Reason, Cash, Client) -> [ @@ -7076,19 +6532,13 @@ await_payment_partial_capture(InvoiceID, PaymentID, Reason, Cash, Client) -> await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cash, Client). await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client) -> - Cost = get_payment_cost(InvoiceID, PaymentID, Client), - await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Client). + hg_invoice_helper:await_payment_capture_finish(InvoiceID, PaymentID, Reason, Client). await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Client) -> - await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, undefined, Client). + hg_invoice_helper:await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Client). await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Cart, Client) -> - [ - ?payment_ev(PaymentID, ?session_ev(?captured(Reason, Cost, Cart, _), ?session_finished(?session_succeeded()))), - ?payment_ev(PaymentID, ?payment_status_changed(?captured(Reason, Cost, Cart, _))), - ?invoice_status_changed(?invoice_paid()) - ] = next_changes(InvoiceID, 3, Client), - PaymentID. + hg_invoice_helper:await_payment_capture_finish(InvoiceID, PaymentID, Reason, Cost, Cart, Client). await_payment_cancel(InvoiceID, PaymentID, Reason, Client) -> [ @@ -7221,43 +6671,7 @@ get_post_request(?payterm_receipt(SPID)) -> {URL, #{<<"tag">> => SPID}}. make_customer_w_rec_tool(PartyID, ShopID, Client, PmtSys) -> - CustomerParams = hg_ct_helper:make_customer_params(PartyID, ShopID, <<"InvoicingTests">>), - #payproc_Customer{id = CustomerID} = - hg_client_customer:create(CustomerParams, Client), - #payproc_CustomerBinding{id = BindingID} = - hg_client_customer:start_binding( - CustomerID, - hg_ct_helper:make_customer_binding_params(hg_dummy_provider:make_payment_tool(no_preauth, PmtSys)), - Client - ), - ok = wait_for_binding_success(CustomerID, BindingID, Client), - CustomerID. - -wait_for_binding_success(CustomerID, BindingID, Client) -> - wait_for_binding_success(CustomerID, BindingID, 20000, Client). - -wait_for_binding_success(CustomerID, BindingID, TimeLeft, Client) when TimeLeft > 0 -> - Target = ?customer_binding_changed(BindingID, ?customer_binding_status_changed(?customer_binding_succeeded())), - Started = genlib_time:ticks(), - Event = hg_client_customer:pull_event(CustomerID, Client), - R = - case Event of - {ok, ?customer_event(Changes)} -> - lists:member(Target, Changes); - _ -> - false - end, - case R of - true -> - ok; - false -> - timer:sleep(200), - Now = genlib_time:ticks(), - TimeLeftNext = TimeLeft - (Now - Started) div 1000, - wait_for_binding_success(CustomerID, BindingID, TimeLeftNext, Client) - end; -wait_for_binding_success(_, _, _, _) -> - timeout. + hg_invoice_helper:make_customer_w_rec_tool(PartyID, ShopID, Client, PmtSys). invoice_create_and_get_revision(PartyID, Client, ShopID) -> InvoiceParams = make_invoice_params(PartyID, ShopID, <<"somePlace">>, make_due_date(10), make_cash(5000)), @@ -7267,9 +6681,7 @@ invoice_create_and_get_revision(PartyID, Client, ShopID) -> {InvoiceRev, InvoiceID}. execute_payment(InvoiceID, Params, Client) -> - PaymentID = process_payment(InvoiceID, Params, Client), - PaymentID = await_payment_capture(InvoiceID, PaymentID, Client), - PaymentID. + hg_invoice_helper:execute_payment(InvoiceID, Params, Client). execute_payment_w_cascade(InvoiceID, Params, Client, CascadeCount) when CascadeCount > 0 -> #payproc_InvoicePayment{payment = _Payment} = hg_client_invoicing:start_payment(InvoiceID, Params, Client), @@ -7431,12 +6843,6 @@ payment_customer_risk_score_check(C) -> % -get_payment_cost(InvoiceID, PaymentID, Client) -> - #payproc_InvoicePayment{ - payment = #domain_InvoicePayment{cost = Cost} - } = hg_client_invoicing:get_payment(InvoiceID, PaymentID, Client), - Cost. - get_payment_cashflow_mapped(InvoiceID, PaymentID, Client) -> #payproc_InvoicePayment{ cash_flow = CashFlow diff --git a/elvis.config b/elvis.config index 575606d..de7cb41 100644 --- a/elvis.config +++ b/elvis.config @@ -20,7 +20,8 @@ hg_ct_helper, hg_customer_tests_SUITE, hg_recurrent_paytools_tests_SUITE, - hg_invoice_tests_SUITE + hg_invoice_tests_SUITE, + hg_invoice_helper ] }}, {elvis_style, no_if_expression}, @@ -37,7 +38,8 @@ hg_routing, hg_route_rules_tests_SUITE, hg_invoice_tests_SUITE, - hg_allocation_tests + hg_allocation_tests, + hg_invoice_helper ] }}, {elvis_style, no_debug_call, #{}}