mirror of
https://github.com/valitydev/fistful-server.git
synced 2024-11-06 02:35:18 +00:00
ED-62: p2p transfer add terminals (#377)
* move terms validation from ff_p2p_provider to p2p_transfer * add terminals to p2p_transfer * fix wrong route return * return withdrawal changes * fix type after merge * add p2p_terminals to tests * add get/2 to p2p_terminal (like p2p_provider's get) * fix wrong ruleset tag * wrong terminal in p2p routing tests * wrong terminal in decisions in p2p routing test rulesets * Revert 2x "Merge branch 'master' into ED-62/ft/p2p_transfer_add_terminals" * Revert "Revert 2x "Merge branch 'master' into ED-62/ft/p2p_transfer_add_terminals"" This reverts commit cd593cb602b3ab06bcbe77e2b7b0974982e7a92c. * return back 6,7,8 withdrawal terminals (being unused for my point of view) * fix wrong func contract * dialyzer
This commit is contained in:
parent
2c49e05d56
commit
eaeb81c95a
@ -24,6 +24,7 @@
|
||||
-export([withdrawal_provider/4]).
|
||||
-export([withdrawal_terminal/1]).
|
||||
-export([p2p_provider/4]).
|
||||
-export([p2p_terminal/1]).
|
||||
|
||||
%%
|
||||
|
||||
@ -106,6 +107,22 @@ p2p_provider(Ref, ProxyRef, IdentityID, C) ->
|
||||
}
|
||||
}}.
|
||||
|
||||
-spec p2p_terminal(?dtp('TerminalRef')) -> object().
|
||||
p2p_terminal(Ref) ->
|
||||
{terminal, #domain_TerminalObject{
|
||||
ref = Ref,
|
||||
data = #domain_Terminal{
|
||||
name = <<"P2PTerminal">>,
|
||||
description = <<"P2P terminal">>,
|
||||
terms = #domain_ProvisionTermSet{
|
||||
wallet = #domain_WalletProvisionTerms{
|
||||
p2p = #domain_P2PProvisionTerms{}
|
||||
}
|
||||
},
|
||||
provider_ref = ?prv(101)
|
||||
}
|
||||
}}.
|
||||
|
||||
-spec withdrawal_provider(?dtp('ProviderRef'), ?dtp('ProxyRef'), binary(), ct_helper:config()) -> object().
|
||||
withdrawal_provider(?prv(16) = Ref, ProxyRef, IdentityID, C) ->
|
||||
AccountID = account(<<"RUB">>, C),
|
||||
|
@ -374,40 +374,71 @@ dummy_provider_identity_id(Options) ->
|
||||
domain_config(Options, C) ->
|
||||
P2PAdapterAdr = maps:get(p2p_adapter_adr, genlib_app:env(fistful, test, #{})),
|
||||
|
||||
Decision1 =
|
||||
WithdrawalDecision1 =
|
||||
{delegates, [
|
||||
delegate(condition(party, <<"12345">>), ?ruleset(2)),
|
||||
delegate(condition(party, <<"67890">>), ?ruleset(4))
|
||||
]},
|
||||
Decision2 =
|
||||
WithdrawalDecision2 =
|
||||
{delegates, [
|
||||
delegate(condition(cost_in, {0, 1000, <<"RUB">>}), ?ruleset(3))
|
||||
]},
|
||||
Decision3 =
|
||||
WithdrawalDecision3 =
|
||||
{candidates, [
|
||||
candidate({constant, true}, ?trm(1)),
|
||||
candidate({constant, true}, ?trm(2))
|
||||
]},
|
||||
Decision4 =
|
||||
WithdrawalDecision4 =
|
||||
{candidates, [
|
||||
candidate({constant, true}, ?trm(3)),
|
||||
candidate({constant, true}, ?trm(4)),
|
||||
candidate({constant, true}, ?trm(5))
|
||||
]},
|
||||
Decision5 =
|
||||
WithdrawalDecision5 =
|
||||
{candidates, [
|
||||
candidate({constant, true}, ?trm(4))
|
||||
]},
|
||||
|
||||
P2PDecision1 =
|
||||
{delegates, [
|
||||
delegate(condition(party, <<"12345">>), ?ruleset(102)),
|
||||
delegate(condition(party, <<"67890">>), ?ruleset(104))
|
||||
]},
|
||||
P2PDecision2 =
|
||||
{delegates, [
|
||||
delegate(condition(cost_in, {0, 1000, <<"RUB">>}), ?ruleset(103))
|
||||
]},
|
||||
P2PDecision3 =
|
||||
{candidates, [
|
||||
candidate({constant, true}, ?trm(101)),
|
||||
candidate({constant, true}, ?trm(102))
|
||||
]},
|
||||
P2PDecision4 =
|
||||
{candidates, [
|
||||
candidate({constant, true}, ?trm(103)),
|
||||
candidate({constant, true}, ?trm(104)),
|
||||
candidate({constant, true}, ?trm(105))
|
||||
]},
|
||||
P2PDecision5 =
|
||||
{candidates, [
|
||||
candidate({constant, true}, ?trm(104))
|
||||
]},
|
||||
|
||||
Default = [
|
||||
ct_domain:globals(?eas(1), [?payinst(1)]),
|
||||
ct_domain:external_account_set(?eas(1), <<"Default">>, ?cur(<<"RUB">>), C),
|
||||
|
||||
routing_ruleset(?ruleset(1), <<"Rule#1">>, Decision1),
|
||||
routing_ruleset(?ruleset(2), <<"Rule#2">>, Decision2),
|
||||
routing_ruleset(?ruleset(3), <<"Rule#3">>, Decision3),
|
||||
routing_ruleset(?ruleset(4), <<"Rule#4">>, Decision4),
|
||||
routing_ruleset(?ruleset(5), <<"Rule#5">>, Decision5),
|
||||
routing_ruleset(?ruleset(1), <<"WithdrawalRuleset#1">>, WithdrawalDecision1),
|
||||
routing_ruleset(?ruleset(2), <<"WithdrawalRuleset#2">>, WithdrawalDecision2),
|
||||
routing_ruleset(?ruleset(3), <<"WithdrawalRuleset#3">>, WithdrawalDecision3),
|
||||
routing_ruleset(?ruleset(4), <<"WithdrawalRuleset#4">>, WithdrawalDecision4),
|
||||
routing_ruleset(?ruleset(5), <<"WithdrawalRuleset#5">>, WithdrawalDecision5),
|
||||
|
||||
routing_ruleset(?ruleset(101), <<"P2PRuleset#1">>, P2PDecision1),
|
||||
routing_ruleset(?ruleset(102), <<"P2PRuleset#2">>, P2PDecision2),
|
||||
routing_ruleset(?ruleset(103), <<"P2PRuleset#3">>, P2PDecision3),
|
||||
routing_ruleset(?ruleset(104), <<"P2PRuleset#4">>, P2PDecision4),
|
||||
routing_ruleset(?ruleset(105), <<"P2PRuleset#5">>, P2PDecision5),
|
||||
|
||||
{payment_institution, #domain_PaymentInstitutionObject{
|
||||
ref = ?payinst(1),
|
||||
@ -420,6 +451,10 @@ domain_config(Options, C) ->
|
||||
policies = ?ruleset(1),
|
||||
prohibitions = ?ruleset(5)
|
||||
},
|
||||
p2p_transfer_routing_rules = #domain_RoutingRules{
|
||||
policies = ?ruleset(101),
|
||||
prohibitions = ?ruleset(105)
|
||||
},
|
||||
inspector = {value, ?insp(1)},
|
||||
residences = ['rus'],
|
||||
realm = live,
|
||||
@ -659,6 +694,12 @@ domain_config(Options, C) ->
|
||||
% Provider 17 satellite
|
||||
ct_domain:withdrawal_terminal(?trm(8)),
|
||||
|
||||
ct_domain:p2p_terminal(?trm(101)),
|
||||
ct_domain:p2p_terminal(?trm(102)),
|
||||
ct_domain:p2p_terminal(?trm(103)),
|
||||
ct_domain:p2p_terminal(?trm(104)),
|
||||
ct_domain:p2p_terminal(?trm(105)),
|
||||
|
||||
ct_domain:currency(?cur(<<"RUB">>)),
|
||||
ct_domain:currency(?cur(<<"USD">>)),
|
||||
ct_domain:currency(?cur(<<"EUR">>)),
|
||||
|
@ -768,7 +768,8 @@ do_process_routing(Withdrawal) ->
|
||||
}),
|
||||
|
||||
do(fun() ->
|
||||
Routes = unwrap(prepare_routes(build_party_varset(VarsetParams), Identity, DomainRevision)),
|
||||
Varset = build_party_varset(VarsetParams),
|
||||
Routes = unwrap(ff_withdrawal_routing:prepare_routes(Varset, Identity, DomainRevision)),
|
||||
case quote(Withdrawal) of
|
||||
undefined ->
|
||||
Routes;
|
||||
@ -779,10 +780,6 @@ do_process_routing(Withdrawal) ->
|
||||
end
|
||||
end).
|
||||
|
||||
-spec prepare_routes(party_varset(), identity(), domain_revision()) -> {ok, [route()]} | {error, route_not_found}.
|
||||
prepare_routes(PartyVarset, Identity, DomainRevision) ->
|
||||
ff_withdrawal_routing:prepare_routes(PartyVarset, Identity, DomainRevision).
|
||||
|
||||
-spec validate_quote_route(route(), quote_state()) -> {ok, valid} | {error, InconsistentQuote} when
|
||||
InconsistentQuote :: {inconsistent_quote_route, {provider_id, provider_id()} | {terminal_id, terminal_id()}}.
|
||||
validate_quote_route(Route, #{route := QuoteRoute}) ->
|
||||
@ -1197,7 +1194,7 @@ get_quote_(Params) ->
|
||||
} = Params,
|
||||
Resource = maps:get(resource, Params, undefined),
|
||||
|
||||
[Route | _] = unwrap(route, prepare_routes(Varset, Identity, DomainRevision)),
|
||||
[Route | _] = unwrap(route, ff_withdrawal_routing:prepare_routes(Varset, Identity, DomainRevision)),
|
||||
{Adapter, AdapterOpts} = ff_withdrawal_session:get_adapter_with_opts(Route),
|
||||
GetQuoteParams = #{
|
||||
external_id => maps:get(external_id, Params, undefined),
|
||||
|
@ -37,7 +37,6 @@
|
||||
-type withdrawal_provision_terms() :: dmsl_domain_thrift:'WithdrawalProvisionTerms'().
|
||||
-type currency_selector() :: dmsl_domain_thrift:'CurrencySelector'().
|
||||
-type cash_limit_selector() :: dmsl_domain_thrift:'CashLimitSelector'().
|
||||
-type provision_terms() :: dmsl_domain_thrift:'WithdrawalProvisionTerms'().
|
||||
|
||||
%%
|
||||
|
||||
@ -86,7 +85,7 @@ get_provider(#{provider_id := ProviderID}) ->
|
||||
get_terminal(Route) ->
|
||||
maps:get(terminal_id, Route, undefined).
|
||||
|
||||
-spec provision_terms(route(), domain_revision()) -> ff_maybe:maybe(provision_terms()).
|
||||
-spec provision_terms(route(), domain_revision()) -> ff_maybe:maybe(withdrawal_provision_terms()).
|
||||
provision_terms(Route, DomainRevision) ->
|
||||
{ok, Provider} = ff_payouts_provider:get(get_provider(Route), DomainRevision),
|
||||
ProviderTerms = ff_payouts_provider:provision_terms(Provider),
|
||||
@ -103,7 +102,7 @@ provision_terms(Route, DomainRevision) ->
|
||||
-spec merge_withdrawal_terms(
|
||||
ff_payouts_provider:provision_terms() | undefined,
|
||||
ff_payouts_terminal:provision_terms() | undefined
|
||||
) -> ff_maybe:maybe(provision_terms()).
|
||||
) -> ff_maybe:maybe(withdrawal_provision_terms()).
|
||||
merge_withdrawal_terms(
|
||||
#domain_WithdrawalProvisionTerms{
|
||||
currencies = PCurrencies,
|
||||
@ -214,7 +213,7 @@ get_valid_terminals_with_priority([{TerminalID, Priority} | Rest], Provider, Par
|
||||
end,
|
||||
get_valid_terminals_with_priority(Rest, Provider, PartyVarset, DomainRevision, Acc).
|
||||
|
||||
-spec validate_terms(provider(), terminal(), hg_selector:varset()) ->
|
||||
-spec validate_terms(provider(), terminal(), party_varset()) ->
|
||||
{ok, valid}
|
||||
| {error, Error :: term()}.
|
||||
validate_terms(Provider, Terminal, PartyVarset) ->
|
||||
@ -231,7 +230,7 @@ assert_terms_defined(undefined, undefined) ->
|
||||
assert_terms_defined(_, _) ->
|
||||
{ok, valid}.
|
||||
|
||||
-spec validate_combined_terms(withdrawal_provision_terms(), hg_selector:varset()) ->
|
||||
-spec validate_combined_terms(withdrawal_provision_terms(), party_varset()) ->
|
||||
{ok, valid}
|
||||
| {error, Error :: term()}.
|
||||
validate_combined_terms(CombinedTerms, PartyVarset) ->
|
||||
@ -265,7 +264,7 @@ validate_selectors_defined(Terms) ->
|
||||
{error, terms_undefined}
|
||||
end.
|
||||
|
||||
-spec validate_currencies(currency_selector(), hg_selector:varset()) ->
|
||||
-spec validate_currencies(currency_selector(), party_varset()) ->
|
||||
{ok, valid}
|
||||
| {error, Error :: term()}.
|
||||
validate_currencies(CurrenciesSelector, #{currency := CurrencyRef} = VS) ->
|
||||
@ -277,7 +276,7 @@ validate_currencies(CurrenciesSelector, #{currency := CurrencyRef} = VS) ->
|
||||
{error, {terms_violation, {not_allowed_currency, {CurrencyRef, Currencies}}}}
|
||||
end.
|
||||
|
||||
-spec validate_cash_limit(cash_limit_selector(), hg_selector:varset()) ->
|
||||
-spec validate_cash_limit(cash_limit_selector(), party_varset()) ->
|
||||
{ok, valid}
|
||||
| {error, Error :: term()}.
|
||||
validate_cash_limit(CashLimitSelector, #{cost := Cash} = VS) ->
|
||||
|
@ -17,30 +17,26 @@
|
||||
-type adapter_opts() :: map().
|
||||
|
||||
-type provider_ref() :: dmsl_domain_thrift:'ProviderRef'().
|
||||
-type currency_ref() :: dmsl_domain_thrift:'CurrencyRef'().
|
||||
-type cash() :: dmsl_domain_thrift:'Cash'().
|
||||
-type cash_range() :: dmsl_domain_thrift:'CashRange'().
|
||||
-type validate_terms_error() ::
|
||||
{terms_violation,
|
||||
{not_allowed_currency, {currency_ref(), [currency_ref()]}}
|
||||
| {cash_range, {cash(), cash_range()}}}.
|
||||
-type term_set() :: dmsl_domain_thrift:'ProvisionTermSet'().
|
||||
-type provision_terms() :: dmsl_domain_thrift:'P2PProvisionTerms'().
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([provider/0]).
|
||||
-export_type([adapter/0]).
|
||||
-export_type([adapter_opts/0]).
|
||||
-export_type([validate_terms_error/0]).
|
||||
-export_type([provision_terms/0]).
|
||||
|
||||
-export([id/1]).
|
||||
-export([accounts/1]).
|
||||
-export([adapter/1]).
|
||||
-export([adapter_opts/1]).
|
||||
-export([terms/1]).
|
||||
-export([provision_terms/1]).
|
||||
|
||||
-export([ref/1]).
|
||||
-export([get/1]).
|
||||
-export([get/2]).
|
||||
-export([compute_fees/2]).
|
||||
-export([validate_terms/2]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
@ -65,6 +61,24 @@ adapter(#{adapter := Adapter}) ->
|
||||
adapter_opts(#{adapter_opts := AdapterOpts}) ->
|
||||
AdapterOpts.
|
||||
|
||||
-spec terms(provider()) -> term_set() | undefined.
|
||||
terms(Provider) ->
|
||||
maps:get(terms, Provider, undefined).
|
||||
|
||||
-spec provision_terms(provider()) -> provision_terms() | undefined.
|
||||
provision_terms(Provider) ->
|
||||
case terms(Provider) of
|
||||
Terms when Terms =/= undefined ->
|
||||
case Terms#domain_ProvisionTermSet.wallet of
|
||||
WalletTerms when WalletTerms =/= undefined ->
|
||||
WalletTerms#domain_WalletProvisionTerms.p2p;
|
||||
_ ->
|
||||
undefined
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
%%
|
||||
|
||||
-spec ref(id()) -> provider_ref().
|
||||
@ -80,9 +94,9 @@ get(ID) ->
|
||||
-spec get(head | ff_domain_config:revision(), id()) ->
|
||||
{ok, provider()}
|
||||
| {error, notfound}.
|
||||
get(Revision, ID) ->
|
||||
get(DomainRevision, ID) ->
|
||||
do(fun() ->
|
||||
P2PProvider = unwrap(ff_domain_config:object(Revision, {provider, ref(ID)})),
|
||||
P2PProvider = unwrap(ff_domain_config:object(DomainRevision, {provider, ref(ID)})),
|
||||
decode(ID, P2PProvider)
|
||||
end).
|
||||
|
||||
@ -96,47 +110,6 @@ compute_fees(#{terms := Terms}, VS) ->
|
||||
postings => ff_cash_flow:decode_domain_postings(CashFlow)
|
||||
}.
|
||||
|
||||
-spec validate_terms(provider(), hg_selector:varset()) ->
|
||||
{ok, valid}
|
||||
| {error, validate_terms_error()}.
|
||||
validate_terms(#{terms := Terms}, VS) ->
|
||||
#domain_ProvisionTermSet{wallet = WalletTerms} = Terms,
|
||||
#domain_WalletProvisionTerms{p2p = P2PTerms} = WalletTerms,
|
||||
#domain_P2PProvisionTerms{
|
||||
currencies = CurrenciesSelector,
|
||||
fees = FeeSelector,
|
||||
cash_limit = CashLimitSelector
|
||||
} = P2PTerms,
|
||||
do(fun() ->
|
||||
valid = unwrap(validate_currencies(CurrenciesSelector, VS)),
|
||||
valid = unwrap(validate_fee_term_is_reduced(FeeSelector, VS)),
|
||||
valid = unwrap(validate_cash_limit(CashLimitSelector, VS))
|
||||
end).
|
||||
|
||||
%%
|
||||
|
||||
validate_currencies(CurrenciesSelector, #{currency := CurrencyRef} = VS) ->
|
||||
{ok, Currencies} = hg_selector:reduce_to_value(CurrenciesSelector, VS),
|
||||
case ordsets:is_element(CurrencyRef, Currencies) of
|
||||
true ->
|
||||
{ok, valid};
|
||||
false ->
|
||||
{error, {terms_violation, {not_allowed_currency, {CurrencyRef, Currencies}}}}
|
||||
end.
|
||||
|
||||
validate_fee_term_is_reduced(FeeSelector, VS) ->
|
||||
{ok, _Fees} = hg_selector:reduce_to_value(FeeSelector, VS),
|
||||
{ok, valid}.
|
||||
|
||||
validate_cash_limit(CashLimitSelector, #{cost := Cash} = VS) ->
|
||||
{ok, CashRange} = hg_selector:reduce_to_value(CashLimitSelector, VS),
|
||||
case hg_cash_range:is_inside(Cash, CashRange) of
|
||||
within ->
|
||||
{ok, valid};
|
||||
_NotInRange ->
|
||||
{error, {terms_violation, {cash_range, {Cash, CashRange}}}}
|
||||
end.
|
||||
|
||||
decode(ID, #domain_Provider{
|
||||
proxy = Proxy,
|
||||
identity = Identity,
|
||||
|
105
apps/fistful/src/ff_p2p_terminal.erl
Normal file
105
apps/fistful/src/ff_p2p_terminal.erl
Normal file
@ -0,0 +1,105 @@
|
||||
-module(ff_p2p_terminal).
|
||||
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
|
||||
-type terminal() :: #{
|
||||
id := id(),
|
||||
name := binary(),
|
||||
description := binary(),
|
||||
options => dmsl_domain_thrift:'ProxyOptions'(),
|
||||
risk_coverage => atom(),
|
||||
provider_ref => dmsl_domain_thrift:'ProviderRef'(),
|
||||
terms => dmsl_domain_thrift:'ProvisionTermSet'()
|
||||
}.
|
||||
|
||||
-type id() :: dmsl_domain_thrift:'ObjectID'().
|
||||
-type terminal_priority() :: integer() | undefined.
|
||||
|
||||
-type terminal_ref() :: dmsl_domain_thrift:'TerminalRef'().
|
||||
-type term_set() :: dmsl_domain_thrift:'ProvisionTermSet'().
|
||||
-type provision_terms() :: dmsl_domain_thrift:'P2PProvisionTerms'().
|
||||
-type domain_revision() :: ff_domain_config:revision().
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([terminal/0]).
|
||||
-export_type([terminal_ref/0]).
|
||||
-export_type([terminal_priority/0]).
|
||||
-export_type([provision_terms/0]).
|
||||
-export_type([domain_revision/0]).
|
||||
|
||||
-export([adapter_opts/1]).
|
||||
-export([terms/1]).
|
||||
-export([provision_terms/1]).
|
||||
|
||||
-export([ref/1]).
|
||||
-export([get/1]).
|
||||
-export([get/2]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
%%
|
||||
|
||||
-spec adapter_opts(terminal()) -> map().
|
||||
adapter_opts(Terminal) ->
|
||||
maps:get(options, Terminal, #{}).
|
||||
|
||||
-spec terms(terminal()) -> term_set() | undefined.
|
||||
terms(Terminal) ->
|
||||
maps:get(terms, Terminal, undefined).
|
||||
|
||||
-spec provision_terms(terminal()) -> provision_terms() | undefined.
|
||||
provision_terms(Terminal) ->
|
||||
case terms(Terminal) of
|
||||
Terms when Terms =/= undefined ->
|
||||
case Terms#domain_ProvisionTermSet.wallet of
|
||||
WalletTerms when WalletTerms =/= undefined ->
|
||||
WalletTerms#domain_WalletProvisionTerms.p2p;
|
||||
_ ->
|
||||
undefined
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
%%
|
||||
|
||||
-spec ref(id()) -> terminal_ref().
|
||||
ref(ID) ->
|
||||
#domain_TerminalRef{id = ID}.
|
||||
|
||||
-spec get(id()) ->
|
||||
{ok, terminal()}
|
||||
| {error, notfound}.
|
||||
get(ID) ->
|
||||
get(head, ID).
|
||||
|
||||
-spec get(head | domain_revision(), id()) ->
|
||||
{ok, terminal()}
|
||||
| {error, notfound}.
|
||||
get(DomainRevision, ID) ->
|
||||
do(fun() ->
|
||||
P2PTerminal = unwrap(ff_domain_config:object(DomainRevision, {terminal, ref(ID)})),
|
||||
decode(ID, P2PTerminal)
|
||||
end).
|
||||
|
||||
%%
|
||||
|
||||
decode(ID, #domain_Terminal{
|
||||
name = Name,
|
||||
description = Description,
|
||||
options = ProxyOptions,
|
||||
risk_coverage = RiskCoverage,
|
||||
provider_ref = ProviderRef,
|
||||
terms = ProvisionTermSet
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
id => ID,
|
||||
name => Name,
|
||||
description => Description,
|
||||
options => ProxyOptions,
|
||||
risk_coverage => RiskCoverage,
|
||||
provider_ref => ProviderRef,
|
||||
terms => ProvisionTermSet
|
||||
}).
|
@ -17,11 +17,16 @@
|
||||
|
||||
%% Tests
|
||||
|
||||
-export([routes_found_test/1]).
|
||||
-export([no_routes_found_test/1]).
|
||||
-export([rejected_by_prohibitions_table_test/1]).
|
||||
-export([ruleset_misconfig_test/1]).
|
||||
-export([rules_not_found_test/1]).
|
||||
-export([withdrawal_routes_found_test/1]).
|
||||
-export([withdrawal_no_routes_found_test/1]).
|
||||
-export([withdrawal_rejected_by_prohibitions_table_test/1]).
|
||||
-export([withdrawal_ruleset_misconfig_test/1]).
|
||||
-export([withdrawal_rules_not_found_test/1]).
|
||||
-export([p2p_routes_found_test/1]).
|
||||
-export([p2p_no_routes_found_test/1]).
|
||||
-export([p2p_rejected_by_prohibitions_table_test/1]).
|
||||
-export([p2p_ruleset_misconfig_test/1]).
|
||||
-export([p2p_rules_not_found_test/1]).
|
||||
|
||||
%% Internal types
|
||||
|
||||
@ -44,11 +49,16 @@ all() ->
|
||||
groups() ->
|
||||
[
|
||||
{default, [
|
||||
routes_found_test,
|
||||
no_routes_found_test,
|
||||
rejected_by_prohibitions_table_test,
|
||||
ruleset_misconfig_test,
|
||||
rules_not_found_test
|
||||
withdrawal_routes_found_test,
|
||||
withdrawal_no_routes_found_test,
|
||||
withdrawal_rejected_by_prohibitions_table_test,
|
||||
withdrawal_ruleset_misconfig_test,
|
||||
withdrawal_rules_not_found_test,
|
||||
p2p_routes_found_test,
|
||||
p2p_no_routes_found_test,
|
||||
p2p_rejected_by_prohibitions_table_test,
|
||||
p2p_ruleset_misconfig_test,
|
||||
p2p_rules_not_found_test
|
||||
]}
|
||||
].
|
||||
|
||||
@ -90,9 +100,10 @@ end_per_testcase(_Name, _C) ->
|
||||
|
||||
%% Tests
|
||||
|
||||
-spec routes_found_test(config()) -> test_return().
|
||||
routes_found_test(_C) ->
|
||||
-spec withdrawal_routes_found_test(config()) -> test_return().
|
||||
withdrawal_routes_found_test(_C) ->
|
||||
VS = make_varset(?cash(999, <<"RUB">>), <<"12345">>),
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[
|
||||
@ -101,23 +112,25 @@ routes_found_test(_C) ->
|
||||
],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(VS, 1)
|
||||
gather_routes(withdrawal_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec no_routes_found_test(config()) -> test_return().
|
||||
no_routes_found_test(_C) ->
|
||||
-spec withdrawal_no_routes_found_test(config()) -> test_return().
|
||||
withdrawal_no_routes_found_test(_C) ->
|
||||
VS = make_varset(?cash(1000, <<"RUB">>), <<"12345">>),
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(VS, 1)
|
||||
gather_routes(withdrawal_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec rejected_by_prohibitions_table_test(config()) -> test_return().
|
||||
rejected_by_prohibitions_table_test(_C) ->
|
||||
-spec withdrawal_rejected_by_prohibitions_table_test(config()) -> test_return().
|
||||
withdrawal_rejected_by_prohibitions_table_test(_C) ->
|
||||
VS = make_varset(?cash(1000, <<"RUB">>), <<"67890">>),
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[
|
||||
@ -130,29 +143,101 @@ rejected_by_prohibitions_table_test(_C) ->
|
||||
]
|
||||
}
|
||||
},
|
||||
gather_routes(VS, 1)
|
||||
gather_routes(withdrawal_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec ruleset_misconfig_test(config()) -> test_return().
|
||||
ruleset_misconfig_test(_C) ->
|
||||
-spec withdrawal_ruleset_misconfig_test(config()) -> test_return().
|
||||
withdrawal_ruleset_misconfig_test(_C) ->
|
||||
VS = #{party_id => <<"12345">>},
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(VS, 1)
|
||||
gather_routes(withdrawal_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec rules_not_found_test(config()) -> test_return().
|
||||
rules_not_found_test(_C) ->
|
||||
-spec withdrawal_rules_not_found_test(config()) -> test_return().
|
||||
withdrawal_rules_not_found_test(_C) ->
|
||||
VS = make_varset(?cash(1000, <<"RUB">>), <<"12345">>),
|
||||
PaymentInstitutionID = 2,
|
||||
?assertMatch(
|
||||
{
|
||||
[],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(VS, 2)
|
||||
gather_routes(withdrawal_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec p2p_routes_found_test(config()) -> test_return().
|
||||
p2p_routes_found_test(_C) ->
|
||||
VS = make_varset(?cash(999, <<"RUB">>), <<"12345">>),
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[
|
||||
#{terminal_ref := ?trm(101)},
|
||||
#{terminal_ref := ?trm(102)}
|
||||
],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(p2p_transfer_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec p2p_no_routes_found_test(config()) -> test_return().
|
||||
p2p_no_routes_found_test(_C) ->
|
||||
VS = make_varset(?cash(1000, <<"RUB">>), <<"12345">>),
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(p2p_transfer_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec p2p_rejected_by_prohibitions_table_test(config()) -> test_return().
|
||||
p2p_rejected_by_prohibitions_table_test(_C) ->
|
||||
VS = make_varset(?cash(1000, <<"RUB">>), <<"67890">>),
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[
|
||||
#{terminal_ref := ?trm(103)},
|
||||
#{terminal_ref := ?trm(105)}
|
||||
],
|
||||
#{
|
||||
rejected_routes := [
|
||||
{_, ?trm(104), {'RoutingRule', <<"Candidate description">>}}
|
||||
]
|
||||
}
|
||||
},
|
||||
gather_routes(p2p_transfer_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec p2p_ruleset_misconfig_test(config()) -> test_return().
|
||||
p2p_ruleset_misconfig_test(_C) ->
|
||||
VS = #{party_id => <<"12345">>},
|
||||
PaymentInstitutionID = 1,
|
||||
?assertMatch(
|
||||
{
|
||||
[],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(p2p_transfer_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
-spec p2p_rules_not_found_test(config()) -> test_return().
|
||||
p2p_rules_not_found_test(_C) ->
|
||||
VS = make_varset(?cash(1000, <<"RUB">>), <<"12345">>),
|
||||
PaymentInstitutionID = 2,
|
||||
?assertMatch(
|
||||
{
|
||||
[],
|
||||
#{rejected_routes := []}
|
||||
},
|
||||
gather_routes(p2p_transfer_routing_rules, PaymentInstitutionID, VS)
|
||||
).
|
||||
|
||||
%%
|
||||
@ -168,12 +253,12 @@ make_varset(Cash, PartyID) ->
|
||||
party_id => PartyID
|
||||
}.
|
||||
|
||||
gather_routes(Varset, PaymentInstitutionID) ->
|
||||
gather_routes(RoutingRulesTag, PaymentInstitutionID, Varset) ->
|
||||
Revision = ff_domain_config:head(),
|
||||
{ok, PaymentInstitution} = ff_payment_institution:get(PaymentInstitutionID, Revision),
|
||||
ff_routing_rule:gather_routes(
|
||||
PaymentInstitution,
|
||||
withdrawal_routing_rules,
|
||||
RoutingRulesTag,
|
||||
Varset,
|
||||
Revision
|
||||
).
|
||||
|
@ -92,9 +92,7 @@
|
||||
provider_fees => ff_fees_final:fees()
|
||||
}.
|
||||
|
||||
-type route() :: #{
|
||||
provider_id := ff_p2p_provider:id()
|
||||
}.
|
||||
-type route() :: p2p_transfer_routing:route().
|
||||
|
||||
-type body() :: ff_transaction:body().
|
||||
|
||||
@ -219,9 +217,25 @@ create(ID, TransferParams, #{
|
||||
|
||||
-spec get_adapter_with_opts(session_state()) -> adapter_with_opts().
|
||||
get_adapter_with_opts(SessionState) ->
|
||||
#{provider_id := ProviderID} = route(SessionState),
|
||||
{ok, Provider} = ff_p2p_provider:get(head, ProviderID),
|
||||
{ff_p2p_provider:adapter(Provider), ff_p2p_provider:adapter_opts(Provider)}.
|
||||
Route = route(SessionState),
|
||||
ProviderID = p2p_transfer_routing:get_provider(Route),
|
||||
TerminalID = p2p_transfer_routing:get_terminal(Route),
|
||||
get_adapter_with_opts(ProviderID, TerminalID).
|
||||
|
||||
-spec get_adapter_with_opts(ProviderID, TerminalID) -> adapter_with_opts() when
|
||||
ProviderID :: ff_p2p_provider:id(),
|
||||
TerminalID :: ff_p2p_terminal:id() | undefined.
|
||||
get_adapter_with_opts(ProviderID, TerminalID) when is_integer(ProviderID) ->
|
||||
{ok, Provider} = ff_p2p_provider:get(ProviderID),
|
||||
ProviderOpts = ff_p2p_provider:adapter_opts(Provider),
|
||||
TerminalOpts = get_adapter_terminal_opts(TerminalID),
|
||||
{ff_p2p_provider:adapter(Provider), maps:merge(ProviderOpts, TerminalOpts)}.
|
||||
|
||||
get_adapter_terminal_opts(undefined) ->
|
||||
#{};
|
||||
get_adapter_terminal_opts(TerminalID) ->
|
||||
{ok, Terminal} = ff_p2p_terminal:get(TerminalID),
|
||||
ff_p2p_terminal:adapter_opts(Terminal).
|
||||
|
||||
-spec process_session(session_state()) -> result().
|
||||
process_session(SessionState) ->
|
||||
|
@ -114,10 +114,7 @@
|
||||
| {resource_owner(), {bin_data, ff_bin_data:bin_data_error()}}
|
||||
| {resource_owner(), different_resource}.
|
||||
|
||||
-type route() :: #{
|
||||
version := 1,
|
||||
provider_id := provider_id()
|
||||
}.
|
||||
-type route() :: p2p_transfer_routing:route().
|
||||
|
||||
-type adjustment_params() :: #{
|
||||
id := adjustment_id(),
|
||||
@ -152,7 +149,6 @@
|
||||
-export_type([quote/0]).
|
||||
-export_type([quote_state/0]).
|
||||
-export_type([event/0]).
|
||||
-export_type([route/0]).
|
||||
-export_type([create_error/0]).
|
||||
-export_type([action/0]).
|
||||
-export_type([adjustment_params/0]).
|
||||
@ -224,7 +220,6 @@
|
||||
-type adjustments_index() :: ff_adjustment_utils:index().
|
||||
-type party_revision() :: ff_party:revision().
|
||||
-type domain_revision() :: ff_domain_config:revision().
|
||||
-type party_varset() :: hg_selector:varset().
|
||||
-type risk_score() :: p2p_inspector:risk_score().
|
||||
-type participant() :: p2p_participant:participant().
|
||||
-type resource() :: ff_resource:resource().
|
||||
@ -234,11 +229,6 @@
|
||||
|
||||
-type wrapped_adjustment_event() :: ff_adjustment_utils:wrapped_event().
|
||||
|
||||
-type provider_id() :: ff_p2p_provider:id().
|
||||
|
||||
-type routing_rule_route() :: ff_routing_rule:route().
|
||||
-type reject_context() :: ff_routing_rule:reject_context().
|
||||
|
||||
-type legacy_event() :: any().
|
||||
|
||||
-type session() :: #{
|
||||
@ -681,105 +671,26 @@ do_risk_scoring(P2PTransferState) ->
|
||||
-spec process_routing(p2p_transfer_state()) -> process_result().
|
||||
process_routing(P2PTransferState) ->
|
||||
case do_process_routing(P2PTransferState) of
|
||||
{ok, ProviderID} ->
|
||||
{ok, Route} ->
|
||||
{continue, [
|
||||
{route_changed, #{
|
||||
version => 1,
|
||||
provider_id => ProviderID
|
||||
}}
|
||||
{route_changed, Route}
|
||||
]};
|
||||
{error, route_not_found} ->
|
||||
process_transfer_fail(route_not_found, P2PTransferState)
|
||||
end.
|
||||
|
||||
-spec do_process_routing(p2p_transfer_state()) -> {ok, provider_id()} | {error, route_not_found}.
|
||||
-spec do_process_routing(p2p_transfer_state()) -> {ok, route()} | {error, route_not_found}.
|
||||
do_process_routing(P2PTransferState) ->
|
||||
DomainRevision = domain_revision(P2PTransferState),
|
||||
{ok, Identity} = get_identity(owner(P2PTransferState)),
|
||||
|
||||
do(fun() ->
|
||||
VarSet = create_varset(Identity, P2PTransferState),
|
||||
unwrap(prepare_route(VarSet, Identity, DomainRevision))
|
||||
Routes = unwrap(p2p_transfer_routing:prepare_routes(VarSet, Identity, DomainRevision)),
|
||||
Route = hd(Routes),
|
||||
Route
|
||||
end).
|
||||
|
||||
-spec prepare_route(party_varset(), identity(), domain_revision()) -> {ok, provider_id()} | {error, route_not_found}.
|
||||
prepare_route(PartyVarset, Identity, DomainRevision) ->
|
||||
{ok, PaymentInstitutionID} = ff_party:get_identity_payment_institution_id(Identity),
|
||||
{ok, PaymentInstitution} = ff_payment_institution:get(PaymentInstitutionID, DomainRevision),
|
||||
{Routes, RejectContext0} = ff_routing_rule:gather_routes(
|
||||
PaymentInstitution,
|
||||
p2p_transfer_routing_rules,
|
||||
PartyVarset,
|
||||
DomainRevision
|
||||
),
|
||||
{ValidatedRoutes, RejectContext1} = filter_valid_routes(Routes, RejectContext0, PartyVarset),
|
||||
case ValidatedRoutes of
|
||||
[] ->
|
||||
ff_routing_rule:log_reject_context(RejectContext1),
|
||||
logger:log(info, "Fallback to legacy method of routes gathering"),
|
||||
{ok, Providers} = ff_payment_institution:compute_p2p_transfer_providers(PaymentInstitution, PartyVarset),
|
||||
choose_provider_legacy(Providers, PartyVarset);
|
||||
[ProviderID | _] ->
|
||||
{ok, ProviderID}
|
||||
end.
|
||||
|
||||
-spec filter_valid_routes([routing_rule_route()], reject_context(), party_varset()) -> {[route()], reject_context()}.
|
||||
filter_valid_routes(Routes, RejectContext, PartyVarset) ->
|
||||
filter_valid_routes_(Routes, PartyVarset, {#{}, RejectContext}).
|
||||
|
||||
filter_valid_routes_([], _, {Acc, RejectContext}) when map_size(Acc) == 0 ->
|
||||
{[], RejectContext};
|
||||
filter_valid_routes_([], _, {Acc, RejectContext}) ->
|
||||
{convert_to_route(Acc), RejectContext};
|
||||
filter_valid_routes_([Route | Rest], PartyVarset, {Acc0, RejectContext0}) ->
|
||||
Terminal = maps:get(terminal, Route),
|
||||
TerminalRef = maps:get(terminal_ref, Route),
|
||||
ProviderRef = Terminal#domain_Terminal.provider_ref,
|
||||
ProviderID = ProviderRef#domain_ProviderRef.id,
|
||||
Priority = maps:get(priority, Route, undefined),
|
||||
{ok, Provider} = ff_p2p_provider:get(ProviderID),
|
||||
{Acc, RejectConext} =
|
||||
case ff_p2p_provider:validate_terms(Provider, PartyVarset) of
|
||||
{ok, valid} ->
|
||||
Terms = maps:get(Priority, Acc0, []),
|
||||
Acc1 = maps:put(Priority, [ProviderID | Terms], Acc0),
|
||||
{Acc1, RejectContext0};
|
||||
{error, RejectReason} ->
|
||||
RejectedRoutes0 = maps:get(rejected_routes, RejectContext0),
|
||||
RejectedRoutes1 = [{ProviderRef, TerminalRef, RejectReason} | RejectedRoutes0],
|
||||
RejectContext1 = maps:put(rejected_routes, RejectedRoutes1, RejectContext0),
|
||||
{Acc0, RejectContext1}
|
||||
end,
|
||||
filter_valid_routes_(Rest, PartyVarset, {RejectConext, Acc}).
|
||||
|
||||
convert_to_route(ProviderTerminalMap) ->
|
||||
lists:foldl(
|
||||
fun({_Priority, Providers}, Acc) ->
|
||||
lists:sort(Providers) ++ Acc
|
||||
end,
|
||||
[],
|
||||
lists:keysort(1, maps:to_list(ProviderTerminalMap))
|
||||
).
|
||||
|
||||
-spec choose_provider_legacy([provider_id()], party_varset()) -> {ok, provider_id()} | {error, route_not_found}.
|
||||
choose_provider_legacy(Providers, VS) ->
|
||||
case lists:filter(fun(P) -> validate_terms(P, VS) end, Providers) of
|
||||
[ProviderID | _] ->
|
||||
{ok, ProviderID};
|
||||
[] ->
|
||||
{error, route_not_found}
|
||||
end.
|
||||
|
||||
-spec validate_terms(provider_id(), party_varset()) -> boolean().
|
||||
validate_terms(ID, VS) ->
|
||||
{ok, Provider} = ff_p2p_provider:get(ID),
|
||||
case ff_p2p_provider:validate_terms(Provider, VS) of
|
||||
{ok, valid} ->
|
||||
true;
|
||||
{error, _Error} ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec process_p_transfer_creation(p2p_transfer_state()) -> process_result().
|
||||
process_p_transfer_creation(P2PTransferState) ->
|
||||
FinalCashFlow = make_final_cash_flow(P2PTransferState),
|
||||
@ -800,11 +711,8 @@ process_session_creation(P2PTransferState) ->
|
||||
merchant_fees => MerchantFees,
|
||||
provider_fees => ProviderFees
|
||||
}),
|
||||
#{provider_id := ProviderID} = route(P2PTransferState),
|
||||
Params = #{
|
||||
route => #{
|
||||
provider_id => ProviderID
|
||||
},
|
||||
route => route(P2PTransferState),
|
||||
domain_revision => domain_revision(P2PTransferState),
|
||||
party_revision => party_revision(P2PTransferState)
|
||||
},
|
||||
|
222
apps/p2p/src/p2p_transfer_routing.erl
Normal file
222
apps/p2p/src/p2p_transfer_routing.erl
Normal file
@ -0,0 +1,222 @@
|
||||
-module(p2p_transfer_routing).
|
||||
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
|
||||
-export([prepare_routes/3]).
|
||||
-export([get_provider/1]).
|
||||
-export([get_terminal/1]).
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
-type route() :: #{
|
||||
version := 1,
|
||||
provider_id := provider_id(),
|
||||
terminal_id => terminal_id()
|
||||
}.
|
||||
|
||||
-export_type([route/0]).
|
||||
|
||||
-type identity() :: ff_identity:identity_state().
|
||||
-type domain_revision() :: ff_domain_config:revision().
|
||||
-type party_varset() :: hg_selector:varset().
|
||||
|
||||
-type provider_id() :: ff_p2p_provider:id().
|
||||
-type provider() :: ff_p2p_provider:provider().
|
||||
|
||||
-type terminal_id() :: ff_p2p_terminal:id().
|
||||
-type terminal() :: ff_p2p_terminal:terminal().
|
||||
|
||||
-type routing_rule_route() :: ff_routing_rule:route().
|
||||
-type reject_context() :: ff_routing_rule:reject_context().
|
||||
|
||||
-type p2p_provision_terms() :: dmsl_domain_thrift:'P2PProvisionTerms'().
|
||||
|
||||
-spec prepare_routes(party_varset(), identity(), domain_revision()) -> {ok, [route()]} | {error, route_not_found}.
|
||||
prepare_routes(PartyVarset, Identity, DomainRevision) ->
|
||||
{ok, PaymentInstitutionID} = ff_party:get_identity_payment_institution_id(Identity),
|
||||
{ok, PaymentInstitution} = ff_payment_institution:get(PaymentInstitutionID, DomainRevision),
|
||||
{Routes, RejectContext0} = ff_routing_rule:gather_routes(
|
||||
PaymentInstitution,
|
||||
p2p_transfer_routing_rules,
|
||||
PartyVarset,
|
||||
DomainRevision
|
||||
),
|
||||
{ValidatedRoutes, RejectContext1} = filter_valid_routes(Routes, RejectContext0, PartyVarset),
|
||||
case ValidatedRoutes of
|
||||
[] ->
|
||||
ff_routing_rule:log_reject_context(RejectContext1),
|
||||
logger:log(info, "Fallback to legacy method of routes gathering"),
|
||||
{ok, Providers} = ff_payment_institution:compute_p2p_transfer_providers(PaymentInstitution, PartyVarset),
|
||||
FilteredRoutes = filter_routes_legacy(Providers, PartyVarset),
|
||||
case FilteredRoutes of
|
||||
[] ->
|
||||
{error, route_not_found};
|
||||
[_Route | _] ->
|
||||
{ok, FilteredRoutes}
|
||||
end;
|
||||
[_Route | _] ->
|
||||
{ok, ValidatedRoutes}
|
||||
end.
|
||||
|
||||
-spec make_route(provider_id(), terminal_id() | undefined) -> route().
|
||||
make_route(ProviderID, TerminalID) ->
|
||||
genlib_map:compact(#{
|
||||
version => 1,
|
||||
provider_id => ProviderID,
|
||||
terminal_id => TerminalID
|
||||
}).
|
||||
|
||||
-spec get_provider(route()) -> provider_id().
|
||||
get_provider(#{provider_id := ProviderID}) ->
|
||||
ProviderID.
|
||||
|
||||
-spec get_terminal(route()) -> ff_maybe:maybe(terminal_id()).
|
||||
get_terminal(Route) ->
|
||||
maps:get(terminal_id, Route, undefined).
|
||||
|
||||
-spec filter_valid_routes([routing_rule_route()], reject_context(), party_varset()) -> {[route()], reject_context()}.
|
||||
filter_valid_routes(Routes, RejectContext, PartyVarset) ->
|
||||
filter_valid_routes_(Routes, PartyVarset, {#{}, RejectContext}).
|
||||
|
||||
filter_valid_routes_([], _, {Acc, RejectContext}) when map_size(Acc) == 0 ->
|
||||
{[], RejectContext};
|
||||
filter_valid_routes_([], _, {Acc, RejectContext}) ->
|
||||
{convert_to_route(Acc), RejectContext};
|
||||
filter_valid_routes_([Route | Rest], PartyVarset, {Acc0, RejectContext0}) ->
|
||||
Terminal = maps:get(terminal, Route),
|
||||
TerminalRef = maps:get(terminal_ref, Route),
|
||||
TerminalID = TerminalRef#domain_TerminalRef.id,
|
||||
ProviderRef = Terminal#domain_Terminal.provider_ref,
|
||||
ProviderID = ProviderRef#domain_ProviderRef.id,
|
||||
Priority = maps:get(priority, Route, undefined),
|
||||
{ok, P2PTerminal} = ff_p2p_terminal:get(TerminalID),
|
||||
{ok, P2PProvider} = ff_p2p_provider:get(ProviderID),
|
||||
{Acc, RejectConext} =
|
||||
case validate_terms(P2PProvider, P2PTerminal, PartyVarset) of
|
||||
{ok, valid} ->
|
||||
Terms = maps:get(Priority, Acc0, []),
|
||||
Acc1 = maps:put(Priority, [{ProviderID, TerminalID} | Terms], Acc0),
|
||||
{Acc1, RejectContext0};
|
||||
{error, RejectReason} ->
|
||||
RejectedRoutes0 = maps:get(rejected_routes, RejectContext0),
|
||||
RejectedRoutes1 = [{ProviderRef, TerminalRef, RejectReason} | RejectedRoutes0],
|
||||
RejectContext1 = maps:put(rejected_routes, RejectedRoutes1, RejectContext0),
|
||||
{Acc0, RejectContext1}
|
||||
end,
|
||||
filter_valid_routes_(Rest, PartyVarset, {RejectConext, Acc}).
|
||||
|
||||
-spec filter_routes_legacy([provider_id()], party_varset()) -> [route()].
|
||||
filter_routes_legacy(Providers, VS) ->
|
||||
lists:foldr(
|
||||
fun(ProviderID, Acc) ->
|
||||
{ok, Provider} = ff_p2p_provider:get(ProviderID),
|
||||
case validate_terms_legacy(Provider, VS) of
|
||||
{ok, valid} ->
|
||||
[make_route(ProviderID, undefined) | Acc];
|
||||
{error, _Error} ->
|
||||
Acc
|
||||
end
|
||||
end,
|
||||
[],
|
||||
Providers
|
||||
).
|
||||
|
||||
-spec validate_terms_legacy(provider(), party_varset()) ->
|
||||
{ok, valid}
|
||||
| {error, Error :: term()}.
|
||||
validate_terms_legacy(Provider, VS) ->
|
||||
do(fun() ->
|
||||
ProviderTerms = ff_p2p_provider:provision_terms(Provider),
|
||||
unwrap(validate_combined_terms(ProviderTerms, VS))
|
||||
end).
|
||||
|
||||
-spec validate_terms(provider(), terminal(), party_varset()) ->
|
||||
{ok, valid}
|
||||
| {error, Error :: term()}.
|
||||
validate_terms(Provider, Terminal, VS) ->
|
||||
do(fun() ->
|
||||
ProviderTerms = ff_p2p_provider:provision_terms(Provider),
|
||||
TerminalTerms = ff_p2p_terminal:provision_terms(Terminal),
|
||||
_ = unwrap(assert_terms_defined(TerminalTerms, ProviderTerms)),
|
||||
CombinedTerms = merge_p2p_terms(ProviderTerms, TerminalTerms),
|
||||
unwrap(validate_combined_terms(CombinedTerms, VS))
|
||||
end).
|
||||
|
||||
assert_terms_defined(undefined, undefined) ->
|
||||
{error, terms_undefined};
|
||||
assert_terms_defined(_, _) ->
|
||||
{ok, valid}.
|
||||
|
||||
-spec validate_combined_terms(p2p_provision_terms(), party_varset()) ->
|
||||
{ok, valid}
|
||||
| {error, Error :: term()}.
|
||||
validate_combined_terms(CombinedTerms, VS) ->
|
||||
do(fun() ->
|
||||
#domain_P2PProvisionTerms{
|
||||
currencies = CurrenciesSelector,
|
||||
fees = FeeSelector,
|
||||
cash_limit = CashLimitSelector
|
||||
} = CombinedTerms,
|
||||
valid = unwrap(validate_currencies(CurrenciesSelector, VS)),
|
||||
valid = unwrap(validate_fee_term_is_reduced(FeeSelector, VS)),
|
||||
valid = unwrap(validate_cash_limit(CashLimitSelector, VS))
|
||||
end).
|
||||
|
||||
validate_currencies(CurrenciesSelector, #{currency := CurrencyRef} = VS) ->
|
||||
{ok, Currencies} = hg_selector:reduce_to_value(CurrenciesSelector, VS),
|
||||
case ordsets:is_element(CurrencyRef, Currencies) of
|
||||
true ->
|
||||
{ok, valid};
|
||||
false ->
|
||||
{error, {terms_violation, {not_allowed_currency, {CurrencyRef, Currencies}}}}
|
||||
end.
|
||||
|
||||
validate_fee_term_is_reduced(FeeSelector, VS) ->
|
||||
{ok, _Fees} = hg_selector:reduce_to_value(FeeSelector, VS),
|
||||
{ok, valid}.
|
||||
|
||||
validate_cash_limit(CashLimitSelector, #{cost := Cash} = VS) ->
|
||||
{ok, CashRange} = hg_selector:reduce_to_value(CashLimitSelector, VS),
|
||||
case hg_cash_range:is_inside(Cash, CashRange) of
|
||||
within ->
|
||||
{ok, valid};
|
||||
_NotInRange ->
|
||||
{error, {terms_violation, {cash_range, {Cash, CashRange}}}}
|
||||
end.
|
||||
|
||||
-spec merge_p2p_terms(
|
||||
ff_p2p_provider:provision_terms() | undefined,
|
||||
ff_p2p_terminal:provision_terms() | undefined
|
||||
) -> ff_maybe:maybe(p2p_provision_terms()).
|
||||
merge_p2p_terms(
|
||||
#domain_P2PProvisionTerms{
|
||||
currencies = PCurrencies,
|
||||
fees = PFees,
|
||||
cash_limit = PCashLimit,
|
||||
cash_flow = PCashflow
|
||||
},
|
||||
#domain_P2PProvisionTerms{
|
||||
currencies = TCurrencies,
|
||||
fees = TFees,
|
||||
cash_limit = TCashLimit,
|
||||
cash_flow = TCashflow
|
||||
}
|
||||
) ->
|
||||
#domain_P2PProvisionTerms{
|
||||
currencies = ff_maybe:get_defined(TCurrencies, PCurrencies),
|
||||
fees = ff_maybe:get_defined(PFees, TFees),
|
||||
cash_limit = ff_maybe:get_defined(TCashLimit, PCashLimit),
|
||||
cash_flow = ff_maybe:get_defined(TCashflow, PCashflow)
|
||||
};
|
||||
merge_p2p_terms(ProviderTerms, TerminalTerms) ->
|
||||
ff_maybe:get_defined(TerminalTerms, ProviderTerms).
|
||||
|
||||
convert_to_route(ProviderTerminalMap) ->
|
||||
lists:foldl(
|
||||
fun({_, Data}, Acc) ->
|
||||
SortedRoutes = [make_route(P, T) || {P, T} <- lists:sort(Data)],
|
||||
SortedRoutes ++ Acc
|
||||
end,
|
||||
[],
|
||||
lists:keysort(1, maps:to_list(ProviderTerminalMap))
|
||||
).
|
Loading…
Reference in New Issue
Block a user