mirror of
https://github.com/valitydev/fistful-server.git
synced 2024-11-06 02:35:18 +00:00
fix: Revert everything & add erts (#335)
* Revert everything back to prometheus_metrics * Include erts
This commit is contained in:
parent
a64579afc3
commit
01fb6d846d
@ -117,8 +117,7 @@ start_app(wapi = AppName) ->
|
||||
validation_opts => #{
|
||||
custom_validator => wapi_swagger_validator
|
||||
}
|
||||
}},
|
||||
{events_fetch_limit, 32}
|
||||
}}
|
||||
]), #{}};
|
||||
|
||||
start_app(wapi_woody_client = AppName) ->
|
||||
@ -130,11 +129,10 @@ 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",
|
||||
p2p_transfer => "http://localhost:8022/v1/p2p_transfer",
|
||||
fistful_withdrawal => "http://localhost:8022/v1/withdrawal",
|
||||
fistful_w2w_transfer => "http://localhost:8022/v1/w2w_transfer",
|
||||
fistful_p2p_template => "http://localhost:8022/v1/p2p_template",
|
||||
fistful_p2p_transfer => "http://localhost:8022/v1/p2p_transfer",
|
||||
fistful_p2p_session => "http://localhost:8022/v1/p2p_transfer/session"
|
||||
fistful_p2p_template => "http://localhost:8022/v1/p2p_template"
|
||||
}},
|
||||
{service_retries, #{
|
||||
fistful_stat => #{
|
||||
|
@ -41,17 +41,17 @@ marshal_destination_state(DestinationState, Context) ->
|
||||
#dst_DestinationState{
|
||||
id = marshal(id, ff_destination:id(DestinationState)),
|
||||
name = marshal(string, ff_destination:name(DestinationState)),
|
||||
resource = maybe_marshal(resource, ff_destination:resource(DestinationState)),
|
||||
external_id = maybe_marshal(id, ff_destination:external_id(DestinationState)),
|
||||
account = maybe_marshal(account, ff_destination:account(DestinationState)),
|
||||
status = maybe_marshal(status, ff_destination:status(DestinationState)),
|
||||
created_at = maybe_marshal(timestamp_ms, ff_destination:created_at(DestinationState)),
|
||||
resource = marshal(resource, ff_destination:resource(DestinationState)),
|
||||
external_id = marshal(id, ff_destination:external_id(DestinationState)),
|
||||
account = marshal(account, ff_destination:account(DestinationState)),
|
||||
status = marshal(status, ff_destination:status(DestinationState)),
|
||||
created_at = marshal(timestamp_ms, ff_destination:created_at(DestinationState)),
|
||||
blocking = Blocking,
|
||||
metadata = maybe_marshal(ctx, ff_destination:metadata(DestinationState)),
|
||||
context = maybe_marshal(ctx, Context)
|
||||
metadata = marshal(ctx, ff_destination:metadata(DestinationState)),
|
||||
context = marshal(ctx, Context)
|
||||
}.
|
||||
|
||||
-spec marshal_event(ff_destination_machine:event()) ->
|
||||
-spec marshal_event(ff_destination:timestamped_event()) ->
|
||||
ff_proto_destination_thrift:'Event'().
|
||||
|
||||
marshal_event({EventID, {ev, Timestamp, Change}}) ->
|
||||
|
@ -23,7 +23,7 @@ handle_function(Func, Args, Opts) ->
|
||||
%%
|
||||
handle_function_('Create', [Params, Ctx], Opts) ->
|
||||
ID = Params#dst_DestinationParams.id,
|
||||
case ff_destination_machine:create(
|
||||
case ff_destination:create(
|
||||
ff_destination_codec:unmarshal_destination_params(Params),
|
||||
ff_destination_codec:unmarshal(ctx, Ctx))
|
||||
of
|
||||
@ -41,10 +41,10 @@ handle_function_('Create', [Params, Ctx], Opts) ->
|
||||
woody_error:raise(system, {internal, result_unexpected, woody_error:format_details(Error)})
|
||||
end;
|
||||
handle_function_('Get', [ID, EventRange], _Opts) ->
|
||||
case ff_destination_machine:get(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
case ff_destination:get_machine(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
{ok, Machine} ->
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
Context = ff_destination_machine:ctx(Machine),
|
||||
Destination = ff_destination:get(Machine),
|
||||
Context = ff_destination:ctx(Machine),
|
||||
Response = ff_destination_codec:marshal_destination_state(Destination, Context),
|
||||
{ok, Response};
|
||||
{error, notfound} ->
|
||||
@ -52,16 +52,16 @@ handle_function_('Get', [ID, EventRange], _Opts) ->
|
||||
end;
|
||||
handle_function_('GetContext', [ID], _Opts) ->
|
||||
ok = scoper:add_meta(#{id => ID}),
|
||||
case ff_destination_machine:get(ID, {undefined, 0}) of
|
||||
case ff_destination:get_machine(ID, {undefined, 0}) of
|
||||
{ok, Machine} ->
|
||||
Context = ff_destination_machine:ctx(Machine),
|
||||
Context = ff_destination:ctx(Machine),
|
||||
{ok, ff_codec:marshal(context, Context)};
|
||||
{error, notfound} ->
|
||||
woody_error:raise(business, #fistful_DestinationNotFound{})
|
||||
end;
|
||||
handle_function_('GetEvents', [ID, EventRange], _Opts) ->
|
||||
ok = scoper:add_meta(#{id => ID}),
|
||||
case ff_destination_machine:events(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
case ff_destination:events(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
{ok, Events} ->
|
||||
{ok, lists:map(fun ff_destination_codec:marshal_event/1, Events)};
|
||||
{error, notfound} ->
|
||||
|
@ -99,7 +99,7 @@ unmarshal_event(undefined = Version, EncodedChange, Context0) ->
|
||||
-spec maybe_migrate(any()) ->
|
||||
event().
|
||||
maybe_migrate({ev, Timestamp, Change}) ->
|
||||
{ev, Timestamp, ff_destination:maybe_migrate(Change, #{timestamp => Timestamp})}.
|
||||
{ev, Timestamp, ff_instrument:maybe_migrate(Change, #{timestamp => Timestamp})}.
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
@ -30,8 +30,6 @@ handle_function_('Create', [IdentityParams, Context], Opts) ->
|
||||
handle_function_('Get', [IdentityID, #'EventRange'{}], Opts);
|
||||
{error, {provider, notfound}} ->
|
||||
woody_error:raise(business, #fistful_ProviderNotFound{});
|
||||
{error, {party, notfound}} ->
|
||||
woody_error:raise(business, #fistful_PartyNotFound{});
|
||||
{error, {identity_class, notfound}} ->
|
||||
woody_error:raise(business, #fistful_IdentityClassNotFound{});
|
||||
{error, {inaccessible, _}} ->
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
-export([marshal_state/2]).
|
||||
|
||||
-export([marshal_event/1]).
|
||||
-export([marshal/2]).
|
||||
-export([unmarshal/2]).
|
||||
|
||||
@ -27,16 +26,6 @@ marshal_state(State, Context) ->
|
||||
context = marshal(ctx, Context)
|
||||
}.
|
||||
|
||||
-spec marshal_event(p2p_transfer_machine:event()) ->
|
||||
ff_proto_p2p_session_thrift:'Event'().
|
||||
|
||||
marshal_event({EventID, {ev, Timestamp, Change}}) ->
|
||||
#p2p_session_Event{
|
||||
event = ff_codec:marshal(event_id, EventID),
|
||||
occured_at = ff_codec:marshal(timestamp, Timestamp),
|
||||
change = marshal(change, Change)
|
||||
}.
|
||||
|
||||
-spec marshal(ff_codec:type_name(), ff_codec:decoded_value()) ->
|
||||
ff_codec:encoded_value().
|
||||
|
||||
|
@ -41,13 +41,4 @@ handle_function_('GetContext', [ID], _Opts) ->
|
||||
{ok, ff_codec:marshal(context, Context)};
|
||||
{error, {unknown_p2p_session, _Ref}} ->
|
||||
woody_error:raise(business, #fistful_P2PSessionNotFound{})
|
||||
end;
|
||||
|
||||
handle_function_('GetEvents', [ID, EventRange], _Opts) ->
|
||||
ok = scoper:add_meta(#{id => ID}),
|
||||
case p2p_session_machine:events(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
{ok, Events} ->
|
||||
{ok, lists:map(fun ff_p2p_session_codec:marshal_event/1, Events)};
|
||||
{error, {unknown_p2p_session, _Ref}} ->
|
||||
woody_error:raise(business, #fistful_P2PSessionNotFound{})
|
||||
end.
|
||||
|
@ -78,8 +78,8 @@ init([]) ->
|
||||
{Backends, MachineHandlers, ModernizerHandlers} = lists:unzip3([
|
||||
contruct_backend_childspec('ff/identity' , ff_identity_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/wallet_v2' , ff_wallet_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/source_v1' , ff_source_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/destination_v2' , ff_destination_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/source_v1' , ff_instrument_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/destination_v2' , ff_instrument_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/deposit_v1' , ff_deposit_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/withdrawal_v2' , ff_withdrawal_machine , PartyClient),
|
||||
contruct_backend_childspec('ff/withdrawal/session_v2' , ff_withdrawal_session_machine , PartyClient),
|
||||
|
@ -25,7 +25,7 @@ handle_function(Func, Args, Opts) ->
|
||||
|
||||
handle_function_('CreateSource', [Params], Opts) ->
|
||||
SourceID = Params#ff_admin_SourceParams.id,
|
||||
case ff_source_machine:create(#{
|
||||
case ff_source:create(#{
|
||||
id => SourceID,
|
||||
identity => Params#ff_admin_SourceParams.identity_id,
|
||||
name => Params#ff_admin_SourceParams.name,
|
||||
@ -43,9 +43,9 @@ handle_function_('CreateSource', [Params], Opts) ->
|
||||
woody_error:raise(system, {internal, result_unexpected, woody_error:format_details(Error)})
|
||||
end;
|
||||
handle_function_('GetSource', [ID], _Opts) ->
|
||||
case ff_source_machine:get(ID) of
|
||||
case ff_source:get_machine(ID) of
|
||||
{ok, Machine} ->
|
||||
Source = ff_source_machine:source(Machine),
|
||||
Source = ff_source:get(Machine),
|
||||
{ok, ff_source_codec:marshal(source, Source)};
|
||||
{error, notfound} ->
|
||||
woody_error:raise(business, #fistful_SourceNotFound{})
|
||||
|
@ -38,19 +38,19 @@ marshal_source_state(SourceState, Context) ->
|
||||
blocked
|
||||
end,
|
||||
#src_SourceState{
|
||||
id = maybe_marshal(id, ff_source:id(SourceState)),
|
||||
id = marshal(id, ff_source:id(SourceState)),
|
||||
name = marshal(string, ff_source:name(SourceState)),
|
||||
resource = marshal(resource, ff_source:resource(SourceState)),
|
||||
external_id = maybe_marshal(id, ff_source:external_id(SourceState)),
|
||||
account = maybe_marshal(account, ff_source:account(SourceState)),
|
||||
status = maybe_marshal(status, ff_source:status(SourceState)),
|
||||
created_at = maybe_marshal(timestamp_ms, ff_source:created_at(SourceState)),
|
||||
external_id = marshal(id, ff_source:external_id(SourceState)),
|
||||
account = marshal(account, ff_source:account(SourceState)),
|
||||
status = marshal(status, ff_source:status(SourceState)),
|
||||
created_at = marshal(timestamp_ms, ff_source:created_at(SourceState)),
|
||||
blocking = Blocking,
|
||||
metadata = maybe_marshal(ctx, ff_source:metadata(SourceState)),
|
||||
context = maybe_marshal(ctx, Context)
|
||||
metadata = marshal(ctx, ff_source:metadata(SourceState)),
|
||||
context = marshal(ctx, Context)
|
||||
}.
|
||||
|
||||
-spec marshal_event(ff_source_machine:event()) ->
|
||||
-spec marshal_event(ff_source:timestamped_event()) ->
|
||||
ff_proto_source_thrift:'Event'().
|
||||
|
||||
marshal_event({EventID, {ev, Timestamp, Change}}) ->
|
||||
|
@ -24,7 +24,7 @@ handle_function(Func, Args, Opts) ->
|
||||
handle_function_('Create', [Params, Ctx], Opts) ->
|
||||
ID = Params#src_SourceParams.id,
|
||||
ok = scoper:add_meta(#{id => ID}),
|
||||
case ff_source_machine:create(
|
||||
case ff_source:create(
|
||||
ff_source_codec:unmarshal_source_params(Params),
|
||||
ff_source_codec:unmarshal(ctx, Ctx))
|
||||
of
|
||||
@ -43,10 +43,10 @@ handle_function_('Create', [Params, Ctx], Opts) ->
|
||||
end;
|
||||
handle_function_('Get', [ID, EventRange], _Opts) ->
|
||||
ok = scoper:add_meta(#{id => ID}),
|
||||
case ff_source_machine:get(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
case ff_source:get_machine(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
{ok, Machine} ->
|
||||
Source = ff_source_machine:source(Machine),
|
||||
Context = ff_source_machine:ctx(Machine),
|
||||
Source = ff_source:get(Machine),
|
||||
Context = ff_source:ctx(Machine),
|
||||
Response = ff_source_codec:marshal_source_state(Source, Context),
|
||||
{ok, Response};
|
||||
{error, notfound} ->
|
||||
@ -54,16 +54,16 @@ handle_function_('Get', [ID, EventRange], _Opts) ->
|
||||
end;
|
||||
handle_function_('GetContext', [ID], _Opts) ->
|
||||
ok = scoper:add_meta(#{id => ID}),
|
||||
case ff_source_machine:get(ID, {undefined, 0}) of
|
||||
case ff_source:get_machine(ID, {undefined, 0}) of
|
||||
{ok, Machine} ->
|
||||
Context = ff_source_machine:ctx(Machine),
|
||||
Context = ff_source:ctx(Machine),
|
||||
{ok, ff_codec:marshal(context, Context)};
|
||||
{error, notfound} ->
|
||||
woody_error:raise(business, #fistful_SourceNotFound{})
|
||||
end;
|
||||
handle_function_('GetEvents', [ID, EventRange], _Opts) ->
|
||||
ok = scoper:add_meta(#{id => ID}),
|
||||
case ff_source_machine:events(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
case ff_source:events(ID, ff_codec:unmarshal(event_range, EventRange)) of
|
||||
{ok, Events} ->
|
||||
{ok, lists:map(fun ff_source_codec:marshal_event/1, Events)};
|
||||
{error, notfound} ->
|
||||
|
@ -97,7 +97,7 @@ unmarshal_event(undefined = Version, EncodedChange, Context0) ->
|
||||
-spec maybe_migrate(any()) ->
|
||||
event().
|
||||
maybe_migrate({ev, Timestamp, Change0}) ->
|
||||
Change = ff_source:maybe_migrate(Change0, #{timestamp => Timestamp}),
|
||||
Change = ff_instrument:maybe_migrate(Change0, #{timestamp => Timestamp}),
|
||||
{ev, Timestamp, Change}.
|
||||
|
||||
-ifdef(TEST).
|
||||
|
@ -109,7 +109,7 @@ maybe_migrate({p_transfer, PEvent}, _MigrateParams) ->
|
||||
maybe_migrate({adjustment, _Payload} = Event, _MigrateParams) ->
|
||||
ff_adjustment_utils:maybe_migrate(Event);
|
||||
maybe_migrate({resource_got, Resource}, _MigrateParams) ->
|
||||
{resource_got, ff_destination:maybe_migrate_resource(Resource)};
|
||||
{resource_got, ff_instrument:maybe_migrate_resource(Resource)};
|
||||
|
||||
% Old events
|
||||
maybe_migrate({limit_check, {wallet, Details}}, MigrateParams) ->
|
||||
|
@ -184,7 +184,7 @@ maybe_migrate({created, Session = #{
|
||||
destination := #{resource := OldResource}
|
||||
}
|
||||
}}, Context) ->
|
||||
NewResource = ff_destination:maybe_migrate_resource(OldResource),
|
||||
NewResource = ff_instrument:maybe_migrate_resource(OldResource),
|
||||
FullResource = try_get_full_resource(NewResource, Context),
|
||||
NewWithdrawal0 = maps:without([destination], Withdrawal),
|
||||
NewWithdrawal1 = NewWithdrawal0#{resource => FullResource},
|
||||
@ -194,7 +194,7 @@ maybe_migrate({created, Session = #{
|
||||
resource := Resource
|
||||
}
|
||||
}}, Context) ->
|
||||
NewResource = ff_destination:maybe_migrate_resource(Resource),
|
||||
NewResource = ff_instrument:maybe_migrate_resource(Resource),
|
||||
maybe_migrate({created, Session#{
|
||||
version => 1,
|
||||
withdrawal => Withdrawal#{
|
||||
|
@ -740,13 +740,12 @@ create_source(IID, _C) ->
|
||||
currency => <<"RUB">>,
|
||||
resource => SrcResource
|
||||
},
|
||||
ok = ff_source_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_source:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
{ok, SrcM} = ff_source:get_machine(ID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -213,7 +213,7 @@ get_create_destination_events_ok(C) ->
|
||||
IID = create_person_identity(Party, C),
|
||||
DestID = create_destination(IID, C),
|
||||
|
||||
{ok, RawEvents} = ff_destination_machine:events(DestID, {undefined, 1000}),
|
||||
{ok, RawEvents} = ff_destination:events(DestID, {undefined, 1000}),
|
||||
{_Events, MaxID} = ct_eventsink:events(LastEvent, 1000, Sink),
|
||||
MaxID = LastEvent + length(RawEvents).
|
||||
|
||||
@ -227,7 +227,7 @@ get_create_source_events_ok(C) ->
|
||||
IID = create_person_identity(Party, C),
|
||||
SrcID = create_source(IID, C),
|
||||
|
||||
{ok, RawEvents} = ff_source_machine:events(SrcID, {undefined, 1000}),
|
||||
{ok, RawEvents} = ff_source:events(SrcID, {undefined, 1000}),
|
||||
{_Events, MaxID} = ct_eventsink:events(LastEvent, 1000, Sink),
|
||||
MaxID = LastEvent + length(RawEvents).
|
||||
|
||||
@ -412,34 +412,32 @@ create_wallet(IdentityID, Name, Currency, _C) ->
|
||||
),
|
||||
ID.
|
||||
|
||||
create_source(IdentityID, Name, Currency, Resource) ->
|
||||
create_instrument(Type, IdentityID, Name, Currency, Resource, C) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_source_machine:create(
|
||||
ok = create_instrument(
|
||||
Type,
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new()
|
||||
ff_entity_context:new(),
|
||||
C
|
||||
),
|
||||
ID.
|
||||
|
||||
create_destination(IdentityID, Name, Currency, Resource) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_destination_machine:create(
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new()
|
||||
),
|
||||
ID.
|
||||
create_instrument(destination, Params, Ctx, _C) ->
|
||||
ff_destination:create(Params, Ctx);
|
||||
create_instrument(source, Params, Ctx, _C) ->
|
||||
ff_source:create(Params, Ctx).
|
||||
|
||||
generate_id() ->
|
||||
genlib:to_binary(genlib_time:ticks()).
|
||||
|
||||
create_source(IID, _C) ->
|
||||
create_source(IID, C) ->
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
SrcID = create_source(IID, <<"XSource">>, <<"RUB">>, SrcResource),
|
||||
SrcID = create_instrument(source, IID, <<"XSource">>, <<"RUB">>, SrcResource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(SrcID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
{ok, SrcM} = ff_source:get_machine(SrcID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
SrcID.
|
||||
@ -455,13 +453,12 @@ process_deposit(SrcID, WalID) ->
|
||||
|
||||
create_destination(IID, C) ->
|
||||
DestResource = {bank_card, #{bank_card => ct_cardstore:bank_card(<<"4150399999000900">>, {12, 2025}, C)}},
|
||||
DestID = create_destination(IID, <<"XDesination">>, <<"RUB">>, DestResource),
|
||||
DestID = create_instrument(destination, IID, <<"XDesination">>, <<"RUB">>, DestResource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
),
|
||||
DestID.
|
||||
|
@ -15,7 +15,6 @@
|
||||
-export([end_per_testcase/2]).
|
||||
|
||||
%% Tests
|
||||
-export([get_p2p_session_events_ok_test/1]).
|
||||
-export([get_p2p_session_context_ok_test/1]).
|
||||
-export([get_p2p_session_ok_test/1]).
|
||||
-export([create_adjustment_ok_test/1]).
|
||||
@ -41,7 +40,6 @@ all() ->
|
||||
groups() ->
|
||||
[
|
||||
{default, [parallel], [
|
||||
get_p2p_session_events_ok_test,
|
||||
get_p2p_session_context_ok_test,
|
||||
get_p2p_session_ok_test,
|
||||
create_adjustment_ok_test,
|
||||
@ -94,13 +92,6 @@ end_per_testcase(_Name, _C) ->
|
||||
|
||||
%% Tests
|
||||
|
||||
-spec get_p2p_session_events_ok_test(config()) -> test_return().
|
||||
get_p2p_session_events_ok_test(C) ->
|
||||
#{
|
||||
session_id := ID
|
||||
} = prepare_standard_environment(C),
|
||||
{ok, [#p2p_session_Event{change = {created, _}} | _Rest]} = call_p2p_session('GetEvents', [ID, #'EventRange'{}]).
|
||||
|
||||
-spec get_p2p_session_context_ok_test(config()) -> test_return().
|
||||
get_p2p_session_context_ok_test(C) ->
|
||||
#{
|
||||
|
@ -619,13 +619,12 @@ create_destination(IID, Currency, Token, C) ->
|
||||
end,
|
||||
Resource = {bank_card, #{bank_card => NewStoreResource}},
|
||||
Params = #{id => ID, identity => IID, name => <<"XDesination">>, currency => Currency, resource => Resource},
|
||||
ok = ff_destination_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_destination:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, Machine} = ff_destination_machine:get(ID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
ff_destination:status(Destination)
|
||||
{ok, Machine} = ff_destination:get_machine(ID),
|
||||
ff_destination:status(ff_destination:get(Machine))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -139,25 +139,31 @@ create_identity(Party, Name, ProviderID, ClassID, _C) ->
|
||||
|
||||
create_destination(IID, C) ->
|
||||
DestResource = {bank_card, #{bank_card => ct_cardstore:bank_card(<<"4150399999000900">>, {12, 2025}, C)}},
|
||||
DestID = create_destination(IID, <<"XDesination">>, <<"RUB">>, DestResource),
|
||||
DestID = create_instrument(destination, IID, <<"XDesination">>, <<"RUB">>, DestResource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
),
|
||||
DestID.
|
||||
|
||||
create_destination(IdentityID, Name, Currency, Resource) ->
|
||||
create_instrument(Type, IdentityID, Name, Currency, Resource, C) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_destination_machine:create(
|
||||
ok = create_instrument(
|
||||
Type,
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new()
|
||||
ff_entity_context:new(),
|
||||
C
|
||||
),
|
||||
ID.
|
||||
|
||||
create_instrument(destination, Params, Ctx, _C) ->
|
||||
ff_destination:create(Params, Ctx);
|
||||
create_instrument(source, Params, Ctx, _C) ->
|
||||
ff_source:create(Params, Ctx).
|
||||
|
||||
create_failed_session(IdentityID, DestinationID, _C) ->
|
||||
ID = genlib:unique(),
|
||||
{ok, IdentityMachine} = ff_identity_machine:get(IdentityID),
|
||||
@ -167,8 +173,8 @@ create_failed_session(IdentityID, DestinationID, _C) ->
|
||||
sender => ff_identity_machine:identity(IdentityMachine),
|
||||
receiver => ff_identity_machine:identity(IdentityMachine)
|
||||
},
|
||||
{ok, DestinationMachine} = ff_destination_machine:get(DestinationID),
|
||||
Destination = ff_destination_machine:destination(DestinationMachine),
|
||||
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
|
||||
Destination = ff_destination:get(DestinationMachine),
|
||||
{ok, DestinationResource} = ff_destination:resource_full(Destination),
|
||||
SessionParams = #{
|
||||
withdrawal_id => ID,
|
||||
|
@ -4,10 +4,6 @@
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_withdrawals_provider_adapter_thrift.hrl").
|
||||
|
||||
%% Accessors
|
||||
|
||||
-export([id/1]).
|
||||
|
||||
%% API
|
||||
|
||||
-export([process_withdrawal/4]).
|
||||
@ -117,15 +113,6 @@
|
||||
-export_type([quote_data/0]).
|
||||
-export_type([identity/0]).
|
||||
|
||||
%%
|
||||
%% Accessors
|
||||
%%
|
||||
|
||||
-spec id(withdrawal()) ->
|
||||
binary().
|
||||
id(Withdrawal) ->
|
||||
maps:get(id, Withdrawal).
|
||||
|
||||
%%
|
||||
%% API
|
||||
%%
|
||||
|
@ -285,8 +285,7 @@ metadata(T) ->
|
||||
create(Params) ->
|
||||
do(fun() ->
|
||||
#{id := ID, source_id := SourceID, wallet_id := WalletID, body := Body} = Params,
|
||||
Machine = unwrap(source, ff_source_machine:get(SourceID)),
|
||||
Source = ff_source_machine:source(Machine),
|
||||
Source = ff_source:get(unwrap(source, ff_source:get_machine(SourceID))),
|
||||
CreatedAt = ff_time:now(),
|
||||
DomainRevision = ff_domain_config:head(),
|
||||
Wallet = unwrap(wallet, get_wallet(WalletID)),
|
||||
@ -594,9 +593,8 @@ process_transfer_fail(limit_check, Deposit) ->
|
||||
make_final_cash_flow(WalletID, SourceID, Body) ->
|
||||
{ok, WalletMachine} = ff_wallet_machine:get(WalletID),
|
||||
WalletAccount = ff_wallet:account(ff_wallet_machine:wallet(WalletMachine)),
|
||||
{ok, SourceMachine} = ff_source_machine:get(SourceID),
|
||||
Source = ff_source_machine:source(SourceMachine),
|
||||
SourceAccount = ff_source:account(Source),
|
||||
{ok, SourceMachine} = ff_source:get_machine(SourceID),
|
||||
SourceAccount = ff_source:account(ff_source:get(SourceMachine)),
|
||||
Constants = #{
|
||||
operation_amount => Body
|
||||
},
|
||||
|
@ -461,9 +461,8 @@ process_transfer_fail(limit_check, Revert) ->
|
||||
make_final_cash_flow(WalletID, SourceID, Body) ->
|
||||
{ok, WalletMachine} = ff_wallet_machine:get(WalletID),
|
||||
WalletAccount = ff_wallet:account(ff_wallet_machine:wallet(WalletMachine)),
|
||||
{ok, SourceMachine} = ff_source_machine:get(SourceID),
|
||||
Source = ff_source_machine:source(SourceMachine),
|
||||
SourceAccount = ff_source:account(Source),
|
||||
{ok, SourceMachine} = ff_source:get_machine(SourceID),
|
||||
SourceAccount = ff_source:account(ff_source:get(SourceMachine)),
|
||||
Constants = #{
|
||||
operation_amount => Body
|
||||
},
|
||||
|
@ -5,17 +5,17 @@
|
||||
%%%
|
||||
%%% - We must consider withdrawal provider terms ensure that the provided
|
||||
%%% Resource is ok to withdraw to.
|
||||
%%%
|
||||
|
||||
-module(ff_destination).
|
||||
|
||||
-type id() :: binary().
|
||||
-type name() :: binary().
|
||||
-type ctx() :: ff_entity_context:context().
|
||||
-type id() :: ff_instrument:id().
|
||||
-type name() :: ff_instrument:name().
|
||||
-type account() :: ff_account:account().
|
||||
-type identity() :: ff_identity:id().
|
||||
-type currency() :: ff_currency:id().
|
||||
-type status() :: unauthorized | authorized.
|
||||
-type metadata() :: ff_entity_context:md().
|
||||
-type timestamp() :: ff_time:timestamp_ms().
|
||||
-type status() :: ff_instrument:status().
|
||||
|
||||
-type resource_type() :: bank_card | crypto_wallet.
|
||||
-type resource() ::
|
||||
@ -89,50 +89,18 @@
|
||||
| {ripple, #{tag => binary()}}
|
||||
.
|
||||
|
||||
-define(ACTUAL_FORMAT_VERSION, 4).
|
||||
-type destination() :: ff_instrument:instrument(resource()).
|
||||
-type destination_state() :: ff_instrument:instrument_state(resource()).
|
||||
-type params() :: ff_instrument_machine:params(resource()).
|
||||
-type machine() :: ff_instrument_machine:st(resource()).
|
||||
-type event_range() :: ff_instrument_machine:event_range().
|
||||
-type event() :: ff_instrument:event(resource()).
|
||||
|
||||
-type destination() :: #{
|
||||
version := ?ACTUAL_FORMAT_VERSION,
|
||||
resource := resource(),
|
||||
name := name(),
|
||||
created_at => timestamp(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type destination_state() :: #{
|
||||
account := account() | undefined,
|
||||
resource := resource(),
|
||||
name := name(),
|
||||
status => status(),
|
||||
created_at => timestamp(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type params() :: #{
|
||||
id := id(),
|
||||
identity := ff_identity:id(),
|
||||
name := name(),
|
||||
currency := ff_currency:id(),
|
||||
resource := resource(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type event() ::
|
||||
{created, destination_state()} |
|
||||
{account, ff_account:event()} |
|
||||
{status_changed, status()}.
|
||||
-type legacy_event() :: any().
|
||||
|
||||
-type create_error() ::
|
||||
{identity, notfound} |
|
||||
{currency, notfound} |
|
||||
ff_account:create_error() |
|
||||
{identity, ff_party:inaccessibility()}.
|
||||
-type events() :: ff_instrument_machine:events(resource()).
|
||||
-type timestamped_event() :: ff_instrument_machine:timestamped_event(resource()).
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([machine/0]).
|
||||
-export_type([destination/0]).
|
||||
-export_type([destination_state/0]).
|
||||
-export_type([status/0]).
|
||||
@ -140,23 +108,23 @@
|
||||
-export_type([resource_id/0]).
|
||||
-export_type([resource_type/0]).
|
||||
-export_type([resource_full/0]).
|
||||
-export_type([params/0]).
|
||||
-export_type([event/0]).
|
||||
-export_type([create_error/0]).
|
||||
-export_type([timestamped_event/0]).
|
||||
-export_type([params/0]).
|
||||
-export_type([exp_date/0]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-export([account/1]).
|
||||
-export([id/1]).
|
||||
-export([name/1]).
|
||||
-export([account/1]).
|
||||
-export([identity/1]).
|
||||
-export([currency/1]).
|
||||
-export([resource/1]).
|
||||
-export([status/1]).
|
||||
-export([external_id/1]).
|
||||
-export([created_at/1]).
|
||||
-export([metadata/1]).
|
||||
-export([external_id/1]).
|
||||
-export([resource_full/1]).
|
||||
-export([resource_full/2]).
|
||||
-export([process_resource_full/2]).
|
||||
@ -164,78 +132,51 @@
|
||||
|
||||
%% API
|
||||
|
||||
-export([create/1]).
|
||||
-export([create/2]).
|
||||
-export([get_machine/1]).
|
||||
-export([get_machine/2]).
|
||||
-export([get/1]).
|
||||
-export([ctx/1]).
|
||||
-export([is_accessible/1]).
|
||||
-export([authorize/1]).
|
||||
-export([apply_event/2]).
|
||||
-export([maybe_migrate/2]).
|
||||
-export([maybe_migrate_resource/1]).
|
||||
-export([events/2]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1, unwrap/2]).
|
||||
-import(ff_pipeline, [do/1, unwrap/2]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-spec id(destination_state()) ->
|
||||
id() | undefined.
|
||||
-spec name(destination_state()) ->
|
||||
name().
|
||||
-spec account(destination_state()) ->
|
||||
account() | undefined.
|
||||
-spec identity(destination_state()) ->
|
||||
identity().
|
||||
-spec currency(destination_state()) ->
|
||||
currency().
|
||||
-spec resource(destination_state()) ->
|
||||
resource().
|
||||
-spec status(destination_state()) ->
|
||||
status() | undefined.
|
||||
-spec id(destination_state()) -> id().
|
||||
-spec name(destination_state()) -> name().
|
||||
-spec account(destination_state()) -> account().
|
||||
-spec identity(destination_state()) -> identity().
|
||||
-spec currency(destination_state()) -> currency().
|
||||
-spec resource(destination_state()) -> resource().
|
||||
-spec status(destination_state()) -> status().
|
||||
|
||||
id(Destination) ->
|
||||
case account(Destination) of
|
||||
undefined ->
|
||||
undefined;
|
||||
Account ->
|
||||
ff_account:id(Account)
|
||||
end.
|
||||
name(#{name := V}) ->
|
||||
V.
|
||||
account(#{account := V}) ->
|
||||
V;
|
||||
account(_) ->
|
||||
undefined.
|
||||
identity(Destination) ->
|
||||
ff_account:identity(account(Destination)).
|
||||
currency(Destination) ->
|
||||
ff_account:currency(account(Destination)).
|
||||
resource(#{resource := V}) ->
|
||||
V.
|
||||
status(#{status := V}) ->
|
||||
V;
|
||||
status(_) ->
|
||||
undefined.
|
||||
|
||||
id(Destination) -> ff_instrument:id(Destination).
|
||||
name(Destination) -> ff_instrument:name(Destination).
|
||||
identity(Destination) -> ff_instrument:identity(Destination).
|
||||
currency(Destination) -> ff_instrument:currency(Destination).
|
||||
resource(Destination) -> ff_instrument:resource(Destination).
|
||||
status(Destination) -> ff_instrument:status(Destination).
|
||||
account(Destination) -> ff_instrument:account(Destination).
|
||||
|
||||
-spec external_id(destination_state()) ->
|
||||
id() | undefined.
|
||||
external_id(#{external_id := ExternalID}) ->
|
||||
ExternalID;
|
||||
external_id(_Destination) ->
|
||||
undefined.
|
||||
|
||||
external_id(T) -> ff_instrument:external_id(T).
|
||||
|
||||
-spec created_at(destination_state()) ->
|
||||
ff_time:timestamp_ms() | undefiend.
|
||||
created_at(#{created_at := CreatedAt}) ->
|
||||
CreatedAt;
|
||||
created_at(_Destination) ->
|
||||
undefined.
|
||||
ff_time:timestamp_ms().
|
||||
|
||||
created_at(T) -> ff_instrument:created_at(T).
|
||||
|
||||
-spec metadata(destination_state()) ->
|
||||
ff_entity_context:context() | undefined.
|
||||
metadata(#{metadata := Metadata}) ->
|
||||
Metadata;
|
||||
metadata(_Destination) ->
|
||||
undefined.
|
||||
ff_entity_context:context().
|
||||
|
||||
metadata(T) -> ff_instrument:metadata(T).
|
||||
|
||||
-spec resource_full(destination_state()) ->
|
||||
{ok, resource_full()} |
|
||||
@ -289,172 +230,54 @@ unwrap_resource_id({bank_card, ID}) ->
|
||||
|
||||
%% API
|
||||
|
||||
-spec create(params()) ->
|
||||
{ok, [event()]} |
|
||||
{error, create_error()}.
|
||||
create(Params) ->
|
||||
do(fun () ->
|
||||
#{
|
||||
id := ID,
|
||||
identity := IdentityID,
|
||||
name := Name,
|
||||
currency := CurrencyID,
|
||||
resource := Resource
|
||||
} = Params,
|
||||
Identity = ff_identity_machine:identity(unwrap(identity, ff_identity_machine:get(IdentityID))),
|
||||
Currency = unwrap(currency, ff_currency:get(CurrencyID)),
|
||||
Events = unwrap(ff_account:create(ID, Identity, Currency)),
|
||||
accessible = unwrap(identity, ff_identity:is_accessible(Identity)),
|
||||
CreatedAt = ff_time:now(),
|
||||
[{created, genlib_map:compact(#{
|
||||
version => ?ACTUAL_FORMAT_VERSION,
|
||||
name => Name,
|
||||
resource => Resource,
|
||||
external_id => maps:get(external_id, Params, undefined),
|
||||
metadata => maps:get(metadata, Params, undefined),
|
||||
created_at => CreatedAt
|
||||
})}] ++
|
||||
[{account, Ev} || Ev <- Events] ++
|
||||
[{status_changed, unauthorized}]
|
||||
end).
|
||||
-define(NS, 'ff/destination_v2').
|
||||
|
||||
-spec create(params(), ctx()) ->
|
||||
ok |
|
||||
{error,
|
||||
_InstrumentCreateError |
|
||||
exists
|
||||
}.
|
||||
|
||||
create(Params, Ctx) ->
|
||||
ff_instrument_machine:create(?NS, Params, Ctx).
|
||||
|
||||
-spec get_machine(id()) ->
|
||||
{ok, machine()} |
|
||||
{error, notfound} .
|
||||
|
||||
get_machine(ID) ->
|
||||
ff_instrument_machine:get(?NS, ID).
|
||||
|
||||
-spec get_machine(id(), event_range()) ->
|
||||
{ok, machine()} |
|
||||
{error, notfound} .
|
||||
|
||||
get_machine(ID, EventRange) ->
|
||||
ff_instrument_machine:get(?NS, ID, EventRange).
|
||||
|
||||
-spec get(machine()) ->
|
||||
destination_state().
|
||||
|
||||
get(Machine) ->
|
||||
ff_instrument_machine:instrument(Machine).
|
||||
|
||||
-spec ctx(machine()) ->
|
||||
ctx().
|
||||
|
||||
ctx(St) ->
|
||||
ff_machine:ctx(St).
|
||||
|
||||
-spec is_accessible(destination_state()) ->
|
||||
{ok, accessible} |
|
||||
{error, ff_party:inaccessibility()}.
|
||||
|
||||
is_accessible(Destination) ->
|
||||
ff_account:is_accessible(account(Destination)).
|
||||
ff_instrument:is_accessible(Destination).
|
||||
|
||||
-spec authorize(destination_state()) ->
|
||||
{ok, [event()]}.
|
||||
authorize(#{status := unauthorized}) ->
|
||||
% TODO
|
||||
% - Do the actual authorization
|
||||
{ok, [{status_changed, authorized}]};
|
||||
authorize(#{status := authorized}) ->
|
||||
{ok, []}.
|
||||
-spec events(id(), event_range()) ->
|
||||
{ok, events()} |
|
||||
{error, notfound}.
|
||||
|
||||
-spec apply_event(event(), ff_maybe:maybe(destination_state())) ->
|
||||
destination_state().
|
||||
|
||||
apply_event({created, Destination}, undefined) ->
|
||||
Destination;
|
||||
apply_event({status_changed, S}, Destination) ->
|
||||
Destination#{status => S};
|
||||
apply_event({account, Ev}, Destination = #{account := Account}) ->
|
||||
Destination#{account => ff_account:apply_event(Ev, Account)};
|
||||
apply_event({account, Ev}, Destination) ->
|
||||
apply_event({account, Ev}, Destination#{account => undefined}).
|
||||
|
||||
|
||||
-spec maybe_migrate(event() | legacy_event(), ff_machine:migrate_params()) ->
|
||||
event().
|
||||
|
||||
maybe_migrate(Event = {created, #{version := ?ACTUAL_FORMAT_VERSION}}, _MigrateParams) ->
|
||||
Event;
|
||||
maybe_migrate({created, Destination = #{version := 3, name := Name}}, MigrateParams) ->
|
||||
maybe_migrate({created, Destination#{
|
||||
version => 4,
|
||||
name => maybe_migrate_name(Name)
|
||||
}}, MigrateParams);
|
||||
maybe_migrate({created, Destination = #{version := 2}}, MigrateParams) ->
|
||||
Context = maps:get(ctx, MigrateParams, undefined),
|
||||
%% TODO add metada migration for eventsink after decouple instruments
|
||||
Metadata = ff_entity_context:try_get_legacy_metadata(Context),
|
||||
maybe_migrate({created, genlib_map:compact(Destination#{
|
||||
version => 3,
|
||||
metadata => Metadata
|
||||
})}, MigrateParams);
|
||||
maybe_migrate({created, Destination = #{version := 1}}, MigrateParams) ->
|
||||
Timestamp = maps:get(timestamp, MigrateParams),
|
||||
CreatedAt = ff_codec:unmarshal(timestamp_ms, ff_codec:marshal(timestamp, Timestamp)),
|
||||
maybe_migrate({created, Destination#{
|
||||
version => 2,
|
||||
created_at => CreatedAt
|
||||
}}, MigrateParams);
|
||||
maybe_migrate({created, Destination = #{
|
||||
resource := Resource,
|
||||
name := Name
|
||||
}}, MigrateParams) ->
|
||||
NewDestination = genlib_map:compact(#{
|
||||
version => 1,
|
||||
resource => maybe_migrate_resource(Resource),
|
||||
name => Name,
|
||||
external_id => maps:get(external_id, Destination, undefined)
|
||||
}),
|
||||
maybe_migrate({created, NewDestination}, MigrateParams);
|
||||
|
||||
%% Other events
|
||||
maybe_migrate(Event, _MigrateParams) ->
|
||||
Event.
|
||||
|
||||
-spec maybe_migrate_resource(any()) ->
|
||||
any().
|
||||
|
||||
maybe_migrate_resource({crypto_wallet, #{id := ID, currency := ripple, tag := Tag}}) ->
|
||||
maybe_migrate_resource({crypto_wallet, #{id => ID, currency => {ripple, #{tag => Tag}}}});
|
||||
maybe_migrate_resource({crypto_wallet, #{id := ID, currency := Currency}}) when is_atom(Currency) ->
|
||||
maybe_migrate_resource({crypto_wallet, #{id => ID, currency => {Currency, #{}}}});
|
||||
|
||||
maybe_migrate_resource({crypto_wallet, #{id := _ID} = CryptoWallet}) ->
|
||||
maybe_migrate_resource({crypto_wallet, #{crypto_wallet => CryptoWallet}});
|
||||
maybe_migrate_resource({bank_card, #{token := _Token} = BankCard}) ->
|
||||
maybe_migrate_resource({bank_card, #{bank_card => BankCard}});
|
||||
|
||||
maybe_migrate_resource(Resource) ->
|
||||
Resource.
|
||||
|
||||
maybe_migrate_name(Name) ->
|
||||
re:replace(Name, "\\d{12,19}", <<"">>, [global, {return, binary}]).
|
||||
|
||||
%% Tests
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-spec test() -> _.
|
||||
|
||||
-spec v1_created_migration_test() -> _.
|
||||
v1_created_migration_test() ->
|
||||
CreatedAt = ff_time:now(),
|
||||
LegacyEvent = {created, #{
|
||||
version => 1,
|
||||
resource => {crypto_wallet, #{crypto_wallet => #{}}},
|
||||
name => <<"some name">>,
|
||||
external_id => genlib:unique()
|
||||
}},
|
||||
{created, #{version := Version}} = maybe_migrate(LegacyEvent, #{
|
||||
timestamp => ff_codec:unmarshal(timestamp, ff_codec:marshal(timestamp_ms, CreatedAt))
|
||||
}),
|
||||
?assertEqual(4, Version).
|
||||
|
||||
-spec v2_created_migration_test() -> _.
|
||||
v2_created_migration_test() ->
|
||||
CreatedAt = ff_time:now(),
|
||||
LegacyEvent = {created, #{
|
||||
version => 2,
|
||||
resource => {crypto_wallet, #{crypto_wallet => #{}}},
|
||||
name => <<"some name">>,
|
||||
external_id => genlib:unique(),
|
||||
created_at => CreatedAt
|
||||
}},
|
||||
{created, #{version := Version, metadata := Metadata}} = maybe_migrate(LegacyEvent, #{
|
||||
ctx => #{
|
||||
<<"com.rbkmoney.wapi">> => #{
|
||||
<<"metadata">> => #{
|
||||
<<"some key">> => <<"some val">>
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
?assertEqual(4, Version),
|
||||
?assertEqual(#{<<"some key">> => <<"some val">>}, Metadata).
|
||||
|
||||
-spec name_migration_test() -> _.
|
||||
name_migration_test() ->
|
||||
?assertEqual(<<"sd">>, maybe_migrate_name(<<"sd123123123123123">>)),
|
||||
?assertEqual(<<"sd1231231231sd23123">>, maybe_migrate_name(<<"sd1231231231sd23123">>)),
|
||||
?assertEqual(<<"sdds123sd">>, maybe_migrate_name(<<"sd123123123123ds123sd">>)),
|
||||
?assertEqual(<<"sdsd">>, maybe_migrate_name(<<"sd123123123123123sd">>)),
|
||||
?assertEqual(<<"sd">>, maybe_migrate_name(<<"123123123123123sd">>)).
|
||||
|
||||
-endif.
|
||||
events(ID, Range) ->
|
||||
ff_instrument_machine:events(?NS, ID, Range).
|
||||
|
@ -1,164 +0,0 @@
|
||||
%%%
|
||||
%%% Destination machine
|
||||
%%%
|
||||
|
||||
-module(ff_destination_machine).
|
||||
|
||||
%% API
|
||||
|
||||
-type id() :: machinery:id().
|
||||
-type ctx() :: ff_entity_context:context().
|
||||
-type destination() :: ff_destination:destination_state().
|
||||
-type change() :: ff_destination:event().
|
||||
-type event() :: {integer(), ff_machine:timestamped_event(change())}.
|
||||
-type events() :: [event()].
|
||||
-type event_range() :: {After :: non_neg_integer() | undefined, Limit :: non_neg_integer() | undefined}.
|
||||
|
||||
-type params() :: ff_destination:params().
|
||||
-type st() :: ff_machine:st(destination()).
|
||||
|
||||
-type repair_error() :: ff_repair:repair_error().
|
||||
-type repair_response() :: ff_repair:repair_response().
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([st/0]).
|
||||
-export_type([event/0]).
|
||||
-export_type([repair_error/0]).
|
||||
-export_type([repair_response/0]).
|
||||
-export_type([params/0]).
|
||||
-export_type([event_range/0]).
|
||||
|
||||
%% API
|
||||
|
||||
-export([create/2]).
|
||||
-export([get/1]).
|
||||
-export([get/2]).
|
||||
-export([events/2]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-export([destination/1]).
|
||||
-export([ctx/1]).
|
||||
|
||||
%% Machinery
|
||||
|
||||
-behaviour(machinery).
|
||||
|
||||
-export([init/4]).
|
||||
-export([process_timeout/3]).
|
||||
-export([process_repair/4]).
|
||||
-export([process_call/4]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
%%
|
||||
-define(NS, 'ff/destination_v2').
|
||||
|
||||
-spec create(params(), ctx()) ->
|
||||
ok |
|
||||
{error, ff_destination:create_error() | exists}.
|
||||
|
||||
create(#{id := ID} = Params, Ctx) ->
|
||||
do(fun () ->
|
||||
Events = unwrap(ff_destination:create(Params)),
|
||||
unwrap(machinery:start(?NS, ID, {Events, Ctx}, backend()))
|
||||
end).
|
||||
|
||||
-spec get(id()) ->
|
||||
{ok, st()} |
|
||||
{error, notfound}.
|
||||
get(ID) ->
|
||||
ff_machine:get(ff_destination, ?NS, ID).
|
||||
|
||||
-spec get(id(), event_range()) ->
|
||||
{ok, st()} |
|
||||
{error, notfound} .
|
||||
get(ID, {After, Limit}) ->
|
||||
ff_machine:get(ff_destination, ?NS, ID, {After, Limit, forward}).
|
||||
|
||||
-spec events(id(), event_range()) ->
|
||||
{ok, events()} |
|
||||
{error, notfound}.
|
||||
|
||||
events(ID, {After, Limit}) ->
|
||||
do(fun () ->
|
||||
History = unwrap(ff_machine:history(ff_destination, ?NS, ID, {After, Limit, forward})),
|
||||
[{EventID, TsEv} || {EventID, _, TsEv} <- History]
|
||||
end).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-spec destination(st()) ->
|
||||
destination().
|
||||
|
||||
destination(St) ->
|
||||
ff_machine:model(St).
|
||||
|
||||
-spec ctx(st()) ->
|
||||
ctx().
|
||||
|
||||
ctx(St) ->
|
||||
ff_machine:ctx(St).
|
||||
|
||||
%% Machinery
|
||||
|
||||
-type machine() :: ff_machine:machine(change()).
|
||||
-type result() :: ff_machine:result(change()).
|
||||
-type handler_opts() :: machinery:handler_opts(_).
|
||||
-type handler_args() :: machinery:handler_args(_).
|
||||
|
||||
-spec init({[change()], ctx()}, machine(), _, handler_opts()) ->
|
||||
result().
|
||||
|
||||
init({Events, Ctx}, #{}, _, _Opts) ->
|
||||
#{
|
||||
events => ff_machine:emit_events(Events),
|
||||
action => continue,
|
||||
aux_state => #{ctx => Ctx}
|
||||
}.
|
||||
|
||||
%%
|
||||
|
||||
-spec process_timeout(machine(), handler_args(), handler_opts()) ->
|
||||
result().
|
||||
|
||||
process_timeout(Machine, _, _Opts) ->
|
||||
St = ff_machine:collapse(ff_destination, Machine),
|
||||
process_timeout(deduce_activity(ff_machine:model(St)), St).
|
||||
|
||||
process_timeout(authorize, St) ->
|
||||
D0 = destination(St),
|
||||
case ff_destination:authorize(D0) of
|
||||
{ok, Events} ->
|
||||
#{
|
||||
events => ff_machine:emit_events(Events)
|
||||
}
|
||||
end.
|
||||
|
||||
deduce_activity(#{status := unauthorized}) ->
|
||||
authorize;
|
||||
deduce_activity(#{}) ->
|
||||
undefined.
|
||||
|
||||
%%
|
||||
|
||||
-spec process_call(_CallArgs, machine(), handler_args(), handler_opts()) ->
|
||||
{ok, result()}.
|
||||
|
||||
process_call(_CallArgs, #{}, _, _Opts) ->
|
||||
{ok, #{}}.
|
||||
|
||||
-spec process_repair(ff_repair:scenario(), machine(), handler_args(), handler_opts()) ->
|
||||
{ok, {repair_response(), result()}} | {error, repair_error()}.
|
||||
|
||||
process_repair(Scenario, Machine, _Args, _Opts) ->
|
||||
ff_repair:apply_scenario(ff_destination, Machine, Scenario).
|
||||
|
||||
|
||||
|
||||
%% Internals
|
||||
|
||||
backend() ->
|
||||
fistful:backend(?NS).
|
327
apps/ff_transfer/src/ff_instrument.erl
Normal file
327
apps/ff_transfer/src/ff_instrument.erl
Normal file
@ -0,0 +1,327 @@
|
||||
%%%
|
||||
%%% Instrument
|
||||
%%%
|
||||
%%% TODOs
|
||||
%%%
|
||||
%%% - We must consider withdrawal provider terms ensure that the provided
|
||||
%%% resource is ok to withdraw to.
|
||||
%%%
|
||||
|
||||
-module(ff_instrument).
|
||||
|
||||
-type id() :: binary().
|
||||
-type external_id() :: id() | undefined.
|
||||
-type name() :: binary().
|
||||
-type metadata() :: ff_entity_context:md().
|
||||
-type resource(T) :: T.
|
||||
-type account() :: ff_account:account().
|
||||
-type identity() :: ff_identity:id().
|
||||
-type currency() :: ff_currency:id().
|
||||
-type timestamp() :: ff_time:timestamp_ms().
|
||||
-type status() :: unauthorized | authorized.
|
||||
|
||||
-define(ACTUAL_FORMAT_VERSION, 4).
|
||||
-type instrument_state(T) :: #{
|
||||
account := account() | undefined,
|
||||
resource := resource(T),
|
||||
name := name(),
|
||||
status := status() | undefined,
|
||||
created_at => timestamp(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type instrument(T) :: #{
|
||||
version := ?ACTUAL_FORMAT_VERSION,
|
||||
resource := resource(T),
|
||||
name := name(),
|
||||
created_at => timestamp(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type event(T) ::
|
||||
{created, instrument_state(T)} |
|
||||
{account, ff_account:event()} |
|
||||
{status_changed, status()}.
|
||||
|
||||
-type legacy_event() :: any().
|
||||
|
||||
-type create_error() ::
|
||||
{identity, notfound} |
|
||||
{currecy, notfoud} |
|
||||
ff_account:create_error() |
|
||||
{identity, ff_party:inaccessibility()}.
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([instrument/1]).
|
||||
-export_type([instrument_state/1]).
|
||||
-export_type([status/0]).
|
||||
-export_type([resource/1]).
|
||||
-export_type([event/1]).
|
||||
-export_type([name/0]).
|
||||
-export_type([metadata/0]).
|
||||
|
||||
-export([account/1]).
|
||||
|
||||
-export([id/1]).
|
||||
-export([name/1]).
|
||||
-export([identity/1]).
|
||||
-export([currency/1]).
|
||||
-export([resource/1]).
|
||||
-export([status/1]).
|
||||
-export([external_id/1]).
|
||||
-export([created_at/1]).
|
||||
-export([metadata/1]).
|
||||
|
||||
-export([create/1]).
|
||||
-export([authorize/1]).
|
||||
|
||||
-export([is_accessible/1]).
|
||||
|
||||
-export([apply_event/2]).
|
||||
-export([maybe_migrate/2]).
|
||||
-export([maybe_migrate_resource/1]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1, unwrap/2]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-spec account(instrument_state(_)) ->
|
||||
account() | undefined.
|
||||
|
||||
account(#{account := V}) ->
|
||||
V;
|
||||
account(_) ->
|
||||
undefined.
|
||||
|
||||
-spec id(instrument_state(_)) ->
|
||||
id().
|
||||
-spec name(instrument_state(_)) ->
|
||||
binary().
|
||||
-spec identity(instrument_state(_)) ->
|
||||
identity().
|
||||
-spec currency(instrument_state(_)) ->
|
||||
currency().
|
||||
-spec resource(instrument_state(T)) ->
|
||||
resource(T).
|
||||
-spec status(instrument_state(_)) ->
|
||||
status() | undefined.
|
||||
|
||||
id(Instrument) ->
|
||||
case account(Instrument) of
|
||||
undefined ->
|
||||
undefined;
|
||||
Account ->
|
||||
ff_account:id(Account)
|
||||
end.
|
||||
name(#{name := V}) ->
|
||||
V.
|
||||
identity(Instrument) ->
|
||||
ff_account:identity(account(Instrument)).
|
||||
currency(Instrument) ->
|
||||
ff_account:currency(account(Instrument)).
|
||||
resource(#{resource := V}) ->
|
||||
V.
|
||||
status(#{status := V}) ->
|
||||
V;
|
||||
status(_) ->
|
||||
undefined.
|
||||
|
||||
-spec external_id(instrument_state(_)) ->
|
||||
external_id().
|
||||
|
||||
external_id(#{external_id := ExternalID}) ->
|
||||
ExternalID;
|
||||
external_id(_Instrument) ->
|
||||
undefined.
|
||||
|
||||
-spec created_at(instrument_state(_)) ->
|
||||
timestamp().
|
||||
|
||||
created_at(#{created_at := CreatedAt}) ->
|
||||
CreatedAt.
|
||||
|
||||
-spec metadata(instrument_state(_)) ->
|
||||
metadata().
|
||||
|
||||
metadata(#{metadata := Metadata}) ->
|
||||
Metadata;
|
||||
metadata(_Instrument) ->
|
||||
undefined.
|
||||
|
||||
%%
|
||||
|
||||
-spec create(ff_instrument_machine:params(T)) ->
|
||||
{ok, [event(T)]} |
|
||||
{error, create_error()}.
|
||||
|
||||
create(Params = #{
|
||||
id := ID,
|
||||
identity := IdentityID,
|
||||
name := Name,
|
||||
currency := CurrencyID,
|
||||
resource := Resource
|
||||
}) ->
|
||||
do(fun () ->
|
||||
Identity = ff_identity_machine:identity(unwrap(identity, ff_identity_machine:get(IdentityID))),
|
||||
Currency = unwrap(currency, ff_currency:get(CurrencyID)),
|
||||
Events = unwrap(ff_account:create(ID, Identity, Currency)),
|
||||
accessible = unwrap(identity, ff_identity:is_accessible(Identity)),
|
||||
CreatedAt = ff_time:now(),
|
||||
[{created, genlib_map:compact(#{
|
||||
version => ?ACTUAL_FORMAT_VERSION,
|
||||
name => Name,
|
||||
resource => Resource,
|
||||
external_id => maps:get(external_id, Params, undefined),
|
||||
metadata => maps:get(metadata, Params, undefined),
|
||||
created_at => CreatedAt
|
||||
})}] ++
|
||||
[{account, Ev} || Ev <- Events] ++
|
||||
[{status_changed, unauthorized}]
|
||||
end).
|
||||
|
||||
-spec authorize(instrument_state(T)) ->
|
||||
{ok, [event(T)]}.
|
||||
|
||||
authorize(#{status := unauthorized}) ->
|
||||
% TODO
|
||||
% - Do the actual authorization
|
||||
{ok, [{status_changed, authorized}]};
|
||||
authorize(#{status := authorized}) ->
|
||||
{ok, []}.
|
||||
|
||||
-spec is_accessible(instrument_state(_)) ->
|
||||
{ok, accessible} |
|
||||
{error, ff_party:inaccessibility()}.
|
||||
|
||||
is_accessible(Instrument) ->
|
||||
ff_account:is_accessible(account(Instrument)).
|
||||
|
||||
%%
|
||||
|
||||
-spec apply_event(event(T), ff_maybe:maybe(instrument_state(T))) ->
|
||||
instrument_state(T).
|
||||
|
||||
apply_event({created, Instrument}, undefined) ->
|
||||
Instrument;
|
||||
apply_event({status_changed, S}, Instrument) ->
|
||||
Instrument#{status => S};
|
||||
apply_event({account, Ev}, Instrument = #{account := Account}) ->
|
||||
Instrument#{account => ff_account:apply_event(Ev, Account)};
|
||||
apply_event({account, Ev}, Instrument) ->
|
||||
apply_event({account, Ev}, Instrument#{account => undefined}).
|
||||
|
||||
-spec maybe_migrate(event(T) | legacy_event(), ff_machine:migrate_params()) ->
|
||||
event(T).
|
||||
|
||||
maybe_migrate(Event = {created, #{version := ?ACTUAL_FORMAT_VERSION}}, _MigrateParams) ->
|
||||
Event;
|
||||
maybe_migrate({created, Instrument = #{version := 3, name := Name}}, MigrateParams) ->
|
||||
maybe_migrate({created, Instrument#{
|
||||
version => 4,
|
||||
name => maybe_migrate_name(Name)
|
||||
}}, MigrateParams);
|
||||
maybe_migrate({created, Instrument = #{version := 2}}, MigrateParams) ->
|
||||
Context = maps:get(ctx, MigrateParams, undefined),
|
||||
%% TODO add metada migration for eventsink after decouple instruments
|
||||
Metadata = ff_entity_context:try_get_legacy_metadata(Context),
|
||||
maybe_migrate({created, genlib_map:compact(Instrument#{
|
||||
version => 3,
|
||||
metadata => Metadata
|
||||
})}, MigrateParams);
|
||||
maybe_migrate({created, Instrument = #{version := 1}}, MigrateParams) ->
|
||||
Timestamp = maps:get(timestamp, MigrateParams),
|
||||
CreatedAt = ff_codec:unmarshal(timestamp_ms, ff_codec:marshal(timestamp, Timestamp)),
|
||||
maybe_migrate({created, Instrument#{
|
||||
version => 2,
|
||||
created_at => CreatedAt
|
||||
}}, MigrateParams);
|
||||
maybe_migrate({created, Instrument = #{
|
||||
resource := Resource,
|
||||
name := Name
|
||||
}}, MigrateParams) ->
|
||||
NewInstrument = genlib_map:compact(#{
|
||||
version => 1,
|
||||
resource => maybe_migrate_resource(Resource),
|
||||
name => Name,
|
||||
external_id => maps:get(external_id, Instrument, undefined)
|
||||
}),
|
||||
maybe_migrate({created, NewInstrument}, MigrateParams);
|
||||
|
||||
%% Other events
|
||||
maybe_migrate(Event, _MigrateParams) ->
|
||||
Event.
|
||||
|
||||
-spec maybe_migrate_resource(any()) ->
|
||||
any().
|
||||
|
||||
maybe_migrate_resource({crypto_wallet, #{id := ID, currency := ripple, tag := Tag}}) ->
|
||||
maybe_migrate_resource({crypto_wallet, #{id => ID, currency => {ripple, #{tag => Tag}}}});
|
||||
maybe_migrate_resource({crypto_wallet, #{id := ID, currency := Currency}}) when is_atom(Currency) ->
|
||||
maybe_migrate_resource({crypto_wallet, #{id => ID, currency => {Currency, #{}}}});
|
||||
|
||||
maybe_migrate_resource({crypto_wallet, #{id := _ID} = CryptoWallet}) ->
|
||||
maybe_migrate_resource({crypto_wallet, #{crypto_wallet => CryptoWallet}});
|
||||
maybe_migrate_resource({bank_card, #{token := _Token} = BankCard}) ->
|
||||
maybe_migrate_resource({bank_card, #{bank_card => BankCard}});
|
||||
|
||||
maybe_migrate_resource(Resource) ->
|
||||
Resource.
|
||||
|
||||
maybe_migrate_name(Name) ->
|
||||
re:replace(Name, "\\d{12,19}", <<"">>, [global, {return, binary}]).
|
||||
|
||||
%% Tests
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-spec test() -> _.
|
||||
|
||||
-spec v1_created_migration_test() -> _.
|
||||
v1_created_migration_test() ->
|
||||
CreatedAt = ff_time:now(),
|
||||
LegacyEvent = {created, #{
|
||||
version => 1,
|
||||
resource => {crypto_wallet, #{crypto_wallet => #{}}},
|
||||
name => <<"some name">>,
|
||||
external_id => genlib:unique()
|
||||
}},
|
||||
{created, #{version := Version}} = maybe_migrate(LegacyEvent, #{
|
||||
timestamp => ff_codec:unmarshal(timestamp, ff_codec:marshal(timestamp_ms, CreatedAt))
|
||||
}),
|
||||
?assertEqual(4, Version).
|
||||
|
||||
-spec v2_created_migration_test() -> _.
|
||||
v2_created_migration_test() ->
|
||||
CreatedAt = ff_time:now(),
|
||||
LegacyEvent = {created, #{
|
||||
version => 2,
|
||||
resource => {crypto_wallet, #{crypto_wallet => #{}}},
|
||||
name => <<"some name">>,
|
||||
external_id => genlib:unique(),
|
||||
created_at => CreatedAt
|
||||
}},
|
||||
{created, #{version := Version, metadata := Metadata}} = maybe_migrate(LegacyEvent, #{
|
||||
ctx => #{
|
||||
<<"com.rbkmoney.wapi">> => #{
|
||||
<<"metadata">> => #{
|
||||
<<"some key">> => <<"some val">>
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
?assertEqual(4, Version),
|
||||
?assertEqual(#{<<"some key">> => <<"some val">>}, Metadata).
|
||||
|
||||
-spec name_migration_test() -> _.
|
||||
name_migration_test() ->
|
||||
?assertEqual(<<"sd">>, maybe_migrate_name(<<"sd123123123123123">>)),
|
||||
?assertEqual(<<"sd1231231231sd23123">>, maybe_migrate_name(<<"sd1231231231sd23123">>)),
|
||||
?assertEqual(<<"sdds123sd">>, maybe_migrate_name(<<"sd123123123123ds123sd">>)),
|
||||
?assertEqual(<<"sdsd">>, maybe_migrate_name(<<"sd123123123123123sd">>)),
|
||||
?assertEqual(<<"sd">>, maybe_migrate_name(<<"123123123123123sd">>)).
|
||||
|
||||
-endif.
|
166
apps/ff_transfer/src/ff_instrument_machine.erl
Normal file
166
apps/ff_transfer/src/ff_instrument_machine.erl
Normal file
@ -0,0 +1,166 @@
|
||||
%%%
|
||||
%%% Instrument machine
|
||||
%%%
|
||||
|
||||
-module(ff_instrument_machine).
|
||||
|
||||
%% API
|
||||
|
||||
-type id() :: machinery:id().
|
||||
-type ns() :: machinery:namespace().
|
||||
-type ctx() :: ff_entity_context:context().
|
||||
-type instrument(T) :: ff_instrument:instrument_state(T).
|
||||
-type metadata() :: ff_instrument:metadata().
|
||||
-type event_range() :: {After :: non_neg_integer() | undefined, Limit :: non_neg_integer() | undefined}.
|
||||
|
||||
-type st(T) ::
|
||||
ff_machine:st(instrument(T)).
|
||||
|
||||
-type repair_error() :: ff_repair:repair_error().
|
||||
-type repair_response() :: ff_repair:repair_response().
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([st/1]).
|
||||
-export_type([repair_error/0]).
|
||||
-export_type([repair_response/0]).
|
||||
-export_type([events/1]).
|
||||
-export_type([timestamped_event/1]).
|
||||
-export_type([params/1]).
|
||||
-export_type([event_range/0]).
|
||||
|
||||
-export([create/3]).
|
||||
-export([get/2]).
|
||||
-export([get/3]).
|
||||
-export([events/3]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-export([instrument/1]).
|
||||
|
||||
%% Machinery
|
||||
|
||||
-behaviour(machinery).
|
||||
|
||||
-export([init/4]).
|
||||
-export([process_timeout/3]).
|
||||
-export([process_repair/4]).
|
||||
-export([process_call/4]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
%%
|
||||
|
||||
-type params(T) :: #{
|
||||
id := id(),
|
||||
identity := ff_identity:id(),
|
||||
name := binary(),
|
||||
currency := ff_currency:id(),
|
||||
resource := ff_instrument:resource(T),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-spec create(ns(), params(_), ctx()) ->
|
||||
ok |
|
||||
{error,
|
||||
_InstrumentCreateError |
|
||||
exists
|
||||
}.
|
||||
|
||||
create(NS, Params = #{id := ID}, Ctx) ->
|
||||
do(fun () ->
|
||||
Events = unwrap(ff_instrument:create(Params)),
|
||||
unwrap(machinery:start(NS, ID, {Events, Ctx}, fistful:backend(NS)))
|
||||
end).
|
||||
|
||||
-spec get(ns(), id()) ->
|
||||
{ok, st(_)} |
|
||||
{error, notfound} .
|
||||
|
||||
get(NS, ID) ->
|
||||
ff_machine:get(ff_instrument, NS, ID).
|
||||
|
||||
-spec get(ns(), id(), event_range()) ->
|
||||
{ok, st(_)} |
|
||||
{error, notfound} .
|
||||
|
||||
get(NS, ID, {After, Limit}) ->
|
||||
ff_machine:get(ff_instrument, NS, ID, {After, Limit, forward}).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-spec instrument(st(T)) ->
|
||||
instrument(T).
|
||||
|
||||
instrument(St) ->
|
||||
ff_machine:model(St).
|
||||
|
||||
%% Machinery
|
||||
|
||||
-type event(T) :: ff_instrument:event(T).
|
||||
-type events(T) :: [{integer(), ff_machine:timestamped_event(event(T))}].
|
||||
-type timestamped_event(T) :: {integer(), ff_machine:timestamped_event(event(T))}.
|
||||
|
||||
-type machine() :: ff_machine:machine(event(_)).
|
||||
-type result() :: ff_machine:result(event(_)).
|
||||
-type handler_opts() :: machinery:handler_opts(_).
|
||||
-type handler_args() :: machinery:handler_args(_).
|
||||
|
||||
-spec init({[event(_)], ctx()}, machine(), _, handler_opts()) ->
|
||||
result().
|
||||
|
||||
init({Events, Ctx}, #{}, _, _Opts) ->
|
||||
#{
|
||||
events => ff_machine:emit_events(Events),
|
||||
action => continue,
|
||||
aux_state => #{ctx => Ctx}
|
||||
}.
|
||||
|
||||
%%
|
||||
|
||||
-spec process_timeout(machine(), handler_args(), handler_opts()) ->
|
||||
result().
|
||||
|
||||
process_timeout(Machine, _, _Opts) ->
|
||||
St = ff_machine:collapse(ff_instrument, Machine),
|
||||
process_timeout(deduce_activity(ff_machine:model(St)), St).
|
||||
|
||||
process_timeout(authorize, St) ->
|
||||
D0 = instrument(St),
|
||||
case ff_instrument:authorize(D0) of
|
||||
{ok, Events} ->
|
||||
#{
|
||||
events => ff_machine:emit_events(Events)
|
||||
}
|
||||
end.
|
||||
|
||||
deduce_activity(#{status := unauthorized}) ->
|
||||
authorize;
|
||||
deduce_activity(#{}) ->
|
||||
undefined.
|
||||
|
||||
%%
|
||||
|
||||
-spec process_call(_CallArgs, machine(), handler_args(), handler_opts()) ->
|
||||
{ok, result()}.
|
||||
|
||||
process_call(_CallArgs, #{}, _, _Opts) ->
|
||||
{ok, #{}}.
|
||||
|
||||
-spec process_repair(ff_repair:scenario(), machine(), handler_args(), handler_opts()) ->
|
||||
{ok, {repair_response(), result()}} | {error, repair_error()}.
|
||||
|
||||
process_repair(Scenario, Machine, _Args, _Opts) ->
|
||||
ff_repair:apply_scenario(ff_instrument, Machine, Scenario).
|
||||
|
||||
-spec events(ns(), id(), event_range()) ->
|
||||
{ok, events(_)} |
|
||||
{error, notfound}.
|
||||
|
||||
events(NS, ID, {After, Limit}) ->
|
||||
do(fun () ->
|
||||
History = unwrap(ff_machine:history(ff_instrument, NS, ID, {After, Limit, forward})),
|
||||
[{EventID, TsEv} || {EventID, _, TsEv} <- History]
|
||||
end).
|
@ -4,65 +4,31 @@
|
||||
%%% TODOs
|
||||
%%%
|
||||
%%% - Implement a generic source instead of a current dummy one.
|
||||
%%%
|
||||
|
||||
-module(ff_source).
|
||||
|
||||
-type id() :: binary().
|
||||
-type name() :: binary().
|
||||
-type ctx() :: ff_entity_context:context().
|
||||
-type id() :: ff_instrument:id().
|
||||
-type name() :: ff_instrument:name().
|
||||
-type account() :: ff_account:account().
|
||||
-type identity() :: ff_identity:id().
|
||||
-type currency() :: ff_currency:id().
|
||||
-type status() :: unauthorized | authorized.
|
||||
-type metadata() :: ff_entity_context:md().
|
||||
-type timestamp() :: ff_time:timestamp_ms().
|
||||
|
||||
-type status() :: ff_instrument:status().
|
||||
-type resource() :: #{
|
||||
type := internal,
|
||||
details => binary()
|
||||
}.
|
||||
|
||||
-define(ACTUAL_FORMAT_VERSION, 4).
|
||||
-type source() :: ff_instrument:instrument(resource()).
|
||||
-type source_state() :: ff_instrument:instrument_state(resource()).
|
||||
-type params() :: ff_instrument_machine:params(resource()).
|
||||
-type machine() :: ff_instrument_machine:st(resource()).
|
||||
-type event_range() :: ff_instrument_machine:event_range().
|
||||
|
||||
-type source() :: #{
|
||||
version := ?ACTUAL_FORMAT_VERSION,
|
||||
resource := resource(),
|
||||
name := name(),
|
||||
created_at => timestamp(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type source_state() :: #{
|
||||
account := account() | undefined,
|
||||
resource := resource(),
|
||||
name := name(),
|
||||
status => status(),
|
||||
created_at => timestamp(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type params() :: #{
|
||||
id := id(),
|
||||
identity := ff_identity:id(),
|
||||
name := name(),
|
||||
currency := ff_currency:id(),
|
||||
resource := resource(),
|
||||
external_id => id(),
|
||||
metadata => metadata()
|
||||
}.
|
||||
|
||||
-type event() ::
|
||||
{created, source_state()} |
|
||||
{account, ff_account:event()} |
|
||||
{status_changed, status()}.
|
||||
-type legacy_event() :: any().
|
||||
|
||||
-type create_error() ::
|
||||
{identity, notfound} |
|
||||
{currency, notfound} |
|
||||
ff_account:create_error() |
|
||||
{identity, ff_party:inaccessibility()}.
|
||||
-type event() :: ff_instrument:event(resource()).
|
||||
-type events() :: ff_instrument_machine:events(resource()).
|
||||
-type timestamped_event() :: ff_instrument_machine:timestamped_event(resource()).
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([source/0]).
|
||||
@ -71,12 +37,12 @@
|
||||
-export_type([resource/0]).
|
||||
-export_type([params/0]).
|
||||
-export_type([event/0]).
|
||||
-export_type([create_error/0]).
|
||||
-export_type([timestamped_event/0]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-export([id/1]).
|
||||
-export([account/1]).
|
||||
-export([id/1]).
|
||||
-export([name/1]).
|
||||
-export([identity/1]).
|
||||
-export([currency/1]).
|
||||
@ -88,172 +54,94 @@
|
||||
|
||||
%% API
|
||||
|
||||
-export([create/1]).
|
||||
-export([create/2]).
|
||||
-export([get_machine/1]).
|
||||
-export([get_machine/2]).
|
||||
-export([ctx/1]).
|
||||
-export([get/1]).
|
||||
-export([is_accessible/1]).
|
||||
-export([authorize/1]).
|
||||
-export([apply_event/2]).
|
||||
-export([maybe_migrate/2]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1, unwrap/2]).
|
||||
-export([events/2]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-spec id(source_state()) ->
|
||||
id() | undefined.
|
||||
-spec name(source_state()) ->
|
||||
name().
|
||||
-spec account(source_state()) ->
|
||||
account() | undefined.
|
||||
-spec identity(source_state()) ->
|
||||
identity().
|
||||
-spec currency(source_state()) ->
|
||||
currency().
|
||||
-spec resource(source_state()) ->
|
||||
resource().
|
||||
-spec status(source_state()) ->
|
||||
status() | undefined.
|
||||
-spec id(source_state()) -> id().
|
||||
-spec name(source_state()) -> name().
|
||||
-spec account(source_state()) -> account().
|
||||
-spec identity(source_state()) -> identity().
|
||||
-spec currency(source_state()) -> currency().
|
||||
-spec resource(source_state()) -> resource().
|
||||
-spec status(source_state()) -> status() | undefined.
|
||||
|
||||
id(Source) ->
|
||||
case account(Source) of
|
||||
undefined ->
|
||||
undefined;
|
||||
Account ->
|
||||
ff_account:id(Account)
|
||||
end.
|
||||
name(#{name := V}) ->
|
||||
V.
|
||||
account(#{account := V}) ->
|
||||
V;
|
||||
account(_) ->
|
||||
undefined.
|
||||
identity(Source) ->
|
||||
ff_account:identity(account(Source)).
|
||||
currency(Source) ->
|
||||
ff_account:currency(account(Source)).
|
||||
resource(#{resource := V}) ->
|
||||
V.
|
||||
status(#{status := V}) ->
|
||||
V;
|
||||
status(_) ->
|
||||
undefined.
|
||||
id(Source) -> ff_instrument:id(Source).
|
||||
name(Source) -> ff_instrument:name(Source).
|
||||
identity(Source) -> ff_instrument:identity(Source).
|
||||
currency(Source) -> ff_instrument:currency(Source).
|
||||
resource(Source) -> ff_instrument:resource(Source).
|
||||
status(Source) -> ff_instrument:status(Source).
|
||||
account(Source) -> ff_instrument:account(Source).
|
||||
|
||||
-spec external_id(source_state()) ->
|
||||
id() | undefined.
|
||||
external_id(#{external_id := ExternalID}) ->
|
||||
ExternalID;
|
||||
external_id(_Source) ->
|
||||
undefined.
|
||||
external_id(T) -> ff_instrument:external_id(T).
|
||||
|
||||
-spec created_at(source_state()) ->
|
||||
ff_time:timestamp_ms() | undefiend.
|
||||
created_at(#{created_at := CreatedAt}) ->
|
||||
CreatedAt;
|
||||
created_at(_Source) ->
|
||||
undefined.
|
||||
ff_time:timestamp_ms().
|
||||
|
||||
created_at(T) -> ff_instrument:created_at(T).
|
||||
|
||||
-spec metadata(source_state()) ->
|
||||
ff_entity_context:context() | undefined.
|
||||
metadata(#{metadata := Metadata}) ->
|
||||
Metadata;
|
||||
metadata(_Source) ->
|
||||
undefined.
|
||||
ff_entity_context:context().
|
||||
|
||||
metadata(T) -> ff_instrument:metadata(T).
|
||||
|
||||
%% API
|
||||
|
||||
-spec create(params()) ->
|
||||
{ok, [event()]} |
|
||||
{error, create_error()}.
|
||||
create(Params) ->
|
||||
do(fun () ->
|
||||
#{
|
||||
id := ID,
|
||||
identity := IdentityID,
|
||||
name := Name,
|
||||
currency := CurrencyID,
|
||||
resource := Resource
|
||||
} = Params,
|
||||
Identity = ff_identity_machine:identity(unwrap(identity, ff_identity_machine:get(IdentityID))),
|
||||
Currency = unwrap(currency, ff_currency:get(CurrencyID)),
|
||||
Events = unwrap(ff_account:create(ID, Identity, Currency)),
|
||||
accessible = unwrap(identity, ff_identity:is_accessible(Identity)),
|
||||
CreatedAt = ff_time:now(),
|
||||
[{created, genlib_map:compact(#{
|
||||
version => ?ACTUAL_FORMAT_VERSION,
|
||||
name => Name,
|
||||
resource => Resource,
|
||||
external_id => maps:get(external_id, Params, undefined),
|
||||
metadata => maps:get(metadata, Params, undefined),
|
||||
created_at => CreatedAt
|
||||
})}] ++
|
||||
[{account, Ev} || Ev <- Events] ++
|
||||
[{status_changed, unauthorized}]
|
||||
end).
|
||||
-define(NS, 'ff/source_v1').
|
||||
|
||||
-spec create(params(), ctx()) ->
|
||||
ok |
|
||||
{error,
|
||||
_InstrumentCreateError |
|
||||
exists
|
||||
}.
|
||||
|
||||
create(Params, Ctx) ->
|
||||
ff_instrument_machine:create(?NS, Params, Ctx).
|
||||
|
||||
-spec get_machine(id()) ->
|
||||
{ok, machine()} |
|
||||
{error, notfound} .
|
||||
get_machine(ID) ->
|
||||
ff_instrument_machine:get(?NS, ID).
|
||||
|
||||
-spec get(machine()) ->
|
||||
source_state().
|
||||
get(Machine) ->
|
||||
ff_instrument_machine:instrument(Machine).
|
||||
|
||||
-spec get_machine(id(), event_range()) ->
|
||||
{ok, machine()} |
|
||||
{error, notfound} .
|
||||
|
||||
get_machine(ID, EventRange) ->
|
||||
ff_instrument_machine:get(?NS, ID, EventRange).
|
||||
|
||||
-spec ctx(machine()) ->
|
||||
ctx().
|
||||
|
||||
ctx(St) ->
|
||||
ff_machine:ctx(St).
|
||||
|
||||
-spec is_accessible(source_state()) ->
|
||||
{ok, accessible} |
|
||||
{error, ff_party:inaccessibility()}.
|
||||
|
||||
is_accessible(Source) ->
|
||||
ff_account:is_accessible(account(Source)).
|
||||
ff_instrument:is_accessible(Source).
|
||||
|
||||
-spec authorize(source_state()) ->
|
||||
{ok, [event()]}.
|
||||
authorize(#{status := unauthorized}) ->
|
||||
% TODO
|
||||
% - Do the actual authorization
|
||||
{ok, [{status_changed, authorized}]};
|
||||
authorize(#{status := authorized}) ->
|
||||
{ok, []}.
|
||||
-spec events(id(), event_range()) ->
|
||||
{ok, events()} |
|
||||
{error, notfound}.
|
||||
|
||||
-spec apply_event(event(), ff_maybe:maybe(source_state())) ->
|
||||
source_state().
|
||||
|
||||
apply_event({created, Source}, undefined) ->
|
||||
Source;
|
||||
apply_event({status_changed, S}, Source) ->
|
||||
Source#{status => S};
|
||||
apply_event({account, Ev}, Source = #{account := Account}) ->
|
||||
Source#{account => ff_account:apply_event(Ev, Account)};
|
||||
apply_event({account, Ev}, Source) ->
|
||||
apply_event({account, Ev}, Source#{account => undefined}).
|
||||
|
||||
-spec maybe_migrate(event() | legacy_event(), ff_machine:migrate_params()) ->
|
||||
event().
|
||||
|
||||
maybe_migrate(Event = {created, #{version := ?ACTUAL_FORMAT_VERSION}}, _MigrateParams) ->
|
||||
Event;
|
||||
maybe_migrate({created, Source = #{version := 3}}, MigrateParams) ->
|
||||
maybe_migrate({created, Source#{
|
||||
version => 4
|
||||
}}, MigrateParams);
|
||||
maybe_migrate({created, Source = #{version := 2}}, MigrateParams) ->
|
||||
Context = maps:get(ctx, MigrateParams, undefined),
|
||||
%% TODO add metada migration for eventsink after decouple instruments
|
||||
Metadata = ff_entity_context:try_get_legacy_metadata(Context),
|
||||
maybe_migrate({created, genlib_map:compact(Source#{
|
||||
version => 3,
|
||||
metadata => Metadata
|
||||
})}, MigrateParams);
|
||||
maybe_migrate({created, Source = #{version := 1}}, MigrateParams) ->
|
||||
Timestamp = maps:get(timestamp, MigrateParams),
|
||||
CreatedAt = ff_codec:unmarshal(timestamp_ms, ff_codec:marshal(timestamp, Timestamp)),
|
||||
maybe_migrate({created, Source#{
|
||||
version => 2,
|
||||
created_at => CreatedAt
|
||||
}}, MigrateParams);
|
||||
maybe_migrate({created, Source = #{
|
||||
resource := Resource,
|
||||
name := Name
|
||||
}}, MigrateParams) ->
|
||||
maybe_migrate({created, genlib_map:compact(#{
|
||||
version => 1,
|
||||
resource => Resource,
|
||||
name => Name,
|
||||
external_id => maps:get(external_id, Source, undefined)
|
||||
})}, MigrateParams);
|
||||
|
||||
%% Other events
|
||||
maybe_migrate(Event, _MigrateParams) ->
|
||||
Event.
|
||||
events(ID, Range) ->
|
||||
ff_instrument_machine:events(?NS, ID, Range).
|
||||
|
@ -1,162 +0,0 @@
|
||||
%%%
|
||||
%%% Source machine
|
||||
%%%
|
||||
|
||||
-module(ff_source_machine).
|
||||
|
||||
%% API
|
||||
|
||||
-type id() :: machinery:id().
|
||||
-type ctx() :: ff_entity_context:context().
|
||||
-type source() :: ff_source:source_state().
|
||||
-type change() :: ff_source:event().
|
||||
-type event() :: {integer(), ff_machine:timestamped_event(change())}.
|
||||
-type events() :: [event()].
|
||||
-type event_range() :: {After :: non_neg_integer() | undefined, Limit :: non_neg_integer() | undefined}.
|
||||
|
||||
-type params() :: ff_source:params().
|
||||
-type st() :: ff_machine:st(source()).
|
||||
|
||||
-type repair_error() :: ff_repair:repair_error().
|
||||
-type repair_response() :: ff_repair:repair_response().
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([st/0]).
|
||||
-export_type([event/0]).
|
||||
-export_type([repair_error/0]).
|
||||
-export_type([repair_response/0]).
|
||||
-export_type([params/0]).
|
||||
-export_type([event_range/0]).
|
||||
|
||||
%% API
|
||||
|
||||
-export([create/2]).
|
||||
-export([get/1]).
|
||||
-export([get/2]).
|
||||
-export([events/2]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-export([source/1]).
|
||||
-export([ctx/1]).
|
||||
|
||||
%% Machinery
|
||||
|
||||
-behaviour(machinery).
|
||||
|
||||
-export([init/4]).
|
||||
-export([process_timeout/3]).
|
||||
-export([process_repair/4]).
|
||||
-export([process_call/4]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
%%
|
||||
-define(NS, 'ff/source_v1').
|
||||
|
||||
-spec create(params(), ctx()) ->
|
||||
ok |
|
||||
{error, ff_source:create_error() | exists}.
|
||||
|
||||
create(#{id := ID} = Params, Ctx) ->
|
||||
do(fun () ->
|
||||
Events = unwrap(ff_source:create(Params)),
|
||||
unwrap(machinery:start(?NS, ID, {Events, Ctx}, backend()))
|
||||
end).
|
||||
|
||||
-spec get(id()) ->
|
||||
{ok, st()} |
|
||||
{error, notfound}.
|
||||
get(ID) ->
|
||||
ff_machine:get(ff_source, ?NS, ID).
|
||||
|
||||
-spec get(id(), event_range()) ->
|
||||
{ok, st()} |
|
||||
{error, notfound} .
|
||||
get(ID, {After, Limit}) ->
|
||||
ff_machine:get(ff_source, ?NS, ID, {After, Limit, forward}).
|
||||
|
||||
-spec events(id(), event_range()) ->
|
||||
{ok, events()} |
|
||||
{error, notfound}.
|
||||
|
||||
events(ID, {After, Limit}) ->
|
||||
do(fun () ->
|
||||
History = unwrap(ff_machine:history(ff_source, ?NS, ID, {After, Limit, forward})),
|
||||
[{EventID, TsEv} || {EventID, _, TsEv} <- History]
|
||||
end).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-spec source(st()) ->
|
||||
source().
|
||||
|
||||
source(St) ->
|
||||
ff_machine:model(St).
|
||||
|
||||
-spec ctx(st()) ->
|
||||
ctx().
|
||||
|
||||
ctx(St) ->
|
||||
ff_machine:ctx(St).
|
||||
|
||||
%% Machinery
|
||||
|
||||
-type machine() :: ff_machine:machine(change()).
|
||||
-type result() :: ff_machine:result(change()).
|
||||
-type handler_opts() :: machinery:handler_opts(_).
|
||||
-type handler_args() :: machinery:handler_args(_).
|
||||
|
||||
-spec init({[change()], ctx()}, machine(), _, handler_opts()) ->
|
||||
result().
|
||||
|
||||
init({Events, Ctx}, #{}, _, _Opts) ->
|
||||
#{
|
||||
events => ff_machine:emit_events(Events),
|
||||
action => continue,
|
||||
aux_state => #{ctx => Ctx}
|
||||
}.
|
||||
|
||||
%%
|
||||
|
||||
-spec process_timeout(machine(), handler_args(), handler_opts()) ->
|
||||
result().
|
||||
|
||||
process_timeout(Machine, _, _Opts) ->
|
||||
St = ff_machine:collapse(ff_source, Machine),
|
||||
process_timeout(deduce_activity(ff_machine:model(St)), St).
|
||||
|
||||
process_timeout(authorize, St) ->
|
||||
D0 = source(St),
|
||||
case ff_source:authorize(D0) of
|
||||
{ok, Events} ->
|
||||
#{
|
||||
events => ff_machine:emit_events(Events)
|
||||
}
|
||||
end.
|
||||
|
||||
deduce_activity(#{status := unauthorized}) ->
|
||||
authorize;
|
||||
deduce_activity(#{}) ->
|
||||
undefined.
|
||||
|
||||
%%
|
||||
|
||||
-spec process_call(_CallArgs, machine(), handler_args(), handler_opts()) ->
|
||||
{ok, result()}.
|
||||
|
||||
process_call(_CallArgs, #{}, _, _Opts) ->
|
||||
{ok, #{}}.
|
||||
|
||||
-spec process_repair(ff_repair:scenario(), machine(), handler_args(), handler_opts()) ->
|
||||
{ok, {repair_response(), result()}} | {error, repair_error()}.
|
||||
|
||||
process_repair(Scenario, Machine, _Args, _Opts) ->
|
||||
ff_repair:apply_scenario(ff_source, Machine, Scenario).
|
||||
|
||||
%% Internals
|
||||
|
||||
backend() ->
|
||||
fistful:backend(?NS).
|
@ -176,7 +176,7 @@
|
||||
-type invalid_withdrawal_status_error() ::
|
||||
{invalid_withdrawal_status, status()}.
|
||||
|
||||
-type action() :: sleep | continue | undefined.
|
||||
-type action() :: poll | continue | undefined.
|
||||
|
||||
-export_type([withdrawal/0]).
|
||||
-export_type([withdrawal_state/0]).
|
||||
@ -199,10 +199,6 @@
|
||||
|
||||
-export([process_transfer/1]).
|
||||
|
||||
%%
|
||||
|
||||
-export([process_session_finished/3]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-export([wallet_id/1]).
|
||||
@ -299,7 +295,7 @@
|
||||
p_transfer_start |
|
||||
p_transfer_prepare |
|
||||
session_starting |
|
||||
session_sleeping |
|
||||
session_polling |
|
||||
p_transfer_commit |
|
||||
p_transfer_cancel |
|
||||
limit_check |
|
||||
@ -332,8 +328,8 @@ destination_resource(#{resource := Resource}) ->
|
||||
Resource;
|
||||
destination_resource(Withdrawal) ->
|
||||
DestinationID = destination_id(Withdrawal),
|
||||
{ok, DestinationMachine} = ff_destination_machine:get(DestinationID),
|
||||
Destination = ff_destination_machine:destination(DestinationMachine),
|
||||
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
|
||||
Destination = ff_destination:get(DestinationMachine),
|
||||
{ok, Resource} = ff_destination:resource_full(Destination),
|
||||
Resource.
|
||||
|
||||
@ -511,51 +507,6 @@ process_transfer(Withdrawal) ->
|
||||
Activity = deduce_activity(Withdrawal),
|
||||
do_process_transfer(Activity, Withdrawal).
|
||||
|
||||
%%
|
||||
|
||||
-spec process_session_finished(session_id(), session_result(), withdrawal_state()) ->
|
||||
{ok, process_result()} | {error, session_not_found | old_session | result_mismatch}.
|
||||
process_session_finished(SessionID, SessionResult, Withdrawal) ->
|
||||
case get_session_by_id(SessionID, Withdrawal) of
|
||||
#{id := SessionID, result := SessionResult} ->
|
||||
{ok, {undefined, []}};
|
||||
#{id := SessionID, result := _OtherSessionResult} ->
|
||||
{error, result_mismatch};
|
||||
#{id := SessionID} ->
|
||||
try_finish_session(SessionID, SessionResult, Withdrawal);
|
||||
undefined ->
|
||||
{error, session_not_found}
|
||||
end.
|
||||
|
||||
-spec get_session_by_id(session_id(), withdrawal_state()) ->
|
||||
session() | undefined.
|
||||
get_session_by_id(SessionID, Withdrawal) ->
|
||||
Sessions = ff_withdrawal_route_attempt_utils:get_sessions(attempts(Withdrawal)),
|
||||
case lists:filter(fun(#{id := SessionID0}) -> SessionID0 =:= SessionID end, Sessions) of
|
||||
[Session] -> Session;
|
||||
[] -> undefined
|
||||
end.
|
||||
|
||||
-spec try_finish_session(session_id(), session_result(), withdrawal_state()) ->
|
||||
{ok, process_result()} | {error, old_session}.
|
||||
try_finish_session(SessionID, SessionResult, Withdrawal) ->
|
||||
case is_current_session(SessionID, Withdrawal) of
|
||||
true ->
|
||||
{ok, {continue, [{session_finished, {SessionID, SessionResult}}]}};
|
||||
false ->
|
||||
{error, old_session}
|
||||
end.
|
||||
|
||||
-spec is_current_session(session_id(), withdrawal_state()) ->
|
||||
boolean().
|
||||
is_current_session(SessionID, Withdrawal) ->
|
||||
case session_id(Withdrawal) of
|
||||
SessionID ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Internals
|
||||
|
||||
-spec do_start_adjustment(adjustment_params(), withdrawal_state()) ->
|
||||
@ -688,7 +639,7 @@ do_pending_activity(#{p_transfer := prepared, limit_check := failed}) ->
|
||||
do_pending_activity(#{p_transfer := cancelled, limit_check := failed}) ->
|
||||
{fail, limit_check};
|
||||
do_pending_activity(#{p_transfer := prepared, session := pending}) ->
|
||||
session_sleeping;
|
||||
session_polling;
|
||||
do_pending_activity(#{p_transfer := prepared, session := succeeded}) ->
|
||||
p_transfer_commit;
|
||||
do_pending_activity(#{p_transfer := committed, session := succeeded}) ->
|
||||
@ -732,8 +683,8 @@ do_process_transfer(limit_check, Withdrawal) ->
|
||||
process_limit_check(Withdrawal);
|
||||
do_process_transfer(session_starting, Withdrawal) ->
|
||||
process_session_creation(Withdrawal);
|
||||
do_process_transfer(session_sleeping, Withdrawal) ->
|
||||
process_session_sleep(Withdrawal);
|
||||
do_process_transfer(session_polling, Withdrawal) ->
|
||||
process_session_poll(Withdrawal);
|
||||
do_process_transfer({fail, Reason}, Withdrawal) ->
|
||||
{ok, Providers} = do_process_routing(Withdrawal),
|
||||
process_route_change(Providers, Withdrawal, Reason);
|
||||
@ -873,8 +824,8 @@ process_session_creation(Withdrawal) ->
|
||||
Wallet = ff_wallet_machine:wallet(WalletMachine),
|
||||
WalletAccount = ff_wallet:account(Wallet),
|
||||
|
||||
{ok, DestinationMachine} = ff_destination_machine:get(DestinationID),
|
||||
Destination = ff_destination_machine:destination(DestinationMachine),
|
||||
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
|
||||
Destination = ff_destination:get(DestinationMachine),
|
||||
DestinationAccount = ff_destination:account(Destination),
|
||||
|
||||
Route = route(Withdrawal),
|
||||
@ -918,15 +869,15 @@ create_session(ID, TransferData, SessionParams) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec process_session_sleep(withdrawal_state()) ->
|
||||
-spec process_session_poll(withdrawal_state()) ->
|
||||
process_result().
|
||||
process_session_sleep(Withdrawal) ->
|
||||
process_session_poll(Withdrawal) ->
|
||||
SessionID = session_id(Withdrawal),
|
||||
{ok, SessionMachine} = ff_withdrawal_session_machine:get(SessionID),
|
||||
Session = ff_withdrawal_session_machine:session(SessionMachine),
|
||||
case ff_withdrawal_session:status(Session) of
|
||||
active ->
|
||||
{sleep, []};
|
||||
{poll, []};
|
||||
{finished, _} ->
|
||||
Result = ff_withdrawal_session:result(Session),
|
||||
{continue, [{session_finished, {SessionID, Result}}]}
|
||||
@ -1047,8 +998,8 @@ get_wallet(WalletID) ->
|
||||
{ok, destination()} | {error, notfound}.
|
||||
get_destination(DestinationID) ->
|
||||
do(fun() ->
|
||||
DestinationMachine = unwrap(ff_destination_machine:get(DestinationID)),
|
||||
ff_destination_machine:destination(DestinationMachine)
|
||||
DestinationMachine = unwrap(ff_destination:get_machine(DestinationID)),
|
||||
ff_destination:get(DestinationMachine)
|
||||
end).
|
||||
|
||||
-spec get_wallet_identity(wallet()) ->
|
||||
|
@ -55,7 +55,6 @@
|
||||
-export([repair/2]).
|
||||
|
||||
-export([start_adjustment/2]).
|
||||
-export([notify_session_finished/3]).
|
||||
|
||||
%% Accessors
|
||||
|
||||
@ -79,12 +78,8 @@
|
||||
|
||||
-type adjustment_params() :: ff_withdrawal:adjustment_params().
|
||||
|
||||
-type session_id() :: ff_withdrawal_session:id().
|
||||
-type session_result() :: ff_withdrawal_session:session_result().
|
||||
|
||||
-type call() ::
|
||||
{start_adjustment, adjustment_params()} |
|
||||
{session_finished, session_id(), session_result()}.
|
||||
{start_adjustment, adjustment_params()}.
|
||||
|
||||
-define(NS, 'ff/withdrawal_v2').
|
||||
|
||||
@ -144,11 +139,6 @@ repair(ID, Scenario) ->
|
||||
start_adjustment(WithdrawalID, Params) ->
|
||||
call(WithdrawalID, {start_adjustment, Params}).
|
||||
|
||||
-spec notify_session_finished(id(), session_id(), session_result()) ->
|
||||
ok | {error, session_not_found | old_session | result_mismatch}.
|
||||
notify_session_finished(WithdrawalID, SessionID, SessionResult) ->
|
||||
call(WithdrawalID, {session_finished, SessionID, SessionResult}).
|
||||
|
||||
%% Accessors
|
||||
|
||||
-spec withdrawal(st()) ->
|
||||
@ -170,6 +160,8 @@ ctx(St) ->
|
||||
-type handler_opts() :: machinery:handler_opts(_).
|
||||
-type handler_args() :: machinery:handler_args(_).
|
||||
|
||||
-define(MAX_SESSION_POLL_TIMEOUT, 4 * 60 * 60).
|
||||
|
||||
backend() ->
|
||||
fistful:backend(?NS).
|
||||
|
||||
@ -196,8 +188,6 @@ process_timeout(Machine, _, _Opts) ->
|
||||
|
||||
process_call({start_adjustment, Params}, Machine, _, _Opts) ->
|
||||
do_start_adjustment(Params, Machine);
|
||||
process_call({session_finished, SessionID, SessionResult}, Machine, _, _Opts) ->
|
||||
do_process_session_finished(SessionID, SessionResult, Machine);
|
||||
process_call(CallArgs, _Machine, _, _Opts) ->
|
||||
erlang:error({unexpected_call, CallArgs}).
|
||||
|
||||
@ -219,17 +209,6 @@ do_start_adjustment(Params, Machine) ->
|
||||
{Error, #{}}
|
||||
end.
|
||||
|
||||
-spec do_process_session_finished(session_id(), session_result(), machine()) -> {Response, result()} when
|
||||
Response :: ok | {error, session_not_found | old_session | result_mismatch}.
|
||||
do_process_session_finished(SessionID, SessionResult, Machine) ->
|
||||
St = ff_machine:collapse(ff_withdrawal, Machine),
|
||||
case ff_withdrawal:process_session_finished(SessionID, SessionResult, withdrawal(St)) of
|
||||
{ok, Result} ->
|
||||
{ok, process_result(Result, St)};
|
||||
{error, _Reason} = Error ->
|
||||
{Error, #{}}
|
||||
end.
|
||||
|
||||
process_result({Action, Events}, St) ->
|
||||
genlib_map:compact(#{
|
||||
events => set_events(Events),
|
||||
@ -245,8 +224,14 @@ set_action(continue, _St) ->
|
||||
continue;
|
||||
set_action(undefined, _St) ->
|
||||
undefined;
|
||||
set_action(sleep, _St) ->
|
||||
unset_timer.
|
||||
set_action(poll, St) ->
|
||||
Now = machinery_time:now(),
|
||||
{set_timer, {timeout, compute_poll_timeout(Now, St)}}.
|
||||
|
||||
compute_poll_timeout(Now, St) ->
|
||||
MaxTimeout = genlib_app:env(ff_transfer, max_session_poll_timeout, ?MAX_SESSION_POLL_TIMEOUT),
|
||||
Timeout0 = machinery_time:interval(Now, ff_machine:updated(St)) div 1000,
|
||||
erlang:min(MaxTimeout, erlang:max(1, Timeout0)).
|
||||
|
||||
call(ID, Call) ->
|
||||
case machinery:call(?NS, ID, Call, backend()) of
|
||||
|
@ -102,18 +102,6 @@
|
||||
opts := ff_withdrawal_provider:adapter_opts()
|
||||
}.
|
||||
|
||||
-type id() :: machinery:id().
|
||||
|
||||
-type action() ::
|
||||
undefined |
|
||||
continue |
|
||||
{setup_callback, machinery:tag(), machinery:timer()} |
|
||||
{setup_timer, machinery:timer()} |
|
||||
retry |
|
||||
finish.
|
||||
|
||||
-type process_result() :: {action(), [event()]}.
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([data/0]).
|
||||
-export_type([event/0]).
|
||||
@ -126,12 +114,15 @@
|
||||
-export_type([callback_params/0]).
|
||||
-export_type([process_callback_response/0]).
|
||||
-export_type([process_callback_error/0]).
|
||||
-export_type([process_result/0]).
|
||||
-export_type([action/0]).
|
||||
|
||||
%%
|
||||
%% Internal types
|
||||
%%
|
||||
-type id() :: machinery:id().
|
||||
|
||||
-type auxst() :: undefined.
|
||||
|
||||
-type result() :: machinery:result(event(), auxst()).
|
||||
-type withdrawal() :: ff_adapter_withdrawal:withdrawal().
|
||||
-type callbacks_index() :: ff_withdrawal_callback_utils:index().
|
||||
-type adapter_with_opts() :: {ff_withdrawal_provider:adapter(), ff_withdrawal_provider:adapter_opts()}.
|
||||
@ -224,18 +215,7 @@ apply_event({callback, _Ev} = WrappedEvent, Session) ->
|
||||
Callbacks1 = ff_withdrawal_callback_utils:apply_event(WrappedEvent, Callbacks0),
|
||||
set_callbacks_index(Callbacks1, Session).
|
||||
|
||||
-spec process_session(session_state()) -> process_result().
|
||||
process_session(#{status := {finished, _}, id := ID, result := Result, withdrawal := Withdrawal}) ->
|
||||
% Session has finished, it should notify the withdrawal machine about the fact
|
||||
WithdrawalID = ff_adapter_withdrawal:id(Withdrawal),
|
||||
case ff_withdrawal_machine:notify_session_finished(WithdrawalID, ID, Result) of
|
||||
ok ->
|
||||
{finish, []};
|
||||
{error, session_not_found} ->
|
||||
{retry, []};
|
||||
{error, _} = Error ->
|
||||
erlang:error({unable_to_finish_session, Error})
|
||||
end;
|
||||
-spec process_session(session_state()) -> result().
|
||||
process_session(#{status := active, withdrawal := Withdrawal, route := Route} = SessionState) ->
|
||||
{Adapter, AdapterOpts} = get_adapter_with_opts(Route),
|
||||
ASt = maps:get(adapter_state, SessionState, undefined),
|
||||
@ -243,7 +223,7 @@ process_session(#{status := active, withdrawal := Withdrawal, route := Route} =
|
||||
#{intent := Intent} = ProcessResult,
|
||||
Events0 = process_next_state(ProcessResult, []),
|
||||
Events1 = process_transaction_info(ProcessResult, Events0, SessionState),
|
||||
process_adapter_intent(Intent, SessionState, Events1).
|
||||
process_intent(Intent, SessionState, Events1).
|
||||
|
||||
process_transaction_info(#{transaction_info := TrxInfo}, Events, SessionState) ->
|
||||
ok = assert_transaction_info(TrxInfo, transaction_info(SessionState)),
|
||||
@ -261,24 +241,27 @@ assert_transaction_info(NewTrxInfo, _TrxInfo) ->
|
||||
erlang:error({transaction_info_is_different, NewTrxInfo}).
|
||||
|
||||
-spec set_session_result(session_result(), session_state()) ->
|
||||
process_result().
|
||||
set_session_result(Result, Session = #{status := active}) ->
|
||||
process_adapter_intent({finish, Result}, Session).
|
||||
result().
|
||||
set_session_result(Result, #{status := active}) ->
|
||||
#{
|
||||
events => [{finished, Result}],
|
||||
action => unset_timer
|
||||
}.
|
||||
|
||||
-spec process_callback(callback_params(), session_state()) ->
|
||||
{ok, {process_callback_response(), process_result()}} |
|
||||
{error, {process_callback_error(), process_result()}}.
|
||||
{ok, {process_callback_response(), result()}} |
|
||||
{error, {process_callback_error(), result()}}.
|
||||
process_callback(#{tag := CallbackTag} = Params, Session) ->
|
||||
{ok, Callback} = find_callback(CallbackTag, Session),
|
||||
case ff_withdrawal_callback:status(Callback) of
|
||||
succeeded ->
|
||||
{ok, {ff_withdrawal_callback:response(Callback), {undefined, []}}};
|
||||
{ok, {ff_withdrawal_callback:response(Callback), #{}}};
|
||||
pending ->
|
||||
case status(Session) of
|
||||
active ->
|
||||
do_process_callback(Params, Callback, Session);
|
||||
{finished, _} ->
|
||||
{error, {{session_already_finished, make_session_finish_params(Session)}, {undefined, []}}}
|
||||
{error, {{session_already_finished, make_session_finish_params(Session)}, #{}}}
|
||||
end
|
||||
end.
|
||||
|
||||
@ -300,7 +283,7 @@ do_process_callback(CallbackParams, Callback, Session) ->
|
||||
Events0 = ff_withdrawal_callback_utils:process_response(Response, Callback),
|
||||
Events1 = process_next_state(HandleCallbackResult, Events0),
|
||||
Events2 = process_transaction_info(HandleCallbackResult, Events1, Session),
|
||||
{ok, {Response, process_adapter_intent(Intent, Session, Events2)}}.
|
||||
{ok, {Response, process_intent(Intent, Session, Events2)}}.
|
||||
|
||||
make_session_finish_params(Session) ->
|
||||
{_Adapter, AdapterOpts} = get_adapter_with_opts(route(Session)),
|
||||
@ -315,21 +298,28 @@ process_next_state(#{next_state := NextState}, Events) ->
|
||||
process_next_state(_, Events) ->
|
||||
Events.
|
||||
|
||||
process_adapter_intent(Intent, Session, Events0) ->
|
||||
{Action, Events1} = process_adapter_intent(Intent, Session),
|
||||
{Action, Events0 ++ Events1}.
|
||||
process_intent(Intent, Session, Events) ->
|
||||
#{events := Events0} = Result = process_intent(Intent, Session),
|
||||
Result#{events => Events ++ Events0}.
|
||||
|
||||
process_adapter_intent({finish, {success, _TransactionInfo}}, _Session) ->
|
||||
process_intent({finish, {success, _TransactionInfo}}, _Session) ->
|
||||
%% we ignore TransactionInfo here
|
||||
%% @see ff_adapter_withdrawal:rebind_transaction_info/1
|
||||
{continue, [{finished, success}]};
|
||||
process_adapter_intent({finish, Result}, _Session) ->
|
||||
{continue, [{finished, Result}]};
|
||||
process_adapter_intent({sleep, #{timer := Timer, tag := Tag}}, Session) ->
|
||||
Events = create_callback(Tag, Session),
|
||||
{{setup_callback, Tag, Timer}, Events};
|
||||
process_adapter_intent({sleep, #{timer := Timer}}, _Session) ->
|
||||
{{setup_timer, Timer}, []}.
|
||||
#{
|
||||
events => [{finished, success}],
|
||||
action => unset_timer
|
||||
};
|
||||
process_intent({finish, Result}, _Session) ->
|
||||
#{
|
||||
events => [{finished, Result}],
|
||||
action => unset_timer
|
||||
};
|
||||
process_intent({sleep, #{timer := Timer} = Params}, Session) ->
|
||||
CallbackEvents = create_callback(Params, Session),
|
||||
#{
|
||||
events => CallbackEvents,
|
||||
action => maybe_add_tag_action(Params, [timer_action(Timer)])
|
||||
}.
|
||||
|
||||
%%
|
||||
|
||||
@ -344,14 +334,16 @@ create_session(ID, Data, #{withdrawal_id := WdthID, resource := Res, route := Ro
|
||||
status => active
|
||||
}.
|
||||
|
||||
create_callback(Tag, Session) ->
|
||||
create_callback(#{tag := Tag}, Session) ->
|
||||
case ff_withdrawal_callback_utils:get_by_tag(Tag, callbacks_index(Session)) of
|
||||
{error, {unknown_callback, Tag}} ->
|
||||
{ok, CallbackEvents} = ff_withdrawal_callback:create(#{tag => Tag}),
|
||||
ff_withdrawal_callback_utils:wrap_events(Tag, CallbackEvents);
|
||||
{ok, Callback} ->
|
||||
erlang:error({callback_already_exists, Callback})
|
||||
end.
|
||||
end;
|
||||
create_callback(_, _) ->
|
||||
[].
|
||||
|
||||
-spec convert_identity_state_to_adapter_identity(ff_identity:identity_state()) ->
|
||||
ff_adapter_withdrawal:identity().
|
||||
@ -409,3 +401,13 @@ create_adapter_withdrawal(#{id := SesID, sender := Sender, receiver := Receiver}
|
||||
-spec set_callbacks_index(callbacks_index(), session_state()) -> session_state().
|
||||
set_callbacks_index(Callbacks, Session) ->
|
||||
Session#{callbacks => Callbacks}.
|
||||
|
||||
-spec timer_action({deadline, binary()} | {timeout, non_neg_integer()}) -> machinery:action().
|
||||
timer_action(Timer) ->
|
||||
{set_timer, Timer}.
|
||||
|
||||
-spec maybe_add_tag_action(SleepIntentParams :: map(), [machinery:action()]) -> [machinery:action()].
|
||||
maybe_add_tag_action(#{tag := Tag}, Actions) ->
|
||||
[{tag, Tag} | Actions];
|
||||
maybe_add_tag_action(_, Actions) ->
|
||||
Actions.
|
||||
|
@ -58,7 +58,6 @@
|
||||
-type st() :: ff_machine:st(session()).
|
||||
-type session() :: ff_withdrawal_session:session_state().
|
||||
-type event() :: ff_withdrawal_session:event().
|
||||
-type action() :: ff_withdrawal_session:action().
|
||||
-type event_range() :: {After :: non_neg_integer() | undefined, Limit :: non_neg_integer() | undefined}.
|
||||
|
||||
-type callback_params() :: ff_withdrawal_session:callback_params().
|
||||
@ -67,8 +66,6 @@
|
||||
{unknown_session, {tag, id()}} |
|
||||
ff_withdrawal_session:process_callback_error().
|
||||
|
||||
-type process_result() :: ff_withdrawal_session:process_result().
|
||||
|
||||
-type ctx() :: ff_entity_context:context().
|
||||
|
||||
%% Pipeline
|
||||
@ -79,9 +76,6 @@
|
||||
%% API
|
||||
%%
|
||||
|
||||
-define(SESSION_RETRY_TIME_LIMIT, 24 * 60 * 60).
|
||||
-define(MAX_SESSION_RETRY_TIMEOUT, 4 * 60 * 60).
|
||||
|
||||
-spec session(st()) -> session().
|
||||
|
||||
session(St) ->
|
||||
@ -153,11 +147,14 @@ init(Events, #{}, _, _Opts) ->
|
||||
result().
|
||||
process_timeout(Machine, _, _Opts) ->
|
||||
State = ff_machine:collapse(ff_withdrawal_session, Machine),
|
||||
Session = session(State),
|
||||
process_result(ff_withdrawal_session:process_session(Session), State).
|
||||
#{events := Events} = Result = ff_withdrawal_session:process_session(session(State)),
|
||||
Result#{
|
||||
events => ff_machine:emit_events(Events)
|
||||
}.
|
||||
|
||||
-spec process_call(any(), machine(), handler_args(), handler_opts()) ->
|
||||
{ok, result()}.
|
||||
|
||||
process_call({process_callback, Params}, Machine, _, _Opts) ->
|
||||
do_process_callback(Params, Machine);
|
||||
process_call(_CallArgs, #{}, _, _Opts) ->
|
||||
@ -169,8 +166,7 @@ process_repair(Scenario, Machine, _Args, _Opts) ->
|
||||
ScenarioProcessors = #{
|
||||
set_session_result => fun(Args, RMachine) ->
|
||||
State = ff_machine:collapse(ff_withdrawal_session, RMachine),
|
||||
{Action, Events} = ff_withdrawal_session:set_session_result(Args, session(State)),
|
||||
{ok, {ok, #{action => set_action(Action, State), events => Events}}}
|
||||
{ok, {ok, ff_withdrawal_session:set_session_result(Args, session(State))}}
|
||||
end
|
||||
},
|
||||
ff_repair:apply_scenario(ff_withdrawal_session, Machine, Scenario, ScenarioProcessors).
|
||||
@ -179,102 +175,6 @@ process_repair(Scenario, Machine, _Args, _Opts) ->
|
||||
%% Internals
|
||||
%%
|
||||
|
||||
-spec process_result(process_result(), st()) ->
|
||||
result().
|
||||
process_result({Action, Events}, St) ->
|
||||
genlib_map:compact(#{
|
||||
events => set_events(Events),
|
||||
action => set_action(Action, St)
|
||||
}).
|
||||
|
||||
-spec set_events([event()]) ->
|
||||
undefined | ff_machine:timestamped_event(event()).
|
||||
set_events([]) ->
|
||||
undefined;
|
||||
set_events(Events) ->
|
||||
ff_machine:emit_events(Events).
|
||||
|
||||
-spec set_action(action(), st()) ->
|
||||
undefined | machinery:action() | [machinery:action()].
|
||||
set_action(continue, _St) ->
|
||||
continue;
|
||||
set_action(undefined, _St) ->
|
||||
undefined;
|
||||
set_action({setup_callback, Tag, Timer}, _St) ->
|
||||
[tag_action(Tag), timer_action(Timer)];
|
||||
set_action({setup_timer, Timer}, _St) ->
|
||||
timer_action(Timer);
|
||||
set_action(retry, St) ->
|
||||
case compute_retry_timer(St) of
|
||||
{ok, Timer} ->
|
||||
timer_action(Timer);
|
||||
{error, deadline_reached} = Error ->
|
||||
erlang:error(Error)
|
||||
end;
|
||||
set_action(finish, _St) ->
|
||||
unset_timer.
|
||||
|
||||
%%
|
||||
|
||||
-spec compute_retry_timer(st()) ->
|
||||
{ok, machinery:timer()} | {error, deadline_reached}.
|
||||
compute_retry_timer(St) ->
|
||||
Now = machinery_time:now(),
|
||||
Updated = ff_machine:updated(St),
|
||||
Deadline = compute_retry_deadline(Updated),
|
||||
Timeout = compute_next_timeout(Now, Updated),
|
||||
check_next_timeout(Timeout, Now, Deadline).
|
||||
|
||||
-spec compute_retry_deadline(machinery:timestamp()) ->
|
||||
machinery:timestamp().
|
||||
compute_retry_deadline(Updated) ->
|
||||
RetryTimeLimit = genlib_app:env(ff_transfer, session_retry_time_limit, ?SESSION_RETRY_TIME_LIMIT),
|
||||
machinery_time:add_seconds(RetryTimeLimit, Updated).
|
||||
|
||||
-spec compute_next_timeout(machinery:timestamp(), machinery:timestamp()) ->
|
||||
timeout().
|
||||
compute_next_timeout(Now, Updated) ->
|
||||
MaxTimeout = genlib_app:env(ff_transfer, max_session_retry_timeout, ?MAX_SESSION_RETRY_TIMEOUT),
|
||||
Timeout0 = machinery_time:interval(Now, Updated) div 1000,
|
||||
erlang:min(MaxTimeout, erlang:max(1, Timeout0)).
|
||||
|
||||
-spec check_next_timeout(timeout(), machinery:timestamp(), machinery:timestamp()) ->
|
||||
{ok, machinery:timer()} | {error, deadline_reached}.
|
||||
check_next_timeout(Timeout, Now, Deadline) ->
|
||||
case check_deadline(machinery_time:add_seconds(Timeout, Now), Deadline) of
|
||||
ok ->
|
||||
{ok, {timeout, Timeout}};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec check_deadline(machinery:timestamp(), machinery:timestamp()) ->
|
||||
ok | {error, deadline_reached}.
|
||||
check_deadline({Now, _}, {Deadline, _}) ->
|
||||
check_deadline_(
|
||||
calendar:datetime_to_gregorian_seconds(Now),
|
||||
calendar:datetime_to_gregorian_seconds(Deadline)
|
||||
).
|
||||
|
||||
-spec check_deadline_(integer(), integer()) ->
|
||||
ok | {error, deadline_reached}.
|
||||
check_deadline_(Now, Deadline) when Now < Deadline ->
|
||||
ok;
|
||||
check_deadline_(Now, Deadline) when Now >= Deadline ->
|
||||
{error, deadline_reached}.
|
||||
|
||||
%%
|
||||
|
||||
-spec timer_action(machinery:timer()) ->
|
||||
machinery:action().
|
||||
timer_action(Timer) ->
|
||||
{set_timer, Timer}.
|
||||
|
||||
-spec tag_action(machinery:tag()) ->
|
||||
machinery:action().
|
||||
tag_action(Tag) ->
|
||||
{tag, Tag}.
|
||||
|
||||
backend() ->
|
||||
fistful:backend(?NS).
|
||||
|
||||
@ -292,10 +192,12 @@ call(Ref, Call) ->
|
||||
{error, ff_withdrawal_session:process_callback_error()}.
|
||||
|
||||
do_process_callback(Params, Machine) ->
|
||||
St= ff_machine:collapse(ff_withdrawal_session, Machine),
|
||||
St = ff_machine:collapse(ff_withdrawal_session, Machine),
|
||||
case ff_withdrawal_session:process_callback(Params, session(St)) of
|
||||
{ok, {Response, #{events := Events} = Result}} ->
|
||||
{{ok, Response}, Result#{events => ff_machine:emit_events(Events)}};
|
||||
{ok, {Response, Result}} ->
|
||||
{{ok, Response}, process_result(Result, St)};
|
||||
{error, {Reason, _Result}} ->
|
||||
{{error, Reason}, #{}}
|
||||
{{ok, Response}, Result};
|
||||
{error, {Reason, Result}} ->
|
||||
{{error, Reason}, Result}
|
||||
end.
|
||||
|
@ -341,13 +341,12 @@ create_source(IID, _C) ->
|
||||
ID = generate_id(),
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{id => ID, identity => IID, name => <<"XSource">>, currency => <<"RUB">>, resource => SrcResource},
|
||||
ok = ff_source_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_source:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
{ok, SrcM} = ff_source:get_machine(ID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -426,9 +426,8 @@ get_wallet_balance(ID) ->
|
||||
get_account_balance(ff_wallet:account(ff_wallet_machine:wallet(Machine))).
|
||||
|
||||
get_source_balance(ID) ->
|
||||
{ok, Machine} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(Machine),
|
||||
get_account_balance(ff_source:account(Source)).
|
||||
{ok, Machine} = ff_source:get_machine(ID),
|
||||
get_account_balance(ff_source:account(ff_source:get(Machine))).
|
||||
|
||||
get_account_balance(Account) ->
|
||||
{ok, {Amounts, Currency}} = ff_transaction:balance(
|
||||
@ -444,13 +443,12 @@ create_source(IID, _C) ->
|
||||
ID = generate_id(),
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{id => ID, identity => IID, name => <<"XSource">>, currency => <<"RUB">>, resource => SrcResource},
|
||||
ok = ff_source_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_source:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
{ok, SrcM} = ff_source:get_machine(ID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -423,9 +423,8 @@ get_wallet_balance(ID) ->
|
||||
get_account_balance(ff_wallet:account(ff_wallet_machine:wallet(Machine))).
|
||||
|
||||
get_source_balance(ID) ->
|
||||
{ok, Machine} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(Machine),
|
||||
get_account_balance(ff_source:account(Source)).
|
||||
{ok, Machine} = ff_source:get_machine(ID),
|
||||
get_account_balance(ff_source:account(ff_source:get(Machine))).
|
||||
|
||||
set_wallet_balance({Amount, Currency}, ID) ->
|
||||
TransactionID = generate_id(),
|
||||
@ -453,13 +452,12 @@ create_source(IID, _C) ->
|
||||
ID = generate_id(),
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{id => ID, identity => IID, name => <<"XSource">>, currency => <<"RUB">>, resource => SrcResource},
|
||||
ok = ff_source_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_source:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
{ok, SrcM} = ff_source:get_machine(ID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -473,9 +473,8 @@ get_wallet_balance(ID) ->
|
||||
get_account_balance(ff_wallet:account(ff_wallet_machine:wallet(Machine))).
|
||||
|
||||
get_source_balance(ID) ->
|
||||
{ok, Machine} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(Machine),
|
||||
get_account_balance(ff_source:account(Source)).
|
||||
{ok, Machine} = ff_source:get_machine(ID),
|
||||
get_account_balance(ff_source:account(ff_source:get(Machine))).
|
||||
|
||||
set_wallet_balance({Amount, Currency}, ID) ->
|
||||
TransactionID = generate_id(),
|
||||
@ -503,13 +502,12 @@ create_source(IID, _C) ->
|
||||
ID = generate_id(),
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{id => ID, identity => IID, name => <<"XSource">>, currency => <<"RUB">>, resource => SrcResource},
|
||||
ok = ff_source_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_source:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(ID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
{ok, SrcM} = ff_source:get_machine(ID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -1,6 +1,7 @@
|
||||
-module(ff_destination_SUITE).
|
||||
-module(ff_instrument_SUITE).
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_destination_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_source_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
-include_lib("stdlib/include/assert.hrl").
|
||||
|
||||
@ -15,10 +16,15 @@
|
||||
-export([end_per_testcase/2]).
|
||||
|
||||
% Tests
|
||||
-export([create_source_ok_test/1]).
|
||||
-export([create_destination_ok_test/1]).
|
||||
-export([create_source_identity_notfound_fail_test/1]).
|
||||
-export([create_destination_identity_notfound_fail_test/1]).
|
||||
-export([create_source_currency_notfound_fail_test/1]).
|
||||
-export([create_destination_currency_notfound_fail_test/1]).
|
||||
-export([get_source_ok_test/1]).
|
||||
-export([get_destination_ok_test/1]).
|
||||
-export([get_source_notfound_fail_test/1]).
|
||||
-export([get_destination_notfound_fail_test/1]).
|
||||
|
||||
-type config() :: ct_helper:config().
|
||||
@ -36,10 +42,15 @@ all() ->
|
||||
groups() ->
|
||||
[
|
||||
{default, [parallel], [
|
||||
create_source_ok_test,
|
||||
create_destination_ok_test,
|
||||
create_source_identity_notfound_fail_test,
|
||||
create_destination_identity_notfound_fail_test,
|
||||
create_source_currency_notfound_fail_test,
|
||||
create_destination_currency_notfound_fail_test,
|
||||
get_source_ok_test,
|
||||
get_destination_ok_test,
|
||||
get_source_notfound_fail_test,
|
||||
get_destination_notfound_fail_test
|
||||
]}
|
||||
].
|
||||
@ -78,6 +89,13 @@ end_per_testcase(_Name, _C) ->
|
||||
|
||||
%% Default group test cases
|
||||
|
||||
-spec create_source_ok_test(config()) -> test_return().
|
||||
create_source_ok_test(C) ->
|
||||
Party = create_party(C),
|
||||
IID = create_person_identity(Party, C),
|
||||
_SourceID = create_source(IID, C),
|
||||
ok.
|
||||
|
||||
-spec create_destination_ok_test(config()) -> test_return().
|
||||
create_destination_ok_test(C) ->
|
||||
Party = create_party(C),
|
||||
@ -85,6 +103,20 @@ create_destination_ok_test(C) ->
|
||||
_DestinationID = create_destination(IID, C),
|
||||
ok.
|
||||
|
||||
-spec create_source_identity_notfound_fail_test(config()) -> test_return().
|
||||
create_source_identity_notfound_fail_test(_C) ->
|
||||
IID = <<"BadIdentityID">>,
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{
|
||||
id => genlib:unique(),
|
||||
identity => IID,
|
||||
name => <<"XSource">>,
|
||||
currency => <<"RUB">>,
|
||||
resource => SrcResource
|
||||
},
|
||||
CreateResult = ff_source:create(Params, ff_entity_context:new()),
|
||||
?assertEqual({error, {identity, notfound}}, CreateResult).
|
||||
|
||||
-spec create_destination_identity_notfound_fail_test(config()) -> test_return().
|
||||
create_destination_identity_notfound_fail_test(C) ->
|
||||
IID = <<"BadIdentityID">>,
|
||||
@ -104,9 +136,24 @@ create_destination_identity_notfound_fail_test(C) ->
|
||||
currency => <<"RUB">>,
|
||||
resource => DestResource
|
||||
},
|
||||
CreateResult = ff_destination_machine:create(Params, ff_entity_context:new()),
|
||||
CreateResult = ff_destination:create(Params, ff_entity_context:new()),
|
||||
?assertEqual({error, {identity, notfound}}, CreateResult).
|
||||
|
||||
-spec create_source_currency_notfound_fail_test(config()) -> test_return().
|
||||
create_source_currency_notfound_fail_test(C) ->
|
||||
Party = create_party(C),
|
||||
IID = create_person_identity(Party, C),
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{
|
||||
id => genlib:unique(),
|
||||
identity => IID,
|
||||
name => <<"XSource">>,
|
||||
currency => <<"BadUnknownCurrency">>,
|
||||
resource => SrcResource
|
||||
},
|
||||
CreateResult = ff_source:create(Params, ff_entity_context:new()),
|
||||
?assertEqual({error, {currency, notfound}}, CreateResult).
|
||||
|
||||
-spec create_destination_currency_notfound_fail_test(config()) -> test_return().
|
||||
create_destination_currency_notfound_fail_test(C) ->
|
||||
Party = create_party(C),
|
||||
@ -128,15 +175,31 @@ create_destination_currency_notfound_fail_test(C) ->
|
||||
currency => <<"BadUnknownCurrency">>,
|
||||
resource => DestResource
|
||||
},
|
||||
CreateResult = ff_destination_machine:create(Params, ff_entity_context:new()),
|
||||
CreateResult = ff_destination:create(Params, ff_entity_context:new()),
|
||||
?assertEqual({error, {currency, notfound}}, CreateResult).
|
||||
|
||||
-spec get_source_ok_test(config()) -> test_return().
|
||||
get_source_ok_test(C) ->
|
||||
Party = create_party(C),
|
||||
IID = create_person_identity(Party, C),
|
||||
SourceID = create_source(IID, C),
|
||||
{ok, SourceMachine} = ff_source:get_machine(SourceID),
|
||||
?assertMatch(
|
||||
#{
|
||||
account := #{currency := <<"RUB">>},
|
||||
name := <<"XSource">>,
|
||||
resource := #{details := <<"Infinite source of cash">>, type := internal},
|
||||
status := authorized
|
||||
},
|
||||
ff_destination:get(SourceMachine)
|
||||
).
|
||||
|
||||
-spec get_destination_ok_test(config()) -> test_return().
|
||||
get_destination_ok_test(C) ->
|
||||
Party = create_party(C),
|
||||
IID = create_person_identity(Party, C),
|
||||
DestinationID = create_destination(IID, C),
|
||||
{ok, DestinationMachine} = ff_destination_machine:get(DestinationID),
|
||||
{ok, DestinationMachine} = ff_destination:get_machine(DestinationID),
|
||||
?assertMatch(
|
||||
#{
|
||||
account := #{currency := <<"RUB">>},
|
||||
@ -153,12 +216,16 @@ get_destination_ok_test(C) ->
|
||||
},
|
||||
status := authorized
|
||||
},
|
||||
ff_destination_machine:destination(DestinationMachine)
|
||||
ff_destination:get(DestinationMachine)
|
||||
).
|
||||
|
||||
-spec get_source_notfound_fail_test(config()) -> test_return().
|
||||
get_source_notfound_fail_test(_C) ->
|
||||
?assertEqual({error, notfound}, ff_source:get_machine(<<"BadID">>)).
|
||||
|
||||
-spec get_destination_notfound_fail_test(config()) -> test_return().
|
||||
get_destination_notfound_fail_test(_C) ->
|
||||
?assertEqual({error, notfound}, ff_destination_machine:get(<<"BadID">>)).
|
||||
?assertEqual({error, notfound}, ff_destination:get_machine(<<"BadID">>)).
|
||||
|
||||
%% Common functions
|
||||
|
||||
@ -184,23 +251,41 @@ create_identity(Party, Name, ProviderID, ClassID, _C) ->
|
||||
),
|
||||
ID.
|
||||
|
||||
create_destination(IID, C) ->
|
||||
DestResource = {bank_card, #{bank_card => ct_cardstore:bank_card(<<"4150399999000900">>, {12, 2025}, C)}},
|
||||
DestID = create_destination(IID, <<"XDestination">>, <<"RUB">>, DestResource),
|
||||
create_instrument(Type, IdentityID, Name, Currency, Resource, C) ->
|
||||
ID = genlib:unique(),
|
||||
ok = create_instrument(
|
||||
Type,
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new(),
|
||||
C
|
||||
),
|
||||
ID.
|
||||
create_instrument(destination, Params, Ctx, _C) ->
|
||||
ff_destination:create(Params, Ctx);
|
||||
create_instrument(source, Params, Ctx, _C) ->
|
||||
ff_source:create(Params, Ctx).
|
||||
|
||||
create_source(IID, C) ->
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
SrcID = create_instrument(source, IID, <<"XSource">>, <<"RUB">>, SrcResource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, SrcM} = ff_source:get_machine(SrcID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
SrcID.
|
||||
|
||||
create_destination(IID, C) ->
|
||||
DestResource = {bank_card, #{bank_card => ct_cardstore:bank_card(<<"4150399999000900">>, {12, 2025}, C)}},
|
||||
DestID = create_instrument(destination, IID, <<"XDestination">>, <<"RUB">>, DestResource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
),
|
||||
DestID.
|
||||
|
||||
create_destination(IdentityID, Name, Currency, Resource) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_destination_machine:create(
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new()
|
||||
),
|
||||
ID.
|
@ -1,180 +0,0 @@
|
||||
-module(ff_source_SUITE).
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_source_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
-include_lib("stdlib/include/assert.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]).
|
||||
|
||||
% Tests
|
||||
-export([create_source_ok_test/1]).
|
||||
|
||||
-export([create_source_identity_notfound_fail_test/1]).
|
||||
-export([create_source_currency_notfound_fail_test/1]).
|
||||
-export([get_source_ok_test/1]).
|
||||
-export([get_source_notfound_fail_test/1]).
|
||||
|
||||
-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().
|
||||
|
||||
-spec all() -> [test_case_name() | {group, group_name()}].
|
||||
all() ->
|
||||
[
|
||||
{group, default}
|
||||
].
|
||||
|
||||
-spec groups() -> [{group_name(), list(), [test_case_name()]}].
|
||||
groups() ->
|
||||
[
|
||||
{default, [parallel], [
|
||||
create_source_ok_test,
|
||||
create_source_identity_notfound_fail_test,
|
||||
create_source_currency_notfound_fail_test,
|
||||
get_source_ok_test,
|
||||
get_source_notfound_fail_test
|
||||
]}
|
||||
].
|
||||
|
||||
-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().
|
||||
|
||||
%% Default group test cases
|
||||
|
||||
-spec create_source_ok_test(config()) -> test_return().
|
||||
create_source_ok_test(C) ->
|
||||
Party = create_party(C),
|
||||
IID = create_person_identity(Party, C),
|
||||
_SourceID = create_source(IID, C),
|
||||
ok.
|
||||
|
||||
-spec create_source_identity_notfound_fail_test(config()) -> test_return().
|
||||
create_source_identity_notfound_fail_test(_C) ->
|
||||
IID = <<"BadIdentityID">>,
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{
|
||||
id => genlib:unique(),
|
||||
identity => IID,
|
||||
name => <<"XSource">>,
|
||||
currency => <<"RUB">>,
|
||||
resource => SrcResource
|
||||
},
|
||||
CreateResult = ff_source_machine:create(Params, ff_entity_context:new()),
|
||||
?assertEqual({error, {identity, notfound}}, CreateResult).
|
||||
|
||||
-spec create_source_currency_notfound_fail_test(config()) -> test_return().
|
||||
create_source_currency_notfound_fail_test(C) ->
|
||||
Party = create_party(C),
|
||||
IID = create_person_identity(Party, C),
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
Params = #{
|
||||
id => genlib:unique(),
|
||||
identity => IID,
|
||||
name => <<"XSource">>,
|
||||
currency => <<"BadUnknownCurrency">>,
|
||||
resource => SrcResource
|
||||
},
|
||||
CreateResult = ff_source_machine:create(Params, ff_entity_context:new()),
|
||||
?assertEqual({error, {currency, notfound}}, CreateResult).
|
||||
|
||||
-spec get_source_ok_test(config()) -> test_return().
|
||||
get_source_ok_test(C) ->
|
||||
Party = create_party(C),
|
||||
IID = create_person_identity(Party, C),
|
||||
SourceID = create_source(IID, C),
|
||||
{ok, SourceMachine} = ff_source_machine:get(SourceID),
|
||||
?assertMatch(
|
||||
#{
|
||||
account := #{currency := <<"RUB">>},
|
||||
name := <<"XSource">>,
|
||||
resource := #{details := <<"Infinite source of cash">>, type := internal},
|
||||
status := authorized
|
||||
},
|
||||
ff_source_machine:source(SourceMachine)
|
||||
).
|
||||
|
||||
-spec get_source_notfound_fail_test(config()) -> test_return().
|
||||
get_source_notfound_fail_test(_C) ->
|
||||
?assertEqual({error, notfound}, ff_source_machine:get(<<"BadID">>)).
|
||||
|
||||
%% Common functions
|
||||
|
||||
create_party(_C) ->
|
||||
ID = genlib:bsuuid(),
|
||||
_ = ff_party:create(ID),
|
||||
ID.
|
||||
|
||||
create_person_identity(Party, C) ->
|
||||
create_person_identity(Party, C, <<"good-one">>).
|
||||
|
||||
create_person_identity(Party, C, ProviderID) ->
|
||||
create_identity(Party, ProviderID, <<"person">>, C).
|
||||
|
||||
create_identity(Party, ProviderID, ClassID, C) ->
|
||||
create_identity(Party, <<"Identity Name">>, ProviderID, ClassID, C).
|
||||
|
||||
create_identity(Party, Name, ProviderID, ClassID, _C) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_identity_machine:create(
|
||||
#{id => ID, name => Name, party => Party, provider => ProviderID, class => ClassID},
|
||||
#{<<"com.rbkmoney.wapi">> => #{<<"name">> => Name}}
|
||||
),
|
||||
ID.
|
||||
|
||||
create_source(IID, _C) ->
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
SrcID = create_source(IID, <<"XSource">>, <<"RUB">>, SrcResource),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(SrcID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
end
|
||||
),
|
||||
SrcID.
|
||||
create_source(IdentityID, Name, Currency, Resource) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_source_machine:create(
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new()
|
||||
),
|
||||
ID.
|
@ -397,9 +397,8 @@ get_wallet_balance(ID) ->
|
||||
get_account_balance(ff_wallet:account(ff_wallet_machine:wallet(Machine))).
|
||||
|
||||
get_destination_balance(ID) ->
|
||||
{ok, Machine} = ff_destination_machine:get(ID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
get_account_balance(ff_destination:account(Destination)).
|
||||
{ok, Machine} = ff_destination:get_machine(ID),
|
||||
get_account_balance(ff_destination:account(ff_destination:get(Machine))).
|
||||
|
||||
get_account_balance(Account) ->
|
||||
{ok, {Amounts, Currency}} = ff_transaction:balance(
|
||||
@ -408,21 +407,20 @@ get_account_balance(Account) ->
|
||||
),
|
||||
{ff_indef:current(Amounts), ff_indef:to_range(Amounts), Currency}.
|
||||
|
||||
create_source(IdentityID, Name, Currency, Resource) ->
|
||||
create_instrument(Type, IdentityID, Name, Currency, Resource, C) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_source_machine:create(
|
||||
ok = create_instrument(
|
||||
Type,
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new()
|
||||
ff_entity_context:new(),
|
||||
C
|
||||
),
|
||||
ID.
|
||||
|
||||
create_destination(IdentityID, Name, Currency, Resource) ->
|
||||
ID = genlib:unique(),
|
||||
ok = ff_destination_machine:create(
|
||||
#{id => ID, identity => IdentityID, name => Name, currency => Currency, resource => Resource},
|
||||
ff_entity_context:new()
|
||||
),
|
||||
ID.
|
||||
create_instrument(destination, Params, Ctx, _C) ->
|
||||
ff_destination:create(Params, Ctx);
|
||||
create_instrument(source, Params, Ctx, _C) ->
|
||||
ff_source:create(Params, Ctx).
|
||||
|
||||
generate_id() ->
|
||||
genlib:to_binary(genlib_time:ticks()).
|
||||
@ -436,15 +434,15 @@ call_admin(Fun, Args) ->
|
||||
}),
|
||||
ff_woody_client:call(Client, Request).
|
||||
|
||||
create_source(IID, _C) ->
|
||||
create_source(IID, C) ->
|
||||
% Create source
|
||||
SrcResource = #{type => internal, details => <<"Infinite source of cash">>},
|
||||
SrcID = create_source(IID, <<"XSource">>, <<"RUB">>, SrcResource),
|
||||
SrcID = create_instrument(source, IID, <<"XSource">>, <<"RUB">>, SrcResource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, SrcM} = ff_source_machine:get(SrcID),
|
||||
Source = ff_source_machine:source(SrcM),
|
||||
ff_source:status(Source)
|
||||
{ok, SrcM} = ff_source:get_machine(SrcID),
|
||||
ff_source:status(ff_source:get(SrcM))
|
||||
end
|
||||
),
|
||||
SrcID.
|
||||
@ -467,29 +465,27 @@ process_deposit(SrcID, WalID) ->
|
||||
|
||||
create_destination(IID, C) ->
|
||||
DestResource = {bank_card, #{bank_card => ct_cardstore:bank_card(<<"4150399999000900">>, {12, 2025}, C)}},
|
||||
DestID = create_destination(IID, <<"XDesination">>, <<"RUB">>, DestResource),
|
||||
DestID = create_instrument(destination, IID, <<"XDesination">>, <<"RUB">>, DestResource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
),
|
||||
DestID.
|
||||
|
||||
create_crypto_destination(IID, _C) ->
|
||||
create_crypto_destination(IID, C) ->
|
||||
Resource = {crypto_wallet, #{crypto_wallet => #{
|
||||
id => <<"a30e277c07400c9940628828949efd48">>,
|
||||
currency => {litecoin, #{}}
|
||||
}}},
|
||||
DestID = create_destination(IID, <<"CryptoDestination">>, <<"RUB">>, Resource),
|
||||
DestID = create_instrument(destination, IID, <<"CryptoDestination">>, <<"RUB">>, Resource, C),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
),
|
||||
DestID.
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
-include_lib("stdlib/include/assert.hrl").
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_withdrawal_session_thrift.hrl").
|
||||
-include_lib("shumpune_proto/include/shumpune_shumpune_thrift.hrl").
|
||||
|
||||
%% Common test API
|
||||
@ -18,7 +17,6 @@
|
||||
|
||||
%% Tests
|
||||
-export([session_fail_test/1]).
|
||||
-export([session_repair_test/1]).
|
||||
-export([quote_fail_test/1]).
|
||||
-export([route_not_found_fail_test/1]).
|
||||
-export([provider_operations_forbidden_fail_test/1]).
|
||||
@ -72,7 +70,6 @@ groups() ->
|
||||
[
|
||||
{default, [parallel], [
|
||||
session_fail_test,
|
||||
session_repair_test,
|
||||
quote_fail_test,
|
||||
route_not_found_fail_test,
|
||||
provider_operations_forbidden_fail_test,
|
||||
@ -585,43 +582,6 @@ provider_callback_test(C) ->
|
||||
% Check that session is still alive
|
||||
?assertEqual({ok, #{payload => CallbackPayload}}, call_process_callback(Callback)).
|
||||
|
||||
-spec session_repair_test(config()) -> test_return().
|
||||
session_repair_test(C) ->
|
||||
Currency = <<"RUB">>,
|
||||
Cash = {700700, Currency},
|
||||
#{
|
||||
wallet_id := WalletID,
|
||||
destination_id := DestinationID
|
||||
} = prepare_standard_environment(Cash, C),
|
||||
WithdrawalID = generate_id(),
|
||||
WithdrawalParams = #{
|
||||
id => WithdrawalID,
|
||||
destination_id => DestinationID,
|
||||
wallet_id => WalletID,
|
||||
body => Cash,
|
||||
quote => #{
|
||||
cash_from => {700700, <<"RUB">>},
|
||||
cash_to => {700700, <<"RUB">>},
|
||||
created_at => <<"2016-03-22T06:12:27Z">>,
|
||||
expires_on => <<"2016-03-22T06:12:27Z">>,
|
||||
route => ff_withdrawal_routing:make_route(11, 1),
|
||||
quote_data => #{<<"test">> => <<"fatal">>}
|
||||
}
|
||||
},
|
||||
Callback = #{
|
||||
tag => <<"cb_", WithdrawalID/binary>>,
|
||||
payload => <<"super_secret">>
|
||||
},
|
||||
ok = ff_withdrawal_machine:create(WithdrawalParams, ff_entity_context:new()),
|
||||
?assertEqual(pending, await_session_processing_status(WithdrawalID, pending)),
|
||||
SessionID = get_session_id(WithdrawalID),
|
||||
?assertEqual(<<"callback_processing">>, await_session_adapter_state(SessionID, <<"callback_processing">>)),
|
||||
?assertError({failed, _, _}, call_process_callback(Callback)),
|
||||
timer:sleep(3000),
|
||||
?assertEqual(pending, await_session_processing_status(WithdrawalID, pending)),
|
||||
ok = repair_withdrawal_session(WithdrawalID),
|
||||
?assertEqual(succeeded, await_final_withdrawal_status(WithdrawalID)).
|
||||
|
||||
%% Utils
|
||||
|
||||
prepare_standard_environment(WithdrawalCash, C) ->
|
||||
@ -766,13 +726,12 @@ create_destination(IID, Currency, Token, C) ->
|
||||
end,
|
||||
Resource = {bank_card, #{bank_card => NewStoreResource}},
|
||||
Params = #{id => ID, identity => IID, name => <<"XDesination">>, currency => Currency, resource => Resource},
|
||||
ok = ff_destination_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_destination:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, Machine} = ff_destination_machine:get(ID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
ff_destination:status(Destination)
|
||||
{ok, Machine} = ff_destination:get_machine(ID),
|
||||
ff_destination:status(ff_destination:get(Machine))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
@ -784,13 +743,12 @@ create_crypto_destination(IID, _C) ->
|
||||
currency => {litecoin, #{}}
|
||||
}}},
|
||||
Params = #{id => ID, identity => IID, name => <<"CryptoDestination">>, currency => <<"RUB">>, resource => Resource},
|
||||
ok = ff_destination_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_destination:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, Machine} = ff_destination_machine:get(ID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(ID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
@ -837,24 +795,3 @@ make_dummy_party_change(PartyID) ->
|
||||
|
||||
call_process_callback(Callback) ->
|
||||
ff_withdrawal_session_machine:process_callback(Callback).
|
||||
|
||||
repair_withdrawal_session(WithdrawalID) ->
|
||||
SessionID = get_session_id(WithdrawalID),
|
||||
{ok, ok} = call_session_repair(SessionID, {set_session_result, #wthd_session_SetResultRepair{
|
||||
result = {success, #wthd_session_SessionResultSuccess{
|
||||
trx_info = #'TransactionInfo'{
|
||||
id = SessionID,
|
||||
extra = #{}
|
||||
}
|
||||
}}
|
||||
}}),
|
||||
ok.
|
||||
|
||||
call_session_repair(SessionID, Scenario) ->
|
||||
Service = {ff_proto_withdrawal_session_thrift, 'Repairer'},
|
||||
Request = {Service, 'Repair', [SessionID, Scenario]},
|
||||
Client = ff_woody_client:new(#{
|
||||
url => <<"http://localhost:8022/v1/repair/withdrawal/session">>,
|
||||
event_handler => scoper_woody_event_handler
|
||||
}),
|
||||
ff_woody_client:call(Client, Request).
|
||||
|
@ -428,9 +428,8 @@ get_wallet_balance(ID) ->
|
||||
get_account_balance(ff_wallet:account(ff_wallet_machine:wallet(Machine))).
|
||||
|
||||
get_destination_balance(ID) ->
|
||||
{ok, Machine} = ff_destination_machine:get(ID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
get_account_balance(ff_destination:account(Destination)).
|
||||
{ok, Machine} = ff_destination:get_machine(ID),
|
||||
get_account_balance(ff_destination:account(ff_destination:get(Machine))).
|
||||
|
||||
get_account_balance(Account) ->
|
||||
{ok, {Amounts, Currency}} = ff_transaction:balance(
|
||||
@ -446,13 +445,12 @@ create_destination(IID, C) ->
|
||||
ID = generate_id(),
|
||||
Resource = {bank_card, #{bank_card => ct_cardstore:bank_card(<<"4150399999000900">>, {12, 2025}, C)}},
|
||||
Params = #{id => ID, identity => IID, name => <<"XDesination">>, currency => <<"RUB">>, resource => Resource},
|
||||
ok = ff_destination_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_destination:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, Machine} = ff_destination_machine:get(ID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
ff_destination:status(Destination)
|
||||
{ok, Machine} = ff_destination:get_machine(ID),
|
||||
ff_destination:status(ff_destination:get(Machine))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -338,13 +338,12 @@ create_destination(IID, Currency, C) ->
|
||||
StoreSource = ct_cardstore:bank_card(<<"4150399999000900">>, {12, Y + 1}, C),
|
||||
Resource = {bank_card, #{bank_card => StoreSource}},
|
||||
Params = #{id => ID, identity => IID, name => <<"XDesination">>, currency => Currency, resource => Resource},
|
||||
ok = ff_destination_machine:create(Params, ff_entity_context:new()),
|
||||
ok = ff_destination:create(Params, ff_entity_context:new()),
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, Machine} = ff_destination_machine:get(ID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
ff_destination:status(Destination)
|
||||
{ok, Machine} = ff_destination:get_machine(ID),
|
||||
ff_destination:status(ff_destination:get(Machine))
|
||||
end
|
||||
),
|
||||
ID.
|
||||
|
@ -82,7 +82,6 @@
|
||||
|
||||
-type create_error() ::
|
||||
{provider, notfound} |
|
||||
{party, notfound} |
|
||||
{identity_class, notfound} |
|
||||
ff_party:inaccessibility() |
|
||||
invalid.
|
||||
|
@ -180,9 +180,7 @@ is_accessible(ID) ->
|
||||
#domain_Party{suspension = {suspended, _}} ->
|
||||
{error, {inaccessible, suspended}};
|
||||
#domain_Party{} ->
|
||||
{ok, accessible};
|
||||
#payproc_PartyNotFound{} ->
|
||||
{error, notfound}
|
||||
{ok, accessible}
|
||||
end.
|
||||
|
||||
-spec get_revision(id()) ->
|
||||
@ -442,10 +440,8 @@ do_get_party(ID) ->
|
||||
case Result of
|
||||
{ok, Party} ->
|
||||
Party;
|
||||
{error, #payproc_PartyNotFound{} = Reason} ->
|
||||
Reason;
|
||||
{error, Unexpected} ->
|
||||
error(Unexpected)
|
||||
{error, Reason} ->
|
||||
error(Reason)
|
||||
end.
|
||||
|
||||
do_get_contract(ID, ContractID) ->
|
||||
|
@ -294,9 +294,8 @@ maybe_migrate_account({wallet, WalletID}) ->
|
||||
{ok, Machine} = ff_wallet_machine:get(WalletID),
|
||||
ff_wallet:account(ff_wallet_machine:wallet(Machine));
|
||||
maybe_migrate_account({destination, DestinationID}) ->
|
||||
{ok, Machine} = ff_destination_machine:get(DestinationID),
|
||||
Destination = ff_destination_machine:destination(Machine),
|
||||
ff_destination:account(Destination);
|
||||
{ok, Machine} = ff_destination:get_machine(DestinationID),
|
||||
ff_destination:account(ff_destination:get(Machine));
|
||||
maybe_migrate_account(Account) when is_map(Account) ->
|
||||
Account.
|
||||
|
||||
|
@ -129,10 +129,9 @@ validate_result(Mod, #{history := History} = Machine, #{events := NewEvents}) ->
|
||||
{ok, valid}
|
||||
catch
|
||||
error:Error:Stack ->
|
||||
Stacktrace = genlib_format:format_stacktrace(Stack),
|
||||
logger:warning("Invalid repair result: ~p, Stack: ~p", [Error, Stacktrace], #{
|
||||
logger:warning("Invalid repair result: ~p", [Error], #{
|
||||
error => genlib:format(Error),
|
||||
stacktrace => Stacktrace
|
||||
stacktrace => genlib_format:format_stacktrace(Stack)
|
||||
}),
|
||||
{error, unexpected_failure}
|
||||
end.
|
||||
|
@ -55,10 +55,6 @@
|
||||
-type status() :: {success, transaction_info()} | {failure, failure()}.
|
||||
-type timer() :: {deadline, binary()} | {timeout, integer()}.
|
||||
|
||||
%%
|
||||
|
||||
-define(DUMMY_QUOTE_ERROR_FATAL, {obj, #{{str, <<"test">>} => {str, <<"fatal">>}}}).
|
||||
|
||||
%%
|
||||
%% API
|
||||
%%
|
||||
@ -106,10 +102,6 @@ get_quote(_Quote, _Options) ->
|
||||
transaction_info => transaction_info()
|
||||
}} when
|
||||
CallbackTag :: binary().
|
||||
handle_callback(_Callback, #{quote := #wthadpt_Quote{quote_data = QuoteData}}, _State, _Options) when
|
||||
QuoteData =:= ?DUMMY_QUOTE_ERROR_FATAL
|
||||
->
|
||||
erlang:error(spanish_inquisition);
|
||||
handle_callback(#{payload := Payload}, _Withdrawal, _State, _Options) ->
|
||||
{ok, #{
|
||||
intent => {finish, success},
|
||||
|
@ -88,7 +88,7 @@ get_context_by_id(p2p_template, TemplateID, WoodyCtx) ->
|
||||
{error, notfound}
|
||||
end;
|
||||
get_context_by_id(w2w_transfer, W2WTransferID, WoodyCtx) ->
|
||||
Request = {fistful_w2w_transfer, 'GetContext', [W2WTransferID]},
|
||||
Request = {w2w_transfer, 'GetContext', [W2WTransferID]},
|
||||
case wapi_handler_utils:service_call(Request, WoodyCtx) of
|
||||
{ok, Context} ->
|
||||
Context;
|
||||
@ -96,7 +96,7 @@ get_context_by_id(w2w_transfer, W2WTransferID, WoodyCtx) ->
|
||||
{error, notfound}
|
||||
end;
|
||||
get_context_by_id(p2p_transfer, P2PTransferID, WoodyCtx) ->
|
||||
Request = {fistful_p2p_transfer, 'GetContext', [P2PTransferID]},
|
||||
Request = {p2p_transfer, 'GetContext', [P2PTransferID]},
|
||||
case wapi_handler_utils:service_call(Request, WoodyCtx) of
|
||||
{ok, Context} ->
|
||||
Context;
|
||||
|
@ -192,24 +192,19 @@ quote_transfer(ID, Params, HandlerContext) ->
|
||||
{error, {token, _}} |
|
||||
{error, {external_id_conflict, _}}.
|
||||
|
||||
create_transfer(ID, #{<<"quoteToken">> := Token} = Params, HandlerContext) ->
|
||||
create_transfer(ID, #{quote_token := Token} = Params, HandlerContext) ->
|
||||
case uac_authorizer_jwt:verify(Token, #{}) of
|
||||
{ok, {_, _, VerifiedToken}} ->
|
||||
case decode_and_validate_token_payload(VerifiedToken, ID, HandlerContext) of
|
||||
{ok, Quote} ->
|
||||
do_create_transfer(ID, Params#{<<"quote">> => Quote}, HandlerContext);
|
||||
create_transfer(ID, Params#{<<"quote">> => Quote}, HandlerContext);
|
||||
{error, token_expired} ->
|
||||
{error, {token, expired}};
|
||||
{error, Error} ->
|
||||
{error, {token, {not_verified, Error}}}
|
||||
{error, {token, expired}}
|
||||
end;
|
||||
{error, Error} ->
|
||||
{error, {token, {not_verified, Error}}}
|
||||
end;
|
||||
create_transfer(ID, Params, HandlerContext) ->
|
||||
do_create_transfer(ID, Params, HandlerContext).
|
||||
|
||||
do_create_transfer(ID, Params, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(p2p_template, ID, HandlerContext) of
|
||||
ok ->
|
||||
TransferID = context_transfer_id(HandlerContext),
|
||||
@ -217,7 +212,7 @@ do_create_transfer(ID, Params, HandlerContext) ->
|
||||
ok ->
|
||||
MarshaledContext = marshal_context(wapi_backend_utils:make_ctx(Params, HandlerContext)),
|
||||
MarshaledParams = marshal_transfer_params(Params#{<<"id">> => TransferID}),
|
||||
call_create_transfer(ID, MarshaledParams, MarshaledContext, HandlerContext);
|
||||
create_transfer(ID, MarshaledParams, MarshaledContext, HandlerContext);
|
||||
{error, {external_id_conflict, _}} = Error ->
|
||||
Error
|
||||
end;
|
||||
@ -226,8 +221,7 @@ do_create_transfer(ID, Params, HandlerContext) ->
|
||||
{error, notfound} ->
|
||||
{error, {p2p_template, notfound}}
|
||||
end.
|
||||
|
||||
call_create_transfer(ID, MarshaledParams, MarshaledContext, HandlerContext) ->
|
||||
create_transfer(ID, MarshaledParams, MarshaledContext, HandlerContext) ->
|
||||
Request = {fistful_p2p_template, 'CreateTransfer', [ID, MarshaledParams, MarshaledContext]},
|
||||
case wapi_handler_utils:service_call(Request, HandlerContext) of
|
||||
{ok, Transfer} ->
|
||||
@ -472,10 +466,10 @@ validate_transfer_id(TransferID, Params, #{woody_context := WoodyContext} = Hand
|
||||
|
||||
validate_identity_id(IdentityID, TemplateID, HandlerContext) ->
|
||||
case get(TemplateID, HandlerContext) of
|
||||
{ok, #{<<"identityID">> := IdentityID}} ->
|
||||
{ok, #{identity_id := IdentityID}} ->
|
||||
ok;
|
||||
{ok, _Template} ->
|
||||
{error, identity_mismatch};
|
||||
{ok, _ } ->
|
||||
{error, {token, {not_verified, identity_mismatch}}};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
@ -597,27 +591,23 @@ marshal_transfer_params(#{
|
||||
|
||||
marshal_sender(#{
|
||||
<<"token">> := Token,
|
||||
<<"authData">> := AuthData,
|
||||
<<"contactInfo">> := ContactInfo
|
||||
}) ->
|
||||
ResourceBankCard = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
Resource = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
unrecognized ->
|
||||
BankCard = wapi_utils:base64url_to_map(Token),
|
||||
#'ResourceBankCard'{
|
||||
{bank_card, #'ResourceBankCard'{
|
||||
bank_card = #'BankCard'{
|
||||
token = maps:get(<<"token">>, BankCard),
|
||||
bin = maps:get(<<"bin">>, BankCard),
|
||||
masked_pan = maps:get(<<"lastDigits">>, BankCard)
|
||||
}
|
||||
};
|
||||
}};
|
||||
{ok, BankCard} ->
|
||||
#'ResourceBankCard'{bank_card = BankCard}
|
||||
{bank_card, #'ResourceBankCard'{bank_card = BankCard}}
|
||||
end,
|
||||
ResourceBankCardAuth = ResourceBankCard#'ResourceBankCard'{
|
||||
auth_data = {session_data, #'SessionAuthData'{id = AuthData}}
|
||||
},
|
||||
{resource, #p2p_transfer_RawResource{
|
||||
resource = {bank_card, ResourceBankCardAuth},
|
||||
resource = Resource,
|
||||
contact_info = marshal_contact_info(ContactInfo)
|
||||
}}.
|
||||
|
||||
|
@ -35,31 +35,13 @@
|
||||
| {p2p_transfer, notfound}
|
||||
.
|
||||
|
||||
-type error_get_events()
|
||||
:: error_get()
|
||||
| {token, {unsupported_version, _}}
|
||||
| {token, {not_verified, _}}
|
||||
.
|
||||
|
||||
-export([create_transfer/2]).
|
||||
-export([get_transfer/2]).
|
||||
-export([quote_transfer/2]).
|
||||
-export([get_transfer_events/3]).
|
||||
-export([get_transfer/2]).
|
||||
|
||||
-import(ff_pipeline, [do/1, unwrap/1]).
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_p2p_transfer_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_p2p_session_thrift.hrl").
|
||||
|
||||
-define(DEFAULT_EVENTS_LIMIT, 50).
|
||||
-define(CONTINUATION_TRANSFER, <<"p2p_transfer_event_id">>).
|
||||
-define(CONTINUATION_SESSION, <<"p2p_session_event_id">>).
|
||||
|
||||
-type event() :: #p2p_transfer_Event{} | #p2p_session_Event{}.
|
||||
-type event_service() :: fistful_p2p_transfer | fistful_p2p_session.
|
||||
-type event_range() :: #'EventRange'{}.
|
||||
-type event_id() :: ff_proto_base_thrift:'EventID'() | undefined.
|
||||
|
||||
-spec create_transfer(req_data(), handler_context()) ->
|
||||
{ok, response_data()} | {error, error_create()}.
|
||||
@ -81,7 +63,7 @@ create_transfer(Params = #{<<"identityID">> := IdentityID}, HandlerContext) ->
|
||||
-spec get_transfer(req_data(), handler_context()) ->
|
||||
{ok, response_data()} | {error, error_get()}.
|
||||
get_transfer(ID, HandlerContext) ->
|
||||
Request = {fistful_p2p_transfer, 'Get', [ID, #'EventRange'{}]},
|
||||
Request = {p2p_transfer, 'Get', [ID, #'EventRange'{}]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, TransferThrift} ->
|
||||
case wapi_access_backend:check_resource(p2p_transfer, TransferThrift, HandlerContext) of
|
||||
@ -107,23 +89,10 @@ quote_transfer(Params = #{<<"identityID">> := IdentityID}, HandlerContext) ->
|
||||
{error, {identity, notfound}}
|
||||
end.
|
||||
|
||||
-spec get_transfer_events(id(), binary() | undefined, handler_context()) ->
|
||||
{ok, response_data()} | {error, error_get_events()}.
|
||||
|
||||
get_transfer_events(ID, Token, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(p2p_transfer, ID, HandlerContext) of
|
||||
ok ->
|
||||
do_get_events(ID, Token, HandlerContext);
|
||||
{error, unauthorized} ->
|
||||
{error, {p2p_transfer, unauthorized}};
|
||||
{error, notfound} ->
|
||||
{error, {p2p_transfer, notfound}}
|
||||
end.
|
||||
|
||||
%% Internal
|
||||
|
||||
do_quote_transfer(Params, HandlerContext) ->
|
||||
Request = {fistful_p2p_transfer, 'GetQuote', [marshal_quote_params(Params)]},
|
||||
Request = {p2p_transfer, 'GetQuote', [marshal_quote_params(Params)]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Quote} ->
|
||||
PartyID = wapi_handler_utils:get_owner(HandlerContext),
|
||||
@ -156,7 +125,7 @@ do_create_transfer(ID, Params, HandlerContext) ->
|
||||
do(fun() ->
|
||||
Context = wapi_backend_utils:make_ctx(Params, HandlerContext),
|
||||
TransferParams = unwrap(build_transfer_params(Params#{<<"id">> => ID})),
|
||||
Request = {fistful_p2p_transfer, 'Create', [TransferParams, marshal(context, Context)]},
|
||||
Request = {p2p_transfer, 'Create', [TransferParams, marshal(context, Context)]},
|
||||
unwrap(process_p2p_transfer_call(Request, HandlerContext))
|
||||
end).
|
||||
|
||||
@ -205,202 +174,6 @@ authorize_p2p_quote_token(_Quote, _IdentityID) ->
|
||||
service_call(Params, HandlerContext) ->
|
||||
wapi_handler_utils:service_call(Params, HandlerContext).
|
||||
|
||||
%% @doc
|
||||
%% The function returns the list of events for the specified Transfer.
|
||||
%%
|
||||
%% First get Transfer for extract the Session ID.
|
||||
%%
|
||||
%% Then, the Continuation Token is verified. Latest EventIDs of Transfer and
|
||||
%% Session are stored in the token for possibility partial load of events.
|
||||
%%
|
||||
%% The events are retrieved no lesser ID than those stored in the token, and count
|
||||
%% is limited by wapi.events_fetch_limit option or ?DEFAULT_EVENTS_LIMIT
|
||||
%%
|
||||
%% The received events are then mixed and ordered by the time of occurrence.
|
||||
%% The resulting set is returned to the client.
|
||||
%%
|
||||
%% @todo Now there is always only zero or one session. But there may be more than one
|
||||
%% session in the future, so the code of polling sessions and mixing results
|
||||
%% will need to be rewrited.
|
||||
|
||||
-spec do_get_events(id(), binary() | undefined, handler_context()) ->
|
||||
{ok, response_data()} | {error, error_get_events()}.
|
||||
|
||||
do_get_events(ID, Token, HandlerContext) ->
|
||||
do(fun() ->
|
||||
PartyID = wapi_handler_utils:get_owner(HandlerContext),
|
||||
SessionID = unwrap(request_session_id(ID, HandlerContext)),
|
||||
|
||||
DecodedToken = unwrap(continuation_token_unpack(Token, PartyID)),
|
||||
PrevTransferCursor = continuation_token_cursor(p2p_transfer, DecodedToken),
|
||||
PrevSessionCursor = continuation_token_cursor(p2p_session, DecodedToken),
|
||||
|
||||
{TransferEvents, TransferCursor} = unwrap(events_collect(
|
||||
fistful_p2p_transfer,
|
||||
ID,
|
||||
events_range(PrevTransferCursor),
|
||||
HandlerContext,
|
||||
[]
|
||||
)),
|
||||
|
||||
{SessionEvents, SessionCursor} = unwrap(events_collect(
|
||||
fistful_p2p_session,
|
||||
SessionID,
|
||||
events_range(PrevSessionCursor),
|
||||
HandlerContext,
|
||||
[]
|
||||
)),
|
||||
|
||||
NewTransferCursor = events_max(PrevTransferCursor, TransferCursor),
|
||||
NewSessionCursor = events_max(PrevSessionCursor, SessionCursor),
|
||||
NewToken = unwrap(continuation_token_pack(NewTransferCursor, NewSessionCursor, PartyID)),
|
||||
|
||||
Events = {NewToken, events_merge([TransferEvents, SessionEvents])},
|
||||
unmarshal_events(Events)
|
||||
end).
|
||||
|
||||
%% get p2p_transfer from backend and return last sesssion ID
|
||||
|
||||
-spec request_session_id(id(), handler_context()) ->
|
||||
{ok, undefined | id ()} | {error, {p2p_transfer, notfound}}.
|
||||
|
||||
request_session_id(ID, HandlerContext) ->
|
||||
Request = {fistful_p2p_transfer, 'Get', [ID, #'EventRange'{}]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, #p2p_transfer_P2PTransferState{sessions = []}} ->
|
||||
{ok, undefined};
|
||||
{ok, #p2p_transfer_P2PTransferState{sessions = Sessions}} ->
|
||||
Session = lists:last(Sessions),
|
||||
{ok, Session#p2p_transfer_SessionState.id};
|
||||
{exception, #fistful_P2PNotFound{}} ->
|
||||
{error, {p2p_transfer, notfound}}
|
||||
end.
|
||||
|
||||
%% create and code a new continuation token
|
||||
|
||||
continuation_token_pack(TransferCursor, SessionCursor, PartyID) ->
|
||||
Token = genlib_map:compact(#{
|
||||
<<"version">> => 1,
|
||||
?CONTINUATION_TRANSFER => TransferCursor,
|
||||
?CONTINUATION_SESSION => SessionCursor
|
||||
}),
|
||||
uac_authorizer_jwt:issue(wapi_utils:get_unique_id(), PartyID, Token, wapi_auth:get_signee()).
|
||||
|
||||
%% verify, decode and check version of continuation token
|
||||
|
||||
continuation_token_unpack(undefined, _PartyID) ->
|
||||
{ok, #{}};
|
||||
continuation_token_unpack(Token, PartyID) ->
|
||||
case uac_authorizer_jwt:verify(Token, #{}) of
|
||||
{ok, {_, PartyID, #{<<"version">> := 1} = VerifiedToken}} ->
|
||||
{ok, VerifiedToken};
|
||||
{ok, {_, PartyID, #{<<"version">> := Version}}} ->
|
||||
{error, {token, {unsupported_version, Version}}};
|
||||
{ok, {_, WrongPatryID, _}} when WrongPatryID /= PartyID ->
|
||||
{error, {token, {not_verified, wrong_party_id}}};
|
||||
{error, Error} ->
|
||||
{error, {token, {not_verified, Error}}}
|
||||
end.
|
||||
|
||||
%% get cursor event id by entity
|
||||
|
||||
continuation_token_cursor(p2p_transfer, DecodedToken) ->
|
||||
maps:get(?CONTINUATION_TRANSFER, DecodedToken, undefined);
|
||||
continuation_token_cursor(p2p_session, DecodedToken) ->
|
||||
maps:get(?CONTINUATION_SESSION, DecodedToken, undefined).
|
||||
|
||||
%% collect events from EventService backend
|
||||
|
||||
-spec events_collect(event_service(), id() | undefined, event_range(), handler_context(), Acc0) ->
|
||||
{ok, {Acc1, event_id()}} | {error, {p2p_transfer, notfound}} when
|
||||
Acc0 :: [] | [event()],
|
||||
Acc1 :: [] | [event()].
|
||||
|
||||
events_collect(fistful_p2p_session, undefined, #'EventRange'{'after' = Cursor}, _HandlerContext, Acc) ->
|
||||
% no session ID is not an error
|
||||
{ok, {Acc, Cursor}};
|
||||
events_collect(_EventService, _EntityID, #'EventRange'{'after' = Cursor, 'limit' = Limit}, _HandlerContext, Acc)
|
||||
when Limit =< 0 ->
|
||||
% Limit < 0 < undefined
|
||||
{ok, {Acc, Cursor}};
|
||||
events_collect(EventService, EntityID, EventRange, HandlerContext, Acc) ->
|
||||
#'EventRange'{'after' = Cursor, limit = Limit} = EventRange,
|
||||
Request = {EventService, 'GetEvents', [EntityID, EventRange]},
|
||||
case events_request(Request, HandlerContext) of
|
||||
{ok, {_Received, [], undefined}} ->
|
||||
% the service has not returned any events, the previous cursor must be kept
|
||||
{ok, {Acc, Cursor}};
|
||||
{ok, {Received, Events, NewCursor}} when Received < Limit ->
|
||||
% service returned less events than requested
|
||||
% or Limit is 'undefined' and service returned all events
|
||||
{ok, {Acc ++ Events, NewCursor}};
|
||||
{ok, {_Received, Events, NewCursor}} ->
|
||||
% Limit is reached but some events can be filtered out
|
||||
NewEventRange = events_range(NewCursor, Limit - length(Events)),
|
||||
events_collect(EventService, EntityID, NewEventRange, HandlerContext, Acc ++ Events);
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec events_request(Request, handler_context()) ->
|
||||
{ok, {integer(), [] | [event()], event_id()}} | {error, {p2p_transfer, notfound}} when
|
||||
Request :: {event_service(), 'GetEvents', [id() | event_range()]}.
|
||||
|
||||
events_request(Request, HandlerContext) ->
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, []} ->
|
||||
{ok, {0, [], undefined}};
|
||||
{ok, EventsThrift} ->
|
||||
Cursor = events_cursor(lists:last(EventsThrift)),
|
||||
Events = lists:filter(fun events_filter/1, EventsThrift),
|
||||
{ok, {length(EventsThrift), Events, Cursor}};
|
||||
{exception, #fistful_P2PNotFound{}} ->
|
||||
{error, {p2p_transfer, notfound}};
|
||||
{exception, #fistful_P2PSessionNotFound{}} ->
|
||||
% P2PSessionNotFound not found - not error
|
||||
{ok, {0, [], undefined}}
|
||||
end.
|
||||
|
||||
events_filter(#p2p_transfer_Event{change = {status_changed, _}}) ->
|
||||
true;
|
||||
events_filter(#p2p_session_Event{change = {ui, #p2p_session_UserInteractionChange{payload = Payload}}}) ->
|
||||
case Payload of
|
||||
{status_changed, #p2p_session_UserInteractionStatusChange{
|
||||
status = {pending, _}
|
||||
}} ->
|
||||
false;
|
||||
_Other ->
|
||||
% {created ...}
|
||||
% {status_changed, ... status = {finished, ...}}
|
||||
% take created & finished user interaction events
|
||||
true
|
||||
end;
|
||||
events_filter(_Event) ->
|
||||
false.
|
||||
|
||||
events_merge(EventsList) ->
|
||||
lists:sort(fun(Ev1, Ev2) -> events_timestamp(Ev1) < events_timestamp(Ev2) end, lists:append(EventsList)).
|
||||
|
||||
events_cursor(#p2p_transfer_Event{event = ID}) ->
|
||||
ID;
|
||||
events_cursor(#p2p_session_Event{event = ID}) ->
|
||||
ID.
|
||||
|
||||
events_timestamp(#p2p_transfer_Event{occured_at = OccuredAt}) ->
|
||||
OccuredAt;
|
||||
events_timestamp(#p2p_session_Event{occured_at = OccuredAt}) ->
|
||||
OccuredAt.
|
||||
|
||||
events_range(CursorID) ->
|
||||
events_range(CursorID, genlib_app:env(wapi, events_fetch_limit, ?DEFAULT_EVENTS_LIMIT)).
|
||||
events_range(CursorID, Limit) ->
|
||||
#'EventRange'{'after' = CursorID, 'limit' = Limit}.
|
||||
|
||||
events_max(NewEventID, OldEventID) when is_integer(NewEventID) andalso is_integer(OldEventID) ->
|
||||
erlang:max(NewEventID, OldEventID);
|
||||
events_max(NewEventID, OldEventID) ->
|
||||
genlib:define(NewEventID, OldEventID).
|
||||
|
||||
%% Marshal
|
||||
|
||||
marshal_quote_params(#{
|
||||
@ -451,27 +224,23 @@ marshal_transfer_params(#{
|
||||
|
||||
marshal_sender(#{
|
||||
<<"token">> := Token,
|
||||
<<"authData">> := AuthData,
|
||||
<<"contactInfo">> := ContactInfo
|
||||
}) ->
|
||||
ResourceBankCard = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
Resource = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
unrecognized ->
|
||||
BankCard = wapi_utils:base64url_to_map(Token),
|
||||
#'ResourceBankCard'{
|
||||
{bank_card, #'ResourceBankCard'{
|
||||
bank_card = #'BankCard'{
|
||||
token = maps:get(<<"token">>, BankCard),
|
||||
bin = maps:get(<<"bin">>, BankCard),
|
||||
masked_pan = maps:get(<<"lastDigits">>, BankCard)
|
||||
}
|
||||
};
|
||||
}};
|
||||
{ok, BankCard} ->
|
||||
#'ResourceBankCard'{bank_card = BankCard}
|
||||
{bank_card, #'ResourceBankCard'{bank_card = BankCard}}
|
||||
end,
|
||||
ResourceBankCardAuth = ResourceBankCard#'ResourceBankCard'{
|
||||
auth_data = {session_data, #'SessionAuthData'{id = AuthData}}
|
||||
},
|
||||
{resource, #p2p_transfer_RawResource{
|
||||
resource = {bank_card, ResourceBankCardAuth},
|
||||
resource = Resource,
|
||||
contact_info = marshal_contact_info(ContactInfo)
|
||||
}}.
|
||||
|
||||
@ -608,94 +377,6 @@ unmarshal_transfer_status({failed, #p2p_status_Failed{failure = Failure}}) ->
|
||||
<<"failure">> => unmarshal(failure, Failure)
|
||||
}.
|
||||
|
||||
unmarshal_events({Token, Events}) ->
|
||||
#{
|
||||
<<"continuationToken">> => unmarshal(string, Token),
|
||||
<<"result">> => [unmarshal_event(Ev) || Ev <- Events]
|
||||
}.
|
||||
|
||||
unmarshal_event(#p2p_transfer_Event{
|
||||
occured_at = OccuredAt,
|
||||
change = Change
|
||||
}) ->
|
||||
#{
|
||||
<<"createdAt">> => unmarshal(string, OccuredAt),
|
||||
<<"change">> => unmarshal_event_change(Change)
|
||||
};
|
||||
unmarshal_event(#p2p_session_Event{
|
||||
occured_at = OccuredAt,
|
||||
change = Change
|
||||
}) ->
|
||||
#{
|
||||
<<"createdAt">> => unmarshal(string, OccuredAt),
|
||||
<<"change">> => unmarshal_event_change(Change)
|
||||
}.
|
||||
|
||||
unmarshal_event_change({status_changed, #p2p_transfer_StatusChange{
|
||||
status = Status
|
||||
}}) ->
|
||||
ChangeType = #{ <<"changeType">> => <<"P2PTransferStatusChanged">>},
|
||||
TransferChange = unmarshal_transfer_status(Status),
|
||||
maps:merge(ChangeType, TransferChange);
|
||||
unmarshal_event_change({ui, #p2p_session_UserInteractionChange{
|
||||
id = ID,
|
||||
payload = Payload
|
||||
}}) ->
|
||||
#{
|
||||
<<"changeType">> => <<"P2PTransferInteractionChanged">>,
|
||||
<<"userInteractionID">> => unmarshal(id, ID),
|
||||
<<"userInteractionChange">> => unmarshal_user_interaction_change(Payload)
|
||||
}.
|
||||
|
||||
unmarshal_user_interaction_change({created, #p2p_session_UserInteractionCreatedChange{
|
||||
ui = #p2p_session_UserInteraction{user_interaction = UserInteraction}
|
||||
}}) ->
|
||||
#{
|
||||
<<"changeType">> => <<"UserInteractionCreated">>,
|
||||
<<"userInteraction">> => unmarshal_user_interaction(UserInteraction)
|
||||
};
|
||||
unmarshal_user_interaction_change({status_changed, #p2p_session_UserInteractionStatusChange{
|
||||
status = {finished, _} % other statuses are skipped
|
||||
}}) ->
|
||||
#{
|
||||
<<"changeType">> => <<"UserInteractionFinished">>
|
||||
}.
|
||||
|
||||
unmarshal_user_interaction({redirect, Redirect}) ->
|
||||
#{
|
||||
<<"interactionType">> => <<"Redirect">>,
|
||||
<<"request">> => unmarshal_request(Redirect)
|
||||
}.
|
||||
|
||||
unmarshal_request({get_request, #ui_BrowserGetRequest{
|
||||
uri = URI
|
||||
}}) ->
|
||||
#{
|
||||
<<"requestType">> => <<"BrowserGetRequest">>,
|
||||
<<"uriTemplate">> => unmarshal(string, URI)
|
||||
};
|
||||
unmarshal_request({post_request, #ui_BrowserPostRequest{
|
||||
uri = URI,
|
||||
form = Form
|
||||
}}) ->
|
||||
#{
|
||||
<<"requestType">> => <<"BrowserPostRequest">>,
|
||||
<<"uriTemplate">> => unmarshal(string, URI),
|
||||
<<"form">> => unmarshal_form(Form)
|
||||
}.
|
||||
|
||||
unmarshal_form(Form) ->
|
||||
maps:fold(
|
||||
fun (Key, Template, AccIn) ->
|
||||
FormField = #{
|
||||
<<"key">> => unmarshal(string, Key),
|
||||
<<"template">> => unmarshal(string, Template)
|
||||
},
|
||||
[FormField | AccIn]
|
||||
end,
|
||||
[], Form
|
||||
).
|
||||
|
||||
unmarshal(T, V) ->
|
||||
ff_codec:unmarshal(T, V).
|
||||
|
||||
@ -703,262 +384,3 @@ maybe_unmarshal(_T, undefined) ->
|
||||
undefined;
|
||||
maybe_unmarshal(T, V) ->
|
||||
unmarshal(T, V).
|
||||
|
||||
%% TESTS
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-spec test() -> _.
|
||||
|
||||
-spec unmarshal_events_test_() ->
|
||||
_.
|
||||
unmarshal_events_test_() ->
|
||||
|
||||
Form = fun() -> {fun unmarshal_form/1,
|
||||
#{ <<"arg1">> => <<"value1">>, <<"arg2">> => <<"value2">> },
|
||||
[
|
||||
#{ <<"key">> => <<"arg2">>, <<"template">> => <<"value2">>},
|
||||
#{ <<"key">> => <<"arg1">>, <<"template">> => <<"value1">>}
|
||||
]
|
||||
} end,
|
||||
|
||||
Request = fun
|
||||
({_, Woody, Swag}) -> {fun unmarshal_request/1,
|
||||
{post_request, #ui_BrowserPostRequest{
|
||||
uri = <<"uri://post">>,
|
||||
form = Woody
|
||||
}},
|
||||
#{
|
||||
<<"requestType">> => <<"BrowserPostRequest">>,
|
||||
<<"uriTemplate">> => <<"uri://post">>,
|
||||
<<"form">> => Swag
|
||||
}
|
||||
};
|
||||
(get_request) -> {fun unmarshal_request/1,
|
||||
{get_request, #ui_BrowserGetRequest{
|
||||
uri = <<"uri://get">>
|
||||
}},
|
||||
#{
|
||||
<<"requestType">> => <<"BrowserGetRequest">>,
|
||||
<<"uriTemplate">> => <<"uri://get">>
|
||||
}
|
||||
}
|
||||
end,
|
||||
|
||||
UIRedirect = fun({_, Woody, Swag}) -> {fun unmarshal_user_interaction/1,
|
||||
{redirect, Woody},
|
||||
#{
|
||||
<<"interactionType">> => <<"Redirect">>,
|
||||
<<"request">> => Swag
|
||||
}
|
||||
} end,
|
||||
|
||||
UIChangePayload = fun
|
||||
({_, Woody, Swag}) -> {fun unmarshal_user_interaction_change/1,
|
||||
{created, #p2p_session_UserInteractionCreatedChange{
|
||||
ui = #p2p_session_UserInteraction{
|
||||
id = <<"id://p2p_session/ui">>,
|
||||
user_interaction = Woody
|
||||
}
|
||||
}},
|
||||
#{
|
||||
<<"changeType">> => <<"UserInteractionCreated">>,
|
||||
<<"userInteraction">> => Swag
|
||||
}
|
||||
};
|
||||
(ui_finished) -> {fun unmarshal_user_interaction_change/1,
|
||||
{status_changed, #p2p_session_UserInteractionStatusChange{
|
||||
status = {finished, #p2p_session_UserInteractionStatusFinished{}}
|
||||
}},
|
||||
#{
|
||||
<<"changeType">> => <<"UserInteractionFinished">>
|
||||
}
|
||||
}
|
||||
end,
|
||||
|
||||
EventChange = fun
|
||||
({_, Woody, Swag}) -> {fun unmarshal_event_change/1,
|
||||
{ui, #p2p_session_UserInteractionChange{
|
||||
id = <<"id://p2p_session/change">>, payload = Woody
|
||||
}},
|
||||
#{
|
||||
<<"changeType">> => <<"P2PTransferInteractionChanged">>,
|
||||
<<"userInteractionID">> => <<"id://p2p_session/change">>,
|
||||
<<"userInteractionChange">> => Swag
|
||||
}
|
||||
};
|
||||
(TransferStatus) -> {fun unmarshal_event_change/1,
|
||||
{status_changed, #p2p_transfer_StatusChange{
|
||||
status = case TransferStatus of
|
||||
pending -> {pending, #p2p_status_Pending{}};
|
||||
succeeded -> {succeeded, #p2p_status_Succeeded{}}
|
||||
end
|
||||
}},
|
||||
#{
|
||||
<<"changeType">> => <<"P2PTransferStatusChanged">>,
|
||||
<<"status">> => case TransferStatus of
|
||||
pending -> <<"Pending">>;
|
||||
succeeded -> <<"Succeeded">>
|
||||
end
|
||||
}
|
||||
}
|
||||
end,
|
||||
|
||||
Event = fun
|
||||
({_, {ui, _} = Woody, Swag}) -> {fun unmarshal_event/1,
|
||||
#p2p_session_Event{
|
||||
event = 1,
|
||||
occured_at = <<"2020-05-25T12:34:56.123456Z">>,
|
||||
change = Woody
|
||||
},
|
||||
#{
|
||||
<<"createdAt">> => <<"2020-05-25T12:34:56.123456Z">>,
|
||||
<<"change">> => Swag
|
||||
}
|
||||
};
|
||||
({_, {status_changed, _} = Woody, Swag}) -> {fun unmarshal_event/1,
|
||||
#p2p_transfer_Event{
|
||||
event = 1,
|
||||
occured_at = <<"2020-05-25T12:34:56.123456Z">>,
|
||||
change = Woody
|
||||
},
|
||||
#{
|
||||
<<"createdAt">> => <<"2020-05-25T12:34:56.123456Z">>,
|
||||
<<"change">> => Swag
|
||||
}
|
||||
}
|
||||
end,
|
||||
|
||||
Events = fun(List) -> {fun unmarshal_events/1,
|
||||
{
|
||||
<<"token">>,
|
||||
[Woody || {_, Woody, _} <- List]
|
||||
},
|
||||
#{
|
||||
<<"continuationToken">> => <<"token">>,
|
||||
<<"result">> => [Swag || {_, _, Swag} <- List]
|
||||
}
|
||||
} end,
|
||||
|
||||
EvList = [E ||
|
||||
Type <- [Form(), get_request],
|
||||
Change <- [UIChangePayload(UIRedirect(Request(Type))), pending, succeeded],
|
||||
E <- [Event(EventChange(Change))]
|
||||
],
|
||||
|
||||
[
|
||||
?_assertEqual(ExpectedSwag, Unmarshal(Woody)) ||
|
||||
{Unmarshal, Woody, ExpectedSwag} <- [Events(EvList) | EvList]
|
||||
].
|
||||
|
||||
-spec events_collect_test_() ->
|
||||
_.
|
||||
events_collect_test_() ->
|
||||
{setup,
|
||||
fun() ->
|
||||
% Construct acceptable event
|
||||
Event = fun(EventID) -> #p2p_transfer_Event{
|
||||
event = EventID,
|
||||
occured_at = <<"2020-05-25T12:34:56.123456Z">>,
|
||||
change = {status_changed, #p2p_transfer_StatusChange{
|
||||
status = {succeeded, #p2p_status_Succeeded{}}
|
||||
}}
|
||||
} end,
|
||||
% Construct rejectable event
|
||||
Reject = fun(EventID) -> #p2p_transfer_Event{
|
||||
event = EventID,
|
||||
occured_at = <<"2020-05-25T12:34:56.123456Z">>,
|
||||
change = {route, #p2p_transfer_RouteChange{}}
|
||||
} end,
|
||||
meck:new([wapi_handler_utils], [passthrough]),
|
||||
%
|
||||
% mock Request: {Service, 'GetEvents', [EntityID, EventRange]},
|
||||
% use Service to select the desired 'GetEvents' result
|
||||
%
|
||||
meck:expect(wapi_handler_utils, service_call, fun
|
||||
({produce_empty, 'GetEvents', _Params}, _Context) ->
|
||||
{ok, []};
|
||||
({produce_triple, 'GetEvents', _Params}, _Context) ->
|
||||
{ok, [Event(N) || N <- lists:seq(1, 3)]};
|
||||
({produce_even, 'GetEvents', [_, EventRange]}, _Context) ->
|
||||
#'EventRange'{'after' = After, limit = Limit} = EventRange,
|
||||
{ok, [Event(N) || N <- lists:seq(After + 1, After + Limit), N rem 2 =:= 0]};
|
||||
({produce_reject, 'GetEvents', [_, EventRange]}, _Context) ->
|
||||
#'EventRange'{'after' = After, limit = Limit} = EventRange,
|
||||
{ok, [
|
||||
case N rem 2 of
|
||||
0 -> Reject(N);
|
||||
_ -> Event(N)
|
||||
end || N <- lists:seq(After + 1, After + Limit)
|
||||
]};
|
||||
({produce_range, 'GetEvents', [_, EventRange]}, _Context) ->
|
||||
#'EventRange'{'after' = After, limit = Limit} = EventRange,
|
||||
{ok, [Event(N) || N <- lists:seq(After + 1, After + Limit)]};
|
||||
({transfer_not_found, 'GetEvents', _Params}, _Context) ->
|
||||
{exception, #fistful_P2PNotFound{}};
|
||||
({session_not_found, 'GetEvents', _Params}, _Context) ->
|
||||
{exception, #fistful_P2PSessionNotFound{}}
|
||||
end),
|
||||
{
|
||||
% Test generator - call 'events_collect' function and compare with 'Expected' result
|
||||
fun _Collect(Service, EntityID, EventRange, Acc, Expected) ->
|
||||
?_assertEqual(Expected, events_collect(Service, EntityID, EventRange, #{}, Acc))
|
||||
end,
|
||||
% Pass event constructor to test cases
|
||||
Event
|
||||
}
|
||||
end,
|
||||
fun(_) ->
|
||||
meck:unload()
|
||||
end,
|
||||
fun({Collect, Event}) ->
|
||||
[
|
||||
% SessionID undefined is not an error
|
||||
Collect(fistful_p2p_session, undefined, events_range(1), [Event(0)],
|
||||
{ok, {[Event(0)], 1}}
|
||||
),
|
||||
% Limit < 0 < undefined
|
||||
Collect(any, <<>>, events_range(1, 0), [],
|
||||
{ok, {[], 1}}
|
||||
),
|
||||
% Limit < 0 < undefined
|
||||
Collect(any, <<>>, events_range(1, 0), [Event(0)],
|
||||
{ok, {[Event(0)], 1}}
|
||||
),
|
||||
% the service has not returned any events
|
||||
Collect(produce_empty, <<>>, events_range(undefined), [],
|
||||
{ok, {[], undefined}}
|
||||
),
|
||||
% the service has not returned any events
|
||||
Collect(produce_empty, <<>>, events_range(0, 1), [],
|
||||
{ok, {[], 0}}
|
||||
),
|
||||
% Limit is 'undefined' and service returned all events
|
||||
Collect(produce_triple, <<>>, events_range(undefined), [Event(0)],
|
||||
{ok, {[Event(0), Event(1), Event(2), Event(3)], 3}}
|
||||
),
|
||||
% or service returned less events than requested
|
||||
Collect(produce_even, <<>>, events_range(0, 4), [],
|
||||
{ok, {[Event(2), Event(4)], 4}}
|
||||
),
|
||||
% Limit is reached but some events can be filtered out
|
||||
Collect(produce_reject, <<>>, events_range(0, 4), [],
|
||||
{ok, {[Event(1), Event(3), Event(5), Event(7)], 7}}
|
||||
),
|
||||
% Accumulate
|
||||
Collect(produce_range, <<>>, events_range(1, 2), [Event(0)],
|
||||
{ok, {[Event(0), Event(2), Event(3)], 3}}
|
||||
),
|
||||
% transfer not found
|
||||
Collect(transfer_not_found, <<>>, events_range(1), [],
|
||||
{error, {p2p_transfer, notfound}}
|
||||
),
|
||||
% P2PSessionNotFound not found - not error
|
||||
Collect(session_not_found, <<>>, events_range(1), [],
|
||||
{ok, {[], 1}}
|
||||
)
|
||||
]
|
||||
end
|
||||
}.
|
||||
|
||||
-endif.
|
||||
|
@ -39,7 +39,7 @@ create_transfer(Params = #{<<"sender">> := SenderID}, HandlerContext) ->
|
||||
|
||||
create_transfer(ID, Params, Context, HandlerContext) ->
|
||||
TransferParams = marshal(transfer_params, Params#{<<"id">> => ID}),
|
||||
Request = {fistful_w2w_transfer, 'Create', [TransferParams, marshal(context, Context)]},
|
||||
Request = {w2w_transfer, 'Create', [TransferParams, marshal(context, Context)]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Transfer} ->
|
||||
{ok, unmarshal(transfer, Transfer)};
|
||||
@ -64,7 +64,7 @@ when
|
||||
|
||||
get_transfer(ID, HandlerContext) ->
|
||||
EventRange = #'EventRange'{},
|
||||
Request = {fistful_w2w_transfer, 'Get', [ID, 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
|
||||
|
@ -155,17 +155,17 @@ get_identity(IdentityId, Context) ->
|
||||
{identity_class, notfound} |
|
||||
{inaccessible, ff_party:inaccessibility()} |
|
||||
{email, notfound} |
|
||||
{external_id_conflict, id(), external_id()} |
|
||||
{party, notfound}
|
||||
{external_id_conflict, id(), external_id()}
|
||||
).
|
||||
create_identity(Params, Context) ->
|
||||
IdentityParams = from_swag(identity_params, Params),
|
||||
CreateFun = fun(ID, EntityCtx) ->
|
||||
CreateIdentity = fun(ID, EntityCtx) ->
|
||||
ff_identity_machine:create(
|
||||
maps:merge(IdentityParams#{id => ID}, #{party => wapi_handler_utils:get_owner(Context)}),
|
||||
add_meta_to_ctx([<<"name">>], Params, EntityCtx)
|
||||
)
|
||||
end,
|
||||
CreateFun = fun(ID, EntityCtx) -> with_party(Context, fun() -> CreateIdentity(ID, EntityCtx) end) end,
|
||||
do(fun() -> unwrap(create_entity(identity, Params, CreateFun, Context)) end).
|
||||
|
||||
-spec get_identity_challenges(id(), [binary()], ctx()) -> result(map(),
|
||||
@ -368,7 +368,7 @@ create_destination(Params = #{<<"identity">> := IdenityId}, Context) ->
|
||||
_ = check_resource(identity, IdenityId, Context),
|
||||
DestinationParams = from_swag(destination_params, Params),
|
||||
Resource = unwrap(construct_resource(maps:get(resource, DestinationParams))),
|
||||
unwrap(ff_destination_machine:create(
|
||||
unwrap(ff_destination:create(
|
||||
DestinationParams#{id => ID, resource => Resource},
|
||||
add_meta_to_ctx([], Params, EntityCtx)
|
||||
))
|
||||
@ -1231,7 +1231,7 @@ get_state(Resource, Id, Context) ->
|
||||
|
||||
do_get_state(identity, Id) -> ff_identity_machine:get(Id);
|
||||
do_get_state(wallet, Id) -> ff_wallet_machine:get(Id);
|
||||
do_get_state(destination, Id) -> ff_destination_machine:get(Id);
|
||||
do_get_state(destination, Id) -> ff_destination:get_machine(Id);
|
||||
do_get_state(withdrawal, Id) -> ff_withdrawal_machine:get(Id);
|
||||
do_get_state(p2p_transfer, Id) -> p2p_transfer_machine:get(Id);
|
||||
do_get_state(p2p_template, Id) -> p2p_template_machine:get(Id);
|
||||
@ -1288,6 +1288,27 @@ handle_create_entity_result(Result, Type, ID, Context) when
|
||||
handle_create_entity_result({error, E}, _Type, _ID, _Context) ->
|
||||
throw(E).
|
||||
|
||||
with_party(Context, Fun) ->
|
||||
try Fun()
|
||||
catch
|
||||
error:#'payproc_PartyNotFound'{} ->
|
||||
ok = create_party(Context),
|
||||
Fun()
|
||||
end.
|
||||
|
||||
create_party(Context) ->
|
||||
_ = ff_party:create(
|
||||
wapi_handler_utils:get_owner(Context),
|
||||
#{email => unwrap(get_email(wapi_handler_utils:get_auth_context(Context)))}
|
||||
),
|
||||
ok.
|
||||
|
||||
get_email(AuthContext) ->
|
||||
case uac_authorizer_jwt:get_claim(<<"email">>, AuthContext, undefined) of
|
||||
undefined -> {error, {email, notfound}};
|
||||
Email -> {ok, Email}
|
||||
end.
|
||||
|
||||
-spec not_implemented() -> no_return().
|
||||
not_implemented() ->
|
||||
wapi_handler_utils:throw_not_implemented().
|
||||
@ -1908,7 +1929,7 @@ to_swag(wallet_account, {OwnAmount, AvailableAmount, Currency}) ->
|
||||
}
|
||||
};
|
||||
to_swag(destination, State) ->
|
||||
Destination = ff_destination_machine:destination(State),
|
||||
Destination = ff_destination:get(State),
|
||||
to_swag(map, maps:merge(
|
||||
#{
|
||||
<<"id">> => ff_destination:id(Destination),
|
||||
|
@ -110,10 +110,8 @@ process_request('CreateIdentity', #{'Identity' := Params}, Context, Opts) ->
|
||||
case wapi_wallet_ff_backend:create_identity(Params, Context) of
|
||||
{ok, Identity = #{<<"id">> := IdentityId}} ->
|
||||
wapi_handler_utils:reply_ok(201, Identity, get_location('GetIdentity', [IdentityId], Opts));
|
||||
{error, {inaccessible, _}} ->
|
||||
{error, {inaccessible, _}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Party inaccessible">>));
|
||||
{error, {party, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Party does not exist">>));
|
||||
{error, {provider, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such provider">>));
|
||||
{error, {identity_class, notfound}} ->
|
||||
|
@ -599,28 +599,6 @@ process_request('GetP2PTransfer', #{p2pTransferID := ID}, Context, _Opts) ->
|
||||
wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
|
||||
process_request('GetP2PTransferEvents', #{p2pTransferID := ID, continuationToken := CT}, Context, _Opts) ->
|
||||
case wapi_p2p_transfer_backend:get_transfer_events(ID, CT, Context) of
|
||||
{ok, P2PTransferEvents} ->
|
||||
wapi_handler_utils:reply_ok(200, P2PTransferEvents);
|
||||
{error, {p2p_transfer, unauthorized}} ->
|
||||
wapi_handler_utils:reply_ok(404);
|
||||
{error, {p2p_transfer, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(404);
|
||||
{error, {token, {not_verified, _}}} ->
|
||||
wapi_handler_utils:reply_error(400, #{
|
||||
<<"errorType">> => <<"InvalidToken">>,
|
||||
<<"name">> => <<"continuationToken">>,
|
||||
<<"description">> => <<"Token can't be verified">>
|
||||
});
|
||||
{error, {token, {unsupported_version, _}}} ->
|
||||
wapi_handler_utils:reply_error(400, #{
|
||||
<<"errorType">> => <<"InvalidToken">>,
|
||||
<<"name">> => <<"continuationToken">>,
|
||||
<<"description">> => <<"Token unsupported version">>
|
||||
})
|
||||
end;
|
||||
|
||||
%% Webhooks
|
||||
|
||||
process_request('CreateWebhook', #{'WebhookParams' := WebhookParams}, Context, _Opts) ->
|
||||
|
@ -275,9 +275,8 @@ wait_for_destination_authorized(DestID) ->
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
).
|
||||
|
||||
|
@ -30,7 +30,6 @@
|
||||
-export([check_withdrawal_limit_test/1]).
|
||||
-export([check_withdrawal_limit_exceeded_test/1]).
|
||||
-export([identity_providers_mismatch_test/1]).
|
||||
-export([lazy_party_creation_forbidden_test/1]).
|
||||
|
||||
-export([consume_eventsinks/1]).
|
||||
|
||||
@ -73,8 +72,7 @@ groups() ->
|
||||
not_allowed_currency_test,
|
||||
check_withdrawal_limit_test,
|
||||
check_withdrawal_limit_exceeded_test,
|
||||
identity_providers_mismatch_test,
|
||||
lazy_party_creation_forbidden_test
|
||||
identity_providers_mismatch_test
|
||||
]},
|
||||
{eventsink, [], [
|
||||
consume_eventsinks
|
||||
@ -136,31 +134,12 @@ end_per_group(_, _) ->
|
||||
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),
|
||||
case Name of
|
||||
woody_retry_test ->
|
||||
Save = application:get_env(wapi_woody_client, service_urls, undefined),
|
||||
ok = application:set_env(
|
||||
wapi_woody_client,
|
||||
service_urls,
|
||||
Save#{fistful_stat => "http://spanish.inquision/fistful_stat"}
|
||||
),
|
||||
lists:keystore(service_urls, 1, C1, {service_urls, Save});
|
||||
_Other ->
|
||||
C1
|
||||
end.
|
||||
C1.
|
||||
|
||||
-spec end_per_testcase(test_case_name(), config()) -> _.
|
||||
|
||||
end_per_testcase(_Name, C) ->
|
||||
ok = ct_helper:unset_context(),
|
||||
case lists:keysearch(service_urls, 1, C) of
|
||||
{value, {_, undefined}} ->
|
||||
application:unset_env(wapi_woody_client, service_urls);
|
||||
{value, {_, Save}} ->
|
||||
application:set_env(wapi_woody_client, service_urls, Save);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
end_per_testcase(_Name, _C) ->
|
||||
ok = ct_helper:unset_context().
|
||||
|
||||
-define(ID_PROVIDER, <<"good-one">>).
|
||||
-define(ID_PROVIDER2, <<"good-two">>).
|
||||
@ -404,24 +383,6 @@ identity_providers_mismatch_test(C) ->
|
||||
})},
|
||||
cfg(context, C)
|
||||
).
|
||||
-spec lazy_party_creation_forbidden_test(config()) -> test_return().
|
||||
lazy_party_creation_forbidden_test(_) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Class = ?ID_CLASS,
|
||||
{Context, _} = create_context_for_group(group_or_smth, <<"Nonexistent party">>),
|
||||
{error, {422, #{<<"message">> := <<"Party does not exist">>}}} = call_api(
|
||||
fun swag_client_wallet_identities_api:create_identity/3,
|
||||
#{body => #{
|
||||
<<"name">> => Name,
|
||||
<<"provider">> => Provider,
|
||||
<<"class">> => Class,
|
||||
<<"metadata">> => #{
|
||||
?STRING => ?STRING
|
||||
}
|
||||
}},
|
||||
Context
|
||||
).
|
||||
|
||||
-spec unknown_withdrawal_test(config()) -> test_return().
|
||||
|
||||
@ -577,6 +538,12 @@ quote_withdrawal_test(C) ->
|
||||
ok = check_withdrawal(WalletID, DestID, WithdrawalID, C).
|
||||
|
||||
woody_retry_test(C) ->
|
||||
Urls = application:get_env(wapi_woody_client, service_urls, #{}),
|
||||
ok = application:set_env(
|
||||
wapi_woody_client,
|
||||
service_urls,
|
||||
Urls#{fistful_stat => "http://spanish.inquision/fistful_stat"}
|
||||
),
|
||||
Params = #{
|
||||
identityID => <<"12332">>,
|
||||
currencyID => <<"RUB">>,
|
||||
@ -595,7 +562,8 @@ woody_retry_test(C) ->
|
||||
end,
|
||||
T2 = erlang:monotonic_time(),
|
||||
Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds),
|
||||
?assert(Time > 3000000).
|
||||
?assert(Time > 3000000),
|
||||
ok = application:set_env(wapi_woody_client, service_urls, Urls).
|
||||
|
||||
-spec get_wallet_by_external_id(config()) ->
|
||||
test_return().
|
||||
@ -789,9 +757,8 @@ await_destination(DestID) ->
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
).
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
-module(wapi_p2p_transfer_tests_SUITE).
|
||||
|
||||
-include_lib("stdlib/include/assert.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("stdlib/include/assert.hrl").
|
||||
|
||||
@ -10,8 +9,6 @@
|
||||
-include_lib("wapi_wallet_dummy_data.hrl").
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_p2p_transfer_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_p2p_session_thrift.hrl").
|
||||
|
||||
|
||||
-export([all/0]).
|
||||
-export([groups/0]).
|
||||
@ -41,9 +38,7 @@
|
||||
get_quote_fail_operation_not_permitted_test/1,
|
||||
get_quote_fail_no_resource_info_test/1,
|
||||
get_ok_test/1,
|
||||
get_fail_p2p_notfound_test/1,
|
||||
get_events_ok/1,
|
||||
get_events_fail/1
|
||||
get_fail_p2p_notfound_test/1
|
||||
]).
|
||||
|
||||
% common-api is used since it is the domain used in production RN
|
||||
@ -92,9 +87,7 @@ groups() ->
|
||||
get_quote_fail_operation_not_permitted_test,
|
||||
get_quote_fail_no_resource_info_test,
|
||||
get_ok_test,
|
||||
get_fail_p2p_notfound_test,
|
||||
get_events_ok,
|
||||
get_events_fail
|
||||
get_fail_p2p_notfound_test
|
||||
]
|
||||
}
|
||||
].
|
||||
@ -166,7 +159,6 @@ end_per_testcase(_Name, C) ->
|
||||
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
|
||||
ok.
|
||||
|
||||
|
||||
%%% Tests
|
||||
|
||||
-spec create_ok_test(config()) ->
|
||||
@ -286,10 +278,8 @@ create_with_quote_token_ok_test(C) ->
|
||||
{bender_thrift, fun('GenerateID', _) -> {ok, ?GENERATE_ID_RESULT} end},
|
||||
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(PartyID)} end},
|
||||
{fistful_p2p_transfer, fun
|
||||
('GetQuote', _) -> {ok, ?P2P_TRANSFER_QUOTE(IdentityID)};
|
||||
('Create', _) -> {ok, ?P2P_TRANSFER(PartyID)}
|
||||
end}
|
||||
{p2p_transfer, fun('GetQuote', _) -> {ok, ?P2P_TRANSFER_QUOTE(IdentityID)};
|
||||
('Create', _) -> {ok, ?P2P_TRANSFER(PartyID)} end}
|
||||
], C),
|
||||
SenderToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
ReceiverToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
@ -460,42 +450,6 @@ get_fail_p2p_notfound_test(C) ->
|
||||
get_call_api(C)
|
||||
).
|
||||
|
||||
-spec get_events_ok(config()) ->
|
||||
_.
|
||||
get_events_ok(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_p2p_transfer, fun
|
||||
('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(PartyID)};
|
||||
('Get', _) -> {ok, ?P2P_TRANSFER_SESSIONS(PartyID)};
|
||||
('GetEvents', [_ID, #'EventRange'{limit = Limit}]) ->
|
||||
{ok, [?P2P_TRANSFER_EVENT(EventID) || EventID <- lists:seq(1, Limit)]}
|
||||
end},
|
||||
{fistful_p2p_session, fun('GetEvents', [_ID, #'EventRange'{limit = Limit}]) ->
|
||||
{ok, [?P2P_SESSION_EVENT(EventID) || EventID <- lists:seq(1, Limit)]}
|
||||
end}
|
||||
], C),
|
||||
|
||||
{ok, #{<<"result">> := Result}} = get_events_call_api(C),
|
||||
|
||||
% Limit is multiplied by two because the selection occurs twice - from session and transfer.
|
||||
{ok, Limit} = application:get_env(wapi, events_fetch_limit),
|
||||
?assertEqual(Limit * 2, erlang:length(Result)),
|
||||
[?assertMatch(#{<<"change">> := _}, Ev) || Ev <- Result].
|
||||
|
||||
-spec get_events_fail(config()) ->
|
||||
_.
|
||||
get_events_fail(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_p2p_transfer, fun
|
||||
('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(PartyID)};
|
||||
('Get', _) -> throw(#fistful_P2PNotFound{})
|
||||
end}
|
||||
], C),
|
||||
|
||||
?assertMatch({error, {404, #{}}}, get_events_call_api(C)).
|
||||
|
||||
%%
|
||||
|
||||
create_party(_C) ->
|
||||
@ -510,17 +464,6 @@ call_api(F, Params, Context) ->
|
||||
Response = F(Url, PreparedParams, Opts),
|
||||
wapi_client_lib:handle_response(Response).
|
||||
|
||||
get_events_call_api(C) ->
|
||||
call_api(
|
||||
fun swag_client_wallet_p2_p_api:get_p2_p_transfer_events/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"p2pTransferID">> => ?STRING
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
create_p2p_transfer_call_api(C) ->
|
||||
SenderToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
ReceiverToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
@ -596,7 +539,7 @@ create_ok_start_mocks(C, ContextPartyID) ->
|
||||
{bender_thrift, fun('GenerateID', _) -> {ok, ?GENERATE_ID_RESULT} end},
|
||||
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(ContextPartyID)} end},
|
||||
{fistful_p2p_transfer, fun('Create', _) -> {ok, ?P2P_TRANSFER(PartyID)} end}
|
||||
{p2p_transfer, fun('Create', _) -> {ok, ?P2P_TRANSFER(PartyID)} end}
|
||||
], C).
|
||||
|
||||
create_fail_start_mocks(C, CreateResultFun) ->
|
||||
@ -608,7 +551,7 @@ create_fail_start_mocks(C, ContextPartyID, CreateResultFun) ->
|
||||
{bender_thrift, fun('GenerateID', _) -> {ok, ?GENERATE_ID_RESULT} end},
|
||||
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(ContextPartyID)} end},
|
||||
{fistful_p2p_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
{p2p_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
], C).
|
||||
|
||||
get_quote_start_mocks(C, GetQuoteResultFun) ->
|
||||
@ -616,12 +559,12 @@ get_quote_start_mocks(C, GetQuoteResultFun) ->
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(PartyID)} end},
|
||||
{fistful_p2p_transfer, fun('GetQuote', _) -> GetQuoteResultFun() end}
|
||||
{p2p_transfer, fun('GetQuote', _) -> GetQuoteResultFun() end}
|
||||
], C).
|
||||
|
||||
get_start_mocks(C, GetResultFun) ->
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_p2p_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
{p2p_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
], C).
|
||||
|
||||
store_bank_card(C, Pan, ExpDate, CardHolder) ->
|
||||
|
@ -32,6 +32,8 @@
|
||||
-type group_name() :: ct_helper:group_name().
|
||||
-type test_return() :: _ | no_return().
|
||||
|
||||
% -import(ct_helper, [cfg/2]).
|
||||
|
||||
-spec all() -> [test_case_name() | {group, group_name()}].
|
||||
|
||||
all() ->
|
||||
@ -58,9 +60,10 @@ groups() ->
|
||||
-spec init_per_suite(config()) -> config().
|
||||
|
||||
init_per_suite(C) ->
|
||||
ct_helper:makeup_cfg([
|
||||
ct_helper:makeup_cfg([
|
||||
ct_helper:test_case_name(init),
|
||||
ct_payment_system:setup(#{
|
||||
default_termset => get_default_termset(),
|
||||
optional_apps => [
|
||||
bender_client,
|
||||
wapi_woody_client,
|
||||
@ -195,21 +198,15 @@ w2w_transfer_check_test(C) ->
|
||||
|
||||
p2p_transfer_check_test(C) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = <<"quote-owner">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Class = ?ID_CLASS,
|
||||
IdentityID = create_identity(Name, Provider, Class, C),
|
||||
Token = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
P2PTransferID = create_p2p_transfer(Token, Token, IdentityID, C),
|
||||
ok = await_p2p_transfer(P2PTransferID, C),
|
||||
P2PTransfer = get_p2p_transfer(P2PTransferID, C),
|
||||
P2PTransferEvents = get_p2p_transfer_events(P2PTransferID, C),
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
IdentityIDThrift = IdentityID,
|
||||
P2PTransferIDThrift = create_p2p_transfer(Token, Token, IdentityIDThrift, C),
|
||||
ok = await_p2p_transfer(P2PTransferIDThrift, C),
|
||||
P2PTransferIDThrift = create_p2p_transfer(Token, Token, IdentityID, C),
|
||||
P2PTransferThrift = get_p2p_transfer(P2PTransferIDThrift, C),
|
||||
P2PTransferEventsThrift = get_p2p_transfer_events(P2PTransferIDThrift, C),
|
||||
?assertEqual(maps:keys(P2PTransferEvents), maps:keys(P2PTransferEventsThrift)),
|
||||
?assertEqual(maps:keys(P2PTransfer), maps:keys(P2PTransferThrift)),
|
||||
?assertEqual(maps:without([<<"id">>, <<"createdAt">>], P2PTransfer),
|
||||
maps:without([<<"id">>, <<"createdAt">>], P2PTransferThrift)).
|
||||
@ -236,31 +233,34 @@ withdrawal_check_test(C) ->
|
||||
|
||||
p2p_template_check_test(C) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = <<"quote-owner">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Class = ?ID_CLASS,
|
||||
Metadata = #{ <<"some key">> => <<"some value">> },
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
|
||||
IdentityID = create_identity(Name, Provider, Class, C),
|
||||
P2PTemplate = create_p2p_template(IdentityID, Metadata, C),
|
||||
P2PTemplate = create_p2p_template(IdentityID, C),
|
||||
#{<<"id">> := P2PTemplateID} = P2PTemplate,
|
||||
P2PTemplateCopy = get_p2p_template(P2PTemplateID, C),
|
||||
?assertEqual(maps:keys(P2PTemplate), maps:keys(P2PTemplateCopy)),
|
||||
|
||||
ValidUntil = woody_deadline:to_binary(woody_deadline:from_timeout(100000)),
|
||||
TemplateToken = get_p2p_template_token(P2PTemplateID, ValidUntil, C),
|
||||
TemplateTicket = get_p2p_template_ticket(P2PTemplateID, TemplateToken, ValidUntil, C),
|
||||
{ok, #{<<"token">> := QuoteToken}} = call_p2p_template_quote(P2PTemplateID, C),
|
||||
{ok, P2PTransfer} = call_p2p_template_transfer(P2PTemplateID, TemplateTicket, QuoteToken, C),
|
||||
?assertMatch(#{<<"identityID">> := IdentityID}, P2PTransfer),
|
||||
#{<<"id">> := P2PTransferID} = P2PTransfer,
|
||||
ok = await_p2p_transfer(P2PTransferID, C),
|
||||
?assertMatch(#{<<"metadata">> := Metadata}, P2PTransfer),
|
||||
?assertEqual(maps:get(<<"identityID">>, P2PTransfer), IdentityID),
|
||||
|
||||
% TODO: #{<<"metadata">> := Metadata} = P2PTransfer,
|
||||
ok = block_p2p_template(P2PTemplateID, C),
|
||||
P2PTemplateBlocked = get_p2p_template(P2PTemplateID, C),
|
||||
?assertMatch(#{<<"isBlocked">> := true}, P2PTemplateBlocked),
|
||||
?assertEqual(maps:get(<<"isBlocked">>, P2PTemplateBlocked), true),
|
||||
|
||||
QuoteBlockedError = call_p2p_template_quote(P2PTemplateID, C),
|
||||
?assertMatch({error, {422, _}}, QuoteBlockedError),
|
||||
|
||||
P2PTransferBlockedError = call_p2p_template_transfer(P2PTemplateID, TemplateTicket, QuoteToken, C),
|
||||
?assertMatch({error, {422, _}}, P2PTransferBlockedError),
|
||||
|
||||
Quote404Error = call_p2p_template_quote(<<"404">>, C),
|
||||
?assertMatch({error, {404, _}}, Quote404Error).
|
||||
|
||||
@ -545,32 +545,12 @@ get_p2p_transfer(P2PTransferID, C) ->
|
||||
),
|
||||
P2PTransfer.
|
||||
|
||||
get_p2p_transfer_events(P2PTransferID, C) ->
|
||||
{ok, P2PTransferEvents} = call_api(
|
||||
fun swag_client_wallet_p2_p_api:get_p2_p_transfer_events/3,
|
||||
#{binding => #{<<"p2pTransferID">> => P2PTransferID}},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
P2PTransferEvents.
|
||||
|
||||
await_p2p_transfer(P2PTransferID, C) ->
|
||||
<<"Succeeded">> = ct_helper:await(
|
||||
<<"Succeeded">>,
|
||||
fun () ->
|
||||
Reply = get_p2p_transfer(P2PTransferID, C),
|
||||
#{<<"status">> := #{<<"status">> := Status}} = Reply,
|
||||
Status
|
||||
end
|
||||
),
|
||||
ok.
|
||||
|
||||
await_destination(DestID) ->
|
||||
authorized = ct_helper:await(
|
||||
authorized,
|
||||
fun () ->
|
||||
{ok, DestM} = ff_destination_machine:get(DestID),
|
||||
Destination = ff_destination_machine:destination(DestM),
|
||||
ff_destination:status(Destination)
|
||||
{ok, DestM} = ff_destination:get_machine(DestID),
|
||||
ff_destination:status(ff_destination:get(DestM))
|
||||
end
|
||||
).
|
||||
|
||||
@ -631,7 +611,7 @@ get_withdrawal(WithdrawalID, C) ->
|
||||
|
||||
%% P2PTemplate
|
||||
|
||||
create_p2p_template(IdentityID, Metadata, C) ->
|
||||
create_p2p_template(IdentityID, C) ->
|
||||
{ok, P2PTemplate} = call_api(
|
||||
fun swag_client_wallet_p2_p_templates_api:create_p2_p_transfer_template/3,
|
||||
#{
|
||||
@ -645,7 +625,9 @@ create_p2p_template(IdentityID, Metadata, C) ->
|
||||
}
|
||||
},
|
||||
<<"metadata">> => #{
|
||||
<<"defaultMetadata">> => Metadata
|
||||
<<"defaultMetadata">> => #{
|
||||
<<"some key">> => <<"some value">>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -678,6 +660,7 @@ block_p2p_template(P2PTemplateID, C) ->
|
||||
),
|
||||
ok.
|
||||
|
||||
|
||||
get_p2p_template_token(P2PTemplateID, ValidUntil, C) ->
|
||||
{ok, #{<<"token">> := Token}} = call_api(
|
||||
fun swag_client_wallet_p2_p_templates_api:issue_p2_p_transfer_template_access_token/3,
|
||||
@ -710,7 +693,8 @@ get_p2p_template_ticket(P2PTemplateID, TemplateToken, ValidUntil, C) ->
|
||||
Ticket.
|
||||
|
||||
call_p2p_template_quote(P2PTemplateID, C) ->
|
||||
Token = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
SenderToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
ReceiverToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
call_api(
|
||||
fun swag_client_wallet_p2_p_templates_api:quote_p2_p_transfer_with_template/3,
|
||||
#{
|
||||
@ -720,11 +704,11 @@ call_p2p_template_quote(P2PTemplateID, C) ->
|
||||
body => #{
|
||||
<<"sender">> => #{
|
||||
<<"type">> => <<"BankCardSenderResource">>,
|
||||
<<"token">> => Token
|
||||
<<"token">> => SenderToken
|
||||
},
|
||||
<<"receiver">> => #{
|
||||
<<"type">> => <<"BankCardReceiverResource">>,
|
||||
<<"token">> => Token
|
||||
<<"token">> => ReceiverToken
|
||||
},
|
||||
<<"body">> => #{
|
||||
<<"amount">> => ?INTEGER,
|
||||
@ -736,7 +720,8 @@ call_p2p_template_quote(P2PTemplateID, C) ->
|
||||
).
|
||||
|
||||
call_p2p_template_transfer(P2PTemplateID, TemplateTicket, QuoteToken, C) ->
|
||||
Token = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
SenderToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
ReceiverToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
Context = maps:merge(ct_helper:cfg(context, C), #{token => TemplateTicket}),
|
||||
call_api(
|
||||
fun swag_client_wallet_p2_p_templates_api:create_p2_p_transfer_with_template/3,
|
||||
@ -747,15 +732,15 @@ call_p2p_template_transfer(P2PTemplateID, TemplateTicket, QuoteToken, C) ->
|
||||
body => #{
|
||||
<<"sender">> => #{
|
||||
<<"type">> => <<"BankCardSenderResourceParams">>,
|
||||
<<"token">> => Token,
|
||||
<<"token">> => SenderToken,
|
||||
<<"authData">> => <<"session id">>
|
||||
},
|
||||
<<"receiver">> => #{
|
||||
<<"type">> => <<"BankCardReceiverResourceParams">>,
|
||||
<<"token">> => Token
|
||||
<<"token">> => ReceiverToken
|
||||
},
|
||||
<<"body">> => #{
|
||||
<<"amount">> => ?INTEGER,
|
||||
<<"amount">> => 101,
|
||||
<<"currency">> => ?RUB
|
||||
},
|
||||
<<"contactInfo">> => #{
|
||||
@ -767,3 +752,124 @@ call_p2p_template_transfer(P2PTemplateID, TemplateTicket, QuoteToken, C) ->
|
||||
},
|
||||
Context
|
||||
).
|
||||
|
||||
%%
|
||||
|
||||
-include_lib("ff_cth/include/ct_domain.hrl").
|
||||
|
||||
get_default_termset() ->
|
||||
#domain_TermSet{
|
||||
wallets = #domain_WalletServiceTerms{
|
||||
currencies = {value, ?ordset([?cur(<<"RUB">>)])},
|
||||
wallet_limit = {decisions, [
|
||||
#domain_CashLimitDecision{
|
||||
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
|
||||
then_ = {value, ?cashrng(
|
||||
{inclusive, ?cash(-10000000, <<"RUB">>)},
|
||||
{exclusive, ?cash( 10000001, <<"RUB">>)}
|
||||
)}
|
||||
}
|
||||
]},
|
||||
withdrawals = #domain_WithdrawalServiceTerms{
|
||||
currencies = {value, ?ordset([?cur(<<"RUB">>)])},
|
||||
cash_limit = {decisions, [
|
||||
#domain_CashLimitDecision{
|
||||
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
|
||||
then_ = {value, ?cashrng(
|
||||
{inclusive, ?cash( 0, <<"RUB">>)},
|
||||
{exclusive, ?cash(10000000, <<"RUB">>)}
|
||||
)}
|
||||
}
|
||||
]},
|
||||
cash_flow = {decisions, [
|
||||
#domain_CashFlowDecision{
|
||||
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
|
||||
then_ = {value, [
|
||||
?cfpost(
|
||||
{wallet, sender_settlement},
|
||||
{wallet, receiver_destination},
|
||||
?share(1, 1, operation_amount)
|
||||
),
|
||||
?cfpost(
|
||||
{wallet, receiver_destination},
|
||||
{system, settlement},
|
||||
?share(10, 100, operation_amount)
|
||||
)
|
||||
]}
|
||||
}
|
||||
]}
|
||||
},
|
||||
p2p = #domain_P2PServiceTerms{
|
||||
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)}
|
||||
}}
|
||||
}
|
||||
]},
|
||||
quote_lifetime = {value, {interval, #domain_LifetimeInterval{
|
||||
days = 1, minutes = 1, seconds = 1
|
||||
}}},
|
||||
templates = #domain_P2PTemplateServiceTerms{
|
||||
allow = {condition, {currency_is, ?cur(<<"RUB">>)}}
|
||||
}
|
||||
},
|
||||
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)}
|
||||
}}
|
||||
}
|
||||
]}
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
@ -155,7 +155,7 @@ create_fail_unauthorized_wallet_test(C) ->
|
||||
wapi_ct_helper:mock_services([
|
||||
{bender_thrift, fun('GenerateID', _) -> {ok, ?GENERATE_ID_RESULT} end},
|
||||
{fistful_wallet, fun('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(<<"someotherparty">>)} end},
|
||||
{fistful_w2w_transfer, fun('Create', _) -> {ok, ?W2W_TRANSFER(PartyID)} end}
|
||||
{w2w_transfer, fun('Create', _) -> {ok, ?W2W_TRANSFER(PartyID)} end}
|
||||
], C),
|
||||
?assertEqual(
|
||||
{error, {422, #{<<"message">> => <<"No such wallet sender">>}}},
|
||||
@ -295,11 +295,11 @@ create_w2_w_transfer_start_mocks(C, CreateResultFun) ->
|
||||
wapi_ct_helper:mock_services([
|
||||
{bender_thrift, fun('GenerateID', _) -> {ok, ?GENERATE_ID_RESULT} end},
|
||||
{fistful_wallet, fun('GetContext', _) -> {ok, ?DEFAULT_CONTEXT(PartyID)} end},
|
||||
{fistful_w2w_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
{w2w_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
], C).
|
||||
|
||||
get_w2_w_transfer_start_mocks(C, GetResultFun) ->
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_w2w_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
{w2w_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
], C).
|
||||
|
||||
|
@ -519,34 +519,6 @@
|
||||
adjustments = []
|
||||
}).
|
||||
|
||||
-define(P2P_TRANSFER_SESSIONS(PartyID), ?P2P_TRANSFER(PartyID)#p2p_transfer_P2PTransferState{
|
||||
sessions = [#p2p_transfer_SessionState{id = ?STRING}]
|
||||
}).
|
||||
|
||||
-define(P2P_TRANSFER_EVENT(EventID), #p2p_transfer_Event{
|
||||
event = EventID,
|
||||
occured_at = ?TIMESTAMP,
|
||||
change = {status_changed, #p2p_transfer_StatusChange{
|
||||
status = {succeeded, #p2p_status_Succeeded{}}
|
||||
}}
|
||||
}).
|
||||
|
||||
-define(P2P_SESSION_EVENT(EventID), #p2p_session_Event{
|
||||
event = EventID,
|
||||
occured_at = ?TIMESTAMP,
|
||||
change = {ui, #p2p_session_UserInteractionChange{
|
||||
id = ?STRING,
|
||||
payload = {created, #p2p_session_UserInteractionCreatedChange{
|
||||
ui = #p2p_session_UserInteraction{
|
||||
id = ?STRING,
|
||||
user_interaction = {redirect, {get_request, #ui_BrowserGetRequest{
|
||||
uri = ?STRING
|
||||
}}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}).
|
||||
|
||||
-define(FEES, #'Fees'{fees = #{operation_amount => ?CASH}}).
|
||||
|
||||
-define(P2P_TRANSFER_QUOTE(IdentityID), #p2p_transfer_Quote{
|
||||
@ -560,4 +532,3 @@
|
||||
receiver = ?RESOURCE_BANK_CARD,
|
||||
fees = ?FEES
|
||||
}).
|
||||
|
||||
|
@ -96,11 +96,9 @@ get_service_modname(fistful_p2p_template) ->
|
||||
{ff_proto_p2p_template_thrift, 'Management'};
|
||||
get_service_modname(webhook_manager) ->
|
||||
{ff_proto_webhooker_thrift, 'WebhookManager'};
|
||||
get_service_modname(fistful_p2p_transfer) ->
|
||||
get_service_modname(p2p_transfer) ->
|
||||
{ff_proto_p2p_transfer_thrift, 'Management'};
|
||||
get_service_modname(fistful_p2p_session) ->
|
||||
{ff_proto_p2p_session_thrift, 'Management'};
|
||||
get_service_modname(fistful_w2w_transfer) ->
|
||||
get_service_modname(w2w_transfer) ->
|
||||
{ff_proto_w2w_transfer_thrift, 'Management'}.
|
||||
|
||||
-spec get_service_deadline(service_name()) -> undefined | woody_deadline:deadline().
|
||||
|
@ -154,18 +154,10 @@
|
||||
|
||||
{wapi_woody_client, [
|
||||
{service_urls, #{
|
||||
webhook_manager => "http://hooker:8022/hook",
|
||||
cds_storage => "http://cds:8022/v1/storage",
|
||||
identdoc_storage => "http://cds:8022/v1/identity_document_storage",
|
||||
fistful_stat => "http://fistful-magista:8022/stat",
|
||||
fistful_wallet => "http://fistful:8022/v1/wallet",
|
||||
fistful_identity => "http://fistful:8022/v1/identity",
|
||||
fistful_destination => "http://fistful:8022/v1/destination",
|
||||
fistful_withdrawal => "http://fistful:8022/v1/withdrawal",
|
||||
fistful_w2w_transfer => "http://fistful:8022/v1/w2w_transfer",
|
||||
fistful_p2p_template => "http://fistful:8022/v1/p2p_template",
|
||||
fistful_p2p_transfer => "http://fistful:8022/v1/p2p_transfer",
|
||||
fistful_p2p_session => "http://fistful:8022/v1/p2p_transfer/session"
|
||||
webhook_manager => "http://hooker:8022/hook",
|
||||
cds_storage => "http://cds:8022/v1/storage",
|
||||
identdoc_storage => "http://cds:8022/v1/identity_document_storage",
|
||||
fistful_stat => "http://fistful-magista:8022/stat"
|
||||
}},
|
||||
{api_deadlines, #{
|
||||
wallet => 5000 % millisec
|
||||
|
@ -176,7 +176,7 @@
|
||||
{vm_args , "./config/vm.args"},
|
||||
{dev_mode , false},
|
||||
{include_src , false},
|
||||
{include_erts , false},
|
||||
{include_erts , true},
|
||||
{extended_start_script , true},
|
||||
%% wapi
|
||||
{overlay, [
|
||||
@ -189,11 +189,6 @@
|
||||
]},
|
||||
|
||||
{test, [
|
||||
{deps, [
|
||||
{meck,
|
||||
"0.9.0"
|
||||
}
|
||||
]},
|
||||
{cover_enabled, true},
|
||||
{cover_excl_apps, [
|
||||
ff_cth,
|
||||
|
29
rebar.lock
29
rebar.lock
@ -1,4 +1,4 @@
|
||||
{"1.1.0",
|
||||
{"1.2.0",
|
||||
[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2},
|
||||
{<<"base64url">>,{pkg,<<"base64url">>,<<"0.0.1">>},0},
|
||||
{<<"bear">>,{pkg,<<"bear">>,<<"0.8.7">>},3},
|
||||
@ -68,7 +68,7 @@
|
||||
0},
|
||||
{<<"fistful_proto">>,
|
||||
{git,"git@github.com:rbkmoney/fistful-proto.git",
|
||||
{ref,"a6ba73813b41bf911b30be0c311cfae7eec09066"}},
|
||||
{ref,"f373e09fc2e451b9ef3b5f86f54f9627fa29c59f"}},
|
||||
0},
|
||||
{<<"fistful_reporter_proto">>,
|
||||
{git,"git@github.com:rbkmoney/fistful-reporter-proto.git",
|
||||
@ -204,5 +204,28 @@
|
||||
{<<"ranch">>, <<"6B1FAB51B49196860B733A49C07604465A47BDB78AA10C1C16A3D199F7F8C881">>},
|
||||
{<<"ssl_verify_fun">>, <<"F0EAFFF810D2041E93F915EF59899C923F4568F4585904D010387ED74988E77B">>},
|
||||
{<<"unicode_util_compat">>, <<"D869E4C68901DD9531385BB0C8C40444EBF624E60B6962D95952775CAC5E90CD">>},
|
||||
{<<"uuid">>, <<"C5DF97D1A3D626235C2415E74053C47B2138BB863C5CD802AB5CAECB8ECC019F">>}]}
|
||||
{<<"uuid">>, <<"C5DF97D1A3D626235C2415E74053C47B2138BB863C5CD802AB5CAECB8ECC019F">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>},
|
||||
{<<"base64url">>, <<"FAB09B20E3F5DB886725544CBCF875B8E73EC93363954EB8A1A9ED834AA8C1F9">>},
|
||||
{<<"bear">>, <<"534217DCE6A719D59E54FB0EB7A367900DBFC5F85757E8C1F94269DF383F6D9B">>},
|
||||
{<<"cache">>, <<"3E7D6706DE5DF76C4D71C895B4BE62B01C3DE6EDB63197035E465C3BCE63F19B">>},
|
||||
{<<"certifi">>, <<"805ABD97539CAF89EC6D4732C91E62BA9DA0CDA51AC462380BBD28EE697A8C42">>},
|
||||
{<<"cowboy">>, <<"04FD8C6A39EDC6AAA9C26123009200FC61F92A3A94F3178C527B70B767C6E605">>},
|
||||
{<<"cowlib">>, <<"79F954A7021B302186A950A32869DBC185523D99D3E44CE430CD1F3289F41ED4">>},
|
||||
{<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>},
|
||||
{<<"hackney">>, <<"C2790C9F0F7205F4A362512192DEE8179097394400E745E4D20BAB7226A8EAAD">>},
|
||||
{<<"idna">>, <<"4BDD305EB64E18B0273864920695CB18D7A2021F31A11B9C5FBCD9A253F936E2">>},
|
||||
{<<"jose">>, <<"3C7DDC8A9394B92891DB7C2771DA94BF819834A1A4C92E30857B7D582E2F8257">>},
|
||||
{<<"jsx">>, <<"B4C5D3230B397C8D95579E4A3D72826BB6463160130CCF4182F5BE8579B5F44C">>},
|
||||
{<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>},
|
||||
{<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>},
|
||||
{<<"prometheus">>, <<"4905FD2992F8038ECCD7AA0CD22F40637ED618C0BED1F75C05AACEC15B7545DE">>},
|
||||
{<<"prometheus_cowboy">>, <<"BA286BECA9302618418892D37BCD5DC669A6CC001F4EB6D6AF85FF81F3F4F34C">>},
|
||||
{<<"prometheus_httpd">>, <<"0BBE831452CFDF9588538EB2F570B26F30C348ADAE5E95A7D87F35A5910BCF92">>},
|
||||
{<<"quickrand">>, <<"E05EE94A9DA317B4B7D9C453638E592D002FE8F2109A0357B0A54F966EDBBA90">>},
|
||||
{<<"ranch">>, <<"451D8527787DF716D99DC36162FCA05934915DB0B6141BBDAC2EA8D3C7AFC7D7">>},
|
||||
{<<"ssl_verify_fun">>, <<"603561DC0FD62F4F2EA9B890F4E20E1A0D388746D6E20557CAFB1B16950DE88C">>},
|
||||
{<<"unicode_util_compat">>, <<"1D1848C40487CDB0B30E8ED975E34E025860C02E419CB615D255849F3427439D">>},
|
||||
{<<"uuid">>, <<"F87BAD1A8E90373B75DAEE259A6EB880293AB178AE2B2779ACB0B00CEA81C602">>}]}
|
||||
].
|
||||
|
Loading…
Reference in New Issue
Block a user