Merge branch 'master' into epic/kill_adjustments

This commit is contained in:
Артем 2023-05-26 16:41:07 +03:00 committed by GitHub
commit a755338392
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 520 additions and 473 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ tags
# make stuff # make stuff
/.image.* /.image.*
Makefile.env Makefile.env
*.iml

View File

@ -11,7 +11,7 @@
-export([get_balance/1]). -export([get_balance/1]).
-export([create_account/1]). -export([create_account/1]).
-export([create_account/2]). -export([create_account/2]).
-export([collect_account_map/8]). -export([collect_account_map/1]).
-export([collect_merchant_account_map/3]). -export([collect_merchant_account_map/3]).
-export([collect_provider_account_map/4]). -export([collect_provider_account_map/4]).
-export([collect_system_account_map/4]). -export([collect_system_account_map/4]).
@ -45,8 +45,20 @@
-type varset() :: hg_varset:varset(). -type varset() :: hg_varset:varset().
-type revision() :: hg_domain:revision(). -type revision() :: hg_domain:revision().
-type collect_account_context() :: #{
payment := payment(),
party := party(),
shop := shop(),
route := route(),
payment_institution := payment_institution(),
provider := provider(),
varset := varset(),
revision := revision()
}.
-export_type([plan_id/0]). -export_type([plan_id/0]).
-export_type([batch/0]). -export_type([batch/0]).
-export_type([collect_account_context/0]).
-export_type([posting_plan_log/0]). -export_type([posting_plan_log/0]).
-type account() :: #{ -type account() :: #{
@ -85,17 +97,17 @@ create_account(CurrencyCode, Description) ->
error({accounting, Exception}) error({accounting, Exception})
end. end.
-spec collect_account_map( -spec collect_account_map(collect_account_context()) -> map().
payment(), collect_account_map(#{
party(), payment := Payment,
shop(), party := Party,
route(), shop := Shop,
payment_institution(), route := Route,
provider(), payment_institution := PaymentInstitution,
varset(), provider := Provider,
revision() varset := VS,
) -> map(). revision := Revision
collect_account_map(Payment, Party, Shop, Route, PaymentInstitution, Provider, VS, Revision) -> }) ->
Map0 = collect_merchant_account_map(Party, Shop, #{}), Map0 = collect_merchant_account_map(Party, Shop, #{}),
Map1 = collect_provider_account_map(Payment, Provider, Route, Map0), Map1 = collect_provider_account_map(Payment, Provider, Route, Map0),
Map2 = collect_system_account_map(Payment, PaymentInstitution, Revision, Map1), Map2 = collect_system_account_map(Payment, PaymentInstitution, Revision, Map1),

View File

@ -0,0 +1,179 @@
-module(hg_cashflow_utils).
-include_lib("damsel/include/dmsl_base_thrift.hrl").
-include_lib("damsel/include/dmsl_payproc_thrift.hrl").
-include_lib("hellgate/include/allocation.hrl").
-include_lib("hellgate/include/domain.hrl").
-include("hg_invoice_payment.hrl").
-type cash_flow_context() :: #{
operation := refund | payment,
provision_terms := dmsl_domain_thrift:'PaymentsProvisionTerms'(),
party := party(),
shop := shop(),
route := route(),
payment := payment(),
provider := provider(),
timestamp := hg_datetime:timestamp(),
varset := hg_varset:varset(),
revision := revision(),
merchant_terms => dmsl_domain_thrift:'PaymentsServiceTerms'(),
refund => refund(),
allocation => hg_allocation:allocation()
}.
-export_type([cash_flow_context/0]).
-export([collect_cashflow/1]).
-export([collect_cashflow/2]).
-type party() :: dmsl_domain_thrift:'Party'().
-type shop() :: dmsl_domain_thrift:'Shop'().
-type route() :: dmsl_domain_thrift:'PaymentRoute'().
-type payment() :: dmsl_domain_thrift:'InvoicePayment'().
-type refund() :: dmsl_domain_thrift:'InvoicePaymentRefund'().
-type provider() :: dmsl_domain_thrift:'Provider'().
-type revision() :: hg_domain:revision().
-type payment_institution() :: hg_payment_institution:t().
-type final_cash_flow() :: hg_cashflow:final_cash_flow().
-spec collect_cashflow(cash_flow_context()) -> final_cash_flow().
collect_cashflow(#{party := Party, shop := Shop, varset := VS, revision := Revision} = Context) ->
PaymentInstitution = get_cashflow_payment_institution(Party, Shop, VS, Revision),
collect_cashflow(PaymentInstitution, Context).
-spec collect_cashflow(payment_institution(), cash_flow_context()) -> final_cash_flow().
collect_cashflow(PaymentInstitution, Context) ->
CF =
case maps:get(allocation, Context, undefined) of
undefined ->
Amount = get_amount(Context),
construct_transaction_cashflow(Amount, PaymentInstitution, Context);
?allocation(Transactions) ->
collect_allocation_cash_flow(Transactions, Context)
end,
ProviderCashflow = construct_provider_cashflow(PaymentInstitution, Context),
CF ++ ProviderCashflow.
%% Internal
collect_allocation_cash_flow(
Transactions,
Context = #{
revision := Revision,
party := Party,
shop := Shop,
varset := VS0
}
) ->
lists:foldl(
fun(?allocation_trx(_ID, Target, Amount), Acc) ->
?allocation_trx_target_shop(PartyID, ShopID) = Target,
TargetParty = hg_party:get_party(PartyID),
TargetShop = hg_party:get_shop(ShopID, TargetParty),
VS1 = VS0#{
party_id => Party#domain_Party.id,
shop_id => Shop#domain_Shop.id,
cost => Amount
},
AllocationPaymentInstitution =
get_cashflow_payment_institution(Party, Shop, VS1, Revision),
construct_transaction_cashflow(
Amount,
AllocationPaymentInstitution,
Context#{party => TargetParty, shop => TargetShop}
) ++ Acc
end,
[],
Transactions
).
construct_transaction_cashflow(
Amount,
PaymentInstitution,
Context = #{
operation := OpType,
party := Party,
shop := Shop,
varset := VS,
revision := Revision,
timestamp := Timestamp
}
) ->
MerchantPaymentsTerms1 =
case maps:get(merchant_terms, Context, undefined) of
undefined ->
TermSet = hg_invoice_utils:get_merchant_terms(Party, Shop, Revision, Timestamp, VS),
TermSet#domain_TermSet.payments;
MerchantPaymentsTerms0 ->
MerchantPaymentsTerms0
end,
MerchantCashflowSelector = get_terms_cashflow(OpType, MerchantPaymentsTerms1),
MerchantCashflow = get_selector_value(merchant_payment_fees, MerchantCashflowSelector),
AccountMap = hg_accounting:collect_account_map(make_collect_account_context(PaymentInstitution, Context)),
construct_final_cashflow(MerchantCashflow, #{operation_amount => Amount}, AccountMap).
construct_provider_cashflow(PaymentInstitution, Context = #{provision_terms := ProvisionTerms}) ->
ProviderCashflowSelector = get_provider_cashflow_selector(ProvisionTerms),
ProviderCashflow = get_selector_value(provider_payment_cash_flow, ProviderCashflowSelector),
AccountMap = hg_accounting:collect_account_map(make_collect_account_context(PaymentInstitution, Context)),
construct_final_cashflow(ProviderCashflow, #{operation_amount => get_amount(Context)}, AccountMap).
construct_final_cashflow(Cashflow, Context, AccountMap) ->
hg_cashflow:finalize(Cashflow, Context, AccountMap).
get_cashflow_payment_institution(Party, Shop, VS, Revision) ->
Contract = hg_party:get_contract(Shop#domain_Shop.contract_id, Party),
PaymentInstitutionRef = Contract#domain_Contract.payment_institution,
hg_payment_institution:compute_payment_institution(
PaymentInstitutionRef,
VS,
Revision
).
get_amount(#{refund := #domain_InvoicePaymentRefund{cash = Cash}}) ->
Cash;
get_amount(#{payment := #domain_InvoicePayment{cost = Cost}}) ->
Cost.
get_provider_cashflow_selector(#domain_PaymentsProvisionTerms{cash_flow = ProviderCashflowSelector}) ->
ProviderCashflowSelector;
get_provider_cashflow_selector(#domain_PaymentRefundsProvisionTerms{cash_flow = ProviderCashflowSelector}) ->
ProviderCashflowSelector.
get_terms_cashflow(payment, MerchantPaymentsTerms) ->
MerchantPaymentsTerms#domain_PaymentsServiceTerms.fees;
get_terms_cashflow(refund, MerchantPaymentsTerms) ->
MerchantRefundTerms = MerchantPaymentsTerms#domain_PaymentsServiceTerms.refunds,
MerchantRefundTerms#domain_PaymentRefundsServiceTerms.fees.
get_selector_value(Name, Selector) ->
case Selector of
{value, V} ->
V;
Ambiguous ->
error({misconfiguration, {'Could not reduce selector to a value', {Name, Ambiguous}}})
end.
-spec make_collect_account_context(payment_institution(), cash_flow_context()) ->
hg_accounting:collect_account_context().
make_collect_account_context(PaymentInstitution, #{
payment := Payment,
party := Party,
shop := Shop,
route := Route,
provider := Provider,
varset := VS,
revision := Revision
}) ->
#{
payment => Payment,
party => Party,
shop => Shop,
route => Route,
payment_institution => PaymentInstitution,
provider => Provider,
varset => VS,
revision => Revision
}.

View File

@ -147,7 +147,11 @@ handle_function_('Create', {InvoiceParams}, _Opts) ->
Party = hg_party:get_party(PartyID), Party = hg_party:get_party(PartyID),
Shop = assert_shop_exists(hg_party:get_shop(ShopID, Party)), Shop = assert_shop_exists(hg_party:get_shop(ShopID, Party)),
_ = assert_party_shop_operable(Shop, Party), _ = assert_party_shop_operable(Shop, Party),
MerchantTerms = get_merchant_terms(Party, DomainRevision, Shop, hg_datetime:format_now(), InvoiceParams), VS = #{
cost => InvoiceParams#payproc_InvoiceParams.cost,
shop_id => Shop#domain_Shop.id
},
MerchantTerms = hg_invoice_utils:get_merchant_terms(Party, Shop, DomainRevision, hg_datetime:format_now(), VS),
ok = validate_invoice_params(InvoiceParams, Shop, MerchantTerms), ok = validate_invoice_params(InvoiceParams, Shop, MerchantTerms),
AllocationPrototype = InvoiceParams#payproc_InvoiceParams.allocation, AllocationPrototype = InvoiceParams#payproc_InvoiceParams.allocation,
Cost = InvoiceParams#payproc_InvoiceParams.cost, Cost = InvoiceParams#payproc_InvoiceParams.cost,
@ -160,7 +164,11 @@ handle_function_('CreateWithTemplate', {Params}, _Opts) ->
_ = set_invoicing_meta(InvoiceID), _ = set_invoicing_meta(InvoiceID),
TplID = Params#payproc_InvoiceWithTemplateParams.template_id, TplID = Params#payproc_InvoiceWithTemplateParams.template_id,
{Party, Shop, InvoiceParams} = make_invoice_params(Params), {Party, Shop, InvoiceParams} = make_invoice_params(Params),
MerchantTerms = get_merchant_terms(Party, DomainRevision, Shop, hg_datetime:format_now(), InvoiceParams), VS = #{
cost => InvoiceParams#payproc_InvoiceParams.cost,
shop_id => Shop#domain_Shop.id
},
MerchantTerms = hg_invoice_utils:get_merchant_terms(Party, Shop, DomainRevision, hg_datetime:format_now(), VS),
ok = validate_invoice_params(InvoiceParams, Shop, MerchantTerms), ok = validate_invoice_params(InvoiceParams, Shop, MerchantTerms),
AllocationPrototype = InvoiceParams#payproc_InvoiceParams.allocation, AllocationPrototype = InvoiceParams#payproc_InvoiceParams.allocation,
Cost = InvoiceParams#payproc_InvoiceParams.cost, Cost = InvoiceParams#payproc_InvoiceParams.cost,
@ -241,7 +249,11 @@ handle_function_('Repair', {InvoiceID, Changes, Action, Params}, _Opts) ->
repair(InvoiceID, {changes, Changes, Action, Params}); repair(InvoiceID, {changes, Changes, Action, Params});
handle_function_('RepairWithScenario', {InvoiceID, Scenario}, _Opts) -> handle_function_('RepairWithScenario', {InvoiceID, Scenario}, _Opts) ->
_ = set_invoicing_meta(InvoiceID), _ = set_invoicing_meta(InvoiceID),
repair(InvoiceID, {scenario, Scenario}). repair(InvoiceID, {scenario, Scenario});
handle_function_('GetPaymentRoutesLimitValues', {InvoiceID, PaymentID}, _Opts) ->
_ = set_invoicing_meta(InvoiceID, PaymentID),
St = get_state(InvoiceID),
hg_invoice_payment:get_limit_values(get_payment_session(PaymentID, St), get_payment_opts(St)).
maybe_allocation(undefined, _Cost, _MerchantTerms, _Party, _Shop) -> maybe_allocation(undefined, _Cost, _MerchantTerms, _Party, _Shop) ->
undefined; undefined;
@ -1312,24 +1324,6 @@ validate_invoice_cost(Cost, Shop, #domain_TermSet{payments = PaymentTerms}) ->
_ = hg_invoice_utils:assert_cost_payable(Cost, PaymentTerms), _ = hg_invoice_utils:assert_cost_payable(Cost, PaymentTerms),
ok. ok.
get_merchant_terms(#domain_Party{id = PartyId, revision = PartyRevision}, Revision, Shop, Timestamp, Params) ->
VS = #{
cost => Params#payproc_InvoiceParams.cost,
shop_id => Shop#domain_Shop.id
},
{Client, Context} = get_party_client(),
{ok, TermSet} = party_client_thrift:compute_contract_terms(
PartyId,
Shop#domain_Shop.contract_id,
Timestamp,
{revision, PartyRevision},
Revision,
hg_varset:prepare_contract_terms_varset(VS),
Client,
Context
),
TermSet.
make_invoice_cart(_, {cart, Cart}, _Shop) -> make_invoice_cart(_, {cart, Cart}, _Shop) ->
Cart; Cart;
make_invoice_cart(Cost, {product, TplProduct}, Shop) -> make_invoice_cart(Cost, {product, TplProduct}, Shop) ->
@ -1397,10 +1391,6 @@ make_invoice_context(Context, _) ->
%% %%
get_party_client() ->
Ctx = hg_context:load(),
{hg_context:get_party_client(Ctx), hg_context:get_party_client_context(Ctx)}.
log_changes(Changes, St) -> log_changes(Changes, St) ->
lists:foreach(fun(C) -> log_change(C, St) end, Changes). lists:foreach(fun(C) -> log_change(C, St) end, Changes).

View File

@ -76,13 +76,13 @@
-export([accept_chargeback/3]). -export([accept_chargeback/3]).
-export([reopen_chargeback/3]). -export([reopen_chargeback/3]).
-export([get_merchant_terms/5]).
-export([get_provider_terminal_terms/3]). -export([get_provider_terminal_terms/3]).
-export([calculate_cashflow/10]). -export([calculate_cashflow/3]).
-export([create_session_event_context/3]). -export([create_session_event_context/3]).
-export([add_session/3]). -export([add_session/3]).
-export([accrue_status_timing/3]). -export([accrue_status_timing/3]).
-export([get_limit_values/2]).
%% Machine like %% Machine like
@ -125,6 +125,7 @@
-export_type([change/0]). -export_type([change/0]).
-export_type([change_opts/0]). -export_type([change_opts/0]).
-export_type([action/0]). -export_type([action/0]).
-export_type([cashflow_context/0]).
-type activity() :: -type activity() ::
payment_activity() payment_activity()
@ -205,6 +206,7 @@
-type recurrent_paytool_service_terms() :: dmsl_domain_thrift:'RecurrentPaytoolsServiceTerms'(). -type recurrent_paytool_service_terms() :: dmsl_domain_thrift:'RecurrentPaytoolsServiceTerms'().
-type session() :: hg_session:t(). -type session() :: hg_session:t().
-type payment_plan_id() :: hg_accounting:plan_id(). -type payment_plan_id() :: hg_accounting:plan_id().
-type route_limit_context() :: dmsl_payproc_thrift:'RouteLimitContext'().
-type opts() :: #{ -type opts() :: #{
party => party(), party => party(),
@ -212,6 +214,17 @@
timestamp => hg_datetime:timestamp() timestamp => hg_datetime:timestamp()
}. }.
-type cashflow_context() :: #{
provision_terms := dmsl_domain_thrift:'PaymentsProvisionTerms'(),
route := route(),
payment := payment(),
timestamp := hg_datetime:timestamp(),
varset := hg_varset:varset(),
revision := hg_domain:revision(),
merchant_terms => dmsl_domain_thrift:'PaymentsServiceTerms'(),
allocation => hg_allocation:allocation()
}.
%% %%
-include("domain.hrl"). -include("domain.hrl").
@ -440,29 +453,9 @@ init_(PaymentID, Params, Opts = #{timestamp := CreatedAt}) ->
get_merchant_payments_terms(Opts, Revision, Timestamp, VS) -> get_merchant_payments_terms(Opts, Revision, Timestamp, VS) ->
Party = get_party(Opts), Party = get_party(Opts),
Shop = get_shop(Opts), Shop = get_shop(Opts),
TermSet = get_merchant_terms(Party, Shop, Revision, Timestamp, VS), TermSet = hg_invoice_utils:get_merchant_terms(Party, Shop, Revision, Timestamp, VS),
TermSet#domain_TermSet.payments. TermSet#domain_TermSet.payments.
-spec get_merchant_terms(party(), shop(), hg_domain:revision(), hg_datetime:timestamp(), hg_varset:varset()) ->
dmsl_domain_thrift:'TermSet'().
get_merchant_terms(Party, Shop, DomainRevision, Timestamp, VS) ->
ContractID = Shop#domain_Shop.contract_id,
Contract = hg_party:get_contract(ContractID, Party),
ok = assert_contract_active(Contract),
PreparedVS = hg_varset:prepare_contract_terms_varset(VS),
{Client, Context} = get_party_client(),
{ok, Terms} = party_client_thrift:compute_contract_terms(
Party#domain_Party.id,
ContractID,
Timestamp,
{revision, Party#domain_Party.revision},
DomainRevision,
PreparedVS,
Client,
Context
),
Terms.
-spec get_provider_terminal_terms(route(), hg_varset:varset(), hg_domain:revision()) -> -spec get_provider_terminal_terms(route(), hg_varset:varset(), hg_domain:revision()) ->
dmsl_domain_thrift:'PaymentsProvisionTerms'() | undefined. dmsl_domain_thrift:'PaymentsProvisionTerms'() | undefined.
get_provider_terminal_terms(?route(ProviderRef, TerminalRef), VS, Revision) -> get_provider_terminal_terms(?route(ProviderRef, TerminalRef), VS, Revision) ->
@ -478,11 +471,6 @@ get_provider_terminal_terms(?route(ProviderRef, TerminalRef), VS, Revision) ->
), ),
TermsSet#domain_ProvisionTermSet.payments. TermsSet#domain_ProvisionTermSet.payments.
assert_contract_active(#domain_Contract{status = {active, _}}) ->
ok;
assert_contract_active(#domain_Contract{status = Status}) ->
throw(#payproc_InvalidContractStatus{status = Status}).
-spec construct_payer(payer_params(), shop()) -> {ok, payer(), map()}. -spec construct_payer(payer_params(), shop()) -> {ok, payer(), map()}.
construct_payer( construct_payer(
{payment_resource, #payproc_PaymentResourcePayerParams{ {payment_resource, #payproc_PaymentResourcePayerParams{
@ -564,7 +552,7 @@ construct_payment(
payment_tool => PaymentTool, payment_tool => PaymentTool,
cost => Cost cost => Cost
}, },
Terms = get_merchant_terms(Party, Shop, Revision, CreatedAt, VS1), Terms = hg_invoice_utils:get_merchant_terms(Party, Shop, Revision, CreatedAt, VS1),
#domain_TermSet{payments = PaymentTerms, recurrent_paytools = RecurrentTerms} = Terms, #domain_TermSet{payments = PaymentTerms, recurrent_paytools = RecurrentTerms} = Terms,
ok = validate_payment_tool( ok = validate_payment_tool(
PaymentTool, PaymentTool,
@ -948,22 +936,6 @@ collect_validation_varset(Party, Shop, Payment, VS) ->
%% %%
construct_final_cashflow(Cashflow, Context, AccountMap) ->
hg_cashflow:finalize(Cashflow, Context, AccountMap).
collect_cash_flow_context(
#domain_InvoicePayment{cost = Cost}
) ->
#{
operation_amount => Cost
};
collect_cash_flow_context(
#domain_InvoicePaymentRefund{cash = Cash}
) ->
#{
operation_amount => Cash
}.
-spec construct_payment_plan_id(st()) -> payment_plan_id(). -spec construct_payment_plan_id(st()) -> payment_plan_id().
construct_payment_plan_id(#st{opts = Opts, payment = Payment, routes = Routes}) -> construct_payment_plan_id(#st{opts = Opts, payment = Payment, routes = Routes}) ->
construct_payment_plan_id(get_invoice(Opts), Payment, Routes, normal). construct_payment_plan_id(get_invoice(Opts), Payment, Routes, normal).
@ -1093,8 +1065,17 @@ partial_capture(St0, Reason, Cost, Cart, Opts, MerchantTerms, Timestamp, Allocat
Route = get_route(St), Route = get_route(St),
ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision), ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
ok = validate_provider_holds_terms(ProviderTerms), ok = validate_provider_holds_terms(ProviderTerms),
FinalCashflow = Context = #{
calculate_cashflow(Route, Payment2, ProviderTerms, MerchantTerms, VS, Revision, Opts, Timestamp, Allocation), provision_terms => ProviderTerms,
merchant_terms => MerchantTerms,
route => Route,
payment => Payment2,
timestamp => Timestamp,
varset => VS,
revision => Revision,
allocation => Allocation
},
FinalCashflow = calculate_cashflow(Context, Opts),
Changes = start_partial_capture(Reason, Cost, Cart, FinalCashflow, Allocation), Changes = start_partial_capture(Reason, Cost, Cart, FinalCashflow, Allocation),
{ok, {Changes, hg_machine_action:instant()}}. {ok, {Changes, hg_machine_action:instant()}}.
@ -1318,27 +1299,24 @@ marshal_allocation_sub_details(no_transaction_to_sub) ->
make_refund_cashflow(Refund, Payment, Revision, St, Opts, MerchantTerms, VS, Timestamp) -> make_refund_cashflow(Refund, Payment, Revision, St, Opts, MerchantTerms, VS, Timestamp) ->
Route = get_route(St), Route = get_route(St),
Party = get_party(Opts),
Shop = get_shop(Opts),
ProviderPaymentsTerms = get_provider_terminal_terms(Route, VS, Revision), ProviderPaymentsTerms = get_provider_terminal_terms(Route, VS, Revision),
ProviderTerms = get_provider_refunds_terms(ProviderPaymentsTerms, Refund, Payment),
Allocation = Refund#domain_InvoicePaymentRefund.allocation, Allocation = Refund#domain_InvoicePaymentRefund.allocation,
Provider = get_route_provider(Route, Revision), CollectCashflowContext = genlib_map:compact(#{
collect_cashflow( operation => refund,
refund, provision_terms => get_provider_refunds_terms(ProviderPaymentsTerms, Refund, Payment),
ProviderTerms, merchant_terms => MerchantTerms,
MerchantTerms, party => get_party(Opts),
Party, shop => get_shop(Opts),
Shop, route => Route,
Route, payment => Payment,
Allocation, provider => get_route_provider(Route, Revision),
Payment, timestamp => Timestamp,
Refund, varset => VS,
Provider, revision => Revision,
Timestamp, refund => Refund,
VS, allocation => Allocation
Revision }),
). hg_cashflow_utils:collect_cashflow(CollectCashflowContext).
assert_refund_cash(Cash, St) -> assert_refund_cash(Cash, St) ->
PaymentAmount = get_remaining_payment_amount(Cash, St), PaymentAmount = get_remaining_payment_amount(Cash, St),
@ -1491,235 +1469,6 @@ validate_common_refund_terms(Terms, Refund, Payment) ->
), ),
ok. ok.
collect_cashflow(
OpType,
ProvisionTerms,
MerchantTerms,
Party,
Shop,
Route,
Allocation,
Payment,
ContextSource,
Provider,
Timestamp,
VS,
Revision
) ->
PaymentInstitution = get_cashflow_payment_institution(Party, Shop, VS, Revision),
collect_cashflow(
OpType,
ProvisionTerms,
MerchantTerms,
Party,
Shop,
PaymentInstitution,
Route,
Allocation,
Payment,
ContextSource,
Provider,
Timestamp,
VS,
Revision
).
collect_cashflow(
OpType,
ProvisionTerms,
MerchantTerms,
Party,
Shop,
PaymentInstitution,
Route,
undefined,
Payment,
ContextSource,
Provider,
Timestamp,
VS,
Revision
) ->
Amount = get_context_source_amount(ContextSource),
CF = construct_transaction_cashflow(
OpType,
Party,
Shop,
PaymentInstitution,
Route,
Amount,
MerchantTerms,
Timestamp,
Payment,
Provider,
VS,
Revision
),
ProviderCashflowSelector = get_provider_cashflow_selector(ProvisionTerms),
ProviderCashflow = construct_provider_cashflow(
ProviderCashflowSelector,
PaymentInstitution,
Party,
Shop,
Route,
ContextSource,
Payment,
Provider,
VS,
Revision
),
CF ++ ProviderCashflow;
collect_cashflow(
OpType,
ProvisionTerms,
_MerchantTerms,
Party,
Shop,
PaymentInstitution,
Route,
?allocation(Transactions),
Payment,
ContextSource,
Provider,
Timestamp,
VS0,
Revision
) ->
CF = lists:foldl(
fun(?allocation_trx(_ID, Target, Amount), Acc) ->
?allocation_trx_target_shop(PartyID, ShopID) = Target,
TargetParty = hg_party:get_party(PartyID),
TargetShop = hg_party:get_shop(ShopID, TargetParty),
VS1 = VS0#{
party_id => Party#domain_Party.id,
shop_id => Shop#domain_Shop.id,
cost => Amount
},
AllocationPaymentInstitution =
get_cashflow_payment_institution(Party, Shop, VS1, Revision),
construct_transaction_cashflow(
OpType,
TargetParty,
TargetShop,
AllocationPaymentInstitution,
Route,
Amount,
undefined,
Timestamp,
Payment,
Provider,
VS1,
Revision
) ++ Acc
end,
[],
Transactions
),
ProviderCashflowSelector = get_provider_cashflow_selector(ProvisionTerms),
ProviderCashflow = construct_provider_cashflow(
ProviderCashflowSelector,
PaymentInstitution,
Party,
Shop,
Route,
ContextSource,
Payment,
Provider,
VS0,
Revision
),
CF ++ ProviderCashflow.
get_cashflow_payment_institution(Party, Shop, VS, Revision) ->
Contract = hg_party:get_contract(Shop#domain_Shop.contract_id, Party),
PaymentInstitutionRef = Contract#domain_Contract.payment_institution,
hg_payment_institution:compute_payment_institution(
PaymentInstitutionRef,
VS,
Revision
).
get_context_source_amount(#domain_InvoicePayment{cost = Cost}) ->
Cost;
get_context_source_amount(#domain_InvoicePaymentRefund{cash = Cash}) ->
Cash.
get_provider_cashflow_selector(#domain_PaymentsProvisionTerms{cash_flow = ProviderCashflowSelector}) ->
ProviderCashflowSelector;
get_provider_cashflow_selector(#domain_PaymentRefundsProvisionTerms{cash_flow = ProviderCashflowSelector}) ->
ProviderCashflowSelector.
construct_transaction_cashflow(
OpType,
Party,
Shop,
PaymentInstitution,
Route,
Amount,
MerchantPaymentsTerms0,
Timestamp,
Payment,
Provider,
VS,
Revision
) ->
MerchantPaymentsTerms1 =
case MerchantPaymentsTerms0 of
undefined ->
TermSet = get_merchant_terms(Party, Shop, Revision, Timestamp, VS),
TermSet#domain_TermSet.payments;
_ ->
MerchantPaymentsTerms0
end,
MerchantCashflowSelector = get_terms_cashflow(OpType, MerchantPaymentsTerms1),
MerchantCashflow = get_selector_value(merchant_payment_fees, MerchantCashflowSelector),
AccountMap = hg_accounting:collect_account_map(
Payment,
Party,
Shop,
Route,
PaymentInstitution,
Provider,
VS,
Revision
),
Context = #{
operation_amount => Amount
},
construct_final_cashflow(MerchantCashflow, Context, AccountMap).
get_terms_cashflow(payment, MerchantPaymentsTerms) ->
MerchantPaymentsTerms#domain_PaymentsServiceTerms.fees;
get_terms_cashflow(refund, MerchantPaymentsTerms) ->
MerchantRefundTerms = MerchantPaymentsTerms#domain_PaymentsServiceTerms.refunds,
MerchantRefundTerms#domain_PaymentRefundsServiceTerms.fees.
construct_provider_cashflow(
ProviderCashflowSelector,
PaymentInstitution,
Party,
Shop,
Route,
ContextSource,
Payment,
Provider,
VS,
Revision
) ->
ProviderCashflow = get_selector_value(provider_payment_cash_flow, ProviderCashflowSelector),
Context = collect_cash_flow_context(ContextSource),
AccountMap = hg_accounting:collect_account_map(
Payment,
Party,
Shop,
Route,
PaymentInstitution,
Provider,
VS,
Revision
),
construct_final_cashflow(ProviderCashflow, Context, AccountMap).
%% %%
-spec create_adjustment(hg_datetime:timestamp(), adjustment_params(), st(), opts()) -> {adjustment(), result()}. -spec create_adjustment(hg_datetime:timestamp(), adjustment_params(), st(), opts()) -> {adjustment(), result()}.
@ -1748,7 +1497,16 @@ create_cash_flow_adjustment(Timestamp, Params, DomainRevision, St, Opts) ->
OldCashFlow = get_final_cashflow(St), OldCashFlow = get_final_cashflow(St),
VS = collect_validation_varset(St, Opts), VS = collect_validation_varset(St, Opts),
Allocation = get_allocation(St), Allocation = get_allocation(St),
NewCashFlow = calculate_cashflow(Route, Payment, Timestamp, VS, NewRevision, Opts, Allocation), Context = #{
provision_terms => get_provider_terminal_terms(Route, VS, NewRevision),
route => Route,
payment => Payment,
timestamp => Timestamp,
varset => VS,
revision => NewRevision,
allocation => Allocation
},
NewCashFlow = calculate_cashflow(Context, Opts),
AdjState = AdjState =
{cash_flow, #domain_InvoicePaymentAdjustmentCashFlowState{ {cash_flow, #domain_InvoicePaymentAdjustmentCashFlowState{
scenario = #domain_InvoicePaymentAdjustmentCashFlow{domain_revision = DomainRevision} scenario = #domain_InvoicePaymentAdjustmentCashFlow{domain_revision = DomainRevision}
@ -1866,90 +1624,40 @@ get_cash_flow_for_target_status({captured, Captured}, St0, Opts) ->
St = St0#st{payment = Payment}, St = St0#st{payment = Payment},
Revision = Payment#domain_InvoicePayment.domain_revision, Revision = Payment#domain_InvoicePayment.domain_revision,
VS = collect_validation_varset(St, Opts), VS = collect_validation_varset(St, Opts),
calculate_cashflow(Route, Payment, Timestamp, VS, Revision, Opts, Allocation); Context = #{
provision_terms => get_provider_terminal_terms(Route, VS, Revision),
route => Route,
payment => Payment,
timestamp => Timestamp,
varset => VS,
revision => Revision,
allocation => Allocation
},
calculate_cashflow(Context, Opts);
get_cash_flow_for_target_status({cancelled, _}, _St, _Opts) -> get_cash_flow_for_target_status({cancelled, _}, _St, _Opts) ->
[]; [];
get_cash_flow_for_target_status({failed, _}, _St, _Opts) -> get_cash_flow_for_target_status({failed, _}, _St, _Opts) ->
[]. [].
-spec calculate_cashflow( -spec calculate_cashflow(cashflow_context(), opts()) -> final_cash_flow().
route(), calculate_cashflow(Context = #{route := Route, revision := Revision}, Opts) ->
payment(), CollectCashflowContext = genlib_map:compact(Context#{
hg_datetime:timestamp(), operation => payment,
hg_varset:varset(), party => get_party(Opts),
hg_domain:revision(), shop => get_shop(Opts),
opts(), provider => get_route_provider(Route, Revision)
hg_allocation:allocation() }),
) -> final_cash_flow(). hg_cashflow_utils:collect_cashflow(CollectCashflowContext).
calculate_cashflow(Route, Payment, Timestamp, VS, Revision, Opts, Allocation) ->
ProviderTerms = get_provider_terminal_terms(Route, VS, Revision),
calculate_cashflow(Route, Payment, ProviderTerms, undefined, VS, Revision, Opts, Timestamp, Allocation).
-spec calculate_cashflow( -spec calculate_cashflow(hg_payment_institution:t(), cashflow_context(), opts()) -> final_cash_flow().
route(), calculate_cashflow(PaymentInstitution, Context = #{route := Route, revision := Revision}, Opts) ->
payment(), CollectCashflowContext = genlib_map:compact(Context#{
dmsl_domain_thrift:'PaymentsProvisionTerms'() | undefined, operation => payment,
dmsl_domain_thrift:'PaymentsServiceTerms'() | undefined, party => get_party(Opts),
hg_varset:varset(), shop => get_shop(Opts),
hg_domain:revision(), provider => get_route_provider(Route, Revision)
opts(), }),
hg_datetime:timestamp(), hg_cashflow_utils:collect_cashflow(PaymentInstitution, CollectCashflowContext).
hg_allocation:allocation()
) -> final_cash_flow().
calculate_cashflow(Route, Payment, ProviderTerms, MerchantTerms, VS, Revision, Opts, Timestamp, Allocation) ->
Provider = get_route_provider(Route, Revision),
Party = get_party(Opts),
Shop = get_shop(Opts),
collect_cashflow(
payment,
ProviderTerms,
MerchantTerms,
Party,
Shop,
Route,
Allocation,
Payment,
Payment,
Provider,
Timestamp,
VS,
Revision
).
-spec calculate_cashflow(
route(),
payment(),
hg_payment_institution:t(),
dmsl_domain_thrift:'PaymentsProvisionTerms'(),
dmsl_domain_thrift:'PaymentsServiceTerms'(),
hg_varset:varset(),
hg_domain:revision(),
opts(),
hg_datetime:timestamp(),
hg_allocation:allocation() | undefined
) -> final_cash_flow().
calculate_cashflow(
Route, Payment, PaymentInstitution, ProviderTerms, MerchantTerms, VS, Revision, Opts, Timestamp, Allocation
) ->
Provider = get_route_provider(Route, Revision),
Party = get_party(Opts),
Shop = get_shop(Opts),
collect_cashflow(
payment,
ProviderTerms,
MerchantTerms,
Party,
Shop,
PaymentInstitution,
Route,
Allocation,
Payment,
Payment,
Provider,
Timestamp,
VS,
Revision
).
-spec construct_adjustment( -spec construct_adjustment(
Timestamp :: hg_datetime:timestamp(), Timestamp :: hg_datetime:timestamp(),
@ -2245,6 +1953,26 @@ process_risk_score(Action, St) ->
-spec process_routing(action(), st()) -> machine_result(). -spec process_routing(action(), st()) -> machine_result().
process_routing(Action, St) -> process_routing(Action, St) ->
{PaymentInstitution, VS, Revision} = route_args(St),
try
AllRoutes = get_candidates(PaymentInstitution, VS, Revision, St),
AvailableRoutes = filter_out_attempted_routes(AllRoutes, St),
%% Since this is routing step then current attempt is not yet accounted for in `St`.
Iter = get_iter(St) + 1,
Events = handle_gathered_route_result(
filter_limit_overflow_routes(AvailableRoutes, VS, Iter, St),
[hg_routing:to_payment_route(R) || R <- AllRoutes],
[hg_routing:to_payment_route(R) || R <- AvailableRoutes],
Revision,
St
),
{next, {Events, hg_machine_action:set_timeout(0, Action)}}
catch
throw:{no_route_found, Reason} ->
handle_choose_route_error(Reason, [], St, Action)
end.
route_args(St) ->
Opts = get_opts(St), Opts = get_opts(St),
Revision = get_payment_revision(St), Revision = get_payment_revision(St),
Payment = get_payment(St), Payment = get_payment(St),
@ -2255,29 +1983,15 @@ process_routing(Action, St) ->
VS2 = collect_refund_varset(MerchantTerms#domain_PaymentsServiceTerms.refunds, PaymentTool, VS1), VS2 = collect_refund_varset(MerchantTerms#domain_PaymentsServiceTerms.refunds, PaymentTool, VS1),
VS3 = collect_chargeback_varset(MerchantTerms#domain_PaymentsServiceTerms.chargebacks, VS2), VS3 = collect_chargeback_varset(MerchantTerms#domain_PaymentsServiceTerms.chargebacks, VS2),
PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision), PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision),
try {PaymentInstitution, VS3, Revision}.
get_candidates(PaymentInstitution, VS, Revision, St) ->
Payer = get_payment_payer(St), Payer = get_payment_payer(St),
AllRoutes =
case get_predefined_route(Payer) of case get_predefined_route(Payer) of
{ok, PaymentRoute} -> {ok, PaymentRoute} ->
[hg_routing:from_payment_route(PaymentRoute)]; [hg_routing:from_payment_route(PaymentRoute)];
undefined -> undefined ->
gather_routes(PaymentInstitution, VS3, Revision, St) gather_routes(PaymentInstitution, VS, Revision, St)
end,
AvailableRoutes = filter_out_attempted_routes(AllRoutes, St),
%% Since this is routing step then current attempt is not yet accounted for in `St`.
Iter = get_iter(St) + 1,
Events = handle_gathered_route_result(
filter_limit_overflow_routes(AvailableRoutes, VS3, Iter, St),
[hg_routing:to_payment_route(R) || R <- AllRoutes],
[hg_routing:to_payment_route(R) || R <- AvailableRoutes],
Revision,
St
),
{next, {Events, hg_machine_action:set_timeout(0, Action)}}
catch
throw:{no_route_found, Reason} ->
handle_choose_route_error(Reason, [], St, Action)
end. end.
filter_out_attempted_routes(Routes, #st{routes = AttemptedRoutes}) -> filter_out_attempted_routes(Routes, #st{routes = AttemptedRoutes}) ->
@ -2337,17 +2051,16 @@ process_cash_flow_building(Action, St) ->
VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0), VS1 = collect_validation_varset(get_party(Opts), get_shop(Opts), Payment, VS0),
ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision), ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision),
Allocation = get_allocation(St), Allocation = get_allocation(St),
FinalCashflow = calculate_cashflow( Context = #{
Route, provision_terms => ProviderTerms,
Payment, route => Route,
ProviderTerms, payment => Payment,
undefined, timestamp => Timestamp,
VS1, varset => VS1,
Revision, revision => Revision,
Opts, allocation => Allocation
Timestamp, },
Allocation FinalCashflow = calculate_cashflow(Context, Opts),
),
_ = rollback_unused_payment_limits(St), _ = rollback_unused_payment_limits(St),
_Clock = hg_accounting:hold( _Clock = hg_accounting:hold(
construct_payment_plan_id(St), construct_payment_plan_id(St),
@ -3418,7 +3131,7 @@ get_routing_attempt_limit(
Party = hg_party:checkout(PartyID, {revision, PartyRevision}), Party = hg_party:checkout(PartyID, {revision, PartyRevision}),
Shop = hg_party:get_shop(ShopID, Party), Shop = hg_party:get_shop(ShopID, Party),
VS = collect_validation_varset(Party, Shop, get_payment(St), #{}), VS = collect_validation_varset(Party, Shop, get_payment(St), #{}),
Terms = get_merchant_terms(Party, Shop, Revision, CreatedAt, VS), Terms = hg_invoice_utils:get_merchant_terms(Party, Shop, Revision, CreatedAt, VS),
#domain_TermSet{payments = PaymentTerms} = Terms, #domain_TermSet{payments = PaymentTerms} = Terms,
log_cascade_attempt_context(PaymentTerms, St), log_cascade_attempt_context(PaymentTerms, St),
get_routing_attempt_limit_value(PaymentTerms#domain_PaymentsServiceTerms.attempt_limit). get_routing_attempt_limit_value(PaymentTerms#domain_PaymentsServiceTerms.attempt_limit).
@ -3504,6 +3217,28 @@ accrue_status_timing(Name, Opts, #st{timings = Timings}) ->
EventTime = define_event_timestamp(Opts), EventTime = define_event_timestamp(Opts),
hg_timings:mark(Name, EventTime, hg_timings:accrue(Name, started, EventTime, Timings)). hg_timings:mark(Name, EventTime, hg_timings:accrue(Name, started, EventTime, Timings)).
-spec get_limit_values(st()) -> route_limit_context().
get_limit_values(St) ->
{PaymentInstitution, VS, Revision} = route_args(St),
Routes = get_candidates(PaymentInstitution, VS, Revision, St),
Payment = get_payment(St),
Invoice = get_invoice(get_opts(St)),
lists:foldl(
fun(Route, Acc) ->
PaymentRoute = hg_routing:to_payment_route(Route),
ProviderTerms = hg_routing:get_payment_terms(PaymentRoute, VS, Revision),
TurnoverLimits = get_turnover_limits(ProviderTerms),
TurnoverLimitValues = hg_limiter:get_limit_values(TurnoverLimits, Invoice, Payment, PaymentRoute),
Acc#{PaymentRoute => TurnoverLimitValues}
end,
#{},
Routes
).
-spec get_limit_values(st(), opts()) -> route_limit_context().
get_limit_values(St, Opts) ->
get_limit_values(St#st{opts = Opts}).
try_accrue_waiting_timing(Opts, #st{payment = Payment, timings = Timings}) -> try_accrue_waiting_timing(Opts, #st{payment = Payment, timings = Timings}) ->
case get_payment_flow(Payment) of case get_payment_flow(Payment) of
?invoice_payment_flow_instant() -> ?invoice_payment_flow_instant() ->

View File

@ -428,7 +428,17 @@ build_chargeback_final_cash_flow(State, Opts) ->
PaymentInstitutionRef = get_payment_institution_ref(get_contract(Party, Shop)), PaymentInstitutionRef = get_payment_institution_ref(get_contract(Party, Shop)),
PaymentInst = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS, Revision), PaymentInst = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS, Revision),
Provider = get_route_provider(Route, Revision), Provider = get_route_provider(Route, Revision),
AccountMap = hg_accounting:collect_account_map(Payment, Party, Shop, Route, PaymentInst, Provider, VS, Revision), CollectAccountContext = #{
payment => Payment,
party => Party,
shop => Shop,
route => Route,
payment_institution => PaymentInst,
provider => Provider,
varset => VS,
revision => Revision
},
AccountMap = hg_accounting:collect_account_map(CollectAccountContext),
ServiceContext = build_service_cash_flow_context(State), ServiceContext = build_service_cash_flow_context(State),
ProviderContext = build_provider_cash_flow_context(State, ProviderFees), ProviderContext = build_provider_cash_flow_context(State, ProviderFees),
ServiceFinalCF = hg_cashflow:finalize(ServiceCashFlow, ServiceContext, AccountMap), ServiceFinalCF = hg_cashflow:finalize(ServiceCashFlow, ServiceContext, AccountMap),

View File

@ -72,18 +72,16 @@ init_(PaymentID, Params, Opts = #{timestamp := CreatedAt0}) ->
MerchantTerms = get_merchant_payment_terms(Party, Shop, Revision, CreatedAt1, VS), MerchantTerms = get_merchant_payment_terms(Party, Shop, Revision, CreatedAt1, VS),
ProviderTerms = hg_invoice_payment:get_provider_terminal_terms(Route, VS, Revision), ProviderTerms = hg_invoice_payment:get_provider_terminal_terms(Route, VS, Revision),
FinalCashflow = hg_invoice_payment:calculate_cashflow( CashflowContext = #{
Route, provision_terms => ProviderTerms,
Payment, merchant_terms => MerchantTerms,
PaymentInstitution, route => Route,
ProviderTerms, payment => Payment,
MerchantTerms, timestamp => CreatedAt1,
VS, varset => VS,
Revision, revision => Revision
Opts, },
CreatedAt1, FinalCashflow = hg_invoice_payment:calculate_cashflow(PaymentInstitution, CashflowContext, Opts),
undefined
),
Events = Events =
[ [
@ -182,7 +180,7 @@ maybe_risk_score_event_list(RiskScore) ->
[?risk_score_changed(RiskScore)]. [?risk_score_changed(RiskScore)].
get_merchant_payment_terms(Party, Shop, DomainRevision, Timestamp, VS) -> get_merchant_payment_terms(Party, Shop, DomainRevision, Timestamp, VS) ->
TermSet = hg_invoice_payment:get_merchant_terms(Party, Shop, DomainRevision, Timestamp, VS), TermSet = hg_invoice_utils:get_merchant_terms(Party, Shop, DomainRevision, Timestamp, VS),
TermSet#domain_TermSet.payments. TermSet#domain_TermSet.payments.
hold_payment_limits(Invoice, Payment, St) -> hold_payment_limits(Invoice, Payment, St) ->

View File

@ -15,6 +15,7 @@
-export([assert_shop_operable/1]). -export([assert_shop_operable/1]).
-export([assert_cost_payable/2]). -export([assert_cost_payable/2]).
-export([compute_shop_terms/5]). -export([compute_shop_terms/5]).
-export([get_merchant_terms/5]).
-export([get_shop_currency/1]). -export([get_shop_currency/1]).
-export([get_cart_amount/1]). -export([get_cart_amount/1]).
-export([check_deadline/1]). -export([check_deadline/1]).
@ -111,6 +112,30 @@ compute_shop_terms(PartyID, ShopID, Timestamp, PartyRevision, Varset) ->
party_client_thrift:compute_shop_terms(PartyID, ShopID, Timestamp, PartyRevision, Varset, Client, Context), party_client_thrift:compute_shop_terms(PartyID, ShopID, Timestamp, PartyRevision, Varset, Client, Context),
TermSet. TermSet.
-spec get_merchant_terms(party(), shop(), hg_domain:revision(), hg_datetime:timestamp(), hg_varset:varset()) ->
term_set().
get_merchant_terms(Party, Shop, DomainRevision, Timestamp, VS) ->
ContractID = Shop#domain_Shop.contract_id,
Contract = hg_party:get_contract(ContractID, Party),
ok = assert_contract_active(Contract),
{Client, Context} = get_party_client(),
{ok, Terms} = party_client_thrift:compute_contract_terms(
Party#domain_Party.id,
ContractID,
Timestamp,
{revision, Party#domain_Party.revision},
DomainRevision,
hg_varset:prepare_contract_terms_varset(VS),
Client,
Context
),
Terms.
assert_contract_active(#domain_Contract{status = {active, _}}) ->
ok;
assert_contract_active(#domain_Contract{status = Status}) ->
throw(#payproc_InvalidContractStatus{status = Status}).
validate_currency_(Currency, Currency) -> validate_currency_(Currency, Currency) ->
ok; ok;
validate_currency_(_, _) -> validate_currency_(_, _) ->

View File

@ -1,5 +1,6 @@
-module(hg_limiter). -module(hg_limiter).
-include_lib("damsel/include/dmsl_payproc_thrift.hrl").
-include_lib("damsel/include/dmsl_domain_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl").
-include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_base_thrift.hrl").
-include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -include_lib("limiter_proto/include/limproto_base_thrift.hrl").
@ -14,6 +15,7 @@
-type refund() :: hg_invoice_payment:domain_refund(). -type refund() :: hg_invoice_payment:domain_refund().
-type cash() :: dmsl_domain_thrift:'Cash'(). -type cash() :: dmsl_domain_thrift:'Cash'().
-type handling_flag() :: ignore_business_error. -type handling_flag() :: ignore_business_error.
-type turnover_limit_value() :: dmsl_payproc_thrift:'TurnoverLimitValue'().
-type change_queue() :: [hg_limiter_client:limit_change()]. -type change_queue() :: [hg_limiter_client:limit_change()].
@ -25,6 +27,7 @@
-export([commit_refund_limits/5]). -export([commit_refund_limits/5]).
-export([rollback_payment_limits/6]). -export([rollback_payment_limits/6]).
-export([rollback_refund_limits/5]). -export([rollback_refund_limits/5]).
-export([get_limit_values/4]).
-define(route(ProviderRef, TerminalRef), #domain_PaymentRoute{ -define(route(ProviderRef, TerminalRef), #domain_PaymentRoute{
provider = ProviderRef, provider = ProviderRef,
@ -39,6 +42,23 @@ get_turnover_limits({value, Limits}) ->
get_turnover_limits(Ambiguous) -> get_turnover_limits(Ambiguous) ->
error({misconfiguration, {'Could not reduce selector to a value', Ambiguous}}). error({misconfiguration, {'Could not reduce selector to a value', Ambiguous}}).
-spec get_limit_values([turnover_limit()], invoice(), payment(), route()) -> [turnover_limit_value()].
get_limit_values(TurnoverLimits, Invoice, Payment, Route) ->
Context = gen_limit_context(Invoice, Payment, Route),
lists:foldl(
fun(TurnoverLimit, Acc) ->
#domain_TurnoverLimit{id = LimitID, domain_revision = Version} = TurnoverLimit,
Clock = get_latest_clock(),
Limit = hg_limiter_client:get(LimitID, Version, Clock, Context),
#limiter_Limit{
amount = LimiterAmount
} = Limit,
[#payproc_TurnoverLimitValue{limit = TurnoverLimit, value = LimiterAmount} | Acc]
end,
[],
TurnoverLimits
).
-spec check_limits([turnover_limit()], invoice(), payment(), route()) -> -spec check_limits([turnover_limit()], invoice(), payment(), route()) ->
{ok, [hg_limiter_client:limit()]} {ok, [hg_limiter_client:limit()]}
| {error, {limit_overflow, [binary()]}}. | {error, {limit_overflow, [binary()]}}.

View File

@ -16,6 +16,7 @@
-export([new/2]). -export([new/2]).
-export([new/4]). -export([new/4]).
-export([new/5]). -export([new/5]).
-export([new/6]).
-export([to_payment_route/1]). -export([to_payment_route/1]).
-export([to_rejected_route/2]). -export([to_rejected_route/2]).
-export([provider_ref/1]). -export([provider_ref/1]).
@ -32,7 +33,8 @@
terminal_ref :: dmsl_domain_thrift:'TerminalRef'(), terminal_ref :: dmsl_domain_thrift:'TerminalRef'(),
weight :: integer(), weight :: integer(),
priority :: integer(), priority :: integer(),
pin :: pin() pin :: pin(),
fd_overrides :: fd_overrides()
}). }).
-type pin() :: #{ -type pin() :: #{
@ -98,6 +100,7 @@
-type varset() :: hg_varset:varset(). -type varset() :: hg_varset:varset().
-type revision() :: hg_domain:revision(). -type revision() :: hg_domain:revision().
-type fd_overrides() :: dmsl_domain_thrift:'RouteFaultDetectorOverrides'().
-record(route_scores, { -record(route_scores, {
availability_condition :: condition_score(), availability_condition :: condition_score(),
@ -136,12 +139,19 @@ new(ProviderRef, TerminalRef, Weight, Priority) ->
new(ProviderRef, TerminalRef, undefined, Priority, Pin) -> new(ProviderRef, TerminalRef, undefined, Priority, Pin) ->
new(ProviderRef, TerminalRef, ?DOMAIN_CANDIDATE_WEIGHT, Priority, Pin); new(ProviderRef, TerminalRef, ?DOMAIN_CANDIDATE_WEIGHT, Priority, Pin);
new(ProviderRef, TerminalRef, Weight, Priority, Pin) -> new(ProviderRef, TerminalRef, Weight, Priority, Pin) ->
new(ProviderRef, TerminalRef, Weight, Priority, Pin, #domain_RouteFaultDetectorOverrides{}).
-spec new(provider_ref(), terminal_ref(), integer(), integer(), pin(), fd_overrides() | undefined) -> route().
new(ProviderRef, TerminalRef, Weight, Priority, Pin, undefined) ->
new(ProviderRef, TerminalRef, Weight, Priority, Pin, #domain_RouteFaultDetectorOverrides{});
new(ProviderRef, TerminalRef, Weight, Priority, Pin, FdOverrides) ->
#route{ #route{
provider_ref = ProviderRef, provider_ref = ProviderRef,
terminal_ref = TerminalRef, terminal_ref = TerminalRef,
weight = Weight, weight = Weight,
priority = Priority, priority = Priority,
pin = Pin pin = Pin,
fd_overrides = FdOverrides
}. }.
-spec provider_ref(route()) -> provider_ref(). -spec provider_ref(route()) -> provider_ref().
@ -164,6 +174,9 @@ weight(#route{weight = Weight}) ->
pin(#route{pin = Pin}) -> pin(#route{pin = Pin}) ->
Pin. Pin.
fd_overrides(#route{fd_overrides = FdOverrides}) ->
FdOverrides.
-spec from_payment_route(payment_route()) -> route(). -spec from_payment_route(payment_route()) -> route().
from_payment_route(Route) -> from_payment_route(Route) ->
?route(ProviderRef, TerminalRef) = Route, ?route(ProviderRef, TerminalRef) = Route,
@ -260,12 +273,13 @@ collect_routes(Predestination, Candidates, VS, Revision, Ctx) ->
% Looks like overhead, we got Terminal only for provider_ref. Maybe we can remove provider_ref from route(). % Looks like overhead, we got Terminal only for provider_ref. Maybe we can remove provider_ref from route().
% https://github.com/rbkmoney/hellgate/pull/583#discussion_r682745123 % https://github.com/rbkmoney/hellgate/pull/583#discussion_r682745123
#domain_Terminal{ #domain_Terminal{
provider_ref = ProviderRef provider_ref = ProviderRef,
route_fd_overrides = FdOverrides
} = hg_domain:get(Revision, {terminal, TerminalRef}), } = hg_domain:get(Revision, {terminal, TerminalRef}),
GatheredPinInfo = gather_pin_info(Pin, Ctx), GatheredPinInfo = gather_pin_info(Pin, Ctx),
try try
true = acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision), true = acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision),
Route = new(ProviderRef, TerminalRef, Weight, Priority, GatheredPinInfo), Route = new(ProviderRef, TerminalRef, Weight, Priority, GatheredPinInfo, FdOverrides),
{[Route | Accepted], Rejected} {[Route | Accepted], Rejected}
catch catch
{rejected, Reason} -> {rejected, Reason} ->
@ -518,17 +532,22 @@ score_routes_with_fault_detector([]) ->
score_routes_with_fault_detector(Routes) -> score_routes_with_fault_detector(Routes) ->
IDs = build_ids(Routes), IDs = build_ids(Routes),
FDStats = hg_fault_detector_client:get_statistics(IDs), FDStats = hg_fault_detector_client:get_statistics(IDs),
[{R, get_provider_status(provider_ref(R), FDStats)} || R <- Routes]. [{R, get_provider_status(R, FDStats)} || R <- Routes].
-spec get_provider_status(provider_ref(), [fd_service_stats()]) -> provider_status(). -spec get_provider_status(route(), [fd_service_stats()]) -> provider_status().
get_provider_status(ProviderRef, FDStats) -> get_provider_status(Route, FDStats) ->
ProviderRef = provider_ref(Route),
FdOverrides = fd_overrides(Route),
AvailabilityServiceID = build_fd_availability_service_id(ProviderRef), AvailabilityServiceID = build_fd_availability_service_id(ProviderRef),
ConversionServiceID = build_fd_conversion_service_id(ProviderRef), ConversionServiceID = build_fd_conversion_service_id(ProviderRef),
AvailabilityStatus = get_provider_availability_status(AvailabilityServiceID, FDStats), AvailabilityStatus = get_provider_availability_status(FdOverrides, AvailabilityServiceID, FDStats),
ConversionStatus = get_provider_conversion_status(ConversionServiceID, FDStats), ConversionStatus = get_provider_conversion_status(FdOverrides, ConversionServiceID, FDStats),
{AvailabilityStatus, ConversionStatus}. {AvailabilityStatus, ConversionStatus}.
get_provider_availability_status(FDID, Stats) -> get_provider_availability_status(#domain_RouteFaultDetectorOverrides{enabled = true}, _FDID, _Stats) ->
%% ignore fd statistic if set override
{alive, 0.0};
get_provider_availability_status(_, FDID, Stats) ->
AvailabilityConfig = maps:get(availability, genlib_app:env(hellgate, fault_detector, #{}), #{}), AvailabilityConfig = maps:get(availability, genlib_app:env(hellgate, fault_detector, #{}), #{}),
CriticalFailRate = maps:get(critical_fail_rate, AvailabilityConfig, 0.7), CriticalFailRate = maps:get(critical_fail_rate, AvailabilityConfig, 0.7),
case lists:keysearch(FDID, #fault_detector_ServiceStatistics.service_id, Stats) of case lists:keysearch(FDID, #fault_detector_ServiceStatistics.service_id, Stats) of
@ -540,7 +559,10 @@ get_provider_availability_status(FDID, Stats) ->
{alive, 0.0} {alive, 0.0}
end. end.
get_provider_conversion_status(FDID, Stats) -> get_provider_conversion_status(#domain_RouteFaultDetectorOverrides{enabled = true}, _FDID, _Stats) ->
%% ignore fd statistic if set override
{normal, 0.0};
get_provider_conversion_status(_, FDID, Stats) ->
ConversionConfig = maps:get(conversion, genlib_app:env(hellgate, fault_detector, #{}), #{}), ConversionConfig = maps:get(conversion, genlib_app:env(hellgate, fault_detector, #{}), #{}),
CriticalFailRate = maps:get(critical_fail_rate, ConversionConfig, 0.7), CriticalFailRate = maps:get(critical_fail_rate, ConversionConfig, 0.7),
case lists:keysearch(FDID, #fault_detector_ServiceStatistics.service_id, Stats) of case lists:keysearch(FDID, #fault_detector_ServiceStatistics.service_id, Stats) of

View File

@ -40,6 +40,7 @@
-export([register_payment_customer_payer_success/1]). -export([register_payment_customer_payer_success/1]).
-export([payment_limit_success/1]). -export([payment_limit_success/1]).
-export([payment_routes_limit_values/1]).
-export([register_payment_limit_success/1]). -export([register_payment_limit_success/1]).
-export([payment_limit_other_shop_success/1]). -export([payment_limit_other_shop_success/1]).
-export([payment_limit_overflow/1]). -export([payment_limit_overflow/1]).
@ -383,6 +384,7 @@ groups() ->
{operation_limits, [], [ {operation_limits, [], [
payment_limit_success, payment_limit_success,
payment_routes_limit_values,
register_payment_limit_success, register_payment_limit_success,
payment_limit_other_shop_success, payment_limit_other_shop_success,
payment_limit_overflow, payment_limit_overflow,
@ -1254,6 +1256,30 @@ payment_limit_success(C) ->
[?payment_state(_Payment)] [?payment_state(_Payment)]
) = create_payment(PartyID, ShopID, 10000, Client, ?pmt_sys(<<"visa-ref">>)). ) = create_payment(PartyID, ShopID, 10000, Client, ?pmt_sys(<<"visa-ref">>)).
-spec payment_routes_limit_values(config()) -> test_return().
payment_routes_limit_values(C) ->
RootUrl = cfg(root_url, C),
PartyClient = cfg(party_client, C),
#{party_id := PartyID} = cfg(limits, C),
ShopID = hg_ct_helper:create_shop(PartyID, ?cat(8), <<"RUB">>, ?tmpl(1), ?pinst(1), PartyClient),
Client = hg_client_invoicing:start_link(hg_ct_helper:create_client(RootUrl)),
#payproc_Invoice{
invoice = #domain_Invoice{id = InvoiceId},
payments = [
#payproc_InvoicePayment{payment = #domain_InvoicePayment{id = PaymentId}}
]
} = create_payment(PartyID, ShopID, 10000, Client, ?pmt_sys(<<"visa-ref">>)),
Route = ?route(?prv(5), ?trm(12)),
#{
Route := [
#payproc_TurnoverLimitValue{
limit = #domain_TurnoverLimit{id = ?LIMIT_ID, upper_boundary = ?LIMIT_UPPER_BOUNDARY},
value = 10000
}
]
} = hg_client_invoicing:get_limit_values(InvoiceId, PaymentId, Client).
-spec register_payment_limit_success(config()) -> test_return(). -spec register_payment_limit_success(config()) -> test_return().
register_payment_limit_success(C0) -> register_payment_limit_success(C0) ->
Client = cfg(client, C0), Client = cfg(client, C0),

View File

@ -31,6 +31,7 @@
-export([terminal_priority_for_shop/1]). -export([terminal_priority_for_shop/1]).
-export([gather_pinned_route/1]). -export([gather_pinned_route/1]).
-export([choose_pinned_route/1]). -export([choose_pinned_route/1]).
-export([choose_route_w_override/1]).
-define(PROVIDER_MIN_ALLOWED, ?cash(1000, <<"RUB">>)). -define(PROVIDER_MIN_ALLOWED, ?cash(1000, <<"RUB">>)).
-define(PROVIDER_MIN_ALLOWED_W_EXTRA_CASH(ExtraCash), ?cash(1000 + ExtraCash, <<"RUB">>)). -define(PROVIDER_MIN_ALLOWED_W_EXTRA_CASH(ExtraCash), ?cash(1000 + ExtraCash, <<"RUB">>)).
@ -72,7 +73,8 @@ groups() ->
terminal_priority_for_shop, terminal_priority_for_shop,
gather_pinned_route, gather_pinned_route,
choose_pinned_route choose_pinned_route,
choose_route_w_override
]} ]}
]. ].
@ -821,6 +823,26 @@ choose_pinned_route(_C) ->
[ChosenRoute | _] = lists:sort(Routes), [ChosenRoute | _] = lists:sort(Routes),
{ChosenRoute, _} = hg_routing:choose_route(Routes). {ChosenRoute, _} = hg_routing:choose_route(Routes).
-spec choose_route_w_override(config()) -> test_return().
choose_route_w_override(_C) ->
%% without overrides
Route1 = hg_routing:new(?prv(1), ?trm(1)),
Route2 = hg_routing:new(?prv(2), ?trm(2)),
Route3 = hg_routing:new(?prv(3), ?trm(3)),
Routes = [Route1, Route2, Route3],
{
Route2,
#{
preferable_route := Route3,
reject_reason := availability
}
} = hg_routing:choose_route(Routes),
%% with overrides
Route3WithOV = hg_routing:new(?prv(3), ?trm(3), 0, 1000, #{}, #domain_RouteFaultDetectorOverrides{enabled = true}),
RoutesWithOV = [Route1, Route2, Route3WithOV],
{Route3WithOV, _} = hg_routing:choose_route(RoutesWithOV).
%%% Domain config fixtures %%% Domain config fixtures
routing_with_risk_score_fixture(Domain, AddRiskScore) -> routing_with_risk_score_fixture(Domain, AddRiskScore) ->

View File

@ -15,6 +15,7 @@
-export([rescind/3]). -export([rescind/3]).
-export([repair/5]). -export([repair/5]).
-export([repair_scenario/3]). -export([repair_scenario/3]).
-export([get_limit_values/3]).
-export([create_invoice_adjustment/3]). -export([create_invoice_adjustment/3]).
-export([get_invoice_adjustment/3]). -export([get_invoice_adjustment/3]).
@ -98,6 +99,8 @@
-type event_range() :: dmsl_payproc_thrift:'EventRange'(). -type event_range() :: dmsl_payproc_thrift:'EventRange'().
-type party_revision_param() :: dmsl_payproc_thrift:'PartyRevisionParam'(). -type party_revision_param() :: dmsl_payproc_thrift:'PartyRevisionParam'().
-type route_limit_context() :: dmsl_payproc_thrift:'RouteLimitContext'().
-spec start(hg_client_api:t()) -> pid(). -spec start(hg_client_api:t()) -> pid().
start(ApiClient) -> start(ApiClient) ->
start(start, ApiClient). start(start, ApiClient).
@ -150,6 +153,10 @@ repair(InvoiceID, Changes, Action, Params, Client) ->
repair_scenario(InvoiceID, Scenario, Client) -> repair_scenario(InvoiceID, Scenario, Client) ->
map_result_error(gen_server:call(Client, {call, 'RepairWithScenario', [InvoiceID, Scenario]})). map_result_error(gen_server:call(Client, {call, 'RepairWithScenario', [InvoiceID, Scenario]})).
-spec get_limit_values(invoice_id(), payment_id(), pid()) -> route_limit_context() | woody_error:business_error().
get_limit_values(InvoiceID, PaymentID, Client) ->
map_result_error(gen_server:call(Client, {call, 'GetPaymentRoutesLimitValues', [InvoiceID, PaymentID]})).
-spec create_invoice_adjustment(invoice_id(), invoice_adjustment_params(), pid()) -> -spec create_invoice_adjustment(invoice_id(), invoice_adjustment_params(), pid()) ->
invoice_adjustment() | woody_error:business_error(). invoice_adjustment() | woody_error:business_error().
create_invoice_adjustment(InvoiceID, Params, Client) -> create_invoice_adjustment(InvoiceID, Params, Client) ->

View File

@ -17,7 +17,7 @@
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2},
{<<"damsel">>, {<<"damsel">>,
{git,"https://github.com/valitydev/damsel.git", {git,"https://github.com/valitydev/damsel.git",
{ref,"03bf41075c39b6731c5ed200d5c4b0faaee9d937"}}, {ref,"bfedcb9dbb0bfdbd7a06a86417b49be6e807b98d"}},
0}, 0},
{<<"dmt_client">>, {<<"dmt_client">>,
{git,"https://github.com/valitydev/dmt-client.git", {git,"https://github.com/valitydev/dmt-client.git",