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:
Артем 2023-04-03 09:16:06 +03:00 committed by GitHub
parent 47feb3b06e
commit 729611ff5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 732 additions and 25 deletions

View File

@ -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

View 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.

View 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"]}
]}.

View 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)
})
}.

View 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).

View 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}.

View File

@ -14,7 +14,8 @@
fistful,
ff_transfer,
w2w,
thrift
thrift,
ff_claim
]},
{env, []},
{modules, []},

View File

@ -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],

View File

@ -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".

View File

@ -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) ->

View File

@ -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).

View File

@ -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) ->

View File

@ -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().

View File

@ -63,6 +63,7 @@
]}.
{project_app_dirs, [
"apps/ff_claim",
"apps/ff_core",
"apps/ff_server",
"apps/ff_transfer",