mirror of
https://github.com/valitydev/fistful-server.git
synced 2024-11-06 02:35:18 +00:00
parent
619720f0e9
commit
c229d491ba
@ -117,7 +117,8 @@ start_app(wapi = AppName) ->
|
||||
validation_opts => #{
|
||||
custom_validator => wapi_swagger_validator
|
||||
}
|
||||
}}
|
||||
}},
|
||||
{events_fetch_limit, 32}
|
||||
]), #{}};
|
||||
|
||||
start_app(wapi_woody_client = AppName) ->
|
||||
@ -129,10 +130,11 @@ 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_p2p_template => "http://localhost:8022/v1/p2p_template"
|
||||
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"
|
||||
}},
|
||||
{service_retries, #{
|
||||
fistful_stat => #{
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
-export([marshal_state/2]).
|
||||
|
||||
-export([marshal_event/1]).
|
||||
-export([marshal/2]).
|
||||
-export([unmarshal/2]).
|
||||
|
||||
@ -26,6 +27,16 @@ 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,4 +41,13 @@ 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.
|
||||
|
@ -15,6 +15,7 @@
|
||||
-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]).
|
||||
@ -40,6 +41,7 @@ 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,
|
||||
@ -92,6 +94,13 @@ 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) ->
|
||||
#{
|
||||
|
@ -88,7 +88,7 @@ get_context_by_id(p2p_template, TemplateID, WoodyCtx) ->
|
||||
{error, notfound}
|
||||
end;
|
||||
get_context_by_id(w2w_transfer, W2WTransferID, WoodyCtx) ->
|
||||
Request = {w2w_transfer, 'GetContext', [W2WTransferID]},
|
||||
Request = {fistful_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 = {p2p_transfer, 'GetContext', [P2PTransferID]},
|
||||
Request = {fistful_p2p_transfer, 'GetContext', [P2PTransferID]},
|
||||
case wapi_handler_utils:service_call(Request, WoodyCtx) of
|
||||
{ok, Context} ->
|
||||
Context;
|
||||
|
@ -192,19 +192,24 @@ quote_transfer(ID, Params, HandlerContext) ->
|
||||
{error, {token, _}} |
|
||||
{error, {external_id_conflict, _}}.
|
||||
|
||||
create_transfer(ID, #{quote_token := Token} = Params, HandlerContext) ->
|
||||
create_transfer(ID, #{<<"quoteToken">> := Token} = Params, HandlerContext) ->
|
||||
case uac_authorizer_jwt:verify(Token, #{}) of
|
||||
{ok, {_, _, VerifiedToken}} ->
|
||||
case decode_and_validate_token_payload(VerifiedToken, ID, HandlerContext) of
|
||||
{ok, Quote} ->
|
||||
create_transfer(ID, Params#{<<"quote">> => Quote}, HandlerContext);
|
||||
do_create_transfer(ID, Params#{<<"quote">> => Quote}, HandlerContext);
|
||||
{error, token_expired} ->
|
||||
{error, {token, expired}}
|
||||
{error, {token, expired}};
|
||||
{error, Error} ->
|
||||
{error, {token, {not_verified, Error}}}
|
||||
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),
|
||||
@ -212,7 +217,7 @@ create_transfer(ID, Params, HandlerContext) ->
|
||||
ok ->
|
||||
MarshaledContext = marshal_context(wapi_backend_utils:make_ctx(Params, HandlerContext)),
|
||||
MarshaledParams = marshal_transfer_params(Params#{<<"id">> => TransferID}),
|
||||
create_transfer(ID, MarshaledParams, MarshaledContext, HandlerContext);
|
||||
call_create_transfer(ID, MarshaledParams, MarshaledContext, HandlerContext);
|
||||
{error, {external_id_conflict, _}} = Error ->
|
||||
Error
|
||||
end;
|
||||
@ -221,7 +226,8 @@ create_transfer(ID, Params, HandlerContext) ->
|
||||
{error, notfound} ->
|
||||
{error, {p2p_template, notfound}}
|
||||
end.
|
||||
create_transfer(ID, MarshaledParams, MarshaledContext, HandlerContext) ->
|
||||
|
||||
call_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} ->
|
||||
@ -466,10 +472,10 @@ validate_transfer_id(TransferID, Params, #{woody_context := WoodyContext} = Hand
|
||||
|
||||
validate_identity_id(IdentityID, TemplateID, HandlerContext) ->
|
||||
case get(TemplateID, HandlerContext) of
|
||||
{ok, #{identity_id := IdentityID}} ->
|
||||
{ok, #{<<"identityID">> := IdentityID}} ->
|
||||
ok;
|
||||
{ok, _ } ->
|
||||
{error, {token, {not_verified, identity_mismatch}}};
|
||||
{ok, _Template} ->
|
||||
{error, identity_mismatch};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
@ -591,23 +597,27 @@ marshal_transfer_params(#{
|
||||
|
||||
marshal_sender(#{
|
||||
<<"token">> := Token,
|
||||
<<"authData">> := AuthData,
|
||||
<<"contactInfo">> := ContactInfo
|
||||
}) ->
|
||||
Resource = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
ResourceBankCard = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
unrecognized ->
|
||||
BankCard = wapi_utils:base64url_to_map(Token),
|
||||
{bank_card, #'ResourceBankCard'{
|
||||
#'ResourceBankCard'{
|
||||
bank_card = #'BankCard'{
|
||||
token = maps:get(<<"token">>, BankCard),
|
||||
bin = maps:get(<<"bin">>, BankCard),
|
||||
masked_pan = maps:get(<<"lastDigits">>, BankCard)
|
||||
}
|
||||
}};
|
||||
};
|
||||
{ok, BankCard} ->
|
||||
{bank_card, #'ResourceBankCard'{bank_card = BankCard}}
|
||||
#'ResourceBankCard'{bank_card = BankCard}
|
||||
end,
|
||||
ResourceBankCardAuth = ResourceBankCard#'ResourceBankCard'{
|
||||
auth_data = {session_data, #'SessionAuthData'{id = AuthData}}
|
||||
},
|
||||
{resource, #p2p_transfer_RawResource{
|
||||
resource = Resource,
|
||||
resource = {bank_card, ResourceBankCardAuth},
|
||||
contact_info = marshal_contact_info(ContactInfo)
|
||||
}}.
|
||||
|
||||
|
@ -35,13 +35,31 @@
|
||||
| {p2p_transfer, notfound}
|
||||
.
|
||||
|
||||
-type error_get_events()
|
||||
:: error_get()
|
||||
| {token, {unsupported_version, _}}
|
||||
| {token, {not_verified, _}}
|
||||
.
|
||||
|
||||
-export([create_transfer/2]).
|
||||
-export([quote_transfer/2]).
|
||||
-export([get_transfer/2]).
|
||||
-export([quote_transfer/2]).
|
||||
-export([get_transfer_events/3]).
|
||||
|
||||
-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()}.
|
||||
@ -63,7 +81,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 = {p2p_transfer, 'Get', [ID, #'EventRange'{}]},
|
||||
Request = {fistful_p2p_transfer, 'Get', [ID, #'EventRange'{}]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, TransferThrift} ->
|
||||
case wapi_access_backend:check_resource(p2p_transfer, TransferThrift, HandlerContext) of
|
||||
@ -89,10 +107,23 @@ 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 = {p2p_transfer, 'GetQuote', [marshal_quote_params(Params)]},
|
||||
Request = {fistful_p2p_transfer, 'GetQuote', [marshal_quote_params(Params)]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Quote} ->
|
||||
PartyID = wapi_handler_utils:get_owner(HandlerContext),
|
||||
@ -125,7 +156,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 = {p2p_transfer, 'Create', [TransferParams, marshal(context, Context)]},
|
||||
Request = {fistful_p2p_transfer, 'Create', [TransferParams, marshal(context, Context)]},
|
||||
unwrap(process_p2p_transfer_call(Request, HandlerContext))
|
||||
end).
|
||||
|
||||
@ -174,6 +205,202 @@ 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(#{
|
||||
@ -224,23 +451,27 @@ marshal_transfer_params(#{
|
||||
|
||||
marshal_sender(#{
|
||||
<<"token">> := Token,
|
||||
<<"authData">> := AuthData,
|
||||
<<"contactInfo">> := ContactInfo
|
||||
}) ->
|
||||
Resource = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
ResourceBankCard = case wapi_crypto:decrypt_bankcard_token(Token) of
|
||||
unrecognized ->
|
||||
BankCard = wapi_utils:base64url_to_map(Token),
|
||||
{bank_card, #'ResourceBankCard'{
|
||||
#'ResourceBankCard'{
|
||||
bank_card = #'BankCard'{
|
||||
token = maps:get(<<"token">>, BankCard),
|
||||
bin = maps:get(<<"bin">>, BankCard),
|
||||
masked_pan = maps:get(<<"lastDigits">>, BankCard)
|
||||
}
|
||||
}};
|
||||
};
|
||||
{ok, BankCard} ->
|
||||
{bank_card, #'ResourceBankCard'{bank_card = BankCard}}
|
||||
#'ResourceBankCard'{bank_card = BankCard}
|
||||
end,
|
||||
ResourceBankCardAuth = ResourceBankCard#'ResourceBankCard'{
|
||||
auth_data = {session_data, #'SessionAuthData'{id = AuthData}}
|
||||
},
|
||||
{resource, #p2p_transfer_RawResource{
|
||||
resource = Resource,
|
||||
resource = {bank_card, ResourceBankCardAuth},
|
||||
contact_info = marshal_contact_info(ContactInfo)
|
||||
}}.
|
||||
|
||||
@ -377,6 +608,94 @@ 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).
|
||||
|
||||
@ -384,3 +703,262 @@ 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 = {w2w_transfer, 'Create', [TransferParams, marshal(context, Context)]},
|
||||
Request = {fistful_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 = {w2w_transfer, 'Get', [ID, EventRange]},
|
||||
Request = {fistful_w2w_transfer, 'Get', [ID, EventRange]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, TransferThrift} ->
|
||||
case wapi_access_backend:check_resource(w2w_transfer, TransferThrift, HandlerContext) of
|
||||
|
@ -599,6 +599,28 @@ 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) ->
|
||||
|
@ -134,12 +134,31 @@ 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),
|
||||
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.
|
||||
|
||||
-spec end_per_testcase(test_case_name(), config()) -> _.
|
||||
|
||||
end_per_testcase(_Name, _C) ->
|
||||
ok = ct_helper:unset_context().
|
||||
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.
|
||||
|
||||
-define(ID_PROVIDER, <<"good-one">>).
|
||||
-define(ID_PROVIDER2, <<"good-two">>).
|
||||
@ -538,12 +557,6 @@ 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">>,
|
||||
@ -562,8 +575,7 @@ woody_retry_test(C) ->
|
||||
end,
|
||||
T2 = erlang:monotonic_time(),
|
||||
Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds),
|
||||
?assert(Time > 3000000),
|
||||
ok = application:set_env(wapi_woody_client, service_urls, Urls).
|
||||
?assert(Time > 3000000).
|
||||
|
||||
-spec get_wallet_by_external_id(config()) ->
|
||||
test_return().
|
||||
|
@ -1,5 +1,6 @@
|
||||
-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").
|
||||
|
||||
@ -9,6 +10,8 @@
|
||||
-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]).
|
||||
@ -38,7 +41,9 @@
|
||||
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_fail_p2p_notfound_test/1,
|
||||
get_events_ok/1,
|
||||
get_events_fail/1
|
||||
]).
|
||||
|
||||
% common-api is used since it is the domain used in production RN
|
||||
@ -87,7 +92,9 @@ groups() ->
|
||||
get_quote_fail_operation_not_permitted_test,
|
||||
get_quote_fail_no_resource_info_test,
|
||||
get_ok_test,
|
||||
get_fail_p2p_notfound_test
|
||||
get_fail_p2p_notfound_test,
|
||||
get_events_ok,
|
||||
get_events_fail
|
||||
]
|
||||
}
|
||||
].
|
||||
@ -159,6 +166,7 @@ end_per_testcase(_Name, C) ->
|
||||
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
|
||||
ok.
|
||||
|
||||
|
||||
%%% Tests
|
||||
|
||||
-spec create_ok_test(config()) ->
|
||||
@ -278,8 +286,10 @@ 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},
|
||||
{p2p_transfer, fun('GetQuote', _) -> {ok, ?P2P_TRANSFER_QUOTE(IdentityID)};
|
||||
('Create', _) -> {ok, ?P2P_TRANSFER(PartyID)} end}
|
||||
{fistful_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">>),
|
||||
@ -450,6 +460,42 @@ 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) ->
|
||||
@ -464,6 +510,17 @@ 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">>),
|
||||
@ -539,7 +596,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},
|
||||
{p2p_transfer, fun('Create', _) -> {ok, ?P2P_TRANSFER(PartyID)} end}
|
||||
{fistful_p2p_transfer, fun('Create', _) -> {ok, ?P2P_TRANSFER(PartyID)} end}
|
||||
], C).
|
||||
|
||||
create_fail_start_mocks(C, CreateResultFun) ->
|
||||
@ -551,7 +608,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},
|
||||
{p2p_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
{fistful_p2p_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
], C).
|
||||
|
||||
get_quote_start_mocks(C, GetQuoteResultFun) ->
|
||||
@ -559,12 +616,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},
|
||||
{p2p_transfer, fun('GetQuote', _) -> GetQuoteResultFun() end}
|
||||
{fistful_p2p_transfer, fun('GetQuote', _) -> GetQuoteResultFun() end}
|
||||
], C).
|
||||
|
||||
get_start_mocks(C, GetResultFun) ->
|
||||
wapi_ct_helper:mock_services([
|
||||
{p2p_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
{fistful_p2p_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
], C).
|
||||
|
||||
store_bank_card(C, Pan, ExpDate, CardHolder) ->
|
||||
|
@ -32,8 +32,6 @@
|
||||
-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() ->
|
||||
@ -60,10 +58,9 @@ 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,
|
||||
@ -198,15 +195,21 @@ w2w_transfer_check_test(C) ->
|
||||
|
||||
p2p_transfer_check_test(C) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Provider = <<"quote-owner">>,
|
||||
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),
|
||||
P2PTransferIDThrift = create_p2p_transfer(Token, Token, IdentityID, C),
|
||||
IdentityIDThrift = IdentityID,
|
||||
P2PTransferIDThrift = create_p2p_transfer(Token, Token, IdentityIDThrift, C),
|
||||
ok = await_p2p_transfer(P2PTransferIDThrift, 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)).
|
||||
@ -233,34 +236,31 @@ withdrawal_check_test(C) ->
|
||||
|
||||
p2p_template_check_test(C) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Provider = <<"quote-owner">>,
|
||||
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, C),
|
||||
P2PTemplate = create_p2p_template(IdentityID, Metadata, 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),
|
||||
?assertEqual(maps:get(<<"identityID">>, P2PTransfer), IdentityID),
|
||||
|
||||
% TODO: #{<<"metadata">> := Metadata} = P2PTransfer,
|
||||
?assertMatch(#{<<"identityID">> := IdentityID}, P2PTransfer),
|
||||
#{<<"id">> := P2PTransferID} = P2PTransfer,
|
||||
ok = await_p2p_transfer(P2PTransferID, C),
|
||||
?assertMatch(#{<<"metadata">> := Metadata}, P2PTransfer),
|
||||
ok = block_p2p_template(P2PTemplateID, C),
|
||||
P2PTemplateBlocked = get_p2p_template(P2PTemplateID, C),
|
||||
?assertEqual(maps:get(<<"isBlocked">>, P2PTemplateBlocked), true),
|
||||
|
||||
?assertMatch(#{<<"isBlocked">> := true}, P2PTemplateBlocked),
|
||||
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,6 +545,25 @@ 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,
|
||||
@ -612,7 +631,7 @@ get_withdrawal(WithdrawalID, C) ->
|
||||
|
||||
%% P2PTemplate
|
||||
|
||||
create_p2p_template(IdentityID, C) ->
|
||||
create_p2p_template(IdentityID, Metadata, C) ->
|
||||
{ok, P2PTemplate} = call_api(
|
||||
fun swag_client_wallet_p2_p_templates_api:create_p2_p_transfer_template/3,
|
||||
#{
|
||||
@ -626,9 +645,7 @@ create_p2p_template(IdentityID, C) ->
|
||||
}
|
||||
},
|
||||
<<"metadata">> => #{
|
||||
<<"defaultMetadata">> => #{
|
||||
<<"some key">> => <<"some value">>
|
||||
}
|
||||
<<"defaultMetadata">> => Metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -661,7 +678,6 @@ 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,
|
||||
@ -694,8 +710,7 @@ get_p2p_template_ticket(P2PTemplateID, TemplateToken, ValidUntil, C) ->
|
||||
Ticket.
|
||||
|
||||
call_p2p_template_quote(P2PTemplateID, C) ->
|
||||
SenderToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
ReceiverToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
Token = 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,
|
||||
#{
|
||||
@ -705,11 +720,11 @@ call_p2p_template_quote(P2PTemplateID, C) ->
|
||||
body => #{
|
||||
<<"sender">> => #{
|
||||
<<"type">> => <<"BankCardSenderResource">>,
|
||||
<<"token">> => SenderToken
|
||||
<<"token">> => Token
|
||||
},
|
||||
<<"receiver">> => #{
|
||||
<<"type">> => <<"BankCardReceiverResource">>,
|
||||
<<"token">> => ReceiverToken
|
||||
<<"token">> => Token
|
||||
},
|
||||
<<"body">> => #{
|
||||
<<"amount">> => ?INTEGER,
|
||||
@ -721,8 +736,7 @@ call_p2p_template_quote(P2PTemplateID, C) ->
|
||||
).
|
||||
|
||||
call_p2p_template_transfer(P2PTemplateID, TemplateTicket, QuoteToken, C) ->
|
||||
SenderToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
ReceiverToken = store_bank_card(C, <<"4150399999000900">>, <<"12/2025">>, <<"Buka Bjaka">>),
|
||||
Token = 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,
|
||||
@ -733,15 +747,15 @@ call_p2p_template_transfer(P2PTemplateID, TemplateTicket, QuoteToken, C) ->
|
||||
body => #{
|
||||
<<"sender">> => #{
|
||||
<<"type">> => <<"BankCardSenderResourceParams">>,
|
||||
<<"token">> => SenderToken,
|
||||
<<"token">> => Token,
|
||||
<<"authData">> => <<"session id">>
|
||||
},
|
||||
<<"receiver">> => #{
|
||||
<<"type">> => <<"BankCardReceiverResourceParams">>,
|
||||
<<"token">> => ReceiverToken
|
||||
<<"token">> => Token
|
||||
},
|
||||
<<"body">> => #{
|
||||
<<"amount">> => 101,
|
||||
<<"amount">> => ?INTEGER,
|
||||
<<"currency">> => ?RUB
|
||||
},
|
||||
<<"contactInfo">> => #{
|
||||
@ -753,124 +767,3 @@ 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},
|
||||
{w2w_transfer, fun('Create', _) -> {ok, ?W2W_TRANSFER(PartyID)} end}
|
||||
{fistful_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},
|
||||
{w2w_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
{fistful_w2w_transfer, fun('Create', _) -> CreateResultFun() end}
|
||||
], C).
|
||||
|
||||
get_w2_w_transfer_start_mocks(C, GetResultFun) ->
|
||||
wapi_ct_helper:mock_services([
|
||||
{w2w_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
{fistful_w2w_transfer, fun('Get', _) -> GetResultFun() end}
|
||||
], C).
|
||||
|
||||
|
@ -519,6 +519,34 @@
|
||||
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{
|
||||
@ -532,3 +560,4 @@
|
||||
receiver = ?RESOURCE_BANK_CARD,
|
||||
fees = ?FEES
|
||||
}).
|
||||
|
||||
|
@ -96,9 +96,11 @@ 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(p2p_transfer) ->
|
||||
get_service_modname(fistful_p2p_transfer) ->
|
||||
{ff_proto_p2p_transfer_thrift, 'Management'};
|
||||
get_service_modname(w2w_transfer) ->
|
||||
get_service_modname(fistful_p2p_session) ->
|
||||
{ff_proto_p2p_session_thrift, 'Management'};
|
||||
get_service_modname(fistful_w2w_transfer) ->
|
||||
{ff_proto_w2w_transfer_thrift, 'Management'}.
|
||||
|
||||
-spec get_service_deadline(service_name()) -> undefined | woody_deadline:deadline().
|
||||
|
@ -154,10 +154,18 @@
|
||||
|
||||
{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"
|
||||
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"
|
||||
}},
|
||||
{api_deadlines, #{
|
||||
wallet => 5000 % millisec
|
||||
|
@ -189,6 +189,11 @@
|
||||
]},
|
||||
|
||||
{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.2.0",
|
||||
{"1.1.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,"f373e09fc2e451b9ef3b5f86f54f9627fa29c59f"}},
|
||||
{ref,"87b13d386969047c9c16d310754f1f18733b36ab"}},
|
||||
0},
|
||||
{<<"fistful_reporter_proto">>,
|
||||
{git,"git@github.com:rbkmoney/fistful-reporter-proto.git",
|
||||
@ -204,28 +204,5 @@
|
||||
{<<"ranch">>, <<"6B1FAB51B49196860B733A49C07604465A47BDB78AA10C1C16A3D199F7F8C881">>},
|
||||
{<<"ssl_verify_fun">>, <<"F0EAFFF810D2041E93F915EF59899C923F4568F4585904D010387ED74988E77B">>},
|
||||
{<<"unicode_util_compat">>, <<"D869E4C68901DD9531385BB0C8C40444EBF624E60B6962D95952775CAC5E90CD">>},
|
||||
{<<"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">>}]}
|
||||
{<<"uuid">>, <<"C5DF97D1A3D626235C2415E74053C47B2138BB863C5CD802AB5CAECB8ECC019F">>}]}
|
||||
].
|
||||
|
Loading…
Reference in New Issue
Block a user