mirror of
https://github.com/valitydev/fistful-server.git
synced 2024-11-06 02:35:18 +00:00
TD-509: Claim commiter (#51)
* added base
* finished claim commiter code
* added base tests
* finished base tests
* renamed cache
* changed cache ref
* changed damsel ref
* reverted to checkout v2
* change to custom workflow
* reverted to base workflow
* added codecove secret
* changed to custom workflow commit
* changed
* added full ref
* changed to new action version, removed local action
* changed action version
* refactored run job
* Revert "refactored run job"
This reverts commit e215103bce
.
* reverted commit id
* updated workflow ref
* reverted workflow change
* added requested changes
* added requested changes
This commit is contained in:
parent
47feb3b06e
commit
729611ff5d
2
.github/workflows/erlang-checks.yml
vendored
2
.github/workflows/erlang-checks.yml
vendored
@ -36,4 +36,4 @@ jobs:
|
||||
use-thrift: true
|
||||
thrift-version: ${{ needs.setup.outputs.thrift-version }}
|
||||
run-ct-with-compose: true
|
||||
cache-version: v2
|
||||
cache-version: v100
|
||||
|
144
apps/ff_claim/include/ff_claim_management.hrl
Normal file
144
apps/ff_claim/include/ff_claim_management.hrl
Normal file
@ -0,0 +1,144 @@
|
||||
-ifndef(__ff_claim_management_hrl__).
|
||||
-define(__ff_claim_management_hrl__, included).
|
||||
|
||||
-define(cm_modification_unit(ModID, Timestamp, Mod, UserInfo), #claimmgmt_ModificationUnit{
|
||||
modification_id = ModID,
|
||||
created_at = Timestamp,
|
||||
modification = Mod,
|
||||
user_info = UserInfo
|
||||
}).
|
||||
|
||||
-define(cm_wallet_modification(ModID, Timestamp, Mod, UserInfo),
|
||||
?cm_modification_unit(ModID, Timestamp, {wallet_modification, Mod}, UserInfo)
|
||||
).
|
||||
|
||||
-define(cm_identity_modification(ModID, Timestamp, Mod, UserInfo),
|
||||
?cm_modification_unit(ModID, Timestamp, {identity_modification, Mod}, UserInfo)
|
||||
).
|
||||
|
||||
%%% Identity
|
||||
|
||||
-define(cm_identity_creation(PartyID, IdentityID, Provider, Params),
|
||||
{identity_modification, #claimmgmt_IdentityModificationUnit{
|
||||
id = IdentityID,
|
||||
modification =
|
||||
{creation,
|
||||
Params = #claimmgmt_IdentityParams{
|
||||
party_id = PartyID,
|
||||
provider = Provider
|
||||
}}
|
||||
}}
|
||||
).
|
||||
|
||||
%%% Wallet
|
||||
|
||||
-define(cm_wallet_creation(IdentityID, WalletID, Currency, Params),
|
||||
{wallet_modification, #claimmgmt_NewWalletModificationUnit{
|
||||
id = WalletID,
|
||||
modification =
|
||||
{creation,
|
||||
Params = #claimmgmt_NewWalletParams{
|
||||
identity_id = IdentityID,
|
||||
currency = Currency
|
||||
}}
|
||||
}}
|
||||
).
|
||||
|
||||
%%% Error
|
||||
|
||||
-define(cm_invalid_changeset(Reason, InvalidChangeset), #claimmgmt_InvalidChangeset{
|
||||
reason = Reason,
|
||||
invalid_changeset = InvalidChangeset
|
||||
}).
|
||||
|
||||
-define(cm_invalid_identity_already_exists(ID),
|
||||
{
|
||||
invalid_identity_changeset,
|
||||
#claimmgmt_InvalidIdentityChangesetReason{
|
||||
id = ID,
|
||||
reason = {already_exists, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_identity_provider_not_found(ID),
|
||||
{
|
||||
invalid_identity_changeset,
|
||||
#claimmgmt_InvalidIdentityChangesetReason{
|
||||
id = ID,
|
||||
reason = {provider_not_found, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_identity_party_not_found(ID),
|
||||
{
|
||||
invalid_identity_changeset,
|
||||
#claimmgmt_InvalidIdentityChangesetReason{
|
||||
id = ID,
|
||||
reason = {party_not_found, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_identity_party_inaccessible(ID),
|
||||
{
|
||||
invalid_identity_changeset,
|
||||
#claimmgmt_InvalidIdentityChangesetReason{
|
||||
id = ID,
|
||||
reason = {party_inaccessible, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_wallet_already_exists(ID),
|
||||
{
|
||||
invalid_wallet_changeset,
|
||||
#claimmgmt_InvalidNewWalletChangesetReason{
|
||||
id = ID,
|
||||
reason = {already_exists, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_wallet_identity_not_found(ID),
|
||||
{
|
||||
invalid_wallet_changeset,
|
||||
#claimmgmt_InvalidNewWalletChangesetReason{
|
||||
id = ID,
|
||||
reason = {identity_not_found, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_wallet_currency_not_found(ID),
|
||||
{
|
||||
invalid_wallet_changeset,
|
||||
#claimmgmt_InvalidNewWalletChangesetReason{
|
||||
id = ID,
|
||||
reason = {currency_not_found, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_wallet_currency_not_allowed(ID),
|
||||
{
|
||||
invalid_wallet_changeset,
|
||||
#claimmgmt_InvalidNewWalletChangesetReason{
|
||||
id = ID,
|
||||
reason = {currency_not_allowed, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-define(cm_invalid_wallet_party_inaccessible(ID),
|
||||
{
|
||||
invalid_wallet_changeset,
|
||||
#claimmgmt_InvalidNewWalletChangesetReason{
|
||||
id = ID,
|
||||
reason = {party_inaccessible, #claimmgmt_InvalidClaimConcreteReason{}}
|
||||
}
|
||||
}
|
||||
).
|
||||
|
||||
-endif.
|
15
apps/ff_claim/src/ff_claim.app.src
Normal file
15
apps/ff_claim/src/ff_claim.app.src
Normal file
@ -0,0 +1,15 @@
|
||||
{application, ff_claim, [
|
||||
{description, "Wallet claims"},
|
||||
{vsn, "1"},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib,
|
||||
genlib,
|
||||
damsel,
|
||||
fistful,
|
||||
ff_transfer
|
||||
]},
|
||||
{licenses, ["Apache 2.0"]},
|
||||
{links, ["https://github.com/rbkmoney/fistful-server"]}
|
||||
]}.
|
193
apps/ff_claim/src/ff_claim_committer.erl
Normal file
193
apps/ff_claim/src/ff_claim_committer.erl
Normal file
@ -0,0 +1,193 @@
|
||||
-module(ff_claim_committer).
|
||||
|
||||
-include_lib("damsel/include/dmsl_claimmgmt_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_base_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_payproc_thrift.hrl").
|
||||
|
||||
-include("ff_claim_management.hrl").
|
||||
|
||||
-export([filter_ff_modifications/1]).
|
||||
-export([assert_modifications_applicable/1]).
|
||||
-export([apply_modifications/1]).
|
||||
|
||||
-type changeset() :: dmsl_claimmgmt_thrift:'ClaimChangeset'().
|
||||
-type modification() :: dmsl_claimmgmt_thrift:'PartyModification'().
|
||||
-type modifications() :: [modification()].
|
||||
|
||||
-export_type([modification/0]).
|
||||
-export_type([modifications/0]).
|
||||
|
||||
-spec filter_ff_modifications(changeset()) -> modifications().
|
||||
filter_ff_modifications(Changeset) ->
|
||||
lists:filtermap(
|
||||
fun
|
||||
(?cm_identity_modification(_, _, Change, _)) ->
|
||||
{true, {identity_modification, Change}};
|
||||
(?cm_wallet_modification(_, _, Change, _)) ->
|
||||
{true, {wallet_modification, Change}};
|
||||
(_) ->
|
||||
false
|
||||
end,
|
||||
Changeset
|
||||
).
|
||||
|
||||
%% Used same checks as in identity/wallet create function
|
||||
-spec assert_modifications_applicable(modifications()) -> ok | no_return().
|
||||
assert_modifications_applicable([FFChange | Others]) ->
|
||||
ok =
|
||||
case FFChange of
|
||||
?cm_identity_creation(PartyID, IdentityID, Provider, _Params) ->
|
||||
case ff_identity_machine:get(IdentityID) of
|
||||
{ok, _Machine} ->
|
||||
raise_invalid_changeset(?cm_invalid_identity_already_exists(IdentityID), [FFChange]);
|
||||
{error, notfound} ->
|
||||
assert_identity_creation_applicable(PartyID, IdentityID, Provider, FFChange)
|
||||
end;
|
||||
?cm_wallet_creation(IdentityID, WalletID, Currency, _Params) ->
|
||||
case ff_wallet_machine:get(WalletID) of
|
||||
{ok, _Machine} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_already_exists(WalletID), [FFChange]);
|
||||
{error, notfound} ->
|
||||
assert_wallet_creation_modification_applicable(IdentityID, WalletID, Currency, FFChange)
|
||||
end
|
||||
end,
|
||||
assert_modifications_applicable(Others);
|
||||
assert_modifications_applicable([]) ->
|
||||
ok.
|
||||
|
||||
-spec apply_modifications(modifications()) -> ok | no_return().
|
||||
apply_modifications([FFChange | Others]) ->
|
||||
ok =
|
||||
case FFChange of
|
||||
?cm_identity_creation(_PartyID, IdentityID, _Provider, Params) ->
|
||||
#claimmgmt_IdentityParams{metadata = Metadata} = Params,
|
||||
apply_identity_creation(IdentityID, Metadata, Params, FFChange);
|
||||
?cm_wallet_creation(_IdentityID, WalletID, _Currency, Params) ->
|
||||
#claimmgmt_NewWalletParams{metadata = Metadata} = Params,
|
||||
apply_wallet_creation(WalletID, Metadata, Params, FFChange)
|
||||
end,
|
||||
apply_modifications(Others);
|
||||
apply_modifications([]) ->
|
||||
ok.
|
||||
|
||||
%%% Internal functions
|
||||
assert_identity_creation_applicable(PartyID, IdentityID, Provider, Change) ->
|
||||
case ff_identity:check_identity_creation(#{party => PartyID, provider => Provider}) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
{error, {provider, notfound}} ->
|
||||
raise_invalid_changeset(?cm_invalid_identity_provider_not_found(IdentityID), [Change]);
|
||||
{error, {party, notfound}} ->
|
||||
throw(#claimmgmt_PartyNotFound{});
|
||||
{error, {party, {inaccessible, _}}} ->
|
||||
raise_invalid_changeset(?cm_invalid_identity_party_inaccessible(IdentityID), [Change])
|
||||
end.
|
||||
|
||||
apply_identity_creation(IdentityID, Metadata, ChangeParams, Change) ->
|
||||
Params = #{party := PartyID} = unmarshal_identity_params(IdentityID, ChangeParams),
|
||||
case ff_identity_machine:create(Params, create_context(PartyID, Metadata)) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, {provider, notfound}} ->
|
||||
raise_invalid_changeset(?cm_invalid_identity_provider_not_found(IdentityID), [Change]);
|
||||
{error, {party, notfound}} ->
|
||||
throw(#claimmgmt_PartyNotFound{});
|
||||
{error, {party, {inaccessible, _}}} ->
|
||||
raise_invalid_changeset(?cm_invalid_identity_party_inaccessible(IdentityID), [Change]);
|
||||
{error, exists} ->
|
||||
raise_invalid_changeset(?cm_invalid_identity_already_exists(IdentityID), [Change]);
|
||||
{error, Error} ->
|
||||
woody_error:raise(system, {internal, result_unexpected, woody_error:format_details(Error)})
|
||||
end.
|
||||
|
||||
assert_wallet_creation_modification_applicable(IdentityID, WalletID, DomainCurrency, Change) ->
|
||||
#domain_CurrencyRef{symbolic_code = CurrencyID} = DomainCurrency,
|
||||
case ff_wallet:check_creation(#{identity => IdentityID, currency => CurrencyID}) of
|
||||
{ok, {Identity, Currency}} ->
|
||||
case ff_account:check_account_creation(WalletID, Identity, Currency) of
|
||||
{ok, valid} ->
|
||||
ok;
|
||||
%% not_allowed_currency
|
||||
{error, {terms, _}} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_currency_not_allowed(WalletID), [Change]);
|
||||
{error, {party, {inaccessible, _}}} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_party_inaccessible(WalletID), [Change])
|
||||
end;
|
||||
{error, {identity, notfound}} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_identity_not_found(WalletID), [Change]);
|
||||
{error, {currency, notfound}} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_currency_not_found(WalletID), [Change])
|
||||
end.
|
||||
|
||||
apply_wallet_creation(WalletID, Metadata, ChangeParams, Change) ->
|
||||
Params = #{identity := IdentityID} = unmarshal_wallet_params(WalletID, ChangeParams),
|
||||
PartyID =
|
||||
case ff_identity_machine:get(IdentityID) of
|
||||
{ok, Machine} ->
|
||||
Identity = ff_identity_machine:identity(Machine),
|
||||
ff_identity:party(Identity);
|
||||
{error, notfound} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_identity_not_found(WalletID), [Change])
|
||||
end,
|
||||
case ff_wallet_machine:create(Params, create_context(PartyID, Metadata)) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, {identity, notfound}} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_identity_not_found(WalletID), [Change]);
|
||||
{error, {currency, notfound}} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_currency_not_found(WalletID), [Change]);
|
||||
{error, {party, _Inaccessible}} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_party_inaccessible(WalletID), [Change]);
|
||||
{error, exists} ->
|
||||
raise_invalid_changeset(?cm_invalid_wallet_already_exists(WalletID), [Change]);
|
||||
{error, Error} ->
|
||||
woody_error:raise(system, {internal, result_unexpected, woody_error:format_details(Error)})
|
||||
end.
|
||||
|
||||
-spec raise_invalid_changeset(dmsl_claimmgmt_thrift:'InvalidChangesetReason'(), modifications()) -> no_return().
|
||||
raise_invalid_changeset(Reason, Modifications) ->
|
||||
throw(?cm_invalid_changeset(Reason, Modifications)).
|
||||
|
||||
unmarshal_identity_params(IdentityID, #claimmgmt_IdentityParams{
|
||||
name = Name,
|
||||
party_id = PartyID,
|
||||
provider = ProviderID,
|
||||
metadata = Metadata
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
id => IdentityID,
|
||||
name => Name,
|
||||
party => PartyID,
|
||||
provider => ProviderID,
|
||||
metadata => maybe_unmarshal_metadata(Metadata)
|
||||
}).
|
||||
|
||||
unmarshal_wallet_params(WalletID, #claimmgmt_NewWalletParams{
|
||||
identity_id = IdentityID,
|
||||
name = Name,
|
||||
currency = DomainCurrency,
|
||||
metadata = Metadata
|
||||
}) ->
|
||||
#domain_CurrencyRef{symbolic_code = CurrencyID} = DomainCurrency,
|
||||
genlib_map:compact(#{
|
||||
id => WalletID,
|
||||
name => Name,
|
||||
identity => IdentityID,
|
||||
currency => CurrencyID,
|
||||
metadata => maybe_unmarshal_metadata(Metadata)
|
||||
}).
|
||||
|
||||
maybe_unmarshal_metadata(undefined) ->
|
||||
undefined;
|
||||
maybe_unmarshal_metadata(Metadata) when is_map(Metadata) ->
|
||||
maps:map(fun(_NS, V) -> ff_adapter_withdrawal_codec:unmarshal_msgpack(V) end, Metadata).
|
||||
|
||||
create_context(PartyID, Metadata) ->
|
||||
#{
|
||||
%% same as used in wapi lib
|
||||
<<"com.rbkmoney.wapi">> => genlib_map:compact(#{
|
||||
<<"owner">> => PartyID,
|
||||
<<"metadata">> => maybe_unmarshal_metadata(Metadata)
|
||||
})
|
||||
}.
|
250
apps/ff_claim/test/ff_claim_SUITE.erl
Normal file
250
apps/ff_claim/test/ff_claim_SUITE.erl
Normal file
@ -0,0 +1,250 @@
|
||||
-module(ff_claim_SUITE).
|
||||
|
||||
-include_lib("stdlib/include/assert.hrl").
|
||||
-include_lib("damsel/include/dmsl_claimmgmt_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
|
||||
-include("ff_claim_management.hrl").
|
||||
|
||||
%% Common test API
|
||||
|
||||
-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]).
|
||||
|
||||
-define(TEST_IDENTITY_CREATION(IdentityID, Params), #claimmgmt_IdentityModificationUnit{
|
||||
id = IdentityID,
|
||||
modification = {creation, Params}
|
||||
}).
|
||||
|
||||
-define(TEST_WALLET_CREATION(WalletID, Params), #claimmgmt_NewWalletModificationUnit{
|
||||
id = WalletID,
|
||||
modification = {creation, Params}
|
||||
}).
|
||||
|
||||
-define(USER_INFO, #claimmgmt_UserInfo{
|
||||
id = <<"id">>,
|
||||
email = <<"email">>,
|
||||
username = <<"username">>,
|
||||
type = {internal_user, #claimmgmt_InternalUser{}}
|
||||
}).
|
||||
|
||||
-define(CLAIM(PartyID, Claim), #claimmgmt_Claim{
|
||||
id = 1,
|
||||
party_id = PartyID,
|
||||
status = {pending, #claimmgmt_ClaimPending{}},
|
||||
revision = 1,
|
||||
created_at = <<"2026-03-22T06:12:27Z">>,
|
||||
changeset = [Claim]
|
||||
}).
|
||||
|
||||
%% Tests
|
||||
|
||||
-export([accept_identity_creation/1]).
|
||||
-export([accept_identity_creation_already_exists/1]).
|
||||
-export([apply_identity_creation/1]).
|
||||
|
||||
-export([accept_wallet_creation/1]).
|
||||
-export([accept_wallet_creation_already_exists/1]).
|
||||
-export([apply_wallet_creation/1]).
|
||||
|
||||
%% Internal types
|
||||
|
||||
-type config() :: ct_helper:config().
|
||||
-type test_case_name() :: ct_helper:test_case_name().
|
||||
-type group_name() :: ct_helper:group_name().
|
||||
-type test_return() :: _ | no_return().
|
||||
|
||||
%% API
|
||||
|
||||
-spec all() -> [test_case_name() | {group, group_name()}].
|
||||
all() ->
|
||||
[{group, default}].
|
||||
|
||||
-spec groups() -> [{group_name(), list(), [test_case_name()]}].
|
||||
groups() ->
|
||||
[
|
||||
{default, [parallel], [
|
||||
accept_identity_creation,
|
||||
accept_identity_creation_already_exists,
|
||||
apply_identity_creation,
|
||||
accept_wallet_creation,
|
||||
accept_wallet_creation_already_exists,
|
||||
apply_wallet_creation
|
||||
]}
|
||||
].
|
||||
|
||||
-spec init_per_suite(config()) -> config().
|
||||
init_per_suite(C) ->
|
||||
ct_helper:makeup_cfg(
|
||||
[
|
||||
ct_helper:test_case_name(init),
|
||||
ct_payment_system:setup()
|
||||
],
|
||||
C
|
||||
).
|
||||
|
||||
-spec end_per_suite(config()) -> _.
|
||||
end_per_suite(C) ->
|
||||
ok = ct_payment_system:shutdown(C).
|
||||
|
||||
%%
|
||||
|
||||
-spec init_per_group(group_name(), config()) -> config().
|
||||
init_per_group(_, C) ->
|
||||
C.
|
||||
|
||||
-spec end_per_group(group_name(), config()) -> _.
|
||||
end_per_group(_, _) ->
|
||||
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),
|
||||
C1.
|
||||
|
||||
-spec end_per_testcase(test_case_name(), config()) -> _.
|
||||
end_per_testcase(_Name, _C) ->
|
||||
ok = ct_helper:unset_context().
|
||||
|
||||
%% Tests
|
||||
|
||||
-spec accept_identity_creation(config()) -> test_return().
|
||||
accept_identity_creation(_C) ->
|
||||
#{party_id := PartyID} = prepare_standard_environment(),
|
||||
IdentityID = genlib:bsuuid(),
|
||||
Claim = make_identity_creation_claim(PartyID, IdentityID, <<"good-one">>),
|
||||
{ok, ok} = call_service('Accept', {PartyID, ?CLAIM(PartyID, Claim)}),
|
||||
ok.
|
||||
|
||||
-spec accept_identity_creation_already_exists(config()) -> test_return().
|
||||
accept_identity_creation_already_exists(_C) ->
|
||||
#{party_id := PartyID, identity_id := IdentityID} = prepare_standard_environment(),
|
||||
Claim = make_identity_creation_claim(PartyID, IdentityID, <<"good-one">>),
|
||||
?assertMatch(
|
||||
{exception, #claimmgmt_InvalidChangeset{reason = ?cm_invalid_identity_already_exists(IdentityID)}},
|
||||
call_service('Accept', {PartyID, ?CLAIM(PartyID, Claim)})
|
||||
).
|
||||
|
||||
-spec apply_identity_creation(config()) -> test_return().
|
||||
apply_identity_creation(_C) ->
|
||||
#{party_id := PartyID} = prepare_standard_environment(),
|
||||
IdentityID = genlib:bsuuid(),
|
||||
Claim = make_identity_creation_claim(PartyID, IdentityID, <<"good-one">>),
|
||||
{ok, ok} = call_service('Commit', {PartyID, ?CLAIM(PartyID, Claim)}),
|
||||
_Identity = get_identity(IdentityID),
|
||||
ok.
|
||||
|
||||
-spec accept_wallet_creation(config()) -> test_return().
|
||||
accept_wallet_creation(_C) ->
|
||||
#{
|
||||
party_id := PartyID,
|
||||
identity_id := IdentityID
|
||||
} = prepare_standard_environment(),
|
||||
WalletID = genlib:bsuuid(),
|
||||
Claim = make_wallet_creation_claim(WalletID, IdentityID, <<"RUB">>),
|
||||
{ok, ok} = call_service('Accept', {PartyID, ?CLAIM(PartyID, Claim)}),
|
||||
ok.
|
||||
|
||||
-spec accept_wallet_creation_already_exists(config()) -> test_return().
|
||||
accept_wallet_creation_already_exists(_C) ->
|
||||
#{
|
||||
party_id := PartyID,
|
||||
identity_id := IdentityID,
|
||||
wallet_id := WalletID
|
||||
} = prepare_standard_environment(),
|
||||
Claim = make_wallet_creation_claim(WalletID, IdentityID, <<"RUB">>),
|
||||
?assertMatch(
|
||||
{exception, #claimmgmt_InvalidChangeset{reason = ?cm_invalid_wallet_already_exists(WalletID)}},
|
||||
call_service('Accept', {PartyID, ?CLAIM(PartyID, Claim)})
|
||||
).
|
||||
|
||||
-spec apply_wallet_creation(config()) -> test_return().
|
||||
apply_wallet_creation(_C) ->
|
||||
#{
|
||||
party_id := PartyID,
|
||||
identity_id := IdentityID
|
||||
} = prepare_standard_environment(),
|
||||
WalletID = genlib:bsuuid(),
|
||||
Claim = make_wallet_creation_claim(WalletID, IdentityID, <<"RUB">>),
|
||||
{ok, ok} = call_service('Commit', {PartyID, ?CLAIM(PartyID, Claim)}),
|
||||
_Wallet = get_wallet(WalletID),
|
||||
ok.
|
||||
|
||||
%% Utils
|
||||
|
||||
call_service(Fun, Args) ->
|
||||
Service = {dmsl_claimmgmt_thrift, 'ClaimCommitter'},
|
||||
Request = {Service, Fun, Args},
|
||||
Client = ff_woody_client:new(#{
|
||||
url => <<"http://localhost:8022/v1/claim_committer">>,
|
||||
event_handler => scoper_woody_event_handler
|
||||
}),
|
||||
ff_woody_client:call(Client, Request).
|
||||
|
||||
prepare_standard_environment() ->
|
||||
PartyID = create_party(),
|
||||
IdentityID = create_identity(PartyID),
|
||||
WalletID = create_wallet(IdentityID, <<"My wallet">>, <<"RUB">>),
|
||||
#{
|
||||
wallet_id => WalletID,
|
||||
identity_id => IdentityID,
|
||||
party_id => PartyID
|
||||
}.
|
||||
|
||||
create_party() ->
|
||||
ID = genlib:bsuuid(),
|
||||
_ = ff_party:create(ID),
|
||||
ID.
|
||||
create_identity(Party) ->
|
||||
Name = <<"Identity Name">>,
|
||||
ID = genlib:unique(),
|
||||
ok = ff_identity_machine:create(
|
||||
#{id => ID, name => Name, party => Party, provider => <<"good-one">>},
|
||||
#{<<"com.rbkmoney.wapi">> => #{<<"name">> => Name, <<"owner">> => Party}}
|
||||
),
|
||||
ID.
|
||||
|
||||
get_identity(ID) ->
|
||||
{ok, Machine} = ff_identity_machine:get(ID),
|
||||
ff_identity_machine:identity(Machine).
|
||||
|
||||
create_wallet(IdentityID, Name, Currency) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_wallet_machine:create(
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency},
|
||||
ff_entity_context:new()
|
||||
),
|
||||
ID.
|
||||
|
||||
get_wallet(ID) ->
|
||||
{ok, Machine} = ff_wallet_machine:get(ID),
|
||||
ff_wallet_machine:wallet(Machine).
|
||||
|
||||
make_identity_creation_claim(PartyID, IdentityID, Provider) ->
|
||||
Params = #claimmgmt_IdentityParams{
|
||||
name = <<"SomeName">>,
|
||||
party_id = PartyID,
|
||||
provider = Provider
|
||||
},
|
||||
Mod = ?TEST_IDENTITY_CREATION(IdentityID, Params),
|
||||
?cm_identity_modification(1, <<"2026-03-22T06:12:27Z">>, Mod, ?USER_INFO).
|
||||
|
||||
make_wallet_creation_claim(WalletID, IdentityID, CurrencyID) ->
|
||||
Params = #claimmgmt_NewWalletParams{
|
||||
name = <<"SomeWalletName">>,
|
||||
identity_id = IdentityID,
|
||||
currency = #domain_CurrencyRef{
|
||||
symbolic_code = CurrencyID
|
||||
}
|
||||
},
|
||||
Mod = ?TEST_WALLET_CREATION(WalletID, Params),
|
||||
?cm_wallet_modification(1, <<"2026-03-22T06:12:27Z">>, Mod, ?USER_INFO).
|
32
apps/ff_server/src/ff_claim_committer_handler.erl
Normal file
32
apps/ff_server/src/ff_claim_committer_handler.erl
Normal file
@ -0,0 +1,32 @@
|
||||
-module(ff_claim_committer_handler).
|
||||
|
||||
-include_lib("damsel/include/dmsl_claimmgmt_thrift.hrl").
|
||||
|
||||
-behaviour(ff_woody_wrapper).
|
||||
|
||||
-export([handle_function/3]).
|
||||
|
||||
-spec handle_function(woody:func(), woody:args(), woody:options()) -> {ok, woody:result()} | no_return().
|
||||
handle_function(Func, Args, Opts) ->
|
||||
scoper:scope(
|
||||
claims,
|
||||
#{},
|
||||
fun() ->
|
||||
handle_function_(Func, Args, Opts)
|
||||
end
|
||||
).
|
||||
|
||||
handle_function_('Accept', {PartyID, #claimmgmt_Claim{changeset = Changeset}}, _Opts) ->
|
||||
ok = scoper:add_meta(#{party_id => PartyID}),
|
||||
Modifications = ff_claim_committer:filter_ff_modifications(Changeset),
|
||||
ok = ff_claim_committer:assert_modifications_applicable(Modifications),
|
||||
{ok, ok};
|
||||
handle_function_('Commit', {PartyID, Claim}, _Opts) ->
|
||||
#claimmgmt_Claim{
|
||||
id = ID,
|
||||
changeset = Changeset
|
||||
} = Claim,
|
||||
ok = scoper:add_meta(#{party_id => PartyID, claim_id => ID}),
|
||||
Modifications = ff_claim_committer:filter_ff_modifications(Changeset),
|
||||
ff_claim_committer:apply_modifications(Modifications),
|
||||
{ok, ok}.
|
@ -14,7 +14,8 @@
|
||||
fistful,
|
||||
ff_transfer,
|
||||
w2w,
|
||||
thrift
|
||||
thrift,
|
||||
ff_claim
|
||||
]},
|
||||
{env, []},
|
||||
{modules, []},
|
||||
|
@ -96,7 +96,8 @@ init([]) ->
|
||||
{withdrawal_repairer, ff_withdrawal_repair},
|
||||
{deposit_repairer, ff_deposit_repair},
|
||||
{w2w_transfer_management, ff_w2w_transfer_handler},
|
||||
{w2w_transfer_repairer, ff_w2w_transfer_repair}
|
||||
{w2w_transfer_repairer, ff_w2w_transfer_repair},
|
||||
{ff_claim_committer, ff_claim_committer_handler}
|
||||
] ++ get_eventsink_handlers(),
|
||||
WoodyHandlers = [get_handler(Service, Handler, WrapperOpts) || {Service, Handler} <- Services],
|
||||
|
||||
|
@ -60,7 +60,9 @@ get_service(w2w_transfer_event_sink) ->
|
||||
get_service(w2w_transfer_repairer) ->
|
||||
{fistful_w2w_transfer_thrift, 'Repairer'};
|
||||
get_service(w2w_transfer_management) ->
|
||||
{fistful_w2w_transfer_thrift, 'Management'}.
|
||||
{fistful_w2w_transfer_thrift, 'Management'};
|
||||
get_service(ff_claim_committer) ->
|
||||
{dmsl_claimmgmt_thrift, 'ClaimCommitter'}.
|
||||
|
||||
-spec get_service_spec(service_name()) -> service_spec().
|
||||
get_service_spec(Name) ->
|
||||
@ -112,4 +114,6 @@ get_service_path(w2w_transfer_event_sink) ->
|
||||
get_service_path(w2w_transfer_repairer) ->
|
||||
"/v1/repair/w2w_transfer";
|
||||
get_service_path(w2w_transfer_management) ->
|
||||
"/v1/w2w_transfer".
|
||||
"/v1/w2w_transfer";
|
||||
get_service_path(ff_claim_committer) ->
|
||||
"/v1/claim_committer".
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
-export([marshal/2]).
|
||||
-export([unmarshal/2]).
|
||||
-export([marshal_msgpack/1]).
|
||||
-export([unmarshal_msgpack/1]).
|
||||
|
||||
-type type_name() :: atom() | {list, atom()}.
|
||||
-type codec() :: module().
|
||||
@ -18,6 +20,19 @@
|
||||
-type decoded_value() :: decoded_value(any()).
|
||||
-type decoded_value(T) :: T.
|
||||
|
||||
%% as stolen from `machinery_msgpack`
|
||||
-type md() ::
|
||||
nil
|
||||
| boolean()
|
||||
| integer()
|
||||
| float()
|
||||
%% string
|
||||
| binary()
|
||||
%% binary
|
||||
| {binary, binary()}
|
||||
| [md()]
|
||||
| #{md() => md()}.
|
||||
|
||||
-export_type([codec/0]).
|
||||
-export_type([type_name/0]).
|
||||
-export_type([encoded_value/0]).
|
||||
@ -377,6 +392,7 @@ maybe_unmarshal(_Type, undefined) ->
|
||||
maybe_unmarshal(Type, Value) ->
|
||||
unmarshal(Type, Value).
|
||||
|
||||
-spec marshal_msgpack(md()) -> tuple().
|
||||
marshal_msgpack(nil) ->
|
||||
{nl, #msgpack_Nil{}};
|
||||
marshal_msgpack(V) when is_boolean(V) ->
|
||||
@ -395,6 +411,7 @@ marshal_msgpack(V) when is_list(V) ->
|
||||
marshal_msgpack(V) when is_map(V) ->
|
||||
{obj, maps:fold(fun(Key, Value, Map) -> Map#{marshal_msgpack(Key) => marshal_msgpack(Value)} end, #{}, V)}.
|
||||
|
||||
-spec unmarshal_msgpack(tuple()) -> md().
|
||||
unmarshal_msgpack({nl, #msgpack_Nil{}}) ->
|
||||
nil;
|
||||
unmarshal_msgpack({b, V}) when is_boolean(V) ->
|
||||
|
@ -51,6 +51,7 @@
|
||||
|
||||
-export([create/3]).
|
||||
-export([is_accessible/1]).
|
||||
-export([check_account_creation/3]).
|
||||
|
||||
-export([apply_event/2]).
|
||||
|
||||
@ -89,21 +90,8 @@ accounter_account_id(#{accounter_account_id := AccounterID}) ->
|
||||
-spec create(id(), identity(), currency()) -> {ok, [event()]} | {error, create_error()}.
|
||||
create(ID, Identity, Currency) ->
|
||||
do(fun() ->
|
||||
PartyID = ff_identity:party(Identity),
|
||||
accessible = unwrap(party, ff_party:is_accessible(PartyID)),
|
||||
TermVarset = #{
|
||||
wallet_id => ID,
|
||||
currency => ff_currency:to_domain_ref(Currency)
|
||||
},
|
||||
{ok, PartyRevision} = ff_party:get_revision(PartyID),
|
||||
DomainRevision = ff_domain_config:head(),
|
||||
Terms = ff_identity:get_terms(Identity, #{
|
||||
party_revision => PartyRevision,
|
||||
domain_revision => DomainRevision,
|
||||
varset => TermVarset
|
||||
}),
|
||||
unwrap(check_account_creation(ID, Identity, Currency)),
|
||||
CurrencyID = ff_currency:id(Currency),
|
||||
valid = unwrap(terms, ff_party:validate_account_creation(Terms, CurrencyID)),
|
||||
CurrencyCode = ff_currency:symcode(Currency),
|
||||
Description = ff_string:join($/, [<<"ff/account">>, ID]),
|
||||
{ok, AccounterID} = ff_accounting:create_account(CurrencyCode, Description),
|
||||
@ -126,6 +114,28 @@ is_accessible(Account) ->
|
||||
accessible = unwrap(ff_identity:is_accessible(Identity))
|
||||
end).
|
||||
|
||||
-spec check_account_creation(id(), identity(), currency()) ->
|
||||
{ok, valid}
|
||||
| {error, create_error()}.
|
||||
check_account_creation(ID, Identity, Currency) ->
|
||||
do(fun() ->
|
||||
DomainRevision = ff_domain_config:head(),
|
||||
PartyID = ff_identity:party(Identity),
|
||||
accessible = unwrap(party, ff_party:is_accessible(PartyID)),
|
||||
TermVarset = #{
|
||||
wallet_id => ID,
|
||||
currency => ff_currency:to_domain_ref(Currency)
|
||||
},
|
||||
{ok, PartyRevision} = ff_party:get_revision(PartyID),
|
||||
Terms = ff_identity:get_terms(Identity, #{
|
||||
party_revision => PartyRevision,
|
||||
domain_revision => DomainRevision,
|
||||
varset => TermVarset
|
||||
}),
|
||||
CurrencyID = ff_currency:id(Currency),
|
||||
valid = unwrap(terms, ff_party:validate_account_creation(Terms, CurrencyID))
|
||||
end).
|
||||
|
||||
get_identity(Account) ->
|
||||
{ok, V} = ff_identity_machine:get(identity(Account)),
|
||||
ff_identity_machine:identity(V).
|
||||
|
@ -71,11 +71,20 @@
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type check_params() :: #{
|
||||
party := ff_party:id(),
|
||||
provider := ff_provider:id()
|
||||
}.
|
||||
|
||||
-type create_error() ::
|
||||
{provider, notfound}
|
||||
| {party, notfound | ff_party:inaccessibility()}
|
||||
| invalid.
|
||||
|
||||
-type check_error() ::
|
||||
{provider, notfound}
|
||||
| {party, notfound | ff_party:inaccessibility()}.
|
||||
|
||||
-type get_terms_params() :: #{
|
||||
party_revision => ff_party:revision(),
|
||||
domain_revision => ff_domain_config:revision(),
|
||||
@ -108,6 +117,7 @@
|
||||
-export([get_withdrawal_methods/1]).
|
||||
-export([get_withdrawal_methods/2]).
|
||||
-export([get_terms/2]).
|
||||
-export([check_identity_creation/1]).
|
||||
|
||||
-export([apply_event/2]).
|
||||
|
||||
@ -178,8 +188,7 @@ set_blocking(Identity) ->
|
||||
| {error, create_error()}.
|
||||
create(Params = #{id := ID, name := Name, party := Party, provider := ProviderID}) ->
|
||||
do(fun() ->
|
||||
accessible = unwrap(party, ff_party:is_accessible(Party)),
|
||||
Provider = unwrap(provider, ff_provider:get(ProviderID)),
|
||||
Provider = unwrap(check_identity_creation(#{party => Party, provider => ProviderID})),
|
||||
Contract = unwrap(
|
||||
ff_party:create_contract(Party, #{
|
||||
payinst => ff_provider:payinst(Provider),
|
||||
@ -203,6 +212,16 @@ create(Params = #{id := ID, name := Name, party := Party, provider := ProviderID
|
||||
]
|
||||
end).
|
||||
|
||||
-spec check_identity_creation(check_params()) ->
|
||||
{ok, ff_provider:provider()}
|
||||
| {error, check_error()}.
|
||||
|
||||
check_identity_creation(#{party := Party, provider := ProviderID}) ->
|
||||
do(fun() ->
|
||||
accessible = unwrap(party, ff_party:is_accessible(Party)),
|
||||
unwrap(provider, ff_provider:get(ProviderID))
|
||||
end).
|
||||
|
||||
-spec get_withdrawal_methods(identity_state()) ->
|
||||
ordsets:ordset(ff_party:method_ref()).
|
||||
get_withdrawal_methods(Identity) ->
|
||||
|
@ -41,11 +41,20 @@
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type check_params() :: #{
|
||||
identity := ff_identity_machine:id(),
|
||||
currency := ff_currency:id()
|
||||
}.
|
||||
|
||||
-type create_error() ::
|
||||
{identity, notfound}
|
||||
| {currency, notfound}
|
||||
| ff_account:create_error().
|
||||
|
||||
-type check_error() ::
|
||||
{identity, notfound}
|
||||
| {currency, notfound}.
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([wallet/0]).
|
||||
-export_type([wallet_state/0]).
|
||||
@ -72,6 +81,7 @@
|
||||
-export([is_accessible/1]).
|
||||
-export([close/1]).
|
||||
-export([get_account_balance/1]).
|
||||
-export([check_creation/1]).
|
||||
|
||||
-export([apply_event/2]).
|
||||
|
||||
@ -133,11 +143,9 @@ metadata(Wallet) ->
|
||||
-spec create(params()) ->
|
||||
{ok, [event()]}
|
||||
| {error, create_error()}.
|
||||
create(Params = #{id := ID, identity := IdentityID, name := Name, currency := CurrencyID}) ->
|
||||
create(Params = #{id := ID, name := Name}) ->
|
||||
do(fun() ->
|
||||
IdentityMachine = unwrap(identity, ff_identity_machine:get(IdentityID)),
|
||||
Identity = ff_identity_machine:identity(IdentityMachine),
|
||||
Currency = unwrap(currency, ff_currency:get(CurrencyID)),
|
||||
{Identity, Currency} = unwrap(check_creation(maps:with([identity, currency], Params))),
|
||||
Wallet = genlib_map:compact(#{
|
||||
version => ?ACTUAL_FORMAT_VERSION,
|
||||
name => Name,
|
||||
@ -171,6 +179,18 @@ close(Wallet) ->
|
||||
[]
|
||||
end).
|
||||
|
||||
-spec check_creation(check_params()) ->
|
||||
{ok, {ff_identity:identity_state(), ff_currency:currency()}}
|
||||
| {error, check_error()}.
|
||||
|
||||
check_creation(#{identity := IdentityID, currency := CurrencyID}) ->
|
||||
do(fun() ->
|
||||
IdentityMachine = unwrap(identity, ff_identity_machine:get(IdentityID)),
|
||||
Identity = ff_identity_machine:identity(IdentityMachine),
|
||||
Currency = unwrap(currency, ff_currency:get(CurrencyID)),
|
||||
{Identity, Currency}
|
||||
end).
|
||||
|
||||
%%
|
||||
|
||||
-spec apply_event(event(), undefined | wallet_state()) -> wallet_state().
|
||||
|
@ -63,6 +63,7 @@
|
||||
]}.
|
||||
|
||||
{project_app_dirs, [
|
||||
"apps/ff_claim",
|
||||
"apps/ff_core",
|
||||
"apps/ff_server",
|
||||
"apps/ff_transfer",
|
||||
|
Loading…
Reference in New Issue
Block a user