mirror of
https://github.com/valitydev/hellgate.git
synced 2024-11-06 02:45:20 +00:00
Merge branch 'master' into epic/kill_adjustments
This commit is contained in:
commit
a755338392
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ tags
|
||||
# make stuff
|
||||
/.image.*
|
||||
Makefile.env
|
||||
*.iml
|
||||
|
@ -11,7 +11,7 @@
|
||||
-export([get_balance/1]).
|
||||
-export([create_account/1]).
|
||||
-export([create_account/2]).
|
||||
-export([collect_account_map/8]).
|
||||
-export([collect_account_map/1]).
|
||||
-export([collect_merchant_account_map/3]).
|
||||
-export([collect_provider_account_map/4]).
|
||||
-export([collect_system_account_map/4]).
|
||||
@ -45,8 +45,20 @@
|
||||
-type varset() :: hg_varset:varset().
|
||||
-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([batch/0]).
|
||||
-export_type([collect_account_context/0]).
|
||||
-export_type([posting_plan_log/0]).
|
||||
|
||||
-type account() :: #{
|
||||
@ -85,17 +97,17 @@ create_account(CurrencyCode, Description) ->
|
||||
error({accounting, Exception})
|
||||
end.
|
||||
|
||||
-spec collect_account_map(
|
||||
payment(),
|
||||
party(),
|
||||
shop(),
|
||||
route(),
|
||||
payment_institution(),
|
||||
provider(),
|
||||
varset(),
|
||||
revision()
|
||||
) -> map().
|
||||
collect_account_map(Payment, Party, Shop, Route, PaymentInstitution, Provider, VS, Revision) ->
|
||||
-spec collect_account_map(collect_account_context()) -> map().
|
||||
collect_account_map(#{
|
||||
payment := Payment,
|
||||
party := Party,
|
||||
shop := Shop,
|
||||
route := Route,
|
||||
payment_institution := PaymentInstitution,
|
||||
provider := Provider,
|
||||
varset := VS,
|
||||
revision := Revision
|
||||
}) ->
|
||||
Map0 = collect_merchant_account_map(Party, Shop, #{}),
|
||||
Map1 = collect_provider_account_map(Payment, Provider, Route, Map0),
|
||||
Map2 = collect_system_account_map(Payment, PaymentInstitution, Revision, Map1),
|
||||
|
179
apps/hellgate/src/hg_cashflow_utils.erl
Normal file
179
apps/hellgate/src/hg_cashflow_utils.erl
Normal 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
|
||||
}.
|
@ -147,7 +147,11 @@ handle_function_('Create', {InvoiceParams}, _Opts) ->
|
||||
Party = hg_party:get_party(PartyID),
|
||||
Shop = assert_shop_exists(hg_party:get_shop(ShopID, 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),
|
||||
AllocationPrototype = InvoiceParams#payproc_InvoiceParams.allocation,
|
||||
Cost = InvoiceParams#payproc_InvoiceParams.cost,
|
||||
@ -160,7 +164,11 @@ handle_function_('CreateWithTemplate', {Params}, _Opts) ->
|
||||
_ = set_invoicing_meta(InvoiceID),
|
||||
TplID = Params#payproc_InvoiceWithTemplateParams.template_id,
|
||||
{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),
|
||||
AllocationPrototype = InvoiceParams#payproc_InvoiceParams.allocation,
|
||||
Cost = InvoiceParams#payproc_InvoiceParams.cost,
|
||||
@ -241,7 +249,11 @@ handle_function_('Repair', {InvoiceID, Changes, Action, Params}, _Opts) ->
|
||||
repair(InvoiceID, {changes, Changes, Action, Params});
|
||||
handle_function_('RepairWithScenario', {InvoiceID, Scenario}, _Opts) ->
|
||||
_ = 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) ->
|
||||
undefined;
|
||||
@ -1312,24 +1324,6 @@ validate_invoice_cost(Cost, Shop, #domain_TermSet{payments = PaymentTerms}) ->
|
||||
_ = hg_invoice_utils:assert_cost_payable(Cost, PaymentTerms),
|
||||
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) ->
|
||||
Cart;
|
||||
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) ->
|
||||
lists:foreach(fun(C) -> log_change(C, St) end, Changes).
|
||||
|
||||
|
@ -76,13 +76,13 @@
|
||||
-export([accept_chargeback/3]).
|
||||
-export([reopen_chargeback/3]).
|
||||
|
||||
-export([get_merchant_terms/5]).
|
||||
-export([get_provider_terminal_terms/3]).
|
||||
-export([calculate_cashflow/10]).
|
||||
-export([calculate_cashflow/3]).
|
||||
|
||||
-export([create_session_event_context/3]).
|
||||
-export([add_session/3]).
|
||||
-export([accrue_status_timing/3]).
|
||||
-export([get_limit_values/2]).
|
||||
|
||||
%% Machine like
|
||||
|
||||
@ -125,6 +125,7 @@
|
||||
-export_type([change/0]).
|
||||
-export_type([change_opts/0]).
|
||||
-export_type([action/0]).
|
||||
-export_type([cashflow_context/0]).
|
||||
|
||||
-type activity() ::
|
||||
payment_activity()
|
||||
@ -205,6 +206,7 @@
|
||||
-type recurrent_paytool_service_terms() :: dmsl_domain_thrift:'RecurrentPaytoolsServiceTerms'().
|
||||
-type session() :: hg_session:t().
|
||||
-type payment_plan_id() :: hg_accounting:plan_id().
|
||||
-type route_limit_context() :: dmsl_payproc_thrift:'RouteLimitContext'().
|
||||
|
||||
-type opts() :: #{
|
||||
party => party(),
|
||||
@ -212,6 +214,17 @@
|
||||
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").
|
||||
@ -440,29 +453,9 @@ init_(PaymentID, Params, Opts = #{timestamp := CreatedAt}) ->
|
||||
get_merchant_payments_terms(Opts, Revision, Timestamp, VS) ->
|
||||
Party = get_party(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.
|
||||
|
||||
-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()) ->
|
||||
dmsl_domain_thrift:'PaymentsProvisionTerms'() | undefined.
|
||||
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.
|
||||
|
||||
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()}.
|
||||
construct_payer(
|
||||
{payment_resource, #payproc_PaymentResourcePayerParams{
|
||||
@ -564,7 +552,7 @@ construct_payment(
|
||||
payment_tool => PaymentTool,
|
||||
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,
|
||||
ok = validate_payment_tool(
|
||||
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().
|
||||
construct_payment_plan_id(#st{opts = Opts, payment = Payment, routes = Routes}) ->
|
||||
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),
|
||||
ProviderTerms = hg_routing:get_payment_terms(Route, VS, Revision),
|
||||
ok = validate_provider_holds_terms(ProviderTerms),
|
||||
FinalCashflow =
|
||||
calculate_cashflow(Route, Payment2, ProviderTerms, MerchantTerms, VS, Revision, Opts, Timestamp, Allocation),
|
||||
Context = #{
|
||||
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),
|
||||
{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) ->
|
||||
Route = get_route(St),
|
||||
Party = get_party(Opts),
|
||||
Shop = get_shop(Opts),
|
||||
ProviderPaymentsTerms = get_provider_terminal_terms(Route, VS, Revision),
|
||||
ProviderTerms = get_provider_refunds_terms(ProviderPaymentsTerms, Refund, Payment),
|
||||
Allocation = Refund#domain_InvoicePaymentRefund.allocation,
|
||||
Provider = get_route_provider(Route, Revision),
|
||||
collect_cashflow(
|
||||
refund,
|
||||
ProviderTerms,
|
||||
MerchantTerms,
|
||||
Party,
|
||||
Shop,
|
||||
Route,
|
||||
Allocation,
|
||||
Payment,
|
||||
Refund,
|
||||
Provider,
|
||||
Timestamp,
|
||||
VS,
|
||||
Revision
|
||||
).
|
||||
CollectCashflowContext = genlib_map:compact(#{
|
||||
operation => refund,
|
||||
provision_terms => get_provider_refunds_terms(ProviderPaymentsTerms, Refund, Payment),
|
||||
merchant_terms => MerchantTerms,
|
||||
party => get_party(Opts),
|
||||
shop => get_shop(Opts),
|
||||
route => Route,
|
||||
payment => Payment,
|
||||
provider => get_route_provider(Route, Revision),
|
||||
timestamp => Timestamp,
|
||||
varset => VS,
|
||||
revision => Revision,
|
||||
refund => Refund,
|
||||
allocation => Allocation
|
||||
}),
|
||||
hg_cashflow_utils:collect_cashflow(CollectCashflowContext).
|
||||
|
||||
assert_refund_cash(Cash, St) ->
|
||||
PaymentAmount = get_remaining_payment_amount(Cash, St),
|
||||
@ -1491,235 +1469,6 @@ validate_common_refund_terms(Terms, Refund, Payment) ->
|
||||
),
|
||||
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()}.
|
||||
@ -1748,7 +1497,16 @@ create_cash_flow_adjustment(Timestamp, Params, DomainRevision, St, Opts) ->
|
||||
OldCashFlow = get_final_cashflow(St),
|
||||
VS = collect_validation_varset(St, Opts),
|
||||
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 =
|
||||
{cash_flow, #domain_InvoicePaymentAdjustmentCashFlowState{
|
||||
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},
|
||||
Revision = Payment#domain_InvoicePayment.domain_revision,
|
||||
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({failed, _}, _St, _Opts) ->
|
||||
[].
|
||||
|
||||
-spec calculate_cashflow(
|
||||
route(),
|
||||
payment(),
|
||||
hg_datetime:timestamp(),
|
||||
hg_varset:varset(),
|
||||
hg_domain:revision(),
|
||||
opts(),
|
||||
hg_allocation:allocation()
|
||||
) -> final_cash_flow().
|
||||
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(cashflow_context(), opts()) -> final_cash_flow().
|
||||
calculate_cashflow(Context = #{route := Route, revision := Revision}, Opts) ->
|
||||
CollectCashflowContext = genlib_map:compact(Context#{
|
||||
operation => payment,
|
||||
party => get_party(Opts),
|
||||
shop => get_shop(Opts),
|
||||
provider => get_route_provider(Route, Revision)
|
||||
}),
|
||||
hg_cashflow_utils:collect_cashflow(CollectCashflowContext).
|
||||
|
||||
-spec calculate_cashflow(
|
||||
route(),
|
||||
payment(),
|
||||
dmsl_domain_thrift:'PaymentsProvisionTerms'() | undefined,
|
||||
dmsl_domain_thrift:'PaymentsServiceTerms'() | undefined,
|
||||
hg_varset:varset(),
|
||||
hg_domain:revision(),
|
||||
opts(),
|
||||
hg_datetime:timestamp(),
|
||||
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 calculate_cashflow(hg_payment_institution:t(), cashflow_context(), opts()) -> final_cash_flow().
|
||||
calculate_cashflow(PaymentInstitution, Context = #{route := Route, revision := Revision}, Opts) ->
|
||||
CollectCashflowContext = genlib_map:compact(Context#{
|
||||
operation => payment,
|
||||
party => get_party(Opts),
|
||||
shop => get_shop(Opts),
|
||||
provider => get_route_provider(Route, Revision)
|
||||
}),
|
||||
hg_cashflow_utils:collect_cashflow(PaymentInstitution, CollectCashflowContext).
|
||||
|
||||
-spec construct_adjustment(
|
||||
Timestamp :: hg_datetime:timestamp(),
|
||||
@ -2245,6 +1953,26 @@ process_risk_score(Action, St) ->
|
||||
|
||||
-spec process_routing(action(), st()) -> machine_result().
|
||||
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),
|
||||
Revision = get_payment_revision(St),
|
||||
Payment = get_payment(St),
|
||||
@ -2255,29 +1983,15 @@ process_routing(Action, St) ->
|
||||
VS2 = collect_refund_varset(MerchantTerms#domain_PaymentsServiceTerms.refunds, PaymentTool, VS1),
|
||||
VS3 = collect_chargeback_varset(MerchantTerms#domain_PaymentsServiceTerms.chargebacks, VS2),
|
||||
PaymentInstitution = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS1, Revision),
|
||||
try
|
||||
Payer = get_payment_payer(St),
|
||||
AllRoutes =
|
||||
case get_predefined_route(Payer) of
|
||||
{ok, PaymentRoute} ->
|
||||
[hg_routing:from_payment_route(PaymentRoute)];
|
||||
undefined ->
|
||||
gather_routes(PaymentInstitution, VS3, 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)
|
||||
{PaymentInstitution, VS3, Revision}.
|
||||
|
||||
get_candidates(PaymentInstitution, VS, Revision, St) ->
|
||||
Payer = get_payment_payer(St),
|
||||
case get_predefined_route(Payer) of
|
||||
{ok, PaymentRoute} ->
|
||||
[hg_routing:from_payment_route(PaymentRoute)];
|
||||
undefined ->
|
||||
gather_routes(PaymentInstitution, VS, Revision, St)
|
||||
end.
|
||||
|
||||
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),
|
||||
ProviderTerms = get_provider_terminal_terms(Route, VS1, Revision),
|
||||
Allocation = get_allocation(St),
|
||||
FinalCashflow = calculate_cashflow(
|
||||
Route,
|
||||
Payment,
|
||||
ProviderTerms,
|
||||
undefined,
|
||||
VS1,
|
||||
Revision,
|
||||
Opts,
|
||||
Timestamp,
|
||||
Allocation
|
||||
),
|
||||
Context = #{
|
||||
provision_terms => ProviderTerms,
|
||||
route => Route,
|
||||
payment => Payment,
|
||||
timestamp => Timestamp,
|
||||
varset => VS1,
|
||||
revision => Revision,
|
||||
allocation => Allocation
|
||||
},
|
||||
FinalCashflow = calculate_cashflow(Context, Opts),
|
||||
_ = rollback_unused_payment_limits(St),
|
||||
_Clock = hg_accounting:hold(
|
||||
construct_payment_plan_id(St),
|
||||
@ -3418,7 +3131,7 @@ get_routing_attempt_limit(
|
||||
Party = hg_party:checkout(PartyID, {revision, PartyRevision}),
|
||||
Shop = hg_party:get_shop(ShopID, Party),
|
||||
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,
|
||||
log_cascade_attempt_context(PaymentTerms, St),
|
||||
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),
|
||||
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}) ->
|
||||
case get_payment_flow(Payment) of
|
||||
?invoice_payment_flow_instant() ->
|
||||
|
@ -428,7 +428,17 @@ build_chargeback_final_cash_flow(State, Opts) ->
|
||||
PaymentInstitutionRef = get_payment_institution_ref(get_contract(Party, Shop)),
|
||||
PaymentInst = hg_payment_institution:compute_payment_institution(PaymentInstitutionRef, VS, 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),
|
||||
ProviderContext = build_provider_cash_flow_context(State, ProviderFees),
|
||||
ServiceFinalCF = hg_cashflow:finalize(ServiceCashFlow, ServiceContext, AccountMap),
|
||||
|
@ -72,18 +72,16 @@ init_(PaymentID, Params, Opts = #{timestamp := CreatedAt0}) ->
|
||||
|
||||
MerchantTerms = get_merchant_payment_terms(Party, Shop, Revision, CreatedAt1, VS),
|
||||
ProviderTerms = hg_invoice_payment:get_provider_terminal_terms(Route, VS, Revision),
|
||||
FinalCashflow = hg_invoice_payment:calculate_cashflow(
|
||||
Route,
|
||||
Payment,
|
||||
PaymentInstitution,
|
||||
ProviderTerms,
|
||||
MerchantTerms,
|
||||
VS,
|
||||
Revision,
|
||||
Opts,
|
||||
CreatedAt1,
|
||||
undefined
|
||||
),
|
||||
CashflowContext = #{
|
||||
provision_terms => ProviderTerms,
|
||||
merchant_terms => MerchantTerms,
|
||||
route => Route,
|
||||
payment => Payment,
|
||||
timestamp => CreatedAt1,
|
||||
varset => VS,
|
||||
revision => Revision
|
||||
},
|
||||
FinalCashflow = hg_invoice_payment:calculate_cashflow(PaymentInstitution, CashflowContext, Opts),
|
||||
|
||||
Events =
|
||||
[
|
||||
@ -182,7 +180,7 @@ maybe_risk_score_event_list(RiskScore) ->
|
||||
[?risk_score_changed(RiskScore)].
|
||||
|
||||
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.
|
||||
|
||||
hold_payment_limits(Invoice, Payment, St) ->
|
||||
|
@ -15,6 +15,7 @@
|
||||
-export([assert_shop_operable/1]).
|
||||
-export([assert_cost_payable/2]).
|
||||
-export([compute_shop_terms/5]).
|
||||
-export([get_merchant_terms/5]).
|
||||
-export([get_shop_currency/1]).
|
||||
-export([get_cart_amount/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),
|
||||
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) ->
|
||||
ok;
|
||||
validate_currency_(_, _) ->
|
||||
|
@ -1,5 +1,6 @@
|
||||
-module(hg_limiter).
|
||||
|
||||
-include_lib("damsel/include/dmsl_payproc_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_base_thrift.hrl").
|
||||
-include_lib("limiter_proto/include/limproto_base_thrift.hrl").
|
||||
@ -14,6 +15,7 @@
|
||||
-type refund() :: hg_invoice_payment:domain_refund().
|
||||
-type cash() :: dmsl_domain_thrift:'Cash'().
|
||||
-type handling_flag() :: ignore_business_error.
|
||||
-type turnover_limit_value() :: dmsl_payproc_thrift:'TurnoverLimitValue'().
|
||||
|
||||
-type change_queue() :: [hg_limiter_client:limit_change()].
|
||||
|
||||
@ -25,6 +27,7 @@
|
||||
-export([commit_refund_limits/5]).
|
||||
-export([rollback_payment_limits/6]).
|
||||
-export([rollback_refund_limits/5]).
|
||||
-export([get_limit_values/4]).
|
||||
|
||||
-define(route(ProviderRef, TerminalRef), #domain_PaymentRoute{
|
||||
provider = ProviderRef,
|
||||
@ -39,6 +42,23 @@ get_turnover_limits({value, Limits}) ->
|
||||
get_turnover_limits(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()) ->
|
||||
{ok, [hg_limiter_client:limit()]}
|
||||
| {error, {limit_overflow, [binary()]}}.
|
||||
|
@ -16,6 +16,7 @@
|
||||
-export([new/2]).
|
||||
-export([new/4]).
|
||||
-export([new/5]).
|
||||
-export([new/6]).
|
||||
-export([to_payment_route/1]).
|
||||
-export([to_rejected_route/2]).
|
||||
-export([provider_ref/1]).
|
||||
@ -32,7 +33,8 @@
|
||||
terminal_ref :: dmsl_domain_thrift:'TerminalRef'(),
|
||||
weight :: integer(),
|
||||
priority :: integer(),
|
||||
pin :: pin()
|
||||
pin :: pin(),
|
||||
fd_overrides :: fd_overrides()
|
||||
}).
|
||||
|
||||
-type pin() :: #{
|
||||
@ -98,6 +100,7 @@
|
||||
|
||||
-type varset() :: hg_varset:varset().
|
||||
-type revision() :: hg_domain:revision().
|
||||
-type fd_overrides() :: dmsl_domain_thrift:'RouteFaultDetectorOverrides'().
|
||||
|
||||
-record(route_scores, {
|
||||
availability_condition :: condition_score(),
|
||||
@ -136,12 +139,19 @@ new(ProviderRef, TerminalRef, Weight, Priority) ->
|
||||
new(ProviderRef, TerminalRef, undefined, Priority, Pin) ->
|
||||
new(ProviderRef, TerminalRef, ?DOMAIN_CANDIDATE_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{
|
||||
provider_ref = ProviderRef,
|
||||
terminal_ref = TerminalRef,
|
||||
weight = Weight,
|
||||
priority = Priority,
|
||||
pin = Pin
|
||||
pin = Pin,
|
||||
fd_overrides = FdOverrides
|
||||
}.
|
||||
|
||||
-spec provider_ref(route()) -> provider_ref().
|
||||
@ -164,6 +174,9 @@ weight(#route{weight = Weight}) ->
|
||||
pin(#route{pin = Pin}) ->
|
||||
Pin.
|
||||
|
||||
fd_overrides(#route{fd_overrides = FdOverrides}) ->
|
||||
FdOverrides.
|
||||
|
||||
-spec from_payment_route(payment_route()) -> route().
|
||||
from_payment_route(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().
|
||||
% https://github.com/rbkmoney/hellgate/pull/583#discussion_r682745123
|
||||
#domain_Terminal{
|
||||
provider_ref = ProviderRef
|
||||
provider_ref = ProviderRef,
|
||||
route_fd_overrides = FdOverrides
|
||||
} = hg_domain:get(Revision, {terminal, TerminalRef}),
|
||||
GatheredPinInfo = gather_pin_info(Pin, Ctx),
|
||||
try
|
||||
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}
|
||||
catch
|
||||
{rejected, Reason} ->
|
||||
@ -518,17 +532,22 @@ score_routes_with_fault_detector([]) ->
|
||||
score_routes_with_fault_detector(Routes) ->
|
||||
IDs = build_ids(Routes),
|
||||
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().
|
||||
get_provider_status(ProviderRef, FDStats) ->
|
||||
-spec get_provider_status(route(), [fd_service_stats()]) -> provider_status().
|
||||
get_provider_status(Route, FDStats) ->
|
||||
ProviderRef = provider_ref(Route),
|
||||
FdOverrides = fd_overrides(Route),
|
||||
AvailabilityServiceID = build_fd_availability_service_id(ProviderRef),
|
||||
ConversionServiceID = build_fd_conversion_service_id(ProviderRef),
|
||||
AvailabilityStatus = get_provider_availability_status(AvailabilityServiceID, FDStats),
|
||||
ConversionStatus = get_provider_conversion_status(ConversionServiceID, FDStats),
|
||||
AvailabilityStatus = get_provider_availability_status(FdOverrides, AvailabilityServiceID, FDStats),
|
||||
ConversionStatus = get_provider_conversion_status(FdOverrides, ConversionServiceID, FDStats),
|
||||
{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, #{}), #{}),
|
||||
CriticalFailRate = maps:get(critical_fail_rate, AvailabilityConfig, 0.7),
|
||||
case lists:keysearch(FDID, #fault_detector_ServiceStatistics.service_id, Stats) of
|
||||
@ -540,7 +559,10 @@ get_provider_availability_status(FDID, Stats) ->
|
||||
{alive, 0.0}
|
||||
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, #{}), #{}),
|
||||
CriticalFailRate = maps:get(critical_fail_rate, ConversionConfig, 0.7),
|
||||
case lists:keysearch(FDID, #fault_detector_ServiceStatistics.service_id, Stats) of
|
||||
|
@ -40,6 +40,7 @@
|
||||
-export([register_payment_customer_payer_success/1]).
|
||||
|
||||
-export([payment_limit_success/1]).
|
||||
-export([payment_routes_limit_values/1]).
|
||||
-export([register_payment_limit_success/1]).
|
||||
-export([payment_limit_other_shop_success/1]).
|
||||
-export([payment_limit_overflow/1]).
|
||||
@ -383,6 +384,7 @@ groups() ->
|
||||
|
||||
{operation_limits, [], [
|
||||
payment_limit_success,
|
||||
payment_routes_limit_values,
|
||||
register_payment_limit_success,
|
||||
payment_limit_other_shop_success,
|
||||
payment_limit_overflow,
|
||||
@ -1254,6 +1256,30 @@ payment_limit_success(C) ->
|
||||
[?payment_state(_Payment)]
|
||||
) = 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().
|
||||
register_payment_limit_success(C0) ->
|
||||
Client = cfg(client, C0),
|
||||
|
@ -31,6 +31,7 @@
|
||||
-export([terminal_priority_for_shop/1]).
|
||||
-export([gather_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_W_EXTRA_CASH(ExtraCash), ?cash(1000 + ExtraCash, <<"RUB">>)).
|
||||
@ -72,7 +73,8 @@ groups() ->
|
||||
terminal_priority_for_shop,
|
||||
|
||||
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, _} = 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
|
||||
|
||||
routing_with_risk_score_fixture(Domain, AddRiskScore) ->
|
||||
|
@ -15,6 +15,7 @@
|
||||
-export([rescind/3]).
|
||||
-export([repair/5]).
|
||||
-export([repair_scenario/3]).
|
||||
-export([get_limit_values/3]).
|
||||
|
||||
-export([create_invoice_adjustment/3]).
|
||||
-export([get_invoice_adjustment/3]).
|
||||
@ -98,6 +99,8 @@
|
||||
-type event_range() :: dmsl_payproc_thrift:'EventRange'().
|
||||
-type party_revision_param() :: dmsl_payproc_thrift:'PartyRevisionParam'().
|
||||
|
||||
-type route_limit_context() :: dmsl_payproc_thrift:'RouteLimitContext'().
|
||||
|
||||
-spec start(hg_client_api:t()) -> pid().
|
||||
start(ApiClient) ->
|
||||
start(start, ApiClient).
|
||||
@ -150,6 +153,10 @@ repair(InvoiceID, Changes, Action, Params, Client) ->
|
||||
repair_scenario(InvoiceID, Scenario, Client) ->
|
||||
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()) ->
|
||||
invoice_adjustment() | woody_error:business_error().
|
||||
create_invoice_adjustment(InvoiceID, Params, Client) ->
|
||||
|
@ -17,7 +17,7 @@
|
||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2},
|
||||
{<<"damsel">>,
|
||||
{git,"https://github.com/valitydev/damsel.git",
|
||||
{ref,"03bf41075c39b6731c5ed200d5c4b0faaee9d937"}},
|
||||
{ref,"bfedcb9dbb0bfdbd7a06a86417b49be6e807b98d"}},
|
||||
0},
|
||||
{<<"dmt_client">>,
|
||||
{git,"https://github.com/valitydev/dmt-client.git",
|
||||
|
Loading…
Reference in New Issue
Block a user