FF-102: Bin data to withdrawal (#109)

* added ff_bin_data

* debugged, not tested

* changed to resource full type

* added requested changes

* nano
This commit is contained in:
Артем 2019-08-14 12:45:21 +03:00 committed by GitHub
parent 07b12ddd9f
commit 682d92fbc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 286 additions and 45 deletions

View File

@ -128,6 +128,8 @@ start_processing_apps(Options) ->
{<<"/v1/identity">>, {{ff_proto_identity_thrift, 'Management'}, {ff_identity_handler, []}}, #{}}),
DummyProviderRoute = ff_server:get_routes(
{<<"/quotebank">>, {{dmsl_withdrawals_provider_adapter_thrift, 'Adapter'}, {ff_ct_provider_handler, []}}, #{}}),
DummyBinbaseRoute = ff_server:get_routes(
{<<"/binbase">>, {{binbase_binbase_thrift, 'Binbase'}, {ff_ct_binbase_handler, []}}, #{}}),
RepairRoutes = get_repair_routes(),
EventsinkRoutes = get_eventsink_routes(BeConf),
{ok, _} = supervisor:start_child(SuiteSup, woody_server:child_spec(
@ -146,7 +148,8 @@ start_processing_apps(Options) ->
IdentityRoutes,
EventsinkRoutes,
RepairRoutes,
DummyProviderRoute
DummyProviderRoute,
DummyBinbaseRoute
])
}
)),
@ -492,7 +495,8 @@ services(Options) ->
cds => "http://cds:8022/v1/storage",
identdocstore => "http://cds:8022/v1/identity_document_storage",
partymgmt => "http://hellgate:8022/v1/processing/partymgmt",
identification => "http://identification:8022/v1/identification"
identification => "http://identification:8022/v1/identification",
binbase => "http://localhost:8022/binbase"
},
maps:get(services, Options, Default).
@ -532,7 +536,12 @@ domain_config(Options, C) ->
identity = payment_inst_identity_id(Options),
withdrawal_providers = {decisions, [
#domain_WithdrawalProviderDecision{
if_ = {condition, {payment_tool, {bank_card, #domain_BankCardCondition{}}}},
if_ = {
condition,
{payment_tool, {bank_card, #domain_BankCardCondition{
definition = {issuer_country_is, 'rus'}
}}}
},
then_ = {value, [?wthdr_prv(1)]}
},
#domain_WithdrawalProviderDecision{

View File

@ -16,14 +16,31 @@
-type identity() :: ff_identity:id().
-type currency() :: ff_currency:id().
-type status() :: ff_instrument:status().
-type resource_type() :: bank_card | crypto_wallet.
-type resource() ::
{bank_card, resource_bank_card()} |
{crypto_wallet, resource_crypto_wallet()}.
-type resource_full() ::
{bank_card, resource_full_bank_card()} |
{crypto_wallet, resource_crypto_wallet()}.
-type resource_full_bank_card() :: #{
token := binary(),
bin => binary(),
payment_system := atom(), % TODO
masked_pan => binary(),
bank_name => binary(),
iso_country_code => atom(),
card_type => charge_card | credit | debit | credit_or_debit,
bin_data_id := ff_bin_data:bin_data_id()
}.
-type resource_bank_card() :: #{
token := binary(),
payment_system => atom(), % TODO
bin => binary(),
payment_system => atom(), % TODO
masked_pan => binary()
}.
@ -44,6 +61,8 @@
-export_type([destination/0]).
-export_type([status/0]).
-export_type([resource/0]).
-export_type([resource_type/0]).
-export_type([resource_full/0]).
-export_type([event/0]).
-export_type([params/0]).
@ -57,6 +76,7 @@
-export([resource/1]).
-export([status/1]).
-export([external_id/1]).
-export([resource_full/1]).
%% API
@ -67,6 +87,10 @@
-export([is_accessible/1]).
-export([events/2]).
%% Pipeline
-import(ff_pipeline, [do/1, unwrap/2]).
%% Accessors
-spec id(destination()) -> id().
@ -77,6 +101,7 @@
-spec resource(destination()) -> resource().
-spec status(destination()) -> status().
id(Destination) -> ff_instrument:id(Destination).
name(Destination) -> ff_instrument:name(Destination).
identity(Destination) -> ff_instrument:identity(Destination).
@ -90,6 +115,25 @@ account(Destination) -> ff_instrument:account(Destination).
external_id(T) -> ff_instrument:external_id(T).
-spec resource_full(destination()) ->
{ok, resource_full()} |
{error,
{bin_data, not_found}
}.
resource_full(Destination) ->
do(fun() ->
case resource(Destination) of
{bank_card, #{token := Token} = BankCard} ->
BinData = unwrap(bin_data, ff_bin_data:get(Token)),
KeyList = [bank_name, iso_country_code, card_type],
ExtendData = maps:with(KeyList, BinData),
{bank_card, maps:merge(BankCard, ExtendData#{bin_data_id => ff_bin_data:id(BinData)})};
{crypto_wallet, _CryptoWallet} = Resource ->
Resource
end
end).
%% API
-define(NS, 'ff/destination_v2').

View File

@ -11,7 +11,8 @@
-type transfer_params() :: #{
wallet_id := wallet_id(),
destination_id := destination_id(),
quote => quote()
quote => quote(),
destination_resource => destination_resource()
}.
-type machine() :: ff_transfer_machine:st(transfer_params()).
@ -22,6 +23,7 @@
}).
% TODO I'm now sure about this change, it may crash old events. Or not. ))
-type provider_id() :: pos_integer() | id().
-type destination_resource() :: ff_destination:resource_full().
-type quote() :: ff_adapter_withdrawal:quote(quote_validation_data()).
@ -50,6 +52,7 @@
-export([params/1]).
-export([route/1]).
-export([external_id/1]).
-export([destination_resource/1]).
%%
-export([transfer_type/0]).
@ -82,7 +85,6 @@
-type wallet() :: ff_wallet:wallet().
-type cash_flow_plan() :: ff_cash_flow:cash_flow_plan().
-type destination_id() :: ff_destination:id().
-type destination() :: ff_destination:destination().
-type process_result() :: {ff_transfer_machine:action(), [event()]}.
-type final_cash_flow() :: ff_cash_flow:final_cash_flow().
@ -124,6 +126,10 @@ route(T) -> ff_transfer:route(T).
id() | undefined.
external_id(T) -> ff_transfer:external_id(T).
-spec destination_resource(withdrawal()) ->
destination_resource() | undefined.
destination_resource(T) -> maps:get(destination_resource, ff_transfer:params(T), undefined).
%%
-define(NS, 'ff/withdrawal_v2').
@ -151,6 +157,13 @@ external_id(T) -> ff_transfer:external_id(T).
external_id => id()
}.
-type varset_params() :: #{
body := body(),
wallet := ff_wallet:wallet(),
destination => ff_destination:destination(),
destination_resource => destination_resource()
}.
-spec create(id(), params(), ctx()) ->
ok |
{error,
@ -158,6 +171,7 @@ external_id(T) -> ff_transfer:external_id(T).
{destination, notfound | unauthorized} |
{terms, ff_party:validate_withdrawal_creation_error()} |
{contract, ff_party:get_contract_terms_error()} |
{destination_resource, {bin_data, not_found}} |
exists
}.
@ -169,11 +183,12 @@ create(ID, Args = #{wallet_id := WalletID, destination_id := DestinationID, body
unwrap(destination, ff_destination:get_machine(DestinationID))
),
ok = unwrap(destination, valid(authorized, ff_destination:status(Destination))),
Resource = unwrap(destination_resource, ff_destination:resource_full(Destination)),
{ok, IdentityMachine} = ff_identity_machine:get(ff_wallet:identity(Wallet)),
Identity = ff_identity_machine:identity(IdentityMachine),
PartyID = ff_identity:party(Identity),
ContractID = ff_identity:contract(Identity),
{ok, VS} = collect_varset(Body, Wallet, Destination),
{ok, VS} = collect_varset(make_varset_params(Body, Wallet, Destination, Resource)),
Terms = unwrap(contract, ff_party:get_contract_terms(PartyID, ContractID, VS, ff_time:now())),
valid = unwrap(terms, ff_party:validate_withdrawal_creation(Terms, Body, WalletAccount)),
@ -183,7 +198,8 @@ create(ID, Args = #{wallet_id := WalletID, destination_id := DestinationID, body
params => genlib_map:compact(#{
wallet_id => WalletID,
destination_id => DestinationID,
quote => maps:get(quote, Args, undefined)
quote => maps:get(quote, Args, undefined),
destination_resource => Resource
}),
external_id => maps:get(external_id, Args, undefined)
},
@ -286,23 +302,33 @@ create_route(Withdrawal) ->
} = params(Withdrawal),
Body = body(Withdrawal),
do(fun () ->
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
Destination = ff_destination:get(DestinationMachine),
{ok, WalletMachine} = ff_wallet_machine:get(WalletID),
Wallet = ff_wallet_machine:wallet(WalletMachine),
ProviderID = unwrap(prepare_route(Wallet, Destination, Body)),
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
Destination = ff_destination:get(DestinationMachine),
ProviderID = unwrap(prepare_route(
Wallet,
Body,
Destination,
destination_resource(Withdrawal)
)),
unwrap(validate_quote_provider(ProviderID, quote(Withdrawal))),
{continue, [{route_changed, #{provider_id => ProviderID}}]}
end).
-spec prepare_route(wallet(), destination(), body()) ->
-spec prepare_route(
wallet(),
body(),
ff_destination:destination() | undefined,
destination_resource() | undefined
) ->
{ok, provider_id()} | {error, _Reason}.
prepare_route(Wallet, Destination, Body) ->
prepare_route(Wallet, Body, Destination, Resource) ->
do(fun () ->
PaymentInstitutionID = unwrap(ff_party:get_wallet_payment_institution_id(Wallet)),
PaymentInstitution = unwrap(ff_payment_institution:get(PaymentInstitutionID)),
{ok, VS} = collect_varset(Body, Wallet, Destination),
{ok, VS} = collect_varset(make_varset_params(Body, Wallet, Destination, Resource)),
Providers = unwrap(ff_payment_institution:compute_withdrawal_providers(PaymentInstitution, VS)),
unwrap(choose_provider(Providers, VS))
end).
@ -364,7 +390,13 @@ create_p_transfer_new_style(Withdrawal) ->
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
Destination = ff_destination:get(DestinationMachine),
DestinationAccount = ff_destination:account(Destination),
VS = unwrap(collect_varset(body(Withdrawal), Wallet, Destination)),
VS = unwrap(collect_varset(make_varset_params(
body(Withdrawal),
Wallet,
Destination,
destination_resource(Withdrawal)
))),
SystemAccounts = unwrap(ff_payment_institution:compute_system_accounts(PaymentInstitution, VS)),
SystemAccount = maps:get(CurrencyID, SystemAccounts, #{}),
@ -420,7 +452,12 @@ create_p_transfer_old_style(Withdrawal) ->
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
Destination = ff_destination:get(DestinationMachine),
DestinationAccount = ff_destination:account(Destination),
VS = unwrap(collect_varset(body(Withdrawal), Wallet, Destination)),
VS = unwrap(collect_varset(make_varset_params(
body(Withdrawal),
Wallet,
Destination,
destination_resource(Withdrawal)
))),
{ok, IdentityMachine} = unwrap(ff_identity_machine:get(ff_wallet:identity(Wallet))),
Identity = ff_identity_machine:identity(IdentityMachine),
@ -552,48 +589,65 @@ finalize_cash_flow(CashFlowPlan, WalletAccount, DestinationAccount,
maybe_migrate(Ev) ->
ff_transfer:maybe_migrate(Ev, withdrawal).
-spec collect_varset(body(), ff_wallet:wallet(), ff_destination:destination() | undefined) ->
-spec make_varset_params(
body(),
ff_wallet:wallet(),
ff_destination:destination() | undefined,
destination_resource() | undefined
) ->
varset_params().
make_varset_params(Body, Wallet, Destination, Resource) ->
genlib_map:compact(#{
body => Body,
wallet => Wallet,
destination => Destination,
destination_resource => Resource
}).
-spec collect_varset(varset_params()) ->
{ok, hg_selector:varset()} | no_return().
collect_varset(Body, Wallet, undefined) ->
collect_varset(Body, Wallet);
collect_varset(Body, Wallet, Destination) ->
do(fun() ->
VS = unwrap(collect_varset(Body, Wallet)),
PaymentTool = construct_payment_tool(ff_destination:resource(Destination)),
VS#{
% TODO it's not fair, because it's PAYOUT not PAYMENT tool.
payment_tool => PaymentTool
}
end).
-spec collect_varset(body(), ff_wallet:wallet()) ->
{ok, hg_selector:varset()} | no_return().
collect_varset({_, CurrencyID} = Body, Wallet) ->
collect_varset(#{body := Body, wallet := Wallet} = Params) ->
{_, CurrencyID} = Body,
Currency = #domain_CurrencyRef{symbolic_code = CurrencyID},
IdentityID = ff_wallet:identity(Wallet),
do(fun() ->
{ok, IdentityMachine} = ff_identity_machine:get(IdentityID),
Identity = ff_identity_machine:identity(IdentityMachine),
PartyID = ff_identity:party(Identity),
#{
Destination = maps:get(destination, Params, undefined),
Resource = maps:get(destination_resource, Params, undefined),
PaymentTool = case {Destination, Resource} of
{undefined, _} ->
undefined;
%% TODO remove this when complete all old withdrawals
{Destination, undefined} ->
construct_payment_tool(ff_destination:resource(Destination));
{_, Resource} ->
construct_payment_tool(Resource)
end,
genlib_map:compact(#{
currency => Currency,
cost => ff_cash:encode(Body),
party_id => PartyID,
wallet_id => ff_wallet:id(Wallet),
payout_method => #domain_PayoutMethodRef{id = wallet_info}
}
payout_method => #domain_PayoutMethodRef{id = wallet_info},
% TODO it's not fair, because it's PAYOUT not PAYMENT tool.
payment_tool => PaymentTool
})
end).
-spec construct_payment_tool(ff_destination:resource()) ->
-spec construct_payment_tool(ff_destination:resource_full() | 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)
masked_pan = maps:get(masked_pan, ResourceBankCard),
payment_system = maps:get(payment_system, ResourceBankCard),
issuer_country = maps:get(iso_country_code, ResourceBankCard, undefined),
bank_name = maps:get(bank_name, ResourceBankCard, undefined)
}};
construct_payment_tool({crypto_wallet, CryptoWallet}) ->
@ -609,28 +663,30 @@ construct_payment_tool({crypto_wallet, CryptoWallet}) ->
{destination, notfound} |
{destination, unauthorized} |
{route, _Reason} |
{wallet, notfound}
{wallet, notfound} |
{destination_resource, {bin_data, not_found}}
}.
get_quote(Params = #{destination_id := DestinationID}) ->
do(fun() ->
DestinationMachine = unwrap(destination, ff_destination:get_machine(DestinationID)),
Destination = ff_destination:get(DestinationMachine),
ok = unwrap(destination, valid(authorized, ff_destination:status(Destination))),
unwrap(get_quote_(Params, Destination))
Resource = unwrap(destination_resource, ff_destination:resource_full(Destination)),
unwrap(get_quote_(Params, Destination, Resource))
end);
get_quote(Params) ->
get_quote_(Params, undefined).
get_quote_(Params, undefined, undefined).
get_quote_(Params = #{
wallet_id := WalletID,
body := Body,
currency_from := CurrencyFrom,
currency_to := CurrencyTo
}, Destination) ->
}, Destination, Resource) ->
do(fun() ->
WalletMachine = unwrap(wallet, ff_wallet_machine:get(WalletID)),
Wallet = ff_wallet_machine:wallet(WalletMachine),
ProviderID = unwrap(route, prepare_route(Wallet, Destination, Body)),
ProviderID = unwrap(route, prepare_route(Wallet, Body, Destination, Resource)),
{Adapter, AdapterOpts} = ff_withdrawal_session:get_adapter_with_opts(ProviderID),
GetQuoteParams = #{
external_id => maps:get(external_id, Params, undefined),

View File

@ -0,0 +1,99 @@
-module(ff_bin_data).
-include_lib("binbase_proto/include/binbase_binbase_thrift.hrl").
-include_lib("binbase_proto/include/binbase_msgpack_thrift.hrl").
-type token() :: binary().
-type bin_data() :: #{
token := token(),
id := bin_data_id(),
payment_system := binary(),
bank_name => binary(),
iso_country_code => atom(),
card_type => charge_card | credit | debit | credit_or_debit,
version := integer()
}.
-type bin_data_id() :: %% as stolen from `machinery_msgpack`
nil |
boolean() |
integer() |
float() |
binary() | %% string
{binary, binary()} | %% binary
[bin_data_id()] |
#{bin_data_id() => bin_data_id()}.
-export_type([bin_data/0]).
-export_type([bin_data_id/0]).
-export([get/1]).
-export([id/1]).
-spec get(token() | undefined) ->
{ok, bin_data() | undefined} | {error, not_found}.
get(undefined) ->
{ok, undefined};
get(Token) ->
case call_binbase('GetByCardToken', [Token]) of
{ok, Result} ->
{ok, decode_result(Token, Result)};
{exception, #binbase_BinNotFound{}} ->
{error, not_found}
end.
-spec id(bin_data()) ->
bin_data_id().
id(Data) ->
maps:get(id, Data).
%%
decode_result(Token, #'binbase_ResponseData'{bin_data = Bindata, version = Version}) ->
#'binbase_BinData'{
payment_system = PaymentSystem,
bank_name = BankName,
iso_country_code = IsoCountryCode,
card_type = CardType,
bin_data_id = BinDataID
} = Bindata,
genlib_map:compact(#{
token => Token,
id => decode_msgpack(BinDataID),
payment_system => PaymentSystem,
bank_name => BankName,
iso_country_code => decode_residence(IsoCountryCode),
card_type => decode_card_type(CardType),
version => Version
}).
decode_msgpack({nl, #'binbase_Nil'{}}) -> nil;
decode_msgpack({b, V}) when is_boolean(V) -> V;
decode_msgpack({i, V}) when is_integer(V) -> V;
decode_msgpack({flt, V}) when is_float(V) -> V;
decode_msgpack({str, V}) when is_binary(V) -> V; % Assuming well-formed UTF-8 bytestring.
decode_msgpack({bin, V}) when is_binary(V) -> {binary, V};
decode_msgpack({arr, V}) when is_list(V) -> [decode_msgpack(ListItem) || ListItem <- V];
decode_msgpack({obj, V}) when is_map(V) ->
maps:fold(fun(Key, Value, Map) -> Map#{decode_msgpack(Key) => decode_msgpack(Value)} end, #{}, V).
decode_card_type(undefined) ->
undefined;
decode_card_type(Type) ->
Type.
decode_residence(undefined) ->
undefined;
decode_residence(Residence) when is_binary(Residence) ->
try
list_to_existing_atom(string:to_lower(binary_to_list(Residence)))
catch
error:badarg ->
throw({decode_residence, invalid_residence})
end.
call_binbase(Function, Args) ->
Service = {binbase_binbase_thrift, 'Binbase'},
ff_woody_client:call(binbase, {Service, Function, Args}).

View File

@ -16,7 +16,8 @@
uuid,
dmsl,
dmt_client,
id_proto
id_proto,
binbase_proto
]},
{env, []},
{modules, []},

View File

@ -0,0 +1,25 @@
-module(ff_ct_binbase_handler).
-behaviour(woody_server_thrift_handler).
-include_lib("binbase_proto/include/binbase_binbase_thrift.hrl").
%% woody_server_thrift_handler callbacks
-export([handle_function/4]).
%%
%% woody_server_thrift_handler callbacks
%%
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) ->
{ok, woody:result()} | no_return().
handle_function('GetByCardToken', [_Token], _Context, _Opts) ->
{ok, #binbase_ResponseData{
bin_data = #binbase_BinData{
payment_system = <<"visa">>,
bank_name = <<"sber">>,
iso_country_code = <<"RUS">>,
bin_data_id = {i, 123}
},
version = 1
}}.

View File

@ -81,6 +81,9 @@
},
{file_storage_proto,
{git, "git@github.com:rbkmoney/file-storage-proto.git", {branch, "master"}}
},
{binbase_proto,
{git, "git@github.com:rbkmoney/binbase-proto.git", {branch, "master"}}
}
]}.

View File

@ -1,5 +1,9 @@
{"1.1.0",
[{<<"base64url">>,{pkg,<<"base64url">>,<<"0.0.1">>},0},
{<<"binbase_proto">>,
{git,"git@github.com:rbkmoney/binbase-proto.git",
{ref,"d0e136deb107683fc6d22ed687a1e6b7c589cd6d"}},
0},
{<<"cache">>,{pkg,<<"cache">>,<<"2.2.0">>},1},
{<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.1">>},1},
{<<"cg_mon">>,