From 574400ea03608210186ad4da635ff3d099be483e Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 16 Jul 2025 15:42:34 +0300 Subject: [PATCH 1/3] Implements `GetWalletAccount` operation bypassing fistful service --- rebar.config | 2 +- rebar.lock | 4 +- src/wapi_domain_backend.erl | 18 +- src/wapi_wallet_backend.erl | 54 ++++++ src/wapi_wallet_handler.erl | 6 +- src/wapi_woody_client.erl | 5 +- test/wapi_ct_helper.erl | 82 +++++---- test/wapi_wallet_dummy_data.hrl | 2 - test/wapi_wallet_tests_SUITE.erl | 158 ++++++++++++++++++ test/wapi_wallet_tests_SUITE_data/jwk.json | 7 + .../jwk.priv.json | 10 ++ .../jwk.publ.json | 9 + test/wapi_wallet_tests_SUITE_data/private.pem | 9 + 13 files changed, 312 insertions(+), 54 deletions(-) create mode 100644 src/wapi_wallet_backend.erl create mode 100644 test/wapi_wallet_tests_SUITE.erl create mode 100644 test/wapi_wallet_tests_SUITE_data/jwk.json create mode 100644 test/wapi_wallet_tests_SUITE_data/jwk.priv.json create mode 100644 test/wapi_wallet_tests_SUITE_data/jwk.publ.json create mode 100644 test/wapi_wallet_tests_SUITE_data/private.pem diff --git a/rebar.config b/rebar.config index fd75e18..9a316d1 100644 --- a/rebar.config +++ b/rebar.config @@ -32,7 +32,7 @@ {thrift, {git, "https://github.com/valitydev/thrift-erlang.git", {tag, "v1.0.0"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, {dmt_client, {git, "https://github.com/valitydev/dmt-client.git", {tag, "v2.0.0"}}}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.0"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "refactor_party_objects"}}}, {identdocstore_proto, {git, "https://github.com/valitydev/identdocstore-proto.git", {branch, "master"}}}, {fistful_proto, {git, "https://github.com/valitydev/fistful-proto.git", {tag, "v2.0.0"}}}, {fistful_reporter_proto, {git, "https://github.com/valitydev/fistful-reporter-proto.git", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index 4e0f9dd..48073ae 100644 --- a/rebar.lock +++ b/rebar.lock @@ -21,7 +21,7 @@ {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"ba7414811590859d058817b8f22d2e9c22f627f8"}}, + {ref,"6a4a1c927d96eae3d6c2adcf46a39485854d1a83"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt-client.git", @@ -41,7 +41,7 @@ 0}, {<<"fistful_reporter_proto">>, {git,"https://github.com/valitydev/fistful-reporter-proto.git", - {ref,"6d5695d2e8aa13247f93451937adefa70c6edeca"}}, + {ref,"533e95934683665bc840a45ca8735b87a3e3e2ba"}}, 0}, {<<"genlib">>, {git,"https://github.com/valitydev/genlib.git", diff --git a/src/wapi_domain_backend.erl b/src/wapi_domain_backend.erl index 3a063d4..0363f45 100644 --- a/src/wapi_domain_backend.erl +++ b/src/wapi_domain_backend.erl @@ -23,8 +23,8 @@ -spec get_party_config(id()) -> {ok, {map(), id()}} | {error, notfound}. get_party_config(PartyID) -> do(fun() -> - Party = unwrap(object({party_config, #domain_PartyConfigRef{id = PartyID}})), - {#{<<"id">> => Party#domain_PartyConfig.id}, PartyID} + _Party = unwrap(object({party_config, #domain_PartyConfigRef{id = PartyID}})), + {#{<<"id">> => PartyID}, PartyID} end). -spec get_wallet_config(id()) -> {ok, {map(), id()}} | {error, notfound}. @@ -33,8 +33,9 @@ get_wallet_config(WalletID) -> Wallet = unwrap(object({wallet_config, #domain_WalletConfigRef{id = WalletID}})), { #{ - <<"id">> => Wallet#domain_WalletConfig.id, - <<"partyID">> => Wallet#domain_WalletConfig.party_id + <<"id">> => WalletID, + <<"partyID">> => Wallet#domain_WalletConfig.party_id, + <<"account">> => unmarshal_wallet_account(Wallet#domain_WalletConfig.account) }, Wallet#domain_WalletConfig.party_id } @@ -56,6 +57,15 @@ get_currency(ID) -> %% Internal %% +unmarshal_wallet_account(#domain_WalletAccount{ + currency = #domain_CurrencyRef{symbolic_code = SymbolicCode}, + settlement = Settlement +}) -> + #{ + <<"currency">> => SymbolicCode, + <<"settlement">> => Settlement + }. + -spec object(dmt_client:object_ref()) -> {ok, object_data()} | {error, notfound}. object(ObjectRef) -> object(latest, ObjectRef). diff --git a/src/wapi_wallet_backend.erl b/src/wapi_wallet_backend.erl new file mode 100644 index 0000000..d9e97dc --- /dev/null +++ b/src/wapi_wallet_backend.erl @@ -0,0 +1,54 @@ +-module(wapi_wallet_backend). + +-type handler_context() :: wapi_handler_utils:handler_context(). +-type response_data() :: wapi_handler_utils:response_data(). +-type id() :: binary(). + +-export([get_account/2]). + +-include_lib("damsel/include/dmsl_payproc_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). + +-spec get_account(id(), handler_context()) -> {ok, response_data()} | {error, {wallet, notfound}}. +get_account(WalletID, HandlerContext) -> + case wapi_domain_backend:get_wallet_config(WalletID) of + {ok, { + #{ + <<"partyID">> := PartyID, + <<"account">> := #{ + <<"currency">> := _Currency, + <<"settlement">> := AccountID + } + }, + _ + }} -> + Request = {config_manager, 'GetAccountState', {PartyID, AccountID}}, + case wapi_handler_utils:service_call(Request, HandlerContext) of + {ok, AccountBalanceThrift} -> + {ok, unmarshal_wallet_account_balance(AccountBalanceThrift)}; + {exception, #payproc_PartyNotFound{}} -> + {error, {wallet, notfound}}; + {exception, #payproc_AccountNotFound{}} -> + {error, {wallet, notfound}} + end; + {error, notfound} -> + {error, {wallet, notfound}} + end. + +%% Marshaling + +unmarshal_wallet_account_balance(#payproc_AccountState{ + own_amount = OwnAmount, + available_amount = AvailableAmount, + currency = #domain_Currency{symbolic_code = CurrencyCode} +}) -> + #{ + <<"own">> => #{ + <<"amount">> => OwnAmount, + <<"currency">> => CurrencyCode + }, + <<"available">> => #{ + <<"amount">> => AvailableAmount, + <<"currency">> => CurrencyCode + } + }. diff --git a/src/wapi_wallet_handler.erl b/src/wapi_wallet_handler.erl index d8a5219..bcd259b 100644 --- a/src/wapi_wallet_handler.erl +++ b/src/wapi_wallet_handler.erl @@ -83,8 +83,10 @@ prepare('GetWalletAccount' = OperationID, #{'walletID' := WalletID}, Context, _O {ok, Resolution} end, Process = fun() -> - %% TODO: implement from new party service - wapi_handler_utils:reply_ok(404) + case wapi_wallet_backend:get_account(WalletID, Context) of + {ok, WalletAccount} -> wapi_handler_utils:reply_ok(200, WalletAccount); + {error, {wallet, notfound}} -> wapi_handler_utils:reply_ok(404) + end end, {ok, #{authorize => Authorize, process => Process}}; %% Destinations diff --git a/src/wapi_woody_client.erl b/src/wapi_woody_client.erl index 0bd2c40..8eff9b6 100644 --- a/src/wapi_woody_client.erl +++ b/src/wapi_woody_client.erl @@ -81,7 +81,10 @@ get_service_modname(fistful_destination) -> get_service_modname(fistful_withdrawal) -> {fistful_wthd_thrift, 'Management'}; get_service_modname(webhook_manager) -> - {fistful_webhooker_thrift, 'WebhookManager'}. + {fistful_webhooker_thrift, 'WebhookManager'}; +%% FIXME naming +get_service_modname(config_manager) -> + {dmsl_payproc_thrift, 'ConfigManagement'}. -spec get_service_deadline(service_name()) -> undefined | woody_deadline:deadline(). get_service_deadline(ServiceName) -> diff --git a/test/wapi_ct_helper.erl b/test/wapi_ct_helper.erl index b5c0d6d..ce7b908 100644 --- a/test/wapi_ct_helper.erl +++ b/test/wapi_ct_helper.erl @@ -133,52 +133,48 @@ start_app(woody = AppName) -> {acceptors_pool_size, 4} ]); start_app({dmt_client = AppName, SupPid}) -> - WalletConfig = #domain_WalletConfig{ - id = ?STRING, - created_at = wapi_time:rfc3339(), - blocking = - {unblocked, #domain_Unblocked{ - reason = <<"">>, - since = wapi_time:rfc3339() - }}, - suspension = - {active, #domain_Active{ - since = wapi_time:rfc3339() - }}, - details = #domain_Details{ - name = <<"Test Wallet">>, - description = <<"Test description">> - }, - currency_configs = #{ - #domain_CurrencyRef{symbolic_code = <<"RUB">>} => #domain_WalletCurrencyConfig{ + WalletConfigObject = #domain_WalletConfigObject{ + ref = #domain_WalletConfigRef{id = ?STRING}, + data = #domain_WalletConfig{ + name = ?STRING, + block = + {unblocked, #domain_Unblocked{ + reason = <<"">>, + since = wapi_time:rfc3339() + }}, + suspension = + {active, #domain_Active{ + since = wapi_time:rfc3339() + }}, + payment_institution = #domain_PaymentInstitutionRef{id = 1}, + terms = #domain_TermSetHierarchyRef{id = 1}, + account = #domain_WalletAccount{ currency = #domain_CurrencyRef{symbolic_code = <<"RUB">>}, settlement = ?INTEGER - } - }, - payment_institution = #domain_PaymentInstitutionRef{id = 1}, - terms = #domain_TermSetHierarchyRef{id = 1}, - party_id = ?STRING + }, + party_id = ?STRING + } }, - WalletConfigObject = #domain_WalletConfigObject{ref = #domain_WalletConfigRef{id = ?STRING}, data = WalletConfig}, - PartyConfig = #domain_PartyConfig{ - id = ?STRING, - contact_info = #domain_PartyContactInfo{ - registration_email = <<"test@test.ru">> - }, - created_at = wapi_time:rfc3339(), - blocking = - {unblocked, #domain_Unblocked{ - reason = <<"">>, - since = wapi_time:rfc3339() - }}, - suspension = - {active, #domain_Active{ - since = wapi_time:rfc3339() - }}, - shops = [], - wallets = [#domain_WalletConfigRef{id = ?STRING}] + PartyConfigObject = #domain_PartyConfigObject{ + ref = #domain_PartyConfigRef{id = ?STRING}, + data = #domain_PartyConfig{ + name = ?STRING, + block = + {unblocked, #domain_Unblocked{ + reason = <<"">>, + since = wapi_time:rfc3339() + }}, + suspension = + {active, #domain_Active{ + since = wapi_time:rfc3339() + }}, + shops = [], + wallets = [#domain_WalletConfigRef{id = ?STRING}], + contact_info = #domain_PartyContactInfo{ + registration_email = <<"test@test.ru">> + } + } }, - PartyConfigObject = #domain_PartyConfigObject{ref = #domain_PartyConfigRef{id = ?STRING}, data = PartyConfig}, Urls = mock_services_( [ {domain_config_client, fun @@ -373,6 +369,8 @@ mock_service_handler({ServiceName = domain_config, Fun}) -> mock_service_handler(ServiceName, {dmsl_domain_conf_v2_thrift, 'Repository'}, Fun); mock_service_handler({ServiceName = domain_config_client, Fun}) -> mock_service_handler(ServiceName, {dmsl_domain_conf_v2_thrift, 'RepositoryClient'}, Fun); +mock_service_handler({ServiceName = config_manager, Fun}) -> + mock_service_handler(ServiceName, {dmsl_payproc_thrift, 'ConfigManagement'}, Fun); mock_service_handler({ServiceName, Fun}) -> mock_service_handler(ServiceName, wapi_woody_client:get_service_modname(ServiceName), Fun); mock_service_handler({ServiceName, WoodyService, Fun}) -> diff --git a/test/wapi_wallet_dummy_data.hrl b/test/wapi_wallet_dummy_data.hrl index d767bfb..ea01965 100644 --- a/test/wapi_wallet_dummy_data.hrl +++ b/test/wapi_wallet_dummy_data.hrl @@ -192,8 +192,6 @@ -define(LAST_DIGITS(CardNumber), string:slice(CardNumber, 12)). --define(DESTINATION_STATUS, {authorized, #destination_Authorized{}}). - -define(DESTINATION(PartyID), ?DESTINATION(PartyID, ?RESOURCE_BANK_CARD)). -define(DESTINATION(PartyID, Resource), #destination_DestinationState{ diff --git a/test/wapi_wallet_tests_SUITE.erl b/test/wapi_wallet_tests_SUITE.erl new file mode 100644 index 0000000..67a121b --- /dev/null +++ b/test/wapi_wallet_tests_SUITE.erl @@ -0,0 +1,158 @@ +-module(wapi_wallet_tests_SUITE). + +-include_lib("stdlib/include/assert.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-include_lib("wapi_wallet_dummy_data.hrl"). + +-include_lib("damsel/include/dmsl_domain_conf_v2_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_payproc_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([ + get_account_ok/1, + get_account_fail_get_context_wallet_notfound/1, + get_account_fail_get_accountbalance_wallet_notfound/1 +]). + +-define(EMPTY_RESP(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() -> [{group, test_case_name()}]. +all() -> + [ + {group, base} + ]. + +-spec groups() -> [{group_name(), list(), [test_case_name()]}]. +groups() -> + [ + {base, [], [ + get_account_ok, + get_account_fail_get_context_wallet_notfound, + get_account_fail_get_accountbalance_wallet_notfound + ]} + ]. + +%% +%% starting/stopping +%% +-spec init_per_suite(config()) -> config(). +init_per_suite(C) -> + wapi_ct_helper:init_suite(?MODULE, C). + +-spec end_per_suite(config()) -> _. +end_per_suite(C) -> + _ = wapi_ct_helper:stop_mocked_service_sup(?config(suite_test_sup, C)), + _ = [application:stop(App) || App <- ?config(apps, C)], + ok. + +-spec init_per_group(group_name(), config()) -> config(). +init_per_group(Group, Config) when Group =:= base -> + Party = ?STRING, + Config1 = [{party, Party} | Config], + GroupSup = wapi_ct_helper:start_mocked_service_sup(?MODULE), + _ = wapi_ct_helper_token_keeper:mock_user_session_token(Party, GroupSup), + [{group_test_sup, GroupSup}, {context, wapi_ct_helper:get_context(?API_TOKEN)} | Config1]; +init_per_group(_, Config) -> + Config. + +-spec end_per_group(group_name(), config()) -> _. +end_per_group(_Group, C) -> + _ = wapi_ct_helper:stop_mocked_service_sup(?config(group_test_sup, C)), + ok. + +-spec init_per_testcase(test_case_name(), config()) -> config(). +init_per_testcase(Name, C) -> + C1 = wapi_ct_helper:makeup_cfg([wapi_ct_helper:test_case_name(Name), wapi_ct_helper:woody_ctx()], C), + [{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1]. + +-spec end_per_testcase(test_case_name(), config()) -> ok. +end_per_testcase(_Name, C) -> + _ = wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)), + ok. + +%%% Tests + +-spec get_account_ok(config()) -> _. +get_account_ok(C) -> + PartyID = ?config(party, C), + _ = wapi_ct_helper_bouncer:mock_assert_wallet_op_ctx(<<"GetWalletAccount">>, ?STRING, PartyID, C), + ok = mock_account_with_balance(?INTEGER, C), + {ok, _} = get_account_call_api(?STRING, C). + +-spec get_account_fail_get_context_wallet_notfound(config()) -> _. +get_account_fail_get_context_wallet_notfound(C) -> + _ = wapi_ct_helper_bouncer:mock_arbiter(wapi_ct_helper_bouncer:judge_always_forbidden(), C), + ok = mock_account_with_balance(?INTEGER, C), + ?assertEqual(?EMPTY_RESP(401), get_account_call_api(<<"non existant wallet id">>, C)). + +-spec get_account_fail_get_accountbalance_wallet_notfound(config()) -> _. +get_account_fail_get_accountbalance_wallet_notfound(C) -> + PartyID = ?config(party, C), + _ = wapi_ct_helper_bouncer:mock_assert_wallet_op_ctx(<<"GetWalletAccount">>, ?STRING, PartyID, C), + ok = mock_account_with_balance(424242, C), + ?assertEqual({error, {404, #{}}}, get_account_call_api(?STRING, 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). + +get_account_call_api(WalletID, C) -> + call_api( + fun swag_client_wallet_wallets_api:get_wallet_account/3, + #{ + binding => #{ + <<"walletID">> => WalletID + } + }, + wapi_ct_helper:cfg(context, C) + ). + +mock_account_with_balance(ExistingAccountID, C) -> + _ = wapi_ct_helper:mock_services( + [ + {config_manager, fun + ('GetAccountState', {_, AccountID}) when AccountID =:= ExistingAccountID -> + {ok, #payproc_AccountState{ + account_id = AccountID, + own_amount = ?INTEGER, + available_amount = ?INTEGER, + currency = #domain_Currency{ + name = ?STRING, + symbolic_code = ?RUB, + numeric_code = ?INTEGER, + exponent = ?INTEGER + } + }}; + ('GetAccountState', {_PartyID, _AccountID}) -> + throw(#payproc_AccountNotFound{}) + end} + ], + C + ), + ok. diff --git a/test/wapi_wallet_tests_SUITE_data/jwk.json b/test/wapi_wallet_tests_SUITE_data/jwk.json new file mode 100644 index 0000000..d380415 --- /dev/null +++ b/test/wapi_wallet_tests_SUITE_data/jwk.json @@ -0,0 +1,7 @@ +{ + "use": "enc", + "kty": "oct", + "kid": "1", + "alg": "dir", + "k": "M3VKOExvQVdhWUtXekduVGt1eDdrUmtwTTNBSko1a2M" +} \ No newline at end of file diff --git a/test/wapi_wallet_tests_SUITE_data/jwk.priv.json b/test/wapi_wallet_tests_SUITE_data/jwk.priv.json new file mode 100644 index 0000000..e7d6557 --- /dev/null +++ b/test/wapi_wallet_tests_SUITE_data/jwk.priv.json @@ -0,0 +1,10 @@ +{ + "use": "enc", + "kty": "EC", + "kid": "kxdD0orVPGoAxWrqAMTeQ0U5MRoK47uZxWiSJdgo0t0", + "crv": "P-256", + "alg": "ECDH-ES", + "x": "nHi7TCgBwfrPuNTf49bGvJMczk6WZOI-mCKAghbrOlM", + "y": "_8kiXGOIWkfz57m8K5dmTfbYzCJVYHZZZisCfbYicr0", + "d": "i45qDiARZ5qbS_uzeT-CiKnPUe64qHitKaVdAvcN6TI" +} \ No newline at end of file diff --git a/test/wapi_wallet_tests_SUITE_data/jwk.publ.json b/test/wapi_wallet_tests_SUITE_data/jwk.publ.json new file mode 100644 index 0000000..00b7002 --- /dev/null +++ b/test/wapi_wallet_tests_SUITE_data/jwk.publ.json @@ -0,0 +1,9 @@ +{ + "use": "enc", + "kty": "EC", + "kid": "kxdD0orVPGoAxWrqAMTeQ0U5MRoK47uZxWiSJdgo0t0", + "crv": "P-256", + "alg": "ECDH-ES", + "x": "nHi7TCgBwfrPuNTf49bGvJMczk6WZOI-mCKAghbrOlM", + "y": "_8kiXGOIWkfz57m8K5dmTfbYzCJVYHZZZisCfbYicr0" +} \ No newline at end of file diff --git a/test/wapi_wallet_tests_SUITE_data/private.pem b/test/wapi_wallet_tests_SUITE_data/private.pem new file mode 100644 index 0000000..4e6d12c --- /dev/null +++ b/test/wapi_wallet_tests_SUITE_data/private.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAK9fx7qOJT7Aoseu7KKgaLagBh3wvDzg7F/ZMtGbPFikJnnvRWvF +B5oEGbMPblvtF0/fjqfu+eqjP3Z1tUSn7TkCAwEAAQJABUY5KIgr4JZEjwLYxQ9T +9uIbLP1Xe/E7yqoqmBk2GGhSrPY0OeRkYnUVLcP96UPQhF63iuG8VF6uZ7oAPsq+ +gQIhANZy3jSCzPjXYHRU1kRqQzpt2S+OqoEiqQ6YG1HrC/VxAiEA0Vq6JlQK2tOX +37SS00dK0Qog4Qi8dN73GliFQNP18EkCIQC4epSA48zkfJMzQBAbRraSuxDNApPX +BzQbo+pMrEDbYQIgY4AncQgIkLB4Qk5kah48JNYXglzQlQtTjiX8Ty9ueGECIQCM +GD3UbQKiA0gf5plBA24I4wFVKxxa4wXbW/7SfP6XmQ== +-----END RSA PRIVATE KEY----- From d07c4fbbd0c495672c093b3854abe3b21d106a5e Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 17 Jul 2025 17:52:11 +0300 Subject: [PATCH 2/3] Adds `GetWallet` operation --- src/wapi_domain_backend.erl | 36 +++++++------------ src/wapi_wallet_backend.erl | 62 ++++++++++++++++++++++++-------- src/wapi_wallet_handler.erl | 19 ++++++++++ test/wapi_wallet_tests_SUITE.erl | 26 ++++++++++++++ 4 files changed, 104 insertions(+), 39 deletions(-) diff --git a/src/wapi_domain_backend.erl b/src/wapi_domain_backend.erl index 0363f45..4051809 100644 --- a/src/wapi_domain_backend.erl +++ b/src/wapi_domain_backend.erl @@ -8,6 +8,8 @@ -export([get_currency/1]). -export([get_party_config/1]). -export([get_wallet_config/1]). +-export([get_object/1]). +-export([get_object/2]). %% @@ -23,19 +25,18 @@ -spec get_party_config(id()) -> {ok, {map(), id()}} | {error, notfound}. get_party_config(PartyID) -> do(fun() -> - _Party = unwrap(object({party_config, #domain_PartyConfigRef{id = PartyID}})), + _Party = unwrap(get_object({party_config, #domain_PartyConfigRef{id = PartyID}})), {#{<<"id">> => PartyID}, PartyID} end). -spec get_wallet_config(id()) -> {ok, {map(), id()}} | {error, notfound}. get_wallet_config(WalletID) -> do(fun() -> - Wallet = unwrap(object({wallet_config, #domain_WalletConfigRef{id = WalletID}})), + Wallet = unwrap(get_object({wallet_config, #domain_WalletConfigRef{id = WalletID}})), { #{ <<"id">> => WalletID, - <<"partyID">> => Wallet#domain_WalletConfig.party_id, - <<"account">> => unmarshal_wallet_account(Wallet#domain_WalletConfig.account) + <<"partyID">> => Wallet#domain_WalletConfig.party_id }, Wallet#domain_WalletConfig.party_id } @@ -44,7 +45,7 @@ get_wallet_config(WalletID) -> -spec get_currency(id()) -> {ok, response_data()} | {error, notfound}. get_currency(ID) -> do(fun() -> - Currency = unwrap(object({currency, #domain_CurrencyRef{symbolic_code = ID}})), + Currency = unwrap(get_object({currency, #domain_CurrencyRef{symbolic_code = ID}})), #{ <<"id">> => genlib_string:to_upper(genlib:to_binary(ID)), <<"name">> => Currency#domain_Currency.name, @@ -53,27 +54,14 @@ get_currency(ID) -> } end). -%% -%% Internal -%% - -unmarshal_wallet_account(#domain_WalletAccount{ - currency = #domain_CurrencyRef{symbolic_code = SymbolicCode}, - settlement = Settlement -}) -> - #{ - <<"currency">> => SymbolicCode, - <<"settlement">> => Settlement - }. - --spec object(dmt_client:object_ref()) -> {ok, object_data()} | {error, notfound}. -object(ObjectRef) -> - object(latest, ObjectRef). +-spec get_object(dmt_client:object_ref()) -> {ok, object_data()} | {error, notfound}. +get_object(ObjectRef) -> + get_object(latest, ObjectRef). --spec object(dmt_client:version(), dmt_client:object_ref()) -> {ok, object_data()} | {error, notfound}. -object(Ref, {Type, ObjectRef}) -> +-spec get_object(dmt_client:version(), dmt_client:object_ref()) -> {ok, object_data()} | {error, notfound}. +get_object(Ref, {Type, ObjectRef}) -> try dmt_client:checkout_object(Ref, {Type, ObjectRef}) of - #domain_conf_v2_VersionedObject{object = {Type, {_RecordName, ObjectRef, ObjectData}}} -> + #domain_conf_v2_VersionedObject{object = {Type, {_, ObjectRef, ObjectData}}} -> {ok, ObjectData} catch #domain_conf_v2_ObjectNotFound{} -> diff --git a/src/wapi_wallet_backend.erl b/src/wapi_wallet_backend.erl index d9e97dc..37ac09f 100644 --- a/src/wapi_wallet_backend.erl +++ b/src/wapi_wallet_backend.erl @@ -4,28 +4,29 @@ -type response_data() :: wapi_handler_utils:response_data(). -type id() :: binary(). +-export([get/2]). -export([get_account/2]). --include_lib("damsel/include/dmsl_payproc_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_payproc_thrift.hrl"). + +-spec get(id(), handler_context()) -> {ok, response_data(), id()} | {error, {wallet, notfound}}. +get(WalletID, _HandlerContext) -> + case get_wallet_config(WalletID) of + {ok, WalletConfig} -> + {ok, unmarshal(wallet, {WalletID, WalletConfig}), WalletConfig#domain_WalletConfig.party_id}; + {error, notfound} -> + {error, {wallet, notfound}} + end. -spec get_account(id(), handler_context()) -> {ok, response_data()} | {error, {wallet, notfound}}. get_account(WalletID, HandlerContext) -> - case wapi_domain_backend:get_wallet_config(WalletID) of - {ok, { - #{ - <<"partyID">> := PartyID, - <<"account">> := #{ - <<"currency">> := _Currency, - <<"settlement">> := AccountID - } - }, - _ - }} -> + case get_wallet_config(WalletID) of + {ok, #domain_WalletConfig{party_id = PartyID, account = #domain_WalletAccount{settlement = AccountID}}} -> Request = {config_manager, 'GetAccountState', {PartyID, AccountID}}, case wapi_handler_utils:service_call(Request, HandlerContext) of {ok, AccountBalanceThrift} -> - {ok, unmarshal_wallet_account_balance(AccountBalanceThrift)}; + {ok, unmarshal(account_state, AccountBalanceThrift)}; {exception, #payproc_PartyNotFound{}} -> {error, {wallet, notfound}}; {exception, #payproc_AccountNotFound{}} -> @@ -35,9 +36,38 @@ get_account(WalletID, HandlerContext) -> {error, {wallet, notfound}} end. +%% Internal + +get_wallet_config(WalletID) -> + ObjectRef = {wallet_config, #domain_WalletConfigRef{id = WalletID}}, + wapi_domain_backend:get_object(ObjectRef). + %% Marshaling -unmarshal_wallet_account_balance(#payproc_AccountState{ +unmarshal( + wallet, + {WalletID, #domain_WalletConfig{ + name = Name, + block = Blocking, + account = #domain_WalletAccount{currency = #domain_CurrencyRef{symbolic_code = Currency}}, + party_id = PartyID + }} +) -> + %% FIXME Temporary stub + CreatedAt = ~b"1970-01-01T00:00:00Z", + genlib_map:compact(#{ + <<"id">> => unmarshal(id, WalletID), + <<"name">> => unmarshal(string, Name), + <<"createdAt">> => CreatedAt, + <<"isBlocked">> => unmarshal(blocking, Blocking), + <<"party">> => PartyID, + <<"currency">> => Currency + }); +unmarshal(blocking, {unblocked, _}) -> + false; +unmarshal(blocking, {blocked, _}) -> + true; +unmarshal(account_state, #payproc_AccountState{ own_amount = OwnAmount, available_amount = AvailableAmount, currency = #domain_Currency{symbolic_code = CurrencyCode} @@ -51,4 +81,6 @@ unmarshal_wallet_account_balance(#payproc_AccountState{ <<"amount">> => AvailableAmount, <<"currency">> => CurrencyCode } - }. + }; +unmarshal(T, V) -> + wapi_codec:unmarshal(T, V). diff --git a/src/wapi_wallet_handler.erl b/src/wapi_wallet_handler.erl index bcd259b..9738571 100644 --- a/src/wapi_wallet_handler.erl +++ b/src/wapi_wallet_handler.erl @@ -72,6 +72,25 @@ mask_notfound(Resolution) -> %% Providers -spec prepare(operation_id(), request_data(), handler_context(), handler_opts()) -> {ok, request_state()}. +%% Wallets +prepare('GetWallet' = OperationID, #{'walletID' := WalletID}, Context, _Opts) -> + {ResultWallet, ResultWalletOwner} = + case wapi_wallet_backend:get(WalletID, Context) of + {ok, Wallet, Owner} -> {Wallet, Owner}; + {error, {wallet, notfound}} -> {undefined, undefined} + end, + Authorize = fun() -> + Prototypes = [ + {operation, #{wallet => WalletID, id => OperationID}}, + {wallet, [wapi_bouncer_context:build_wallet_entity(wallet, ResultWallet, {party, ResultWalletOwner})]} + ], + Resolution = mask_notfound(wapi_auth:authorize_operation(Prototypes, Context)), + {ok, Resolution} + end, + Process = fun() -> + wapi_handler_utils:reply_ok(200, ResultWallet) + end, + {ok, #{authorize => Authorize, process => Process}}; prepare('GetWalletAccount' = OperationID, #{'walletID' := WalletID}, Context, _Opts) -> AuthContext = build_auth_context([{wallet, WalletID}], [], Context), Authorize = fun() -> diff --git a/test/wapi_wallet_tests_SUITE.erl b/test/wapi_wallet_tests_SUITE.erl index 67a121b..e028cc1 100644 --- a/test/wapi_wallet_tests_SUITE.erl +++ b/test/wapi_wallet_tests_SUITE.erl @@ -21,6 +21,8 @@ -export([init/1]). -export([ + get_ok/1, + get_fail_wallet_notfound/1, get_account_ok/1, get_account_fail_get_context_wallet_notfound/1, get_account_fail_get_accountbalance_wallet_notfound/1 @@ -48,6 +50,8 @@ all() -> groups() -> [ {base, [], [ + get_ok, + get_fail_wallet_notfound, get_account_ok, get_account_fail_get_context_wallet_notfound, get_account_fail_get_accountbalance_wallet_notfound @@ -94,6 +98,17 @@ end_per_testcase(_Name, C) -> %%% Tests +-spec get_ok(config()) -> _. +get_ok(C) -> + PartyID = ?config(party, C), + _ = wapi_ct_helper_bouncer:mock_assert_wallet_op_ctx(<<"GetWallet">>, ?STRING, PartyID, C), + {ok, _} = get_wallet_call_api(?STRING, C). + +-spec get_fail_wallet_notfound(config()) -> _. +get_fail_wallet_notfound(C) -> + _ = wapi_ct_helper_bouncer:mock_arbiter(wapi_ct_helper_bouncer:judge_always_forbidden(), C), + ?assertEqual(?EMPTY_RESP(404), get_wallet_call_api(<<"non existant wallet id">>, C)). + -spec get_account_ok(config()) -> _. get_account_ok(C) -> PartyID = ?config(party, C), @@ -122,6 +137,17 @@ call_api(F, Params, Context) -> Response = F(Url, PreparedParams, Opts), wapi_client_lib:handle_response(Response). +get_wallet_call_api(WalletID, C) -> + call_api( + fun swag_client_wallet_wallets_api:get_wallet/3, + #{ + binding => #{ + <<"walletID">> => WalletID + } + }, + wapi_ct_helper:cfg(context, C) + ). + get_account_call_api(WalletID, C) -> call_api( fun swag_client_wallet_wallets_api:get_wallet_account/3, From b2479f9ba276dd243b1b815af4b70cc931871d11 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Fri, 18 Jul 2025 11:45:30 +0300 Subject: [PATCH 3/3] Reverts auth context for wallets to use backend module --- src/wapi_domain_backend.erl | 14 -------------- src/wapi_wallet_handler.erl | 8 ++++---- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/wapi_domain_backend.erl b/src/wapi_domain_backend.erl index 4051809..87a2ffd 100644 --- a/src/wapi_domain_backend.erl +++ b/src/wapi_domain_backend.erl @@ -7,7 +7,6 @@ -export([get_currency/1]). -export([get_party_config/1]). --export([get_wallet_config/1]). -export([get_object/1]). -export([get_object/2]). @@ -29,19 +28,6 @@ get_party_config(PartyID) -> {#{<<"id">> => PartyID}, PartyID} end). --spec get_wallet_config(id()) -> {ok, {map(), id()}} | {error, notfound}. -get_wallet_config(WalletID) -> - do(fun() -> - Wallet = unwrap(get_object({wallet_config, #domain_WalletConfigRef{id = WalletID}})), - { - #{ - <<"id">> => WalletID, - <<"partyID">> => Wallet#domain_WalletConfig.party_id - }, - Wallet#domain_WalletConfig.party_id - } - end). - -spec get_currency(id()) -> {ok, response_data()} | {error, notfound}. get_currency(ID) -> do(fun() -> diff --git a/src/wapi_wallet_handler.erl b/src/wapi_wallet_handler.erl index 9738571..f15037e 100644 --- a/src/wapi_wallet_handler.erl +++ b/src/wapi_wallet_handler.erl @@ -820,11 +820,11 @@ build_auth_context({party, PartyID}, _Context) -> {error, notfound} -> {undefined, undefined} end, {party, {PartyID, ResultParty, ResultPartyOwner}}; -build_auth_context({wallet, WalletID}, _Context) -> +build_auth_context({wallet, WalletID}, Context) -> {ResultWallet, ResultWalletOwner} = - case wapi_domain_backend:get_wallet_config(WalletID) of - {ok, {WalletConfig, Owner}} -> {WalletConfig, Owner}; - {error, notfound} -> {undefined, undefined} + case wapi_wallet_backend:get(WalletID, Context) of + {ok, Wallet, Owner} -> {Wallet, Owner}; + {error, {wallet, notfound}} -> {undefined, undefined} end, {wallet, {WalletID, ResultWallet, ResultWalletOwner}}; build_auth_context({destination, DestinationID}, Context) ->