From 1d94dd0ff6ca47213b976ae2d180ca36740e3ccc Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 3 Sep 2020 19:13:58 +0300 Subject: [PATCH] FF-210: W2W via Thrift (#286) --- apps/ff_cth/src/ct_helper.erl | 1 + apps/wapi/src/wapi_access_backend.erl | 14 +- apps/wapi/src/wapi_w2w_backend.erl | 165 ++++++++++++++ apps/wapi/src/wapi_wallet_thrift_handler.erl | 36 +++ apps/wapi/test/wapi_thrift_SUITE.erl | 77 +++++++ apps/wapi/test/wapi_w2w_tests_SUITE.erl | 210 ++++++++++++++++++ apps/wapi/test/wapi_wallet_dummy_data.hrl | 25 +++ .../src/wapi_woody_client.erl | 4 +- 8 files changed, 530 insertions(+), 2 deletions(-) create mode 100644 apps/wapi/src/wapi_w2w_backend.erl create mode 100644 apps/wapi/test/wapi_w2w_tests_SUITE.erl diff --git a/apps/ff_cth/src/ct_helper.erl b/apps/ff_cth/src/ct_helper.erl index 5a6e86e..08845f0 100644 --- a/apps/ff_cth/src/ct_helper.erl +++ b/apps/ff_cth/src/ct_helper.erl @@ -129,6 +129,7 @@ start_app(wapi_woody_client = AppName) -> fistful_wallet => "http://localhost:8022/v1/wallet", fistful_identity => "http://localhost:8022/v1/identity", fistful_destination => "http://localhost:8022/v1/destination", + w2w_transfer => "http://localhost:8022/v1/w2w_transfer", fistful_withdrawal => "http://localhost:8022/v1/withdrawal" }}, {service_retries, #{ diff --git a/apps/wapi/src/wapi_access_backend.erl b/apps/wapi/src/wapi_access_backend.erl index 1dc3bec..d8c3101 100644 --- a/apps/wapi/src/wapi_access_backend.erl +++ b/apps/wapi/src/wapi_access_backend.erl @@ -3,13 +3,15 @@ -include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl"). -include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl"). -include_lib("fistful_proto/include/ff_proto_destination_thrift.hrl"). +-include_lib("fistful_proto/include/ff_proto_w2w_transfer_thrift.hrl"). -include_lib("fistful_proto/include/ff_proto_withdrawal_thrift.hrl"). -export([check_resource/3]). -export([check_resource_by_id/3]). -type id() :: binary(). --type resource_type() :: identity | wallet | destination | withdrawal. +-type resource_type() :: identity | wallet | destination | w2w_transfer | withdrawal. + -type handler_context() :: wapi_handler:context(). -type data() :: ff_proto_identity_thrift:'IdentityState'() | @@ -66,6 +68,14 @@ get_context_by_id(destination, DestinationID, WoodyCtx) -> {exception, #fistful_DestinationNotFound{}} -> {error, notfound} end; +get_context_by_id(w2w_transfer, W2WTransferID, WoodyCtx) -> + Request = {w2w_transfer, 'GetContext', [W2WTransferID]}, + case wapi_handler_utils:service_call(Request, WoodyCtx) of + {ok, Context} -> + Context; + {exception, #fistful_W2WNotFound{}} -> + {error, notfound} + end; get_context_by_id(withdrawal, WithdrawalID, WoodyCtx) -> Request = {fistful_withdrawal, 'GetContext', [WithdrawalID]}, case wapi_handler_utils:service_call(Request, WoodyCtx) of @@ -81,6 +91,8 @@ get_context_from_state(wallet, #wlt_WalletState{context = Context}) -> Context; get_context_from_state(destination, #dst_DestinationState{context = Context}) -> Context; +get_context_from_state(w2w_transfer, #w2w_transfer_W2WTransferState{context = Context}) -> + Context; get_context_from_state(withdrawal, #wthd_WithdrawalState{context = Context}) -> Context. diff --git a/apps/wapi/src/wapi_w2w_backend.erl b/apps/wapi/src/wapi_w2w_backend.erl new file mode 100644 index 0000000..c49ca20 --- /dev/null +++ b/apps/wapi/src/wapi_w2w_backend.erl @@ -0,0 +1,165 @@ +-module(wapi_w2w_backend). + +-type req_data() :: wapi_handler:req_data(). +-type handler_context() :: wapi_handler:context(). +-type response_data() :: wapi_handler:response_data(). + +-type id() :: binary(). +-type external_id() :: id(). + +-export([create_transfer/2]). +-export([get_transfer/2]). + +-include_lib("fistful_proto/include/ff_proto_w2w_transfer_thrift.hrl"). + +-spec create_transfer(req_data(), handler_context()) -> + {ok, response_data()} | {error, CreateError} +when + CreateError :: + {external_id_conflict, external_id()} | + {wallet_from, unauthorized} | + {wallet_from | wallet_to, notfound} | + bad_w2w_transfer_amount | + not_allowed_currency | + inconsistent_currency. + +create_transfer(Params = #{<<"sender">> := SenderID}, HandlerContext) -> + case wapi_access_backend:check_resource_by_id(wallet, SenderID, HandlerContext) of + ok -> + case wapi_backend_utils:gen_id(w2w_transfer, Params, HandlerContext) of + {ok, ID} -> + Context = wapi_backend_utils:make_ctx(Params, HandlerContext), + create_transfer(ID, Params, Context, HandlerContext); + {error, {external_id_conflict, _}} = Error -> + Error + end; + {error, unauthorized} -> + {error, {wallet_from, unauthorized}} + end. + +create_transfer(ID, Params, Context, HandlerContext) -> + TransferParams = marshal(transfer_params, Params#{<<"id">> => ID}), + Request = {w2w_transfer, 'Create', [TransferParams, marshal(context, Context)]}, + case service_call(Request, HandlerContext) of + {ok, Transfer} -> + {ok, unmarshal(transfer, Transfer)}; + {exception, #fistful_WalletNotFound{id = ID}} -> + {error, wallet_not_found_error(unmarshal(id, ID), Params)}; + {exception, #fistful_ForbiddenOperationCurrency{}} -> + {error, not_allowed_currency}; + {exception, #w2w_transfer_InconsistentW2WTransferCurrency{}} -> + {error, inconsistent_currency}; + {exception, #fistful_InvalidOperationAmount{}} -> + {error, bad_w2w_transfer_amount} + end. + +-spec get_transfer(req_data(), handler_context()) -> + {ok, response_data()} | {error, GetError} +when + GetError :: + {w2w_transfer, unauthorized} | + {w2w_transfer, {unknown_w2w_transfer, id()}}. + +get_transfer(ID, HandlerContext) -> + EventRange = #'EventRange'{}, + Request = {w2w_transfer, 'Get', [ID, EventRange]}, + case service_call(Request, HandlerContext) of + {ok, TransferThrift} -> + case wapi_access_backend:check_resource(w2w_transfer, TransferThrift, HandlerContext) of + ok -> + {ok, unmarshal(transfer, TransferThrift)}; + {error, unauthorized} -> + {error, {w2w_transfer, unauthorized}} + end; + {exception, #fistful_W2WNotFound{}} -> + {error, {w2w_transfer, {unknown_w2w_transfer, ID}}} + end. + +%% +%% Internal +%% + +service_call(Params, Ctx) -> + wapi_handler_utils:service_call(Params, Ctx). + +wallet_not_found_error(WalletID, #{<<"sender">> := WalletID}) -> + {wallet_from, notfound}; +wallet_not_found_error(WalletID, #{<<"receiver">> := WalletID}) -> + {wallet_to, notfound}. + +%% Marshaling + +marshal(transfer_params, #{ + <<"id">> := ID, + <<"sender">> := SenderID, + <<"receiver">> := ReceiverID, + <<"body">> := Body +} = Params) -> + #w2w_transfer_W2WTransferParams{ + id = marshal(id, ID), + wallet_from_id = marshal(id, SenderID), + wallet_to_id = marshal(id, ReceiverID), + body = marshal(body, Body), + external_id = maps:get(<<"externalId">>, Params, undefined) + }; + +marshal(body, #{ + <<"amount">> := Amount, + <<"currency">> := Currency +}) -> + #'Cash'{ + amount = marshal(amount, Amount), + currency = marshal(currency_ref, Currency) + }; + +marshal(context, Ctx) -> + ff_codec:marshal(context, Ctx); + +marshal(T, V) -> + ff_codec:marshal(T, V). + +unmarshal(transfer, #w2w_transfer_W2WTransferState{ + id = ID, + wallet_from_id = SenderID, + wallet_to_id = ReceiverID, + body = Body, + created_at = CreatedAt, + status = Status, + external_id = ExternalID +}) -> + genlib_map:compact(#{ + <<"id">> => unmarshal(id, ID), + <<"createdAt">> => CreatedAt, + <<"body">> => unmarshal(body, Body), + <<"sender">> => unmarshal(id, SenderID), + <<"receiver">> => unmarshal(id, ReceiverID), + <<"status">> => unmarshal(transfer_status, Status), + <<"externalID">> => maybe_unmarshal(id, ExternalID) + }); + +unmarshal(body, #'Cash'{ + amount = Amount, + currency = Currency +}) -> + #{ + <<"amount">> => unmarshal(amount, Amount), + <<"currency">> => unmarshal(currency_ref, Currency) + }; + +unmarshal(transfer_status, {pending, _}) -> + #{<<"status">> => <<"Pending">>}; +unmarshal(transfer_status, {succeeded, _}) -> + #{<<"status">> => <<"Succeeded">>}; +unmarshal(transfer_status, {failed, #w2w_status_Failed{failure = Failure}}) -> + #{ + <<"status">> => <<"Failed">>, + <<"failure">> => unmarshal(failure, Failure) + }; + +unmarshal(T, V) -> + ff_codec:unmarshal(T, V). + +maybe_unmarshal(_T, undefined) -> + undefined; +maybe_unmarshal(T, V) -> + unmarshal(T, V). diff --git a/apps/wapi/src/wapi_wallet_thrift_handler.erl b/apps/wapi/src/wapi_wallet_thrift_handler.erl index d84c4c4..550d2a0 100644 --- a/apps/wapi/src/wapi_wallet_thrift_handler.erl +++ b/apps/wapi/src/wapi_wallet_thrift_handler.erl @@ -366,6 +366,42 @@ process_request('ListDeposits', Params, Context, _Opts) -> }) end; +%% W2W + +process_request('CreateW2WTransfer', #{'W2WTransferParameters' := Params}, Context, _Opts) -> + case wapi_w2w_backend:create_transfer(Params, Context) of + {ok, W2WTransfer} -> + wapi_handler_utils:reply_ok(202, W2WTransfer); + {error, {wallet_from, notfound}} -> + wapi_handler_utils:reply_ok(422, + wapi_handler_utils:get_error_msg(<<"No such wallet sender">>)); + {error, {wallet_from, unauthorized}} -> + wapi_handler_utils:reply_ok(422, + wapi_handler_utils:get_error_msg(<<"No such wallet sender">>)); + {error, {wallet_to, notfound}} -> + wapi_handler_utils:reply_ok(422, + wapi_handler_utils:get_error_msg(<<"No such wallet receiver">>)); + {error, not_allowed_currency} -> + wapi_handler_utils:reply_ok(422, + wapi_handler_utils:get_error_msg(<<"Currency not allowed">>)); + {error, bad_w2w_transfer_amount} -> + wapi_handler_utils:reply_ok(422, + wapi_handler_utils:get_error_msg(<<"Bad transfer amount">>)); + {error, inconsistent_currency} -> + wapi_handler_utils:reply_ok(422, + wapi_handler_utils:get_error_msg(<<"Inconsistent currency">>)) + end; + +process_request('GetW2WTransfer', #{w2wTransferID := ID}, Context, _Opts) -> + case wapi_w2w_backend:get_transfer(ID, Context) of + {ok, W2WTransfer} -> + wapi_handler_utils:reply_ok(200, W2WTransfer); + {error, {w2w_transfer, unauthorized}} -> + wapi_handler_utils:reply_ok(404); + {error, {w2w_transfer, {unknown_w2w_transfer, _ID}}} -> + wapi_handler_utils:reply_ok(404) + end; + process_request(OperationID, Params, Context, Opts) -> wapi_wallet_handler:process_request(OperationID, Params, Context, Opts). diff --git a/apps/wapi/test/wapi_thrift_SUITE.erl b/apps/wapi/test/wapi_thrift_SUITE.erl index 98aa1c0..a16d0fd 100644 --- a/apps/wapi/test/wapi_thrift_SUITE.erl +++ b/apps/wapi/test/wapi_thrift_SUITE.erl @@ -17,6 +17,7 @@ -export([identity_check_test/1]). -export([identity_challenge_check_test/1]). -export([destination_check_test/1]). +-export([w2w_transfer_check_test/1]). -export([withdrawal_check_test/1]). % common-api is used since it is the domain used in production RN @@ -46,6 +47,7 @@ groups() -> identity_challenge_check_test, wallet_check_test, destination_check_test, + w2w_transfer_check_test, withdrawal_check_test ]} ]. @@ -169,6 +171,24 @@ destination_check_test(C) -> DestinationID2 = create_destination(IdentityID2, C), ?assertEqual(Keys, maps:keys(get_destination(DestinationID2, C))). +-spec w2w_transfer_check_test(config()) -> test_return(). + +w2w_transfer_check_test(C) -> + Name = <<"Keyn Fawkes">>, + Provider = ?ID_PROVIDER, + Class = ?ID_CLASS, + IdentityID1 = create_identity(Name, Provider, Class, C), + WalletID11 = create_wallet(IdentityID1, C), + WalletID12 = create_wallet(IdentityID1, C), + W2WTransferID1 = create_w2w_transfer(WalletID11, WalletID12, C), + Keys = maps:keys(get_w2w_transfer(W2WTransferID1, C)), + ok = application:set_env(wapi, transport, thrift), + IdentityID2 = create_identity(Name, Provider, Class, C), + WalletID21 = create_wallet(IdentityID2, C), + WalletID22 = create_wallet(IdentityID2, C), + W2WTransferID2 = create_w2w_transfer(WalletID21, WalletID22, C), + ?assertEqual(Keys, maps:keys(get_w2w_transfer(W2WTransferID2, C))). + -spec withdrawal_check_test(config()) -> test_return(). withdrawal_check_test(C) -> @@ -357,6 +377,30 @@ get_destination(DestinationID, C) -> ), Destination. +create_w2w_transfer(WalletID1, WalletID2, C) -> + DefaultParams = #{ + <<"sender">> => WalletID1, + <<"receiver">> => WalletID2, + <<"body">> => #{ + <<"amount">> => 1000, + <<"currency">> => ?RUB + } + }, + {ok, W2WTransfer} = call_api( + fun swag_client_wallet_w2_w_api:create_w2_w_transfer/3, + #{body => DefaultParams}, + ct_helper:cfg(context, C) + ), + maps:get(<<"id">>, W2WTransfer). + +get_w2w_transfer(W2WTransferID2, C) -> + {ok, W2WTransfer} = call_api( + fun swag_client_wallet_w2_w_api:get_w2_w_transfer/3, + #{binding => #{<<"w2wTransferID">> => W2WTransferID2}}, + ct_helper:cfg(context, C) + ), + W2WTransfer. + await_destination(DestID) -> authorized = ct_helper:await( authorized, @@ -466,6 +510,39 @@ get_default_termset() -> ]} } ]} + }, + w2w = #domain_W2WServiceTerms{ + currencies = {value, ?ordset([?cur(<<"RUB">>), ?cur(<<"USD">>)])}, + allow = {constant, true}, + cash_limit = {decisions, [ + #domain_CashLimitDecision{ + if_ = {condition, {currency_is, ?cur(<<"RUB">>)}}, + then_ = {value, ?cashrng( + {inclusive, ?cash( 0, <<"RUB">>)}, + {exclusive, ?cash(10001, <<"RUB">>)} + )} + } + ]}, + cash_flow = {decisions, [ + #domain_CashFlowDecision{ + if_ = {condition, {currency_is, ?cur(<<"RUB">>)}}, + then_ = {value, [ + ?cfpost( + {wallet, sender_settlement}, + {wallet, receiver_settlement}, + ?share(1, 1, operation_amount) + ) + ]} + } + ]}, + fees = {decisions, [ + #domain_FeeDecision{ + if_ = {condition, {currency_is, ?cur(<<"RUB">>)}}, + then_ = {value, #domain_Fees{ + fees = #{surplus => ?share(1, 1, operation_amount)} + }} + } + ]} } } }. diff --git a/apps/wapi/test/wapi_w2w_tests_SUITE.erl b/apps/wapi/test/wapi_w2w_tests_SUITE.erl new file mode 100644 index 0000000..979fceb --- /dev/null +++ b/apps/wapi/test/wapi_w2w_tests_SUITE.erl @@ -0,0 +1,210 @@ +-module(wapi_w2w_tests_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-include_lib("damsel/include/dmsl_domain_config_thrift.hrl"). + +-include_lib("jose/include/jose_jwk.hrl"). +-include_lib("wapi_wallet_dummy_data.hrl"). + +-include_lib("fistful_proto/include/ff_proto_w2w_transfer_thrift.hrl"). + +-export([all/0]). +-export([groups/0]). +-export([init_per_suite/1]). +-export([end_per_suite/1]). +-export([init_per_group/2]). +-export([end_per_group/2]). +-export([init_per_testcase/2]). +-export([end_per_testcase/2]). + +-export([init/1]). + +-export([ + create/1, + get/1, + fail_unauthorized_wallet/1 +]). + +% common-api is used since it is the domain used in production RN +% TODO: change to wallet-api (or just omit since it is the default one) when new tokens will be a thing +-define(DOMAIN, <<"common-api">>). +-define(badresp(Code), {error, {invalid_response_code, Code}}). +-define(emptyresp(Code), {error, {Code, #{}}}). + +-type test_case_name() :: atom(). +-type config() :: [{atom(), any()}]. +-type group_name() :: atom(). + +-behaviour(supervisor). + +-spec init([]) -> + {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +init([]) -> + {ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}. + +-spec all() -> + [test_case_name()]. +all() -> + [ + {group, base} + ]. + +-spec groups() -> + [{group_name(), list(), [test_case_name()]}]. +groups() -> + [ + {base, [], + [ + create, + get, + fail_unauthorized_wallet + ] + } + ]. + +%% +%% starting/stopping +%% +-spec init_per_suite(config()) -> + config(). +init_per_suite(Config) -> + %% TODO remove this after cut off wapi + ok = application:set_env(wapi, transport, thrift), + ct_helper:makeup_cfg([ + ct_helper:test_case_name(init), + ct_payment_system:setup(#{ + optional_apps => [ + bender_client, + wapi_woody_client, + wapi + ] + }) + ], Config). + +-spec end_per_suite(config()) -> + _. +end_per_suite(C) -> + %% TODO remove this after cut off wapi + ok = application:unset_env(wapi, transport), + ok = ct_payment_system:shutdown(C). + +-spec init_per_group(group_name(), config()) -> + config(). +init_per_group(Group, Config) when Group =:= base -> + ok = ff_context:save(ff_context:create(#{ + party_client => party_client:create_client(), + woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(Group, utf8))/binary>>) + })), + Party = create_party(Config), + BasePermissions = [ + {[w2w], read}, + {[w2w], write} + ], + {ok, Token} = wapi_ct_helper:issue_token(Party, BasePermissions, {deadline, 10}, ?DOMAIN), + Config1 = [{party, Party} | Config], + [{context, wapi_ct_helper:get_context(Token)} | Config1]; +init_per_group(_, Config) -> + Config. + +-spec end_per_group(group_name(), config()) -> + _. +end_per_group(_Group, _C) -> + ok. + +-spec init_per_testcase(test_case_name(), config()) -> + config(). +init_per_testcase(Name, C) -> + C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C), + ok = ct_helper:set_context(C1), + [{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1]. + +-spec end_per_testcase(test_case_name(), config()) -> + config(). +end_per_testcase(_Name, C) -> + ok = ct_helper:unset_context(), + wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)), + ok. + +%%% Tests + +-spec create(config()) -> + _. +create(C) -> + PartyID = ?config(party, C), + wapi_ct_helper:mock_services([ + {bender_thrift, fun('GenerateID', _) -> {ok, ?GENERATE_ID_RESULT} end}, + {fistful_wallet, fun('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(PartyID)} end}, + {w2w_transfer, fun('Create', _) -> {ok, ?W2W_TRANSFER(PartyID)} end} + ], C), + {ok, _} = call_api( + fun swag_client_wallet_w2_w_api:create_w2_w_transfer/3, + #{ + body => #{ + <<"sender">> => ?STRING, + <<"receiver">> => ?STRING, + <<"body">> => #{ + <<"amount">> => ?INTEGER, + <<"currency">> => ?RUB + } + } + }, + ct_helper:cfg(context, C) + ). + +-spec get(config()) -> + _. +get(C) -> + PartyID = ?config(party, C), + wapi_ct_helper:mock_services([ + {w2w_transfer, fun('Get', _) -> {ok, ?W2W_TRANSFER(PartyID)} end} + ], C), + {ok, _} = call_api( + fun swag_client_wallet_w2_w_api:get_w2_w_transfer/3, + #{ + binding => #{ + <<"w2wTransferID">> => ?STRING + } + }, + ct_helper:cfg(context, C) +). + +-spec fail_unauthorized_wallet(config()) -> + _. +fail_unauthorized_wallet(C) -> + PartyID = ?config(party, C), + wapi_ct_helper:mock_services([ + {bender_thrift, fun('GenerateID', _) -> {ok, ?GENERATE_ID_RESULT} end}, + {fistful_wallet, fun('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(<<"someotherparty">>)} end}, + {w2w_transfer, fun('Create', _) -> {ok, ?W2W_TRANSFER(PartyID)} end} + ], C), + {error, {422, #{ + <<"message">> := <<"No such wallet sender">> + }}} = call_api( + fun swag_client_wallet_w2_w_api:create_w2_w_transfer/3, + #{ + body => #{ + <<"sender">> => ?STRING, + <<"receiver">> => ?STRING, + <<"body">> => #{ + <<"amount">> => ?INTEGER, + <<"currency">> => ?RUB + } + } + }, + ct_helper:cfg(context, C) + ). + +%% + +-spec call_api(function(), map(), wapi_client_lib:context()) -> + {ok, term()} | {error, term()}. +call_api(F, Params, Context) -> + {Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params), + Response = F(Url, PreparedParams, Opts), + wapi_client_lib:handle_response(Response). + +create_party(_C) -> + ID = genlib:bsuuid(), + _ = ff_party:create(ID), + ID. diff --git a/apps/wapi/test/wapi_wallet_dummy_data.hrl b/apps/wapi/test/wapi_wallet_dummy_data.hrl index 805d48e..dde2295 100644 --- a/apps/wapi/test/wapi_wallet_dummy_data.hrl +++ b/apps/wapi/test/wapi_wallet_dummy_data.hrl @@ -35,6 +35,13 @@ undefined }). +-define(GENERATE_ID_RESULT, { + 'bender_GenerationResult', + ?STRING, + undefined, + undefined +}). + -define(WITHDRAWAL_STATUS, {pending, #wthd_status_Pending{}}). -define(WITHDRAWAL(PartyID), #wthd_WithdrawalState{ @@ -264,6 +271,24 @@ enabled = false }). +-define(W2W_TRANSFER(PartyID), #w2w_transfer_W2WTransferState{ + id = ?STRING, + wallet_from_id = ?STRING, + wallet_to_id = ?STRING, + body = ?CASH, + created_at = ?TIMESTAMP, + domain_revision = ?INTEGER, + party_revision = ?INTEGER, + status = {pending, #w2w_status_Pending{}}, + external_id = ?STRING, + metadata = ?DEFAULT_METADATA(), + context = ?DEFAULT_CONTEXT(PartyID), + effective_final_cash_flow = #cashflow_FinalCashFlow{ + postings = [] + }, + adjustments = [] +}). + -define(SNAPSHOT, #'Snapshot'{ version = ?INTEGER, domain = #{ diff --git a/apps/wapi_woody_client/src/wapi_woody_client.erl b/apps/wapi_woody_client/src/wapi_woody_client.erl index 4d86ae8..870b48c 100644 --- a/apps/wapi_woody_client/src/wapi_woody_client.erl +++ b/apps/wapi_woody_client/src/wapi_woody_client.erl @@ -91,7 +91,9 @@ get_service_modname(fistful_destination) -> get_service_modname(fistful_withdrawal) -> {ff_proto_withdrawal_thrift, 'Management'}; get_service_modname(webhook_manager) -> - {ff_proto_webhooker_thrift, 'WebhookManager'}. + {ff_proto_webhooker_thrift, 'WebhookManager'}; +get_service_modname(w2w_transfer) -> + {ff_proto_w2w_transfer_thrift, 'Management'}. -spec get_service_deadline(service_name()) -> undefined | woody_deadline:deadline().