mirror of
https://github.com/valitydev/wapi-lib.git
synced 2024-11-07 02:35:18 +00:00
DC-104: withdrawals routing (#54)
* DC-104: withdrawals routing shittycode typhoon * linter fix * renaming * remove default IdentityID * postmerge fix * better identity IDs in tests
This commit is contained in:
parent
487cfaf843
commit
27bea54b03
@ -18,6 +18,7 @@
|
|||||||
-define(eas(ID), #domain_ExternalAccountSetRef{id = ID}).
|
-define(eas(ID), #domain_ExternalAccountSetRef{id = ID}).
|
||||||
-define(insp(ID), #domain_InspectorRef{id = ID}).
|
-define(insp(ID), #domain_InspectorRef{id = ID}).
|
||||||
-define(payinst(ID), #domain_PaymentInstitutionRef{id = ID}).
|
-define(payinst(ID), #domain_PaymentInstitutionRef{id = ID}).
|
||||||
|
-define(wthdr_prv(ID), #domain_WithdrawalProviderRef{id = ID}).
|
||||||
|
|
||||||
-define(cash(Amount, SymCode),
|
-define(cash(Amount, SymCode),
|
||||||
#domain_Cash{amount = Amount, currency = ?cur(SymCode)}
|
#domain_Cash{amount = Amount, currency = ?cur(SymCode)}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
-export([inspector/4]).
|
-export([inspector/4]).
|
||||||
-export([proxy/2]).
|
-export([proxy/2]).
|
||||||
-export([proxy/3]).
|
-export([proxy/3]).
|
||||||
|
-export([proxy/4]).
|
||||||
-export([system_account_set/4]).
|
-export([system_account_set/4]).
|
||||||
-export([external_account_set/4]).
|
-export([external_account_set/4]).
|
||||||
-export([term_set_hierarchy/1]).
|
-export([term_set_hierarchy/1]).
|
||||||
@ -19,6 +20,7 @@
|
|||||||
-export([term_set_hierarchy/3]).
|
-export([term_set_hierarchy/3]).
|
||||||
-export([timed_term_set/1]).
|
-export([timed_term_set/1]).
|
||||||
-export([globals/2]).
|
-export([globals/2]).
|
||||||
|
-export([withdrawal_provider/4]).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
@ -30,6 +32,46 @@
|
|||||||
-type object() ::
|
-type object() ::
|
||||||
dmsl_domain_thrift:'DomainObject'().
|
dmsl_domain_thrift:'DomainObject'().
|
||||||
|
|
||||||
|
-spec withdrawal_provider(?dtp('WithdrawalProviderRef'), ?dtp('ProxyRef'), binary(), ct_helper:config()) ->
|
||||||
|
object().
|
||||||
|
|
||||||
|
withdrawal_provider(Ref, ProxyRef, IdentityID, C) ->
|
||||||
|
AccountID = account(<<"RUB">>, C),
|
||||||
|
{withdrawal_provider, #domain_WithdrawalProviderObject{
|
||||||
|
ref = Ref,
|
||||||
|
data = #domain_WithdrawalProvider{
|
||||||
|
name = <<"WithdrawalProvider">>,
|
||||||
|
proxy = #domain_Proxy{ref = ProxyRef, additional = #{}},
|
||||||
|
identity = IdentityID,
|
||||||
|
withdrawal_terms = #domain_WithdrawalProvisionTerms{
|
||||||
|
currencies = {value, ?ordset([])},
|
||||||
|
payout_methods = {value, ?ordset([])},
|
||||||
|
cash_limit = {value, ?cashrng(
|
||||||
|
{inclusive, ?cash( 0, <<"RUB">>)},
|
||||||
|
{exclusive, ?cash(10000000, <<"RUB">>)}
|
||||||
|
)},
|
||||||
|
cash_flow = {decisions, [
|
||||||
|
#domain_CashFlowDecision{
|
||||||
|
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
|
||||||
|
then_ = {value, [
|
||||||
|
?cfpost(
|
||||||
|
{system, settlement},
|
||||||
|
{provider, settlement},
|
||||||
|
{product, {min_of, ?ordset([
|
||||||
|
?fixed(10, <<"RUB">>),
|
||||||
|
?share(5, 100, operation_amount, round_half_towards_zero)
|
||||||
|
])}}
|
||||||
|
)
|
||||||
|
]}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
},
|
||||||
|
accounts = #{
|
||||||
|
?cur(<<"RUB">>) => #domain_ProviderAccount{settlement = AccountID}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}.
|
||||||
|
|
||||||
-spec currency(?dtp('CurrencyRef')) ->
|
-spec currency(?dtp('CurrencyRef')) ->
|
||||||
object().
|
object().
|
||||||
|
|
||||||
@ -120,22 +162,29 @@ inspector(Ref, Name, ProxyRef, Additional) ->
|
|||||||
}
|
}
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
-spec proxy(?dtp('ProxyRef'), binary()) ->
|
-spec proxy(?dtp('ProxyRef'), Name :: binary()) ->
|
||||||
object().
|
object().
|
||||||
|
|
||||||
proxy(Ref, Name) ->
|
proxy(Ref, Name) ->
|
||||||
proxy(Ref, Name, #{}).
|
proxy(Ref, Name, <<>>).
|
||||||
|
|
||||||
-spec proxy(?dtp('ProxyRef'), binary(), ?dtp('ProxyOptions')) ->
|
-spec proxy(?dtp('ProxyRef'), Name :: binary(), URL :: binary()) ->
|
||||||
object().
|
object().
|
||||||
|
|
||||||
proxy(Ref, Name, Opts) ->
|
proxy(Ref, Name, URL) ->
|
||||||
|
proxy(Ref, Name, URL, #{}).
|
||||||
|
|
||||||
|
|
||||||
|
-spec proxy(?dtp('ProxyRef'), Name :: binary(), URL :: binary(), ?dtp('ProxyOptions')) ->
|
||||||
|
object().
|
||||||
|
|
||||||
|
proxy(Ref, Name, URL, Opts) ->
|
||||||
{proxy, #domain_ProxyObject{
|
{proxy, #domain_ProxyObject{
|
||||||
ref = Ref,
|
ref = Ref,
|
||||||
data = #domain_ProxyDefinition{
|
data = #domain_ProxyDefinition{
|
||||||
name = Name,
|
name = Name,
|
||||||
description = <<>>,
|
description = <<>>,
|
||||||
url = <<>>,
|
url = URL,
|
||||||
options = Opts
|
options = Opts
|
||||||
}
|
}
|
||||||
}}.
|
}}.
|
||||||
|
@ -14,7 +14,9 @@
|
|||||||
services => map(),
|
services => map(),
|
||||||
domain_config => list(),
|
domain_config => list(),
|
||||||
default_termset => dmsl_domain_thrift:'TermSet'(),
|
default_termset => dmsl_domain_thrift:'TermSet'(),
|
||||||
company_termset => dmsl_domain_thrift:'TermSet'()
|
company_termset => dmsl_domain_thrift:'TermSet'(),
|
||||||
|
payment_inst_identity_id => id(),
|
||||||
|
provider_identity_id => id()
|
||||||
}.
|
}.
|
||||||
-opaque system() :: #{
|
-opaque system() :: #{
|
||||||
started_apps := [atom()],
|
started_apps := [atom()],
|
||||||
@ -48,7 +50,11 @@ shutdown(C) ->
|
|||||||
%% Internals
|
%% Internals
|
||||||
|
|
||||||
-spec do_setup(options(), config()) -> config().
|
-spec do_setup(options(), config()) -> config().
|
||||||
do_setup(Options, C0) ->
|
do_setup(Options0, C0) ->
|
||||||
|
Options = Options0#{
|
||||||
|
payment_inst_identity_id => genlib:unique(),
|
||||||
|
provider_identity_id => genlib:unique()
|
||||||
|
},
|
||||||
{ok, Processing0} = start_processing_apps(Options),
|
{ok, Processing0} = start_processing_apps(Options),
|
||||||
C1 = ct_helper:makeup_cfg([ct_helper:woody_ctx()], [{services, services(Options)} | C0]),
|
C1 = ct_helper:makeup_cfg([ct_helper:woody_ctx()], [{services, services(Options)} | C0]),
|
||||||
ok = ff_woody_ctx:set(ct_helper:get_woody_ctx(C1)),
|
ok = ff_woody_ctx:set(ct_helper:get_woody_ctx(C1)),
|
||||||
@ -128,7 +134,7 @@ start_processing_apps(Options) ->
|
|||||||
setup_dominant(Options, C) ->
|
setup_dominant(Options, C) ->
|
||||||
ok = ct_domain_config:upsert(domain_config(Options, C)).
|
ok = ct_domain_config:upsert(domain_config(Options, C)).
|
||||||
|
|
||||||
configure_processing_apps(_Options) ->
|
configure_processing_apps(Options) ->
|
||||||
ok = set_app_env(
|
ok = set_app_env(
|
||||||
[ff_transfer, withdrawal, system, accounts, settlement, <<"RUB">>],
|
[ff_transfer, withdrawal, system, accounts, settlement, <<"RUB">>],
|
||||||
create_company_account()
|
create_company_account()
|
||||||
@ -140,7 +146,8 @@ configure_processing_apps(_Options) ->
|
|||||||
ok = set_app_env(
|
ok = set_app_env(
|
||||||
[ff_transfer, withdrawal, provider, <<"mocketbank">>, accounts, <<"RUB">>],
|
[ff_transfer, withdrawal, provider, <<"mocketbank">>, accounts, <<"RUB">>],
|
||||||
create_company_account()
|
create_company_account()
|
||||||
).
|
),
|
||||||
|
ok = create_crunch_identity(Options).
|
||||||
|
|
||||||
construct_handler(Module, Suffix, BeConf) ->
|
construct_handler(Module, Suffix, BeConf) ->
|
||||||
{{fistful, Module},
|
{{fistful, Module},
|
||||||
@ -196,6 +203,14 @@ get_eventsink_routes(BeConf) ->
|
|||||||
DepositRoute
|
DepositRoute
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
create_crunch_identity(Options) ->
|
||||||
|
PartyID = create_party(),
|
||||||
|
PaymentInstIdentityID = payment_inst_identity_id(Options),
|
||||||
|
PaymentInstIdentityID = create_identity(PaymentInstIdentityID, PartyID, <<"good-one">>, <<"church">>),
|
||||||
|
ProviderIdentityID = provider_identity_id(Options),
|
||||||
|
ProviderIdentityID = create_identity(ProviderIdentityID, PartyID, <<"good-one">>, <<"church">>),
|
||||||
|
ok.
|
||||||
|
|
||||||
create_company_account() ->
|
create_company_account() ->
|
||||||
PartyID = create_party(),
|
PartyID = create_party(),
|
||||||
IdentityID = create_company_identity(PartyID),
|
IdentityID = create_company_identity(PartyID),
|
||||||
@ -205,19 +220,22 @@ create_company_account() ->
|
|||||||
{ok, [{created, Account}]} = ff_account:create(PartyID, Identity, Currency),
|
{ok, [{created, Account}]} = ff_account:create(PartyID, Identity, Currency),
|
||||||
Account.
|
Account.
|
||||||
|
|
||||||
create_company_identity(Party) ->
|
create_company_identity(PartyID) ->
|
||||||
create_identity(Party, <<"good-one">>, <<"church">>).
|
create_identity(PartyID, <<"good-one">>, <<"church">>).
|
||||||
|
|
||||||
create_party() ->
|
create_party() ->
|
||||||
ID = genlib:bsuuid(),
|
ID = genlib:bsuuid(),
|
||||||
_ = ff_party:create(ID),
|
_ = ff_party:create(ID),
|
||||||
ID.
|
ID.
|
||||||
|
|
||||||
create_identity(Party, ProviderID, ClassID) ->
|
create_identity(PartyID, ProviderID, ClassID) ->
|
||||||
ID = genlib:unique(),
|
ID = genlib:unique(),
|
||||||
|
create_identity(ID, PartyID, ProviderID, ClassID).
|
||||||
|
|
||||||
|
create_identity(ID, PartyID, ProviderID, ClassID) ->
|
||||||
ok = ff_identity_machine:create(
|
ok = ff_identity_machine:create(
|
||||||
ID,
|
ID,
|
||||||
#{party => Party, provider => ProviderID, class => ClassID},
|
#{party => PartyID, provider => ProviderID, class => ClassID},
|
||||||
ff_ctx:new()
|
ff_ctx:new()
|
||||||
),
|
),
|
||||||
ID.
|
ID.
|
||||||
@ -374,6 +392,12 @@ services(Options) ->
|
|||||||
|
|
||||||
-include_lib("ff_cth/include/ct_domain.hrl").
|
-include_lib("ff_cth/include/ct_domain.hrl").
|
||||||
|
|
||||||
|
payment_inst_identity_id(Options) ->
|
||||||
|
maps:get(payment_inst_identity_id, Options).
|
||||||
|
|
||||||
|
provider_identity_id(Options) ->
|
||||||
|
maps:get(provider_identity_id, Options).
|
||||||
|
|
||||||
domain_config(Options, C) ->
|
domain_config(Options, C) ->
|
||||||
Default = [
|
Default = [
|
||||||
|
|
||||||
@ -389,7 +413,10 @@ domain_config(Options, C) ->
|
|||||||
providers = {value, ?ordset([])},
|
providers = {value, ?ordset([])},
|
||||||
inspector = {value, ?insp(1)},
|
inspector = {value, ?insp(1)},
|
||||||
residences = ['rus'],
|
residences = ['rus'],
|
||||||
realm = live
|
realm = live,
|
||||||
|
wallet_system_account_set = {value, ?sas(1)},
|
||||||
|
identity = payment_inst_identity_id(Options),
|
||||||
|
withdrawal_providers = {value, ?ordset([?wthdr_prv(1)])}
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
|
|
||||||
@ -397,6 +424,9 @@ domain_config(Options, C) ->
|
|||||||
|
|
||||||
ct_domain:inspector(?insp(1), <<"Low Life">>, ?prx(1), #{<<"risk_score">> => <<"low">>}),
|
ct_domain:inspector(?insp(1), <<"Low Life">>, ?prx(1), #{<<"risk_score">> => <<"low">>}),
|
||||||
ct_domain:proxy(?prx(1), <<"Inspector proxy">>),
|
ct_domain:proxy(?prx(1), <<"Inspector proxy">>),
|
||||||
|
ct_domain:proxy(?prx(2), <<"Mocket proxy">>, <<"http://adapter-mocketbank:8022/proxy/mocketbank/p2p-credit">>),
|
||||||
|
|
||||||
|
ct_domain:withdrawal_provider(?wthdr_prv(1), ?prx(2), provider_identity_id(Options), C),
|
||||||
|
|
||||||
ct_domain:contract_template(?tmpl(1), ?trms(1)),
|
ct_domain:contract_template(?tmpl(1), ?trms(1)),
|
||||||
ct_domain:term_set_hierarchy(?trms(1), [ct_domain:timed_term_set(default_termset(Options))]),
|
ct_domain:term_set_hierarchy(?trms(1), [ct_domain:timed_term_set(default_termset(Options))]),
|
||||||
|
@ -173,7 +173,7 @@ marshal(withdrawal_route_changed, #{
|
|||||||
provider_id := ProviderID
|
provider_id := ProviderID
|
||||||
}) ->
|
}) ->
|
||||||
#wthd_RouteChange{
|
#wthd_RouteChange{
|
||||||
id = marshal(id, ProviderID)
|
id = marshal(id, genlib:to_binary(ProviderID))
|
||||||
};
|
};
|
||||||
|
|
||||||
marshal(T, V) ->
|
marshal(T, V) ->
|
||||||
|
@ -68,7 +68,7 @@ marshal(session, #{
|
|||||||
id = marshal(id, SessionID),
|
id = marshal(id, SessionID),
|
||||||
status = marshal(session_status, SessionStatus),
|
status = marshal(session_status, SessionStatus),
|
||||||
withdrawal = marshal(withdrawal, Withdrawal),
|
withdrawal = marshal(withdrawal, Withdrawal),
|
||||||
provider = marshal(id, ProviderID)
|
provider = marshal(id, genlib:to_binary(ProviderID))
|
||||||
};
|
};
|
||||||
|
|
||||||
marshal(session_status, active) ->
|
marshal(session_status, active) ->
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
-type events() :: ff_transfer_machine:events(ff_transfer:event(transfer_params(), route())).
|
-type events() :: ff_transfer_machine:events(ff_transfer:event(transfer_params(), route())).
|
||||||
-type event() :: ff_transfer_machine:event(ff_transfer:event(transfer_params(), route())).
|
-type event() :: ff_transfer_machine:event(ff_transfer:event(transfer_params(), route())).
|
||||||
-type route() :: ff_transfer:route(#{
|
-type route() :: ff_transfer:route(#{
|
||||||
provider_id := id()
|
% TODO I'm now sure about this change, it may crash old events. Or not. ))
|
||||||
|
provider_id := pos_integer() | id()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-export_type([withdrawal/0]).
|
-export_type([withdrawal/0]).
|
||||||
@ -228,19 +229,81 @@ do_process_transfer(idle, Withdrawal) ->
|
|||||||
{error, _Reason}.
|
{error, _Reason}.
|
||||||
create_route(Withdrawal) ->
|
create_route(Withdrawal) ->
|
||||||
#{
|
#{
|
||||||
|
wallet_id := WalletID,
|
||||||
destination_id := DestinationID
|
destination_id := DestinationID
|
||||||
} = params(Withdrawal),
|
} = params(Withdrawal),
|
||||||
|
Body = body(Withdrawal),
|
||||||
do(fun () ->
|
do(fun () ->
|
||||||
|
Wallet = ff_wallet_machine:wallet(unwrap(wallet, ff_wallet_machine:get(WalletID))),
|
||||||
|
PaymentInstitutionID = unwrap(ff_party:get_wallet_payment_institution_id(Wallet)),
|
||||||
|
PaymentInstitution = unwrap(ff_payment_institution:get(PaymentInstitutionID)),
|
||||||
DestinationMachine = unwrap(destination, ff_destination:get_machine(DestinationID)),
|
DestinationMachine = unwrap(destination, ff_destination:get_machine(DestinationID)),
|
||||||
Destination = ff_destination:get(DestinationMachine),
|
Destination = ff_destination:get(DestinationMachine),
|
||||||
ProviderID = unwrap(route, ff_withdrawal_provider:choose(Destination, body(Withdrawal))),
|
VS = unwrap(collect_varset(Body, Wallet, Destination)),
|
||||||
|
ProviderID = unwrap(ff_payment_institution:compute_withdrawal_provider(PaymentInstitution, VS)),
|
||||||
{continue, [{route_changed, #{provider_id => ProviderID}}]}
|
{continue, [{route_changed, #{provider_id => ProviderID}}]}
|
||||||
end).
|
end).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_p_transfer(withdrawal()) ->
|
-spec create_p_transfer(withdrawal()) ->
|
||||||
{ok, process_result()} |
|
{ok, process_result()} |
|
||||||
{error, _Reason}.
|
{error, _Reason}.
|
||||||
create_p_transfer(Withdrawal) ->
|
create_p_transfer(Withdrawal) ->
|
||||||
|
#{provider_id := ProviderID} = route(Withdrawal),
|
||||||
|
case is_integer(ProviderID) of
|
||||||
|
true ->
|
||||||
|
create_p_transfer_new_style(Withdrawal);
|
||||||
|
false when is_binary(ProviderID) ->
|
||||||
|
create_p_transfer_old_style(Withdrawal)
|
||||||
|
end.
|
||||||
|
|
||||||
|
create_p_transfer_new_style(Withdrawal) ->
|
||||||
|
#{
|
||||||
|
wallet_id := WalletID,
|
||||||
|
wallet_account := WalletAccount,
|
||||||
|
destination_id := DestinationID,
|
||||||
|
destination_account := DestinationAccount,
|
||||||
|
wallet_cash_flow_plan := WalletCashFlowPlan
|
||||||
|
} = params(Withdrawal),
|
||||||
|
{_Amount, CurrencyID} = body(Withdrawal),
|
||||||
|
#{provider_id := ProviderID} = route(Withdrawal),
|
||||||
|
do(fun () ->
|
||||||
|
Provider = unwrap(provider, ff_payouts_provider:get(ProviderID)),
|
||||||
|
ProviderAccounts = ff_payouts_provider:accounts(Provider),
|
||||||
|
ProviderAccount = maps:get(CurrencyID, ProviderAccounts, undefined),
|
||||||
|
|
||||||
|
Wallet = ff_wallet_machine:wallet(unwrap(wallet, ff_wallet_machine:get(WalletID))),
|
||||||
|
PaymentInstitutionID = unwrap(ff_party:get_wallet_payment_institution_id(Wallet)),
|
||||||
|
PaymentInstitution = unwrap(ff_payment_institution:get(PaymentInstitutionID)),
|
||||||
|
DestinationMachine = unwrap(destination, ff_destination:get_machine(DestinationID)),
|
||||||
|
Destination = ff_destination:get(DestinationMachine),
|
||||||
|
VS = unwrap(collect_varset(body(Withdrawal), Wallet, Destination)),
|
||||||
|
SystemAccounts = unwrap(ff_payment_institution:compute_system_accounts(PaymentInstitution, VS)),
|
||||||
|
|
||||||
|
SystemAccount = maps:get(CurrencyID, SystemAccounts, #{}),
|
||||||
|
SettlementAccount = maps:get(settlement, SystemAccount, undefined),
|
||||||
|
SubagentAccount = maps:get(subagent, SystemAccount, undefined),
|
||||||
|
|
||||||
|
ProviderFee = ff_payouts_provider:compute_fees(Provider, VS),
|
||||||
|
|
||||||
|
CashFlowPlan = unwrap(provider_fee, ff_cash_flow:add_fee(WalletCashFlowPlan, ProviderFee)),
|
||||||
|
FinalCashFlow = unwrap(cash_flow, finalize_cash_flow(
|
||||||
|
CashFlowPlan,
|
||||||
|
WalletAccount,
|
||||||
|
DestinationAccount,
|
||||||
|
SettlementAccount,
|
||||||
|
SubagentAccount,
|
||||||
|
ProviderAccount,
|
||||||
|
body(Withdrawal)
|
||||||
|
)),
|
||||||
|
PTransferID = construct_p_transfer_id(id(Withdrawal)),
|
||||||
|
PostingsTransferEvents = unwrap(p_transfer, ff_postings_transfer:create(PTransferID, FinalCashFlow)),
|
||||||
|
{continue, [{p_transfer, Ev} || Ev <- PostingsTransferEvents]}
|
||||||
|
end).
|
||||||
|
|
||||||
|
% TODO backward compatibility, remove after successful update
|
||||||
|
create_p_transfer_old_style(Withdrawal) ->
|
||||||
#{
|
#{
|
||||||
wallet_account := WalletAccount,
|
wallet_account := WalletAccount,
|
||||||
destination_account := DestinationAccount,
|
destination_account := DestinationAccount,
|
||||||
@ -360,3 +423,32 @@ finalize_cash_flow(CashFlowPlan, WalletAccount, DestinationAccount,
|
|||||||
ff_transfer:event().
|
ff_transfer:event().
|
||||||
maybe_migrate(Ev) ->
|
maybe_migrate(Ev) ->
|
||||||
ff_transfer:maybe_migrate(Ev, withdrawal).
|
ff_transfer:maybe_migrate(Ev, withdrawal).
|
||||||
|
|
||||||
|
collect_varset({_, CurrencyID} = Body, Wallet, Destination) ->
|
||||||
|
Currency = #domain_CurrencyRef{symbolic_code = CurrencyID},
|
||||||
|
IdentityID = ff_wallet:identity(Wallet),
|
||||||
|
do(fun() ->
|
||||||
|
IdentityMachine = unwrap(ff_identity_machine:get(IdentityID)),
|
||||||
|
Identity = ff_identity_machine:identity(IdentityMachine),
|
||||||
|
PartyID = ff_identity:party(Identity),
|
||||||
|
PaymentTool = construct_payment_tool(ff_destination:resource(Destination)),
|
||||||
|
#{
|
||||||
|
currency => Currency,
|
||||||
|
cost => ff_cash:encode(Body),
|
||||||
|
% TODO it's not fair, because it's PAYOUT not PAYMENT tool.
|
||||||
|
payment_tool => PaymentTool,
|
||||||
|
party_id => PartyID,
|
||||||
|
wallet_id => ff_wallet:id(Wallet),
|
||||||
|
payout_method => #domain_PayoutMethodRef{id = wallet_info}
|
||||||
|
}
|
||||||
|
end).
|
||||||
|
|
||||||
|
-spec construct_payment_tool(ff_destination:resource()) ->
|
||||||
|
dmsl_domain_thrift:'PaymentTool'().
|
||||||
|
construct_payment_tool({bank_card, ResourceBankCard}) ->
|
||||||
|
{bank_card, #domain_BankCard{
|
||||||
|
token = maps:get(token, ResourceBankCard),
|
||||||
|
payment_system = maps:get(payment_system, ResourceBankCard),
|
||||||
|
bin = maps:get(bin, ResourceBankCard),
|
||||||
|
masked_pan = maps:get(masked_pan, ResourceBankCard)
|
||||||
|
}}.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
%%%
|
%%%
|
||||||
%%% TODOs
|
%%% TODOs
|
||||||
%%%
|
%%%
|
||||||
%%% - Anything remotely similar to routing!
|
%%% - Replace with ff_payouts_provider after update!
|
||||||
%%%
|
%%%
|
||||||
|
|
||||||
-module(ff_withdrawal_provider).
|
-module(ff_withdrawal_provider).
|
||||||
|
@ -198,8 +198,14 @@ create_session(ID, Data, #{destination := DestinationID, provider_id := Provider
|
|||||||
status => active
|
status => active
|
||||||
}.
|
}.
|
||||||
|
|
||||||
-spec get_adapter_with_opts(ff_withdrawal_provider:id()) -> adapter_with_opts().
|
-spec get_adapter_with_opts(ff_payouts_provider:id() | ff_withdrawal_provider:id()) -> adapter_with_opts().
|
||||||
get_adapter_with_opts(ProviderID) ->
|
get_adapter_with_opts(ProviderID) when is_integer(ProviderID) ->
|
||||||
|
%% new_style
|
||||||
|
Provider = unwrap(ff_payouts_provider:get(ProviderID)),
|
||||||
|
{ff_payouts_provider:adapter(Provider), ff_payouts_provider:adapter_opts(Provider)};
|
||||||
|
get_adapter_with_opts(ProviderID) when is_binary(ProviderID) ->
|
||||||
|
%% old style
|
||||||
|
%% TODO remove after update
|
||||||
{ok, Provider} = ff_withdrawal_provider:get(ProviderID),
|
{ok, Provider} = ff_withdrawal_provider:get(ProviderID),
|
||||||
{ff_withdrawal_provider:adapter(Provider), ff_withdrawal_provider:adapter_opts(Provider)}.
|
{ff_withdrawal_provider:adapter(Provider), ff_withdrawal_provider:adapter_opts(Provider)}.
|
||||||
|
|
||||||
|
17
apps/fistful/src/ff_cash.erl
Normal file
17
apps/fistful/src/ff_cash.erl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
-module(ff_cash).
|
||||||
|
|
||||||
|
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
|
||||||
|
|
||||||
|
-export([decode/1]).
|
||||||
|
-export([encode/1]).
|
||||||
|
|
||||||
|
-spec decode(dmsl_domain_thrift:'Cash'()) -> ff_transaction:body().
|
||||||
|
decode(#domain_Cash{amount = Amount, currency = Currency}) ->
|
||||||
|
{Amount, Currency#domain_CurrencyRef.symbolic_code}.
|
||||||
|
|
||||||
|
-spec encode(ff_transaction:body()) -> dmsl_domain_thrift:'Cash'().
|
||||||
|
encode({Amount, CurrencyID}) ->
|
||||||
|
#domain_Cash{
|
||||||
|
amount = Amount,
|
||||||
|
currency = #domain_CurrencyRef{symbolic_code = CurrencyID}
|
||||||
|
}.
|
@ -5,6 +5,7 @@
|
|||||||
-export([gather_used_accounts/1]).
|
-export([gather_used_accounts/1]).
|
||||||
-export([finalize/3]).
|
-export([finalize/3]).
|
||||||
-export([add_fee/2]).
|
-export([add_fee/2]).
|
||||||
|
-export([decode_domain_postings/1]).
|
||||||
|
|
||||||
%% Domain types
|
%% Domain types
|
||||||
-type plan_posting() :: #{
|
-type plan_posting() :: #{
|
||||||
@ -66,6 +67,7 @@
|
|||||||
type => plan_account()
|
type => plan_account()
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
-export_type([plan_posting/0]).
|
||||||
-export_type([plan_volume/0]).
|
-export_type([plan_volume/0]).
|
||||||
-export_type([plan_constant/0]).
|
-export_type([plan_constant/0]).
|
||||||
-export_type([plan_operation/0]).
|
-export_type([plan_operation/0]).
|
||||||
@ -117,6 +119,61 @@ finalize(Plan, Accounts, Constants) ->
|
|||||||
add_fee(#{postings := PlanPostings} = Plan, #{postings := FeePostings}) ->
|
add_fee(#{postings := PlanPostings} = Plan, #{postings := FeePostings}) ->
|
||||||
{ok, Plan#{postings => PlanPostings ++ FeePostings}}.
|
{ok, Plan#{postings => PlanPostings ++ FeePostings}}.
|
||||||
|
|
||||||
|
%% Domain cash flow unmarshalling
|
||||||
|
|
||||||
|
-spec decode_domain_postings(dmsl_domain_thrift:'CashFlow'()) ->
|
||||||
|
[plan_posting()].
|
||||||
|
decode_domain_postings(DomainPostings) ->
|
||||||
|
[decode_domain_posting(P) || P <- DomainPostings].
|
||||||
|
|
||||||
|
-spec decode_domain_posting(dmsl_domain_thrift:'CashFlowPosting'()) ->
|
||||||
|
plan_posting().
|
||||||
|
decode_domain_posting(
|
||||||
|
#domain_CashFlowPosting{
|
||||||
|
source = Source,
|
||||||
|
destination = Destination,
|
||||||
|
volume = Volume,
|
||||||
|
details = Details
|
||||||
|
}
|
||||||
|
) ->
|
||||||
|
#{
|
||||||
|
sender => decode_domain_plan_account(Source),
|
||||||
|
receiver => decode_domain_plan_account(Destination),
|
||||||
|
volume => decode_domain_plan_volume(Volume),
|
||||||
|
details => Details
|
||||||
|
}.
|
||||||
|
|
||||||
|
-spec decode_domain_plan_account(dmsl_domain_thrift:'CashFlowAccount'()) ->
|
||||||
|
ff_cash_flow:plan_account().
|
||||||
|
decode_domain_plan_account({_AccountNS, _AccountType} = Account) ->
|
||||||
|
Account.
|
||||||
|
|
||||||
|
-spec decode_domain_plan_volume(dmsl_domain_thrift:'CashVolume'()) ->
|
||||||
|
ff_cash_flow:plan_volume().
|
||||||
|
decode_domain_plan_volume({fixed, #domain_CashVolumeFixed{cash = Cash}}) ->
|
||||||
|
{fixed, ff_cash:decode(Cash)};
|
||||||
|
decode_domain_plan_volume({share, Share}) ->
|
||||||
|
#domain_CashVolumeShare{
|
||||||
|
parts = Parts,
|
||||||
|
'of' = Of,
|
||||||
|
rounding_method = RoundingMethod
|
||||||
|
} = Share,
|
||||||
|
{share, {decode_rational(Parts), Of, decode_rounding_method(RoundingMethod)}};
|
||||||
|
decode_domain_plan_volume({product, {Fun, CVs}}) ->
|
||||||
|
{product, {Fun, lists:map(fun decode_domain_plan_volume/1, CVs)}}.
|
||||||
|
|
||||||
|
-spec decode_rounding_method(dmsl_domain_thrift:'RoundingMethod'() | undefined) ->
|
||||||
|
ff_cash_flow:rounding_method().
|
||||||
|
decode_rounding_method(undefined) ->
|
||||||
|
default;
|
||||||
|
decode_rounding_method(RoundingMethod) ->
|
||||||
|
RoundingMethod.
|
||||||
|
|
||||||
|
-spec decode_rational(dmsl_base_thrift:'Rational'()) ->
|
||||||
|
genlib_rational:t().
|
||||||
|
decode_rational(#'Rational'{p = P, q = Q}) ->
|
||||||
|
genlib_rational:new(P, Q).
|
||||||
|
|
||||||
%% Internals
|
%% Internals
|
||||||
|
|
||||||
%% Finalizing
|
%% Finalizing
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
-export([get_contract_terms/3]).
|
-export([get_contract_terms/3]).
|
||||||
-export([get_contract_terms/4]).
|
-export([get_contract_terms/4]).
|
||||||
-export([get_withdrawal_cash_flow_plan/1]).
|
-export([get_withdrawal_cash_flow_plan/1]).
|
||||||
|
-export([get_wallet_payment_institution_id/1]).
|
||||||
|
|
||||||
%% Internal types
|
%% Internal types
|
||||||
-type body() :: ff_transfer:body().
|
-type body() :: ff_transfer:body().
|
||||||
@ -61,6 +62,7 @@
|
|||||||
-type cash_range() :: dmsl_domain_thrift:'CashRange'().
|
-type cash_range() :: dmsl_domain_thrift:'CashRange'().
|
||||||
-type timestamp() :: ff_time:timestamp_ms().
|
-type timestamp() :: ff_time:timestamp_ms().
|
||||||
-type wallet() :: ff_wallet:wallet().
|
-type wallet() :: ff_wallet:wallet().
|
||||||
|
-type payment_institution_id() :: ff_payment_institution:id().
|
||||||
|
|
||||||
-type currency_validation_error() :: {terms_violation, {not_allowed_currency, _Details}}.
|
-type currency_validation_error() :: {terms_violation, {not_allowed_currency, _Details}}.
|
||||||
-type withdrawal_currency_error() :: {invalid_withdrawal_currency, currency_id(), {wallet_currency, currency_id()}}.
|
-type withdrawal_currency_error() :: {invalid_withdrawal_currency, currency_id(), {wallet_currency, currency_id()}}.
|
||||||
@ -102,7 +104,7 @@ is_accessible(ID) ->
|
|||||||
%%
|
%%
|
||||||
|
|
||||||
-type contract_prototype() :: #{
|
-type contract_prototype() :: #{
|
||||||
payinst := _PaymentInstitutionRef,
|
payinst := dmsl_domain_thrift:'PaymentInstitutionRef'(),
|
||||||
contract_template := dmsl_domain_thrift:'ContractTemplateRef'(),
|
contract_template := dmsl_domain_thrift:'ContractTemplateRef'(),
|
||||||
contractor_level := dmsl_domain_thrift:'ContractorIdentificationLevel'()
|
contractor_level := dmsl_domain_thrift:'ContractorIdentificationLevel'()
|
||||||
}.
|
}.
|
||||||
@ -136,6 +138,25 @@ change_contractor_level(ID, ContractID, ContractorLevel) ->
|
|||||||
ok
|
ok
|
||||||
end).
|
end).
|
||||||
|
|
||||||
|
-spec get_wallet_payment_institution_id(wallet()) -> Result when
|
||||||
|
Result :: {ok, payment_institution_id()} | {error, Error},
|
||||||
|
Error ::
|
||||||
|
{party_not_found, id()} |
|
||||||
|
{contract_not_found, id()} |
|
||||||
|
{exception, any()}.
|
||||||
|
|
||||||
|
get_wallet_payment_institution_id(Wallet) ->
|
||||||
|
IdentityID = ff_wallet:identity(Wallet),
|
||||||
|
do(fun() ->
|
||||||
|
IdentityMachine = unwrap(ff_identity_machine:get(IdentityID)),
|
||||||
|
Identity = ff_identity_machine:identity(IdentityMachine),
|
||||||
|
PartyID = ff_identity:party(Identity),
|
||||||
|
ContractID = ff_identity:contract(Identity),
|
||||||
|
Contract = unwrap(do_get_contract(PartyID, ContractID)),
|
||||||
|
#domain_PaymentInstitutionRef{id = ID} = Contract#domain_Contract.payment_institution,
|
||||||
|
ID
|
||||||
|
end).
|
||||||
|
|
||||||
-spec get_contract_terms(wallet(), body(), timestamp()) -> Result when
|
-spec get_contract_terms(wallet(), body(), timestamp()) -> Result when
|
||||||
Result :: {ok, terms()} | {error, Error},
|
Result :: {ok, terms()} | {error, Error},
|
||||||
Error ::
|
Error ::
|
||||||
@ -149,8 +170,11 @@ get_contract_terms(Wallet, Body, Timestamp) ->
|
|||||||
do(fun() ->
|
do(fun() ->
|
||||||
IdentityMachine = unwrap(ff_identity_machine:get(IdentityID)),
|
IdentityMachine = unwrap(ff_identity_machine:get(IdentityID)),
|
||||||
Identity = ff_identity_machine:identity(IdentityMachine),
|
Identity = ff_identity_machine:identity(IdentityMachine),
|
||||||
ContractID = ff_identity:contract(Identity),
|
|
||||||
PartyID = ff_identity:party(Identity),
|
PartyID = ff_identity:party(Identity),
|
||||||
|
ContractID = ff_identity:contract(Identity),
|
||||||
|
% TODO this is not level itself! Dont know how to get it here.
|
||||||
|
% Currently we use Contract's level in PartyManagement, but I'm not sure about correctness of this.
|
||||||
|
% Level = ff_identity:level(Identity),
|
||||||
{_Amount, CurrencyID} = Body,
|
{_Amount, CurrencyID} = Body,
|
||||||
TermVarset = #{
|
TermVarset = #{
|
||||||
amount => Body,
|
amount => Body,
|
||||||
@ -160,20 +184,20 @@ get_contract_terms(Wallet, Body, Timestamp) ->
|
|||||||
unwrap(get_contract_terms(PartyID, ContractID, TermVarset, Timestamp))
|
unwrap(get_contract_terms(PartyID, ContractID, TermVarset, Timestamp))
|
||||||
end).
|
end).
|
||||||
|
|
||||||
-spec get_contract_terms(id(), contract_id(), term_varset(), timestamp()) -> Result when
|
-spec get_contract_terms(PartyID :: id(), contract_id(), term_varset(), timestamp()) -> Result when
|
||||||
Result :: {ok, terms()} | {error, Error},
|
Result :: {ok, terms()} | {error, Error},
|
||||||
Error :: {party_not_found, id()} | {party_not_exists_yet, id()} | {exception, any()}.
|
Error :: {party_not_found, id()} | {party_not_exists_yet, id()} | {exception, any()}.
|
||||||
|
|
||||||
get_contract_terms(ID, ContractID, Varset, Timestamp) ->
|
get_contract_terms(PartyID, ContractID, Varset, Timestamp) ->
|
||||||
DomainVarset = encode_varset(Varset),
|
DomainVarset = encode_varset(Varset),
|
||||||
Args = [ID, ContractID, ff_time:to_rfc3339(Timestamp), DomainVarset],
|
Args = [PartyID, ContractID, ff_time:to_rfc3339(Timestamp), DomainVarset],
|
||||||
case call('ComputeWalletTermsNew', Args) of
|
case call('ComputeWalletTermsNew', Args) of
|
||||||
{ok, Terms} ->
|
{ok, Terms} ->
|
||||||
{ok, Terms};
|
{ok, Terms};
|
||||||
{exception, #payproc_PartyNotFound{}} ->
|
{exception, #payproc_PartyNotFound{}} ->
|
||||||
{error, {party_not_found, ID}};
|
{error, {party_not_found, PartyID}};
|
||||||
{exception, #payproc_PartyNotExistsYet{}} ->
|
{exception, #payproc_PartyNotExistsYet{}} ->
|
||||||
{error, {party_not_exists_yet, ID}};
|
{error, {party_not_exists_yet, PartyID}};
|
||||||
{exception, Unexpected} ->
|
{exception, Unexpected} ->
|
||||||
{error, {exception, Unexpected}}
|
{error, {exception, Unexpected}}
|
||||||
end.
|
end.
|
||||||
@ -236,7 +260,7 @@ get_withdrawal_cash_flow_plan(Terms) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} = Terms,
|
} = Terms,
|
||||||
Postings = decode_domain_postings(DomainPostings),
|
Postings = ff_cash_flow:decode_domain_postings(DomainPostings),
|
||||||
{ok, #{postings => Postings}}.
|
{ok, #{postings => Postings}}.
|
||||||
|
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
@ -269,15 +293,17 @@ do_get_party(ID) ->
|
|||||||
error(Unexpected)
|
error(Unexpected)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
% do_get_contract(ID, ContractID) ->
|
do_get_contract(ID, ContractID) ->
|
||||||
% case call('GetContract', [ID, ContractID]) of
|
case call('GetContract', [ID, ContractID]) of
|
||||||
% {ok, #domain_Contract{} = Contract} ->
|
{ok, #domain_Contract{} = Contract} ->
|
||||||
% Contract;
|
{ok, Contract};
|
||||||
% {exception, #payproc_ContractNotFound{}} ->
|
{exception, #payproc_PartyNotFound{}} ->
|
||||||
% {error, notfound};
|
{error, {party_not_found, ID}};
|
||||||
% {exception, Unexpected} ->
|
{exception, #payproc_ContractNotFound{}} ->
|
||||||
% error(Unexpected)
|
{error, {contract_not_found, ContractID}};
|
||||||
% end.
|
{exception, Unexpected} ->
|
||||||
|
error(Unexpected)
|
||||||
|
end.
|
||||||
|
|
||||||
do_create_claim(ID, Changeset) ->
|
do_create_claim(ID, Changeset) ->
|
||||||
case call('CreateClaim', [ID, Changeset]) of
|
case call('CreateClaim', [ID, Changeset]) of
|
||||||
@ -573,73 +599,6 @@ compare_cash(
|
|||||||
) ->
|
) ->
|
||||||
Fun(A, Am).
|
Fun(A, Am).
|
||||||
|
|
||||||
%% Domain cash flow unmarshalling
|
|
||||||
|
|
||||||
-spec decode_domain_postings(ff_cash_flow:domain_plan_postings()) ->
|
|
||||||
[ff_cash_flow:plan_posting()].
|
|
||||||
decode_domain_postings(DomainPostings) ->
|
|
||||||
[decode_domain_posting(P) || P <- DomainPostings].
|
|
||||||
|
|
||||||
-spec decode_domain_posting(dmsl_domain_thrift:'CashFlowPosting'()) ->
|
|
||||||
ff_cash_flow:plan_posting().
|
|
||||||
decode_domain_posting(
|
|
||||||
#domain_CashFlowPosting{
|
|
||||||
source = Source,
|
|
||||||
destination = Destination,
|
|
||||||
volume = Volume,
|
|
||||||
details = Details
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
#{
|
|
||||||
sender => decode_domain_plan_account(Source),
|
|
||||||
receiver => decode_domain_plan_account(Destination),
|
|
||||||
volume => decode_domain_plan_volume(Volume),
|
|
||||||
details => Details
|
|
||||||
}.
|
|
||||||
|
|
||||||
-spec decode_domain_plan_account(dmsl_domain_thrift:'CashFlowAccount'()) ->
|
|
||||||
ff_cash_flow:plan_account().
|
|
||||||
decode_domain_plan_account({_AccountNS, _AccountType} = Account) ->
|
|
||||||
Account.
|
|
||||||
|
|
||||||
-spec decode_domain_plan_volume(dmsl_domain_thrift:'CashVolume'()) ->
|
|
||||||
ff_cash_flow:plan_volume().
|
|
||||||
decode_domain_plan_volume({fixed, #domain_CashVolumeFixed{cash = Cash}}) ->
|
|
||||||
{fixed, decode_domain_cash(Cash)};
|
|
||||||
decode_domain_plan_volume({share, Share}) ->
|
|
||||||
#domain_CashVolumeShare{
|
|
||||||
parts = Parts,
|
|
||||||
'of' = Of,
|
|
||||||
rounding_method = RoundingMethod
|
|
||||||
} = Share,
|
|
||||||
{share, {decode_rational(Parts), Of, decode_rounding_method(RoundingMethod)}};
|
|
||||||
decode_domain_plan_volume({product, {Fun, CVs}}) ->
|
|
||||||
{product, {Fun, lists:map(fun decode_domain_plan_volume/1, CVs)}}.
|
|
||||||
|
|
||||||
-spec decode_rounding_method(dmsl_domain_thrift:'RoundingMethod'() | undefined) ->
|
|
||||||
ff_cash_flow:rounding_method().
|
|
||||||
decode_rounding_method(undefined) ->
|
|
||||||
default;
|
|
||||||
decode_rounding_method(RoundingMethod) ->
|
|
||||||
RoundingMethod.
|
|
||||||
|
|
||||||
-spec decode_rational(dmsl_base_thrift:'Rational'()) ->
|
|
||||||
genlib_rational:t().
|
|
||||||
decode_rational(#'Rational'{p = P, q = Q}) ->
|
|
||||||
genlib_rational:new(P, Q).
|
|
||||||
|
|
||||||
-spec decode_domain_cash(domain_cash()) ->
|
|
||||||
ff_cash_flow:cash().
|
|
||||||
decode_domain_cash(
|
|
||||||
#domain_Cash{
|
|
||||||
amount = Amount,
|
|
||||||
currency = #domain_CurrencyRef{
|
|
||||||
symbolic_code = SymbolicCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
{Amount, SymbolicCode}.
|
|
||||||
|
|
||||||
%% Varset stuff
|
%% Varset stuff
|
||||||
|
|
||||||
-spec encode_varset(term_varset()) ->
|
-spec encode_varset(term_varset()) ->
|
||||||
|
133
apps/fistful/src/ff_payment_institution.erl
Normal file
133
apps/fistful/src/ff_payment_institution.erl
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
-module(ff_payment_institution).
|
||||||
|
|
||||||
|
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
|
||||||
|
|
||||||
|
-type id() :: dmsl_domain_thrift:'ObjectID'().
|
||||||
|
-type payment_institution() :: #{
|
||||||
|
id := id(),
|
||||||
|
system_accounts := dmsl_domain_thrift:'SystemAccountSetSelector'(),
|
||||||
|
identity := binary(),
|
||||||
|
providers := dmsl_domain_thrift:'WithdrawalProviderSelector'()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-type payinst_ref() :: dmsl_domain_thrift:'PaymentInstitutionRef'().
|
||||||
|
|
||||||
|
-type system_accounts() :: #{
|
||||||
|
ff_currency:id() => system_account()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-type system_account() :: #{
|
||||||
|
settlement => ff_account:account(),
|
||||||
|
subagent => ff_account:account()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-export_type([id/0]).
|
||||||
|
-export_type([payment_institution/0]).
|
||||||
|
|
||||||
|
-export([id/1]).
|
||||||
|
|
||||||
|
-export([ref/1]).
|
||||||
|
-export([get/1]).
|
||||||
|
-export([compute_withdrawal_provider/2]).
|
||||||
|
-export([compute_system_accounts/2]).
|
||||||
|
|
||||||
|
%% Pipeline
|
||||||
|
|
||||||
|
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec id(payment_institution()) -> id().
|
||||||
|
|
||||||
|
id(#{id := ID}) ->
|
||||||
|
ID.
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec ref(id()) -> payinst_ref().
|
||||||
|
|
||||||
|
ref(ID) ->
|
||||||
|
#domain_PaymentInstitutionRef{id = ID}.
|
||||||
|
|
||||||
|
-spec get(id()) ->
|
||||||
|
{ok, payment_institution()} |
|
||||||
|
{error, notfound}.
|
||||||
|
|
||||||
|
get(ID) ->
|
||||||
|
do(fun () ->
|
||||||
|
PaymentInstitution = unwrap(ff_domain_config:object({payment_institution, ref(ID)})),
|
||||||
|
decode(ID, PaymentInstitution)
|
||||||
|
end).
|
||||||
|
|
||||||
|
-spec compute_withdrawal_provider(payment_institution(), hg_selector:varset()) ->
|
||||||
|
{ok, ff_payouts_provider:id()} | {error, term()}.
|
||||||
|
|
||||||
|
compute_withdrawal_provider(#{providers := ProviderSelector}, VS) ->
|
||||||
|
do(fun() ->
|
||||||
|
Providers = unwrap(hg_selector:reduce_to_value(ProviderSelector, VS)),
|
||||||
|
%% TODO choose wizely one of them
|
||||||
|
[#domain_WithdrawalProviderRef{id = ProviderID} | _] = Providers,
|
||||||
|
ProviderID
|
||||||
|
end).
|
||||||
|
|
||||||
|
-spec compute_system_accounts(payment_institution(), hg_selector:varset()) ->
|
||||||
|
{ok, system_accounts()} | {error, term()}.
|
||||||
|
|
||||||
|
compute_system_accounts(PaymentInstitution, VS) ->
|
||||||
|
#{
|
||||||
|
identity := Identity,
|
||||||
|
system_accounts := SystemAccountsSelector
|
||||||
|
} = PaymentInstitution,
|
||||||
|
do(fun() ->
|
||||||
|
SystemAccountSetRef = unwrap(hg_selector:reduce_to_value(SystemAccountsSelector, VS)),
|
||||||
|
SystemAccountSet = unwrap(ff_domain_config:object({system_account_set, SystemAccountSetRef})),
|
||||||
|
decode_system_account_set(Identity, SystemAccountSet)
|
||||||
|
end).
|
||||||
|
%%
|
||||||
|
|
||||||
|
decode(ID, #domain_PaymentInstitution{
|
||||||
|
wallet_system_account_set = SystemAccounts,
|
||||||
|
identity = Identity,
|
||||||
|
withdrawal_providers = Providers
|
||||||
|
}) ->
|
||||||
|
#{
|
||||||
|
id => ID,
|
||||||
|
system_accounts => SystemAccounts,
|
||||||
|
identity => Identity,
|
||||||
|
providers => Providers
|
||||||
|
}.
|
||||||
|
|
||||||
|
decode_system_account_set(Identity, #domain_SystemAccountSet{accounts = Accounts}) ->
|
||||||
|
maps:fold(
|
||||||
|
fun(CurrencyRef, SystemAccount, Acc) ->
|
||||||
|
#domain_CurrencyRef{symbolic_code = CurrencyID} = CurrencyRef,
|
||||||
|
maps:put(
|
||||||
|
CurrencyID,
|
||||||
|
decode_system_account(SystemAccount, CurrencyID, Identity),
|
||||||
|
Acc
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
#{},
|
||||||
|
Accounts
|
||||||
|
).
|
||||||
|
|
||||||
|
decode_system_account(SystemAccount, CurrencyID, Identity) ->
|
||||||
|
#domain_SystemAccount{
|
||||||
|
settlement = SettlementAccountID,
|
||||||
|
subagent = SubagentAccountID
|
||||||
|
} = SystemAccount,
|
||||||
|
#{
|
||||||
|
settlement => decode_account(SettlementAccountID, CurrencyID, Identity),
|
||||||
|
subagent => decode_account(SubagentAccountID, CurrencyID, Identity)
|
||||||
|
}.
|
||||||
|
|
||||||
|
decode_account(AccountID, CurrencyID, Identity) when AccountID =/= undefined ->
|
||||||
|
#{
|
||||||
|
% FIXME
|
||||||
|
id => Identity,
|
||||||
|
identity => Identity,
|
||||||
|
currency => CurrencyID,
|
||||||
|
accounter_account_id => AccountID
|
||||||
|
};
|
||||||
|
decode_account(undefined, _, _) ->
|
||||||
|
undefined.
|
129
apps/fistful/src/ff_payouts_provider.erl
Normal file
129
apps/fistful/src/ff_payouts_provider.erl
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
-module(ff_payouts_provider).
|
||||||
|
|
||||||
|
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
|
||||||
|
|
||||||
|
-type withdrawal_provider() :: #{
|
||||||
|
id := id(),
|
||||||
|
identity := ff_identity:id(),
|
||||||
|
withdrawal_terms := dmsl_domain_thrift:'WithdrawalProvisionTerms'(),
|
||||||
|
accounts := accounts(),
|
||||||
|
adapter := ff_adapter:adapter(),
|
||||||
|
adapter_opts := map()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-type id() :: dmsl_domain_thrift:'ObjectID'().
|
||||||
|
-type accounts() :: #{ff_currency:id() => ff_account:account()}.
|
||||||
|
|
||||||
|
-type withdrawal_provider_ref() :: dmsl_domain_thrift:'WithdrawalProviderRef'().
|
||||||
|
|
||||||
|
-export_type([id/0]).
|
||||||
|
-export_type([withdrawal_provider/0]).
|
||||||
|
|
||||||
|
-export([id/1]).
|
||||||
|
-export([accounts/1]).
|
||||||
|
-export([adapter/1]).
|
||||||
|
-export([adapter_opts/1]).
|
||||||
|
|
||||||
|
-export([ref/1]).
|
||||||
|
-export([get/1]).
|
||||||
|
-export([compute_fees/2]).
|
||||||
|
|
||||||
|
%% Pipeline
|
||||||
|
|
||||||
|
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec id(withdrawal_provider()) -> id().
|
||||||
|
-spec accounts(withdrawal_provider()) -> accounts().
|
||||||
|
-spec adapter(withdrawal_provider()) -> ff_adapter:adapter().
|
||||||
|
-spec adapter_opts(withdrawal_provider()) -> map().
|
||||||
|
|
||||||
|
id(#{id := ID}) ->
|
||||||
|
ID.
|
||||||
|
|
||||||
|
accounts(#{accounts := Accounts}) ->
|
||||||
|
Accounts.
|
||||||
|
|
||||||
|
adapter(#{adapter := Adapter}) ->
|
||||||
|
Adapter.
|
||||||
|
|
||||||
|
adapter_opts(#{adapter_opts := AdapterOpts}) ->
|
||||||
|
AdapterOpts.
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec ref(id()) -> withdrawal_provider_ref().
|
||||||
|
|
||||||
|
ref(ID) ->
|
||||||
|
#domain_WithdrawalProviderRef{id = ID}.
|
||||||
|
|
||||||
|
-spec get(id()) ->
|
||||||
|
{ok, withdrawal_provider()} |
|
||||||
|
{error, notfound}.
|
||||||
|
|
||||||
|
get(ID) ->
|
||||||
|
do(fun () ->
|
||||||
|
WithdrawalProvider = unwrap(ff_domain_config:object({withdrawal_provider, ref(ID)})),
|
||||||
|
decode(ID, WithdrawalProvider)
|
||||||
|
end).
|
||||||
|
|
||||||
|
-spec compute_fees(withdrawal_provider(), hg_selector:varset()) -> ff_cash_flow:cash_flow_fee().
|
||||||
|
|
||||||
|
compute_fees(#{withdrawal_terms := WithdrawalTerms}, VS) ->
|
||||||
|
#domain_WithdrawalProvisionTerms{cash_flow = CashFlowSelector} = WithdrawalTerms,
|
||||||
|
CashFlow = unwrap(hg_selector:reduce_to_value(CashFlowSelector, VS)),
|
||||||
|
#{
|
||||||
|
postings => ff_cash_flow:decode_domain_postings(CashFlow)
|
||||||
|
}.
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
decode(ID, #domain_WithdrawalProvider{
|
||||||
|
proxy = Proxy,
|
||||||
|
identity = Identity,
|
||||||
|
withdrawal_terms = WithdrawalTerms,
|
||||||
|
accounts = Accounts
|
||||||
|
}) ->
|
||||||
|
maps:merge(
|
||||||
|
#{
|
||||||
|
id => ID,
|
||||||
|
identity => Identity,
|
||||||
|
withdrawal_terms => WithdrawalTerms,
|
||||||
|
accounts => decode_accounts(Identity, Accounts)
|
||||||
|
},
|
||||||
|
decode_adapter(Proxy)
|
||||||
|
).
|
||||||
|
|
||||||
|
decode_accounts(Identity, Accounts) ->
|
||||||
|
maps:fold(
|
||||||
|
fun(CurrencyRef, ProviderAccount, Acc) ->
|
||||||
|
#domain_CurrencyRef{symbolic_code = CurrencyID} = CurrencyRef,
|
||||||
|
#domain_ProviderAccount{settlement = AccountID} = ProviderAccount,
|
||||||
|
maps:put(
|
||||||
|
CurrencyID,
|
||||||
|
#{
|
||||||
|
% FIXME
|
||||||
|
id => Identity,
|
||||||
|
identity => Identity,
|
||||||
|
currency => CurrencyID,
|
||||||
|
accounter_account_id => AccountID
|
||||||
|
},
|
||||||
|
Acc
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
#{},
|
||||||
|
Accounts
|
||||||
|
).
|
||||||
|
|
||||||
|
decode_adapter(#domain_Proxy{ref = ProxyRef, additional = ProviderOpts}) ->
|
||||||
|
Proxy = unwrap(ff_domain_config:object({proxy, ProxyRef})),
|
||||||
|
#domain_ProxyDefinition{
|
||||||
|
url = URL,
|
||||||
|
options = ProxyOpts
|
||||||
|
} = Proxy,
|
||||||
|
#{
|
||||||
|
adapter => ff_woody_client:new(URL),
|
||||||
|
adapter_opts => maps:merge(ProviderOpts, ProxyOpts)
|
||||||
|
}.
|
||||||
|
|
40
apps/fistful/src/hg_cash_range.erl
Normal file
40
apps/fistful/src/hg_cash_range.erl
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
%% TODO merge with ff_range
|
||||||
|
|
||||||
|
-module(hg_cash_range).
|
||||||
|
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
|
||||||
|
|
||||||
|
-export([is_inside/2]).
|
||||||
|
|
||||||
|
-type cash_range() :: dmsl_domain_thrift:'CashRange'().
|
||||||
|
-type cash() :: dmsl_domain_thrift:'Cash'().
|
||||||
|
|
||||||
|
-spec is_inside(cash(), cash_range()) ->
|
||||||
|
within | {exceeds, lower | upper}.
|
||||||
|
|
||||||
|
is_inside(Cash, CashRange = #domain_CashRange{lower = Lower, upper = Upper}) ->
|
||||||
|
case {
|
||||||
|
compare_cash(fun erlang:'>'/2, Cash, Lower),
|
||||||
|
compare_cash(fun erlang:'<'/2, Cash, Upper)
|
||||||
|
} of
|
||||||
|
{true, true} ->
|
||||||
|
within;
|
||||||
|
{false, true} ->
|
||||||
|
{exceeds, lower};
|
||||||
|
{true, false} ->
|
||||||
|
{exceeds, upper};
|
||||||
|
_ ->
|
||||||
|
error({misconfiguration, {'Invalid cash range specified', CashRange, Cash}})
|
||||||
|
end.
|
||||||
|
|
||||||
|
-define(cash(Amount, SymCode),
|
||||||
|
#domain_Cash{
|
||||||
|
amount = Amount,
|
||||||
|
currency = #domain_CurrencyRef{symbolic_code = SymCode}
|
||||||
|
}).
|
||||||
|
|
||||||
|
compare_cash(_, V, {inclusive, V}) ->
|
||||||
|
true;
|
||||||
|
compare_cash(F, ?cash(A, C), {_, ?cash(Am, C)}) ->
|
||||||
|
F(A, Am);
|
||||||
|
compare_cash(_, _, _) ->
|
||||||
|
error.
|
47
apps/fistful/src/hg_condition.erl
Normal file
47
apps/fistful/src/hg_condition.erl
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
-module(hg_condition).
|
||||||
|
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-export([test/2]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-type condition() :: dmsl_domain_thrift:'Condition'().
|
||||||
|
-type varset() :: hg_selector:varset().
|
||||||
|
|
||||||
|
-spec test(condition(), varset()) ->
|
||||||
|
true | false | undefined.
|
||||||
|
|
||||||
|
test({category_is, V1}, #{category := V2}) ->
|
||||||
|
V1 =:= V2;
|
||||||
|
test({currency_is, V1}, #{currency := V2}) ->
|
||||||
|
V1 =:= V2;
|
||||||
|
test({cost_in, V}, #{cost := C}) ->
|
||||||
|
hg_cash_range:is_inside(C, V) =:= within;
|
||||||
|
test({payment_tool, C}, #{payment_tool := V}) ->
|
||||||
|
hg_payment_tool:test_condition(C, V);
|
||||||
|
test({shop_location_is, V}, #{shop := S}) ->
|
||||||
|
V =:= S#domain_Shop.location;
|
||||||
|
test({party, V}, #{party_id := PartyID} = VS) ->
|
||||||
|
test_party(V, PartyID, VS);
|
||||||
|
test({payout_method_is, V1}, #{payout_method := V2}) ->
|
||||||
|
V1 =:= V2;
|
||||||
|
test({identification_level_is, V1}, #{identification_level := V2}) ->
|
||||||
|
V1 =:= V2;
|
||||||
|
test(_, #{}) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
test_party(#domain_PartyCondition{id = PartyID, definition = Def}, PartyID, VS) ->
|
||||||
|
test_party_definition(Def, VS);
|
||||||
|
test_party(_, _, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
test_party_definition(undefined, _) ->
|
||||||
|
true;
|
||||||
|
test_party_definition({shop_is, ID1}, #{shop_id := ID2}) ->
|
||||||
|
ID1 =:= ID2;
|
||||||
|
test_party_definition({wallet_is, ID1}, #{wallet_id := ID2}) ->
|
||||||
|
ID1 =:= ID2;
|
||||||
|
test_party_definition(_, _) ->
|
||||||
|
undefined.
|
91
apps/fistful/src/hg_payment_tool.erl
Normal file
91
apps/fistful/src/hg_payment_tool.erl
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
%%% Payment tools
|
||||||
|
|
||||||
|
-module(hg_payment_tool).
|
||||||
|
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
|
||||||
|
|
||||||
|
%%
|
||||||
|
-export([test_condition/2]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-type t() :: dmsl_domain_thrift:'PaymentTool'().
|
||||||
|
-type condition() :: dmsl_domain_thrift:'PaymentToolCondition'().
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec test_condition(condition(), t()) -> boolean() | undefined.
|
||||||
|
|
||||||
|
test_condition({bank_card, C}, {bank_card, V = #domain_BankCard{}}) ->
|
||||||
|
test_bank_card_condition(C, V);
|
||||||
|
test_condition({payment_terminal, C}, {payment_terminal, V = #domain_PaymentTerminal{}}) ->
|
||||||
|
test_payment_terminal_condition(C, V);
|
||||||
|
test_condition({digital_wallet, C}, {digital_wallet, V = #domain_DigitalWallet{}}) ->
|
||||||
|
test_digital_wallet_condition(C, V);
|
||||||
|
test_condition(_PaymentTool, _Condition) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
test_bank_card_condition(#domain_BankCardCondition{definition = Def}, V) when Def /= undefined ->
|
||||||
|
test_bank_card_condition_def(Def, V);
|
||||||
|
test_bank_card_condition(#domain_BankCardCondition{}, _) ->
|
||||||
|
true.
|
||||||
|
|
||||||
|
% legacy
|
||||||
|
test_bank_card_condition_def(
|
||||||
|
{payment_system_is, Ps},
|
||||||
|
#domain_BankCard{payment_system = Ps, token_provider = undefined}
|
||||||
|
) ->
|
||||||
|
true;
|
||||||
|
test_bank_card_condition_def({payment_system_is, _Ps}, #domain_BankCard{}) ->
|
||||||
|
false;
|
||||||
|
|
||||||
|
test_bank_card_condition_def({payment_system, PaymentSystem}, V) ->
|
||||||
|
test_payment_system_condition(PaymentSystem, V);
|
||||||
|
test_bank_card_condition_def({issuer_country_is, IssuerCountry}, V) ->
|
||||||
|
test_issuer_country_condition(IssuerCountry, V);
|
||||||
|
test_bank_card_condition_def({issuer_bank_is, BankRef}, V) ->
|
||||||
|
test_issuer_bank_condition(BankRef, V).
|
||||||
|
|
||||||
|
test_payment_system_condition(
|
||||||
|
#domain_PaymentSystemCondition{payment_system_is = Ps, token_provider_is = Tp},
|
||||||
|
#domain_BankCard{payment_system = Ps, token_provider = Tp}
|
||||||
|
) ->
|
||||||
|
true;
|
||||||
|
test_payment_system_condition(#domain_PaymentSystemCondition{}, #domain_BankCard{}) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
test_issuer_country_condition(_Country, #domain_BankCard{issuer_country = undefined}) ->
|
||||||
|
undefined;
|
||||||
|
test_issuer_country_condition(Country, #domain_BankCard{issuer_country = TargetCountry}) ->
|
||||||
|
Country == TargetCountry.
|
||||||
|
|
||||||
|
test_issuer_bank_condition(BankRef, #domain_BankCard{bank_name = BankName, bin = BIN}) ->
|
||||||
|
%% TODO this is complete bullshitery. Rework this check so we don't need to go to domain_config.
|
||||||
|
Bank = ff_pipeline:unwrap(ff_domain_config:object({bank, BankRef})),
|
||||||
|
#domain_Bank{binbase_id_patterns = Patterns, bins = BINs} = Bank,
|
||||||
|
case {Patterns, BankName} of
|
||||||
|
{P, B} when is_list(P) and is_binary(B) ->
|
||||||
|
test_bank_card_patterns(Patterns, BankName);
|
||||||
|
% TODO т.к. BinBase не обладает полным объемом данных, при их отсутствии мы возвращаемся к проверкам по бинам.
|
||||||
|
% B будущем стоит избавиться от этого.
|
||||||
|
{_, _} -> test_bank_card_bins(BIN, BINs)
|
||||||
|
end.
|
||||||
|
|
||||||
|
test_bank_card_bins(BIN, BINs) ->
|
||||||
|
ordsets:is_element(BIN, BINs).
|
||||||
|
|
||||||
|
test_bank_card_patterns(Patterns, BankName) ->
|
||||||
|
Matches = ordsets:filter(fun(E) -> genlib_wildcard:match(BankName, E) end, Patterns),
|
||||||
|
ordsets:size(Matches) > 0.
|
||||||
|
|
||||||
|
test_payment_terminal_condition(#domain_PaymentTerminalCondition{definition = Def}, V) ->
|
||||||
|
Def =:= undefined orelse test_payment_terminal_condition_def(Def, V).
|
||||||
|
|
||||||
|
test_payment_terminal_condition_def({provider_is, V1}, #domain_PaymentTerminal{terminal_type = V2}) ->
|
||||||
|
V1 =:= V2.
|
||||||
|
|
||||||
|
test_digital_wallet_condition(#domain_DigitalWalletCondition{definition = Def}, V) ->
|
||||||
|
Def =:= undefined orelse test_digital_wallet_condition_def(Def, V).
|
||||||
|
|
||||||
|
test_digital_wallet_condition_def({provider_is, V1}, #domain_DigitalWallet{provider = V2}) ->
|
||||||
|
V1 =:= V2.
|
||||||
|
|
162
apps/fistful/src/hg_selector.erl
Normal file
162
apps/fistful/src/hg_selector.erl
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
%%% Domain selectors manipulation
|
||||||
|
%%%
|
||||||
|
%%% TODO
|
||||||
|
%%% - Manipulating predicates w/o respect to their struct infos is dangerous
|
||||||
|
%%% - Decide on semantics
|
||||||
|
%%% - First satisfiable predicate wins?
|
||||||
|
%%% If not, it would be harder to join / overlay selectors
|
||||||
|
|
||||||
|
-module(hg_selector).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-type t() ::
|
||||||
|
dmsl_domain_thrift:'CurrencySelector'() |
|
||||||
|
dmsl_domain_thrift:'CategorySelector'() |
|
||||||
|
dmsl_domain_thrift:'CashLimitSelector'() |
|
||||||
|
dmsl_domain_thrift:'CashFlowSelector'() |
|
||||||
|
dmsl_domain_thrift:'PaymentMethodSelector'() |
|
||||||
|
dmsl_domain_thrift:'ProviderSelector'() |
|
||||||
|
dmsl_domain_thrift:'TerminalSelector'() |
|
||||||
|
dmsl_domain_thrift:'SystemAccountSetSelector'() |
|
||||||
|
dmsl_domain_thrift:'ExternalAccountSetSelector'() |
|
||||||
|
dmsl_domain_thrift:'HoldLifetimeSelector'() |
|
||||||
|
dmsl_domain_thrift:'CashValueSelector'() |
|
||||||
|
dmsl_domain_thrift:'CumulativeLimitSelector'() |
|
||||||
|
dmsl_domain_thrift:'WithdrawalProviderSelector'() |
|
||||||
|
dmsl_domain_thrift:'TimeSpanSelector'().
|
||||||
|
|
||||||
|
-type value() ::
|
||||||
|
_. %% FIXME
|
||||||
|
|
||||||
|
-type varset() :: #{
|
||||||
|
category => dmsl_domain_thrift:'CategoryRef'(),
|
||||||
|
currency => dmsl_domain_thrift:'CurrencyRef'(),
|
||||||
|
cost => dmsl_domain_thrift:'Cash'(),
|
||||||
|
payment_tool => dmsl_domain_thrift:'PaymentTool'(),
|
||||||
|
party_id => dmsl_domain_thrift:'PartyID'(),
|
||||||
|
shop_id => dmsl_domain_thrift:'ShopID'(),
|
||||||
|
risk_score => dmsl_domain_thrift:'RiskScore'(),
|
||||||
|
flow => instant | {hold, dmsl_domain_thrift:'HoldLifetime'()},
|
||||||
|
payout_method => dmsl_domain_thrift:'PayoutMethodRef'(),
|
||||||
|
wallet_id => dmsl_domain_thrift:'WalletID'(),
|
||||||
|
identification_level => dmsl_domain_thrift:'ContractorIdentificationLevel'()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-export_type([varset/0]).
|
||||||
|
|
||||||
|
-export([fold/3]).
|
||||||
|
-export([collect/1]).
|
||||||
|
-export([reduce/2]).
|
||||||
|
-export([reduce_to_value/2]).
|
||||||
|
|
||||||
|
-define(const(Bool), {constant, Bool}).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec fold(FoldWith :: fun((Value :: _, Acc) -> Acc), Acc, t()) ->
|
||||||
|
Acc when
|
||||||
|
Acc :: term().
|
||||||
|
|
||||||
|
fold(FoldWith, Acc, {value, V}) ->
|
||||||
|
FoldWith(V, Acc);
|
||||||
|
fold(FoldWith, Acc, {decisions, Ps}) ->
|
||||||
|
fold_decisions(FoldWith, Acc, Ps).
|
||||||
|
|
||||||
|
fold_decisions(FoldWith, Acc, [{_Type, _, S} | Rest]) ->
|
||||||
|
fold_decisions(FoldWith, fold(FoldWith, Acc, S), Rest);
|
||||||
|
fold_decisions(_, Acc, []) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
-spec collect(t()) ->
|
||||||
|
[value()].
|
||||||
|
|
||||||
|
collect(S) ->
|
||||||
|
fold(fun (V, Acc) -> [V | Acc] end, [], S).
|
||||||
|
|
||||||
|
|
||||||
|
-spec reduce_to_value(t(), varset()) -> {ok, value()} | {error, term()}.
|
||||||
|
|
||||||
|
reduce_to_value(Selector, VS) ->
|
||||||
|
case reduce(Selector, VS) of
|
||||||
|
{value, Value} ->
|
||||||
|
{ok, Value};
|
||||||
|
_ ->
|
||||||
|
{error, {misconfiguration, {'Can\'t reduce selector to value', Selector, VS}}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec reduce(t(), varset()) ->
|
||||||
|
t().
|
||||||
|
|
||||||
|
reduce({value, _} = V, _) ->
|
||||||
|
V;
|
||||||
|
reduce({decisions, Ps}, VS) ->
|
||||||
|
case reduce_decisions(Ps, VS) of
|
||||||
|
[{_Type, ?const(true), S} | _] ->
|
||||||
|
S;
|
||||||
|
Ps1 ->
|
||||||
|
{decisions, Ps1}
|
||||||
|
end.
|
||||||
|
|
||||||
|
reduce_decisions([{Type, V, S} | Rest], VS) ->
|
||||||
|
case reduce_predicate(V, VS) of
|
||||||
|
?const(false) ->
|
||||||
|
reduce_decisions(Rest, VS);
|
||||||
|
V1 ->
|
||||||
|
case reduce(S, VS) of
|
||||||
|
{decisions, []} ->
|
||||||
|
reduce_decisions(Rest, VS);
|
||||||
|
S1 ->
|
||||||
|
[{Type, V1, S1} | reduce_decisions(Rest, VS)]
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
reduce_decisions([], _) ->
|
||||||
|
[].
|
||||||
|
|
||||||
|
reduce_predicate(?const(B), _) ->
|
||||||
|
?const(B);
|
||||||
|
|
||||||
|
reduce_predicate({condition, C0}, VS) ->
|
||||||
|
case reduce_condition(C0, VS) of
|
||||||
|
?const(B) ->
|
||||||
|
?const(B);
|
||||||
|
C1 ->
|
||||||
|
{condition, C1}
|
||||||
|
end;
|
||||||
|
|
||||||
|
reduce_predicate({is_not, P0}, VS) ->
|
||||||
|
case reduce_predicate(P0, VS) of
|
||||||
|
?const(B) ->
|
||||||
|
?const(not B);
|
||||||
|
P1 ->
|
||||||
|
{is_not, P1}
|
||||||
|
end;
|
||||||
|
|
||||||
|
reduce_predicate({all_of, Ps}, VS) ->
|
||||||
|
reduce_combination(all_of, false, Ps, VS, []);
|
||||||
|
|
||||||
|
reduce_predicate({any_of, Ps}, VS) ->
|
||||||
|
reduce_combination(any_of, true, Ps, VS, []).
|
||||||
|
|
||||||
|
reduce_combination(Type, Fix, [P | Ps], VS, PAcc) ->
|
||||||
|
case reduce_predicate(P, VS) of
|
||||||
|
?const(Fix) ->
|
||||||
|
?const(Fix);
|
||||||
|
?const(_) ->
|
||||||
|
reduce_combination(Type, Fix, Ps, VS, PAcc);
|
||||||
|
P1 ->
|
||||||
|
reduce_combination(Type, Fix, Ps, VS, [P1 | PAcc])
|
||||||
|
end;
|
||||||
|
reduce_combination(_, Fix, [], _, []) ->
|
||||||
|
?const(not Fix);
|
||||||
|
reduce_combination(Type, _, [], _, PAcc) ->
|
||||||
|
{Type, lists:reverse(PAcc)}.
|
||||||
|
|
||||||
|
reduce_condition(C, VS) ->
|
||||||
|
case hg_condition:test(C, VS) of
|
||||||
|
B when is_boolean(B) ->
|
||||||
|
?const(B);
|
||||||
|
undefined ->
|
||||||
|
% Irreducible, return as is
|
||||||
|
C
|
||||||
|
end.
|
@ -83,7 +83,7 @@
|
|||||||
id => <<"some_id">>,
|
id => <<"some_id">>,
|
||||||
identity => <<"some_other_id">>,
|
identity => <<"some_other_id">>,
|
||||||
currency => <<"RUB">>,
|
currency => <<"RUB">>,
|
||||||
accounter_account_id => <<"some_third_id">>
|
accounter_account_id => 123
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fee => #{<<"RUB">> => #{postings => []}}
|
fee => #{<<"RUB">> => #{postings => []}}
|
||||||
|
@ -46,7 +46,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
hellgate:
|
hellgate:
|
||||||
image: dr.rbkmoney.com/rbkmoney/hellgate:8d7f618f6f2e1d8410384797b8f9a76150580f46
|
image: dr.rbkmoney.com/rbkmoney/hellgate:a1ea6053fe2d0d446e1c69735ca63ab0d493a87a
|
||||||
command: /opt/hellgate/bin/hellgate foreground
|
command: /opt/hellgate/bin/hellgate foreground
|
||||||
depends_on:
|
depends_on:
|
||||||
machinegun:
|
machinegun:
|
||||||
@ -86,7 +86,7 @@ services:
|
|||||||
retries: 20
|
retries: 20
|
||||||
|
|
||||||
dominant:
|
dominant:
|
||||||
image: dr.rbkmoney.com/rbkmoney/dominant:3cf6c46d482f0057d117209170c831f5a238d95a
|
image: dr.rbkmoney.com/rbkmoney/dominant:2f9f7e3d06972bc341bf55e9948435e202b578a2
|
||||||
command: /opt/dominant/bin/dominant foreground
|
command: /opt/dominant/bin/dominant foreground
|
||||||
depends_on:
|
depends_on:
|
||||||
machinegun:
|
machinegun:
|
||||||
@ -187,8 +187,8 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
environment:
|
environment:
|
||||||
- SPRING_DATASOURCE_PASSWORD=postgres
|
- SPRING_DATASOURCE_PASSWORD=postgres
|
||||||
- SERVICE_NAME=ffmagista
|
- SERVICE_NAME=ffmagista
|
||||||
|
|
||||||
ffmagista-db:
|
ffmagista-db:
|
||||||
image: dr.rbkmoney.com/rbkmoney/postgres:9.6
|
image: dr.rbkmoney.com/rbkmoney/postgres:9.6
|
||||||
environment:
|
environment:
|
||||||
|
Loading…
Reference in New Issue
Block a user