mirror of
https://github.com/valitydev/capi-v2.git
synced 2024-11-06 01:55:20 +00:00
CAPI-231: Put the notion of digital wallets into this shithole (#145)
* CAPI-231: Proudly employ cowboy_access_log * BA-52: Bump to master rbkmoney/swag@13e66dd * BA-52: Bump to master rbkmoney/damsel@620cca5
This commit is contained in:
parent
99dc16fd96
commit
f653e420a0
@ -16,6 +16,7 @@
|
||||
swag_server,
|
||||
jose,
|
||||
cowboy_cors,
|
||||
cowboy_access_log,
|
||||
rfc3339,
|
||||
base64url,
|
||||
snowflake,
|
||||
|
@ -159,18 +159,25 @@ process_request('CreatePayment', Req, Context, ReqCtx) ->
|
||||
|
||||
process_request('CreatePaymentResource', Req, Context, ReqCtx) ->
|
||||
Params = maps:get('PaymentResourceParams', Req),
|
||||
ClientInfo = maps:get(<<"clientInfo">>, Params),
|
||||
PaymentTool = maps:get(<<"paymentTool">>, Params),
|
||||
case PaymentTool of
|
||||
#{<<"paymentToolType">> := <<"CardData">>} ->
|
||||
process_card_data(ClientInfo, PaymentTool, Context, ReqCtx);
|
||||
#{<<"paymentToolType">> := <<"PaymentTerminalData">>} ->
|
||||
process_payment_terminal_data(ClientInfo, PaymentTool, Context);
|
||||
_ ->
|
||||
{ok, {400, [], logic_error(
|
||||
invalidPaymentTool,
|
||||
<<"Specified payment tool is invalid or unsupported">>
|
||||
)}}
|
||||
ClientInfo = enrich_client_info(maps:get(<<"clientInfo">>, Params), Context),
|
||||
try
|
||||
V = maps:get(<<"paymentTool">>, Params),
|
||||
{PaymentTool, PaymentSessionID} = case V of
|
||||
#{<<"paymentToolType">> := <<"CardData">>} ->
|
||||
process_card_data(V, ReqCtx);
|
||||
#{<<"paymentToolType">> := <<"PaymentTerminalData">>} ->
|
||||
process_payment_terminal_data(V, ReqCtx);
|
||||
#{<<"paymentToolType">> := <<"DigitalWalletData">>} ->
|
||||
process_digital_wallet_data(V, ReqCtx)
|
||||
end,
|
||||
{ok, {201, [], decode_disposable_payment_resource(#domain_DisposablePaymentResource{
|
||||
payment_tool = PaymentTool,
|
||||
payment_session_id = PaymentSessionID,
|
||||
client_info = encode_client_info(ClientInfo)
|
||||
})}}
|
||||
catch
|
||||
Result ->
|
||||
Result
|
||||
end;
|
||||
|
||||
process_request('CreateInvoiceAccessToken', Req, Context, ReqCtx) ->
|
||||
@ -1907,7 +1914,7 @@ encode_payer_params(#{
|
||||
<<"contactInfo">> := ContactInfo
|
||||
}) ->
|
||||
PaymentTool = encode_payment_tool_token(Token),
|
||||
{ClientInfo, PaymentSession} = unwrap_session(EncodedSession),
|
||||
{ClientInfo, PaymentSession} = unwrap_payment_session(EncodedSession),
|
||||
{payment_resource, #payproc_PaymentResourcePayerParams{
|
||||
resource = #domain_DisposablePaymentResource{
|
||||
payment_tool = PaymentTool,
|
||||
@ -1922,7 +1929,9 @@ encode_payment_tool_token(Token) ->
|
||||
#{<<"type">> := <<"bank_card">>} = Encoded ->
|
||||
encode_bank_card(Encoded);
|
||||
#{<<"type">> := <<"payment_terminal">>} = Encoded ->
|
||||
encode_payment_terminal(Encoded)
|
||||
encode_payment_terminal(Encoded);
|
||||
#{<<"type">> := <<"digital_wallet">>} = Encoded ->
|
||||
encode_digital_wallet(Encoded)
|
||||
catch
|
||||
error:badarg ->
|
||||
erlang:throw(invalid_token)
|
||||
@ -1950,6 +1959,22 @@ decode_payment_terminal(#domain_PaymentTerminal{
|
||||
<<"terminal_type">> => Type
|
||||
}).
|
||||
|
||||
decode_digital_wallet(#domain_DigitalWallet{
|
||||
provider = Provider,
|
||||
id = ID
|
||||
}) ->
|
||||
capi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"digital_wallet">>,
|
||||
<<"provider">> => atom_to_binary(Provider, utf8),
|
||||
<<"id">> => ID
|
||||
}).
|
||||
|
||||
decode_client_info(ClientInfo) ->
|
||||
#{
|
||||
<<"fingerprint">> => ClientInfo#domain_ClientInfo.fingerprint,
|
||||
<<"ip">> => ClientInfo#domain_ClientInfo.ip_address
|
||||
}.
|
||||
|
||||
encode_client_info(ClientInfo) ->
|
||||
#domain_ClientInfo{
|
||||
fingerprint = maps:get(<<"fingerprint">>, ClientInfo),
|
||||
@ -2334,6 +2359,12 @@ encode_payment_terminal(#{<<"terminal_type">> := Type}) ->
|
||||
terminal_type = binary_to_existing_atom(Type, utf8)
|
||||
}}.
|
||||
|
||||
encode_digital_wallet(#{<<"provider">> := Provider, <<"id">> := ID}) ->
|
||||
{digital_wallet, #domain_DigitalWallet{
|
||||
provider = binary_to_existing_atom(Provider, utf8),
|
||||
id = ID
|
||||
}}.
|
||||
|
||||
encode_customer_params(PartyID, Params) ->
|
||||
#payproc_CustomerParams{
|
||||
party_id = PartyID,
|
||||
@ -2358,7 +2389,7 @@ encode_customer_binding_params(#{
|
||||
}
|
||||
}) ->
|
||||
PaymentTool = encode_payment_tool_token(Token),
|
||||
{ClientInfo, PaymentSession} = unwrap_session(EncodedSession),
|
||||
{ClientInfo, PaymentSession} = unwrap_payment_session(EncodedSession),
|
||||
#payproc_CustomerBindingParams{
|
||||
payment_resource = #domain_DisposablePaymentResource{
|
||||
payment_tool = PaymentTool,
|
||||
@ -2367,23 +2398,6 @@ encode_customer_binding_params(#{
|
||||
}
|
||||
}.
|
||||
|
||||
wrap_session(ClientInfo, PaymentSession) ->
|
||||
capi_utils:map_to_base64url(#{
|
||||
<<"clientInfo">> => ClientInfo,
|
||||
<<"paymentSession">> => PaymentSession
|
||||
}).
|
||||
|
||||
unwrap_session(Encoded) ->
|
||||
#{
|
||||
<<"clientInfo">> := ClientInfo,
|
||||
<<"paymentSession">> := PaymentSession
|
||||
} = try capi_utils:base64url_to_map(Encoded)
|
||||
catch
|
||||
error:badarg ->
|
||||
erlang:throw(invalid_payment_session)
|
||||
end,
|
||||
{ClientInfo, PaymentSession}.
|
||||
|
||||
decode_invoice_event(#payproc_Event{
|
||||
id = EventID,
|
||||
created_at = CreatedAt,
|
||||
@ -2584,26 +2598,40 @@ decode_payer({payment_resource, #domain_PaymentResourcePayer{
|
||||
decode_payment_tool_token({bank_card, BankCard}) ->
|
||||
decode_bank_card(BankCard);
|
||||
decode_payment_tool_token({payment_terminal, PaymentTerminal}) ->
|
||||
decode_payment_terminal(PaymentTerminal).
|
||||
decode_payment_terminal(PaymentTerminal);
|
||||
decode_payment_tool_token({digital_wallet, DigitalWallet}) ->
|
||||
decode_digital_wallet(DigitalWallet).
|
||||
|
||||
decode_payment_tool_details({bank_card, BankCard}) ->
|
||||
decode_bank_card_details(<<"PaymentToolDetailsBankCard">>, BankCard);
|
||||
decode_payment_tool_details({payment_terminal, #domain_PaymentTerminal{
|
||||
decode_payment_tool_details({bank_card, V}) ->
|
||||
decode_bank_card_details(V, #{<<"detailsType">> => <<"PaymentToolDetailsBankCard">>});
|
||||
decode_payment_tool_details({payment_terminal, V}) ->
|
||||
decode_payment_terminal_details(V, #{<<"detailsType">> => <<"PaymentToolDetailsPaymentTerminal">>});
|
||||
decode_payment_tool_details({digital_wallet, V}) ->
|
||||
decode_digital_wallet_details(V, #{<<"detailsType">> => <<"PaymentToolDetailsDigitalWallet">>}).
|
||||
|
||||
decode_bank_card_details(#domain_BankCard{
|
||||
'payment_system' = PaymentSystem,
|
||||
'masked_pan' = MaskedPan
|
||||
}, V) ->
|
||||
V#{
|
||||
<<"cardNumberMask">> => decode_masked_pan(MaskedPan),
|
||||
<<"paymentSystem">> => genlib:to_binary(PaymentSystem)
|
||||
}.
|
||||
|
||||
decode_payment_terminal_details(#domain_PaymentTerminal{
|
||||
terminal_type = Type
|
||||
}}) ->
|
||||
#{
|
||||
<<"detailsType">> => <<"PaymentToolDetailsPaymentTerminal">>,
|
||||
}, V) ->
|
||||
V#{
|
||||
<<"provider">> => genlib:to_binary(Type)
|
||||
}.
|
||||
|
||||
decode_bank_card_details(DetailsType, #domain_BankCard{
|
||||
'payment_system' = PaymentSystem,
|
||||
'masked_pan' = MaskedPan
|
||||
}) ->
|
||||
#{
|
||||
<<"detailsType">> => DetailsType,
|
||||
<<"cardNumberMask">> => decode_masked_pan(MaskedPan),
|
||||
<<"paymentSystem">> => genlib:to_binary(PaymentSystem)
|
||||
decode_digital_wallet_details(#domain_DigitalWallet{
|
||||
provider = qiwi,
|
||||
id = ID
|
||||
}, V) ->
|
||||
V#{
|
||||
<<"digitalWalletDetailsType">> => <<"DigitalWalletDetailsQIWI">>,
|
||||
<<"phoneNumberMask">> => mask_phone_number(ID)
|
||||
}.
|
||||
|
||||
-define(MASKED_PAN_MAX_LENGTH, 4).
|
||||
@ -2613,6 +2641,9 @@ decode_masked_pan(MaskedPan) when byte_size(MaskedPan) > ?MASKED_PAN_MAX_LENGTH
|
||||
decode_masked_pan(MaskedPan) ->
|
||||
MaskedPan.
|
||||
|
||||
mask_phone_number(PhoneNumber) ->
|
||||
capi_utils:redact(PhoneNumber, <<"^\\+\\d(\\d{1,10}?)\\d{2,4}$">>).
|
||||
|
||||
decode_contact_info(#domain_ContactInfo{
|
||||
phone_number = PhoneNumber,
|
||||
email = Email
|
||||
@ -3047,8 +3078,8 @@ decode_russian_bank_account(#domain_RussianBankAccount{
|
||||
bank_name = BankName,
|
||||
bank_post_account = BankPostAccount,
|
||||
bank_bik = BankBik
|
||||
}) ->
|
||||
#{
|
||||
}, V) ->
|
||||
V#{
|
||||
<<"account">> => Account,
|
||||
<<"bankName">> => BankName,
|
||||
<<"bankPostAccount">> => BankPostAccount,
|
||||
@ -3061,8 +3092,8 @@ decode_international_bank_account(#domain_InternationalBankAccount{
|
||||
bank_address = BankAddress,
|
||||
iban = Iban,
|
||||
bic = Bic
|
||||
}) ->
|
||||
#{
|
||||
}, V) ->
|
||||
V#{
|
||||
<<"accountHolder">> => AccountHolder,
|
||||
<<"bankName">> => BankName,
|
||||
<<"bankAddress">> => BankAddress,
|
||||
@ -3158,7 +3189,7 @@ decode_legal_entity({
|
||||
<<"representativePosition">> => RepresentativePosition,
|
||||
<<"representativeFullName">> => RepresentativeFullName,
|
||||
<<"representativeDocument">> => RepresentativeDocument,
|
||||
<<"bankAccount">> => decode_russian_bank_account(BankAccount)
|
||||
<<"bankAccount">> => decode_russian_bank_account(BankAccount, #{})
|
||||
};
|
||||
decode_legal_entity({
|
||||
international_legal_entity,
|
||||
@ -3336,18 +3367,12 @@ decode_stat_payout_status({Status, _}) ->
|
||||
decode_stat_payout_tool_details(PayoutType) ->
|
||||
decode_payout_tool_details(merchstat_to_domain(PayoutType)).
|
||||
|
||||
decode_payout_tool_details({bank_card, BankCard}) ->
|
||||
decode_bank_card_details(<<"PayoutToolDetailsBankCard">>, BankCard);
|
||||
decode_payout_tool_details({russian_bank_account, BankAccount}) ->
|
||||
maps:merge(
|
||||
#{<<"detailsType">> => <<"PayoutToolDetailsBankAccount">>},
|
||||
decode_russian_bank_account(BankAccount)
|
||||
);
|
||||
decode_payout_tool_details({international_bank_account, BankAccount}) ->
|
||||
maps:merge(
|
||||
#{<<"detailsType">> => <<"PayoutToolDetailsInternationalBankAccount">>},
|
||||
decode_international_bank_account(BankAccount)
|
||||
).
|
||||
decode_payout_tool_details({bank_card, V}) ->
|
||||
decode_bank_card_details(V, #{<<"detailsType">> => <<"PayoutToolDetailsBankCard">>});
|
||||
decode_payout_tool_details({russian_bank_account, V}) ->
|
||||
decode_russian_bank_account(V, #{<<"detailsType">> => <<"PayoutToolDetailsBankAccount">>});
|
||||
decode_payout_tool_details({international_bank_account, V}) ->
|
||||
decode_international_bank_account(V, #{<<"detailsType">> => <<"PayoutToolDetailsInternationalBankAccount">>}).
|
||||
|
||||
encode_payout_type('PayoutCard') ->
|
||||
<<"bank_card">>;
|
||||
@ -3840,20 +3865,15 @@ decode_customer_binding(#payproc_CustomerBinding{
|
||||
|
||||
decode_disposable_payment_resource(#domain_DisposablePaymentResource{
|
||||
payment_tool = PaymentTool,
|
||||
payment_session_id = PaymentSession,
|
||||
client_info = #domain_ClientInfo{
|
||||
fingerprint = Fingerprint,
|
||||
ip_address = IP
|
||||
}
|
||||
payment_session_id = PaymentSessionID,
|
||||
client_info = ClientInfo0
|
||||
}) ->
|
||||
ClientInfo = decode_client_info(ClientInfo0),
|
||||
#{
|
||||
<<"paymentToolToken">> => decode_payment_tool_token(PaymentTool),
|
||||
<<"paymentSession">> => PaymentSession,
|
||||
<<"paymentSession">> => wrap_payment_session(ClientInfo, PaymentSessionID),
|
||||
<<"paymentToolDetails">> => decode_payment_tool_details(PaymentTool),
|
||||
<<"clientInfo">> => #{
|
||||
<<"ip">> => IP,
|
||||
<<"fingerprint">> => Fingerprint
|
||||
}
|
||||
<<"clientInfo">> => ClientInfo
|
||||
}.
|
||||
|
||||
decode_customer_binding_status({Status, StatusInfo}) ->
|
||||
@ -4122,24 +4142,12 @@ process_search_request_result(QueryType, Result, #{decode_fun := DecodeFun}) ->
|
||||
end.
|
||||
|
||||
get_time(Key, Req) ->
|
||||
to_universal_time(genlib_map:get(Key, Req)).
|
||||
|
||||
to_universal_time(Tz = undefined) ->
|
||||
Tz;
|
||||
to_universal_time(Tz) ->
|
||||
{ok, {Date, Time, Usec, TzOffset}} = rfc3339:parse(Tz),
|
||||
TzSec = calendar:datetime_to_gregorian_seconds({Date, Time}),
|
||||
%% The following crappy code is a dialyzer workaround
|
||||
%% for the wrong rfc3339:parse/1 spec.
|
||||
{UtcDate, UtcTime} = calendar:gregorian_seconds_to_datetime(
|
||||
case TzOffset of
|
||||
_ when is_integer(TzOffset) ->
|
||||
TzSec - (60*TzOffset);
|
||||
_ ->
|
||||
TzSec
|
||||
end),
|
||||
{ok, Utc} = rfc3339:format({UtcDate, UtcTime, Usec, 0}),
|
||||
Utc.
|
||||
case genlib_map:get(Key, Req) of
|
||||
Timestamp when is_binary(Timestamp) ->
|
||||
capi_utils:to_universal_time(Timestamp);
|
||||
undefined ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
get_split_interval(SplitSize, minute) ->
|
||||
SplitSize * 60;
|
||||
@ -4362,7 +4370,9 @@ decode_payment_methods({value, PaymentMethodRefs}) ->
|
||||
decode_payment_method(bank_card, PaymentSystems) ->
|
||||
#{<<"method">> => <<"BankCard">>, <<"paymentSystems">> => lists:map(fun genlib:to_binary/1, PaymentSystems)};
|
||||
decode_payment_method(payment_terminal, Providers) ->
|
||||
#{<<"method">> => <<"PaymentTerminal">>, <<"providers">> => lists:map(fun genlib:to_binary/1, Providers)}.
|
||||
#{<<"method">> => <<"PaymentTerminal">>, <<"providers">> => lists:map(fun genlib:to_binary/1, Providers)};
|
||||
decode_payment_method(digital_wallet, Providers) ->
|
||||
#{<<"method">> => <<"DigitalWallet">>, <<"providers">> => lists:map(fun genlib:to_binary/1, Providers)}.
|
||||
|
||||
compute_terms(ServiceName, Args, Context) ->
|
||||
service_call(
|
||||
@ -4375,81 +4385,87 @@ compute_terms(ServiceName, Args, Context) ->
|
||||
reply_5xx(Code) when Code >= 500 andalso Code < 600 ->
|
||||
{Code, [], <<>>}.
|
||||
|
||||
process_card_data(ClientInfo0, PaymentTool, Context, ReqCtx) ->
|
||||
CardData = get_card_data(PaymentTool),
|
||||
process_card_data(Data, ReqCtx) ->
|
||||
Result = service_call(
|
||||
cds_storage,
|
||||
'PutCardData',
|
||||
[CardData],
|
||||
[encode_card_data(Data)],
|
||||
ReqCtx
|
||||
),
|
||||
case Result of
|
||||
{ok, #'PutCardDataResult'{
|
||||
session_id = PaymentSession,
|
||||
session_id = SessionID,
|
||||
bank_card = BankCard
|
||||
}} ->
|
||||
Token = decode_bank_card(BankCard),
|
||||
PreparedIP = get_prepared_ip(Context),
|
||||
ClientInfo = ClientInfo0#{<<"ip">> => PreparedIP},
|
||||
|
||||
Session = wrap_session(ClientInfo, PaymentSession),
|
||||
Resp = #{
|
||||
<<"paymentToolToken">> => Token,
|
||||
<<"paymentSession">> => Session,
|
||||
<<"paymentToolDetails">> => decode_payment_tool_details({bank_card, BankCard}),
|
||||
<<"clientInfo">> => ClientInfo
|
||||
},
|
||||
{ok, {201, [], Resp}};
|
||||
{{bank_card, BankCard}, SessionID};
|
||||
{exception, Exception} ->
|
||||
case Exception of
|
||||
#'InvalidCardData'{} ->
|
||||
{ok, {400, [], logic_error(invalidRequest, <<"Card data is invalid">>)}};
|
||||
throw({ok, {400, [], logic_error(invalidRequest, <<"Card data is invalid">>)}});
|
||||
#'KeyringLocked'{} ->
|
||||
% TODO
|
||||
% It's better for the cds to signal woody-level unavailability when the
|
||||
% keyring is locked, isn't it? It could always mention keyring lock as a
|
||||
% reason in a woody error definition.
|
||||
{error, reply_5xx(503)}
|
||||
throw({error, reply_5xx(503)})
|
||||
end
|
||||
end.
|
||||
|
||||
get_card_data(PaymentTool) ->
|
||||
{Month, Year} = parse_exp_date(genlib_map:get(<<"expDate">>, PaymentTool)),
|
||||
CardNumber = genlib:to_binary(genlib_map:get(<<"cardNumber">>, PaymentTool)),
|
||||
encode_card_data(CardData) ->
|
||||
{Month, Year} = parse_exp_date(genlib_map:get(<<"expDate">>, CardData)),
|
||||
CardNumber = genlib:to_binary(genlib_map:get(<<"cardNumber">>, CardData)),
|
||||
#'CardData'{
|
||||
pan = CardNumber,
|
||||
exp_date = #'ExpDate'{
|
||||
month = Month,
|
||||
year = Year
|
||||
},
|
||||
cardholder_name = genlib_map:get(<<"cardHolder">>, PaymentTool),
|
||||
cvv = genlib_map:get(<<"cvv">>, PaymentTool)
|
||||
cardholder_name = genlib_map:get(<<"cardHolder">>, CardData),
|
||||
cvv = genlib_map:get(<<"cvv">>, CardData)
|
||||
}.
|
||||
|
||||
get_prepared_ip(Context) ->
|
||||
#{
|
||||
ip_address := IP
|
||||
} = get_peer_info(Context),
|
||||
genlib:to_binary(inet:ntoa(IP)).
|
||||
|
||||
process_payment_terminal_data(ClientInfo0, TerminalData, Context) ->
|
||||
process_payment_terminal_data(Data, _ReqCtx) ->
|
||||
PaymentTerminal = #domain_PaymentTerminal{
|
||||
terminal_type = binary_to_existing_atom(
|
||||
genlib_map:get(<<"provider">>, TerminalData),
|
||||
genlib_map:get(<<"provider">>, Data),
|
||||
utf8
|
||||
)
|
||||
},
|
||||
Token = decode_payment_terminal(PaymentTerminal),
|
||||
PreparedIP = get_prepared_ip(Context),
|
||||
ClientInfo = ClientInfo0#{<<"ip">> => PreparedIP},
|
||||
Session = wrap_session(ClientInfo, <<"">>),
|
||||
Resp = #{
|
||||
<<"paymentToolToken">> => Token,
|
||||
<<"paymentSession">> => Session,
|
||||
<<"paymentToolDetails">> => decode_payment_tool_details({payment_terminal, PaymentTerminal}),
|
||||
<<"clientInfo">> => ClientInfo
|
||||
},
|
||||
{ok, {201, [], Resp}}.
|
||||
{{payment_terminal, PaymentTerminal}, <<>>}.
|
||||
|
||||
process_digital_wallet_data(Data, _ReqCtx) ->
|
||||
DigitalWallet = case Data of
|
||||
#{<<"digitalWalletType">> := <<"DigitalWalletQIWI">>} ->
|
||||
#domain_DigitalWallet{
|
||||
provider = qiwi,
|
||||
id = maps:get(<<"phoneNumber">>, Data)
|
||||
}
|
||||
end,
|
||||
{{digital_wallet, DigitalWallet}, <<>>}.
|
||||
|
||||
enrich_client_info(ClientInfo, Context) ->
|
||||
ClientInfo#{<<"ip">> => prepare_client_ip(Context)}.
|
||||
|
||||
prepare_client_ip(Context) ->
|
||||
#{ip_address := IP} = get_peer_info(Context),
|
||||
genlib:to_binary(inet:ntoa(IP)).
|
||||
|
||||
wrap_payment_session(ClientInfo, PaymentSession) ->
|
||||
capi_utils:map_to_base64url(#{
|
||||
<<"clientInfo">> => ClientInfo,
|
||||
<<"paymentSession">> => PaymentSession
|
||||
}).
|
||||
|
||||
unwrap_payment_session(Encoded) ->
|
||||
#{
|
||||
<<"clientInfo">> := ClientInfo,
|
||||
<<"paymentSession">> := PaymentSession
|
||||
} = try capi_utils:base64url_to_map(Encoded)
|
||||
catch
|
||||
error:badarg ->
|
||||
erlang:throw(invalid_payment_session)
|
||||
end,
|
||||
{ClientInfo, PaymentSession}.
|
||||
|
||||
get_default_url_lifetime() ->
|
||||
Now = erlang:system_time(second),
|
||||
@ -4460,18 +4476,3 @@ get_default_url_lifetime() ->
|
||||
Error ->
|
||||
error(Error)
|
||||
end.
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-spec test() -> _.
|
||||
|
||||
-spec to_universal_time_test() -> _.
|
||||
to_universal_time_test() ->
|
||||
?assertEqual(undefined, to_universal_time(undefined)),
|
||||
?assertEqual(<<"2017-04-19T13:56:07Z">>, to_universal_time(<<"2017-04-19T13:56:07Z">>)),
|
||||
?assertEqual(<<"2017-04-19T13:56:07.530000Z">>, to_universal_time(<<"2017-04-19T13:56:07.53Z">>)),
|
||||
?assertEqual(<<"2017-04-19T10:36:07.530000Z">>, to_universal_time(<<"2017-04-19T13:56:07.53+03:20">>)),
|
||||
?assertEqual(<<"2017-04-19T17:16:07.530000Z">>, to_universal_time(<<"2017-04-19T13:56:07.53-03:20">>)).
|
||||
|
||||
-endif. %%TEST
|
||||
|
@ -39,7 +39,7 @@ get_cowboy_config(LogicHandler) ->
|
||||
cowboy_cors,
|
||||
cowboy_handler
|
||||
]},
|
||||
{onrequest, fun ?MODULE:request_hook/1},
|
||||
{onrequest, cowboy_access_log:get_request_hook()},
|
||||
{onresponse, fun ?MODULE:response_hook/4}
|
||||
].
|
||||
|
||||
@ -52,10 +52,10 @@ request_hook(Req) ->
|
||||
-spec response_hook(cowboy:http_status(), cowboy:http_headers(), iodata(), cowboy_req:req()) ->
|
||||
cowboy_req:req().
|
||||
|
||||
response_hook(Code, Headers, _, Req) ->
|
||||
response_hook(Code, Headers, Body, Req) ->
|
||||
try
|
||||
{Code1, Headers1, Req1} = handle_response(Code, Headers, Req),
|
||||
_ = log_access(Code1, Headers1, Req1),
|
||||
_ = log_access(Code1, Headers1, Body, Req1),
|
||||
Req1
|
||||
catch
|
||||
Class:Reason ->
|
||||
@ -110,49 +110,6 @@ get_oops_body_safe(Code) ->
|
||||
get_oops_body(Code) ->
|
||||
genlib_map:get(Code, genlib_app:env(?APP, oops_bodies, #{}), undefined).
|
||||
|
||||
log_access(Code, Headers, Req) ->
|
||||
{Method, _} = cowboy_req:method(Req),
|
||||
{Path, _} = cowboy_req:path(Req),
|
||||
{ReqLen, _} = cowboy_req:body_length(Req),
|
||||
{ReqId, _} = cowboy_req:header(<<"x-request-id">>, Req, undefined),
|
||||
RemAddr = get_remote_addr(Req),
|
||||
RespLen = get_response_len(Headers),
|
||||
Duration = get_request_duration(Req),
|
||||
ReqMeta = [
|
||||
{remote_addr, RemAddr},
|
||||
{request_method, Method},
|
||||
{request_path, Path},
|
||||
{request_length, ReqLen},
|
||||
{response_length, RespLen},
|
||||
{request_time, Duration},
|
||||
{'http_x-request-id', ReqId},
|
||||
{status, Code}
|
||||
],
|
||||
Meta = orddict:merge(fun(_Key, New, _Old) -> New end, ReqMeta, lager:md()),
|
||||
%% Call lager:log/5 here directly in order to pass request metadata (fused into
|
||||
%% lager metadata) without storing it in a process dict via lager:md/1.
|
||||
lager:log(capi_access_lager_event, info, Meta, "", []).
|
||||
|
||||
get_remote_addr(Req) ->
|
||||
case swag_server_handler_api:determine_peer(Req) of
|
||||
{{ok, #{ip_address := IP}}, _} ->
|
||||
genlib:to_binary(inet:ntoa(IP));
|
||||
{_, _} ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
get_request_duration(Req) ->
|
||||
case cowboy_req:meta(?START_TIME_TAG, Req) of
|
||||
{undefined, _} ->
|
||||
undefined;
|
||||
{StartTime, _} ->
|
||||
(genlib_time:ticks() - StartTime) / 1000000
|
||||
end.
|
||||
|
||||
get_response_len(Headers) ->
|
||||
case lists:keyfind(<<"content-length">>, 1, Headers) of
|
||||
{_, Len} ->
|
||||
genlib:to_int(Len);
|
||||
false ->
|
||||
undefined
|
||||
end.
|
||||
log_access(Code, Headers, Body, Req) ->
|
||||
LogFun = cowboy_access_log:get_response_hook(capi_access_lager_event),
|
||||
LogFun(Code, Headers, Body, Req).
|
||||
|
@ -4,6 +4,10 @@
|
||||
-export([base64url_to_map/1]).
|
||||
-export([map_to_base64url/1]).
|
||||
|
||||
-export([to_universal_time/1]).
|
||||
|
||||
-export([redact/2]).
|
||||
|
||||
-spec logtag_process(atom(), any()) -> ok.
|
||||
|
||||
logtag_process(Key, Value) when is_atom(Key) ->
|
||||
@ -27,3 +31,57 @@ map_to_base64url(Map) when is_map(Map) ->
|
||||
_ = lager:debug("encoding map ~p to base64 failed with ~p:~p", [Map, Class, Reason]),
|
||||
erlang:error(badarg)
|
||||
end.
|
||||
|
||||
-spec redact(Subject :: binary(), Pattern :: binary()) -> Redacted :: binary().
|
||||
redact(Subject, Pattern) ->
|
||||
case re:run(Subject, Pattern, [global, {capture, all_but_first, index}]) of
|
||||
{match, Captures} ->
|
||||
lists:foldl(fun redact_match/2, Subject, Captures);
|
||||
nomatch ->
|
||||
Subject
|
||||
end.
|
||||
|
||||
redact_match({S, Len}, Subject) ->
|
||||
<<Pre:S/binary, _:Len/binary, Rest/binary>> = Subject,
|
||||
<<Pre/binary, (binary:copy(<<"*">>, Len))/binary, Rest/binary>>;
|
||||
redact_match([Capture], Message) ->
|
||||
redact_match(Capture, Message).
|
||||
|
||||
-spec to_universal_time(Timestamp :: binary()) -> TimestampUTC :: binary().
|
||||
to_universal_time(Timestamp) ->
|
||||
{ok, {Date, Time, Usec, TZOffset}} = rfc3339:parse(Timestamp),
|
||||
Seconds = calendar:datetime_to_gregorian_seconds({Date, Time}),
|
||||
%% The following crappy code is a dialyzer workaround
|
||||
%% for the wrong rfc3339:parse/1 spec.
|
||||
{DateUTC, TimeUTC} = calendar:gregorian_seconds_to_datetime(
|
||||
case TZOffset of
|
||||
_ when is_integer(TZOffset) ->
|
||||
Seconds - (60 * TZOffset);
|
||||
_ ->
|
||||
Seconds
|
||||
end
|
||||
),
|
||||
{ok, TimestampUTC} = rfc3339:format({DateUTC, TimeUTC, Usec, 0}),
|
||||
TimestampUTC.
|
||||
|
||||
%%
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-spec test() -> _.
|
||||
|
||||
-spec to_universal_time_test() -> _.
|
||||
to_universal_time_test() ->
|
||||
?assertEqual(<<"2017-04-19T13:56:07Z">>, to_universal_time(<<"2017-04-19T13:56:07Z">>)),
|
||||
?assertEqual(<<"2017-04-19T13:56:07.530000Z">>, to_universal_time(<<"2017-04-19T13:56:07.53Z">>)),
|
||||
?assertEqual(<<"2017-04-19T10:36:07.530000Z">>, to_universal_time(<<"2017-04-19T13:56:07.53+03:20">>)),
|
||||
?assertEqual(<<"2017-04-19T17:16:07.530000Z">>, to_universal_time(<<"2017-04-19T13:56:07.53-03:20">>)).
|
||||
|
||||
-spec redact_test() -> _.
|
||||
redact_test() ->
|
||||
P1 = <<"^\\+\\d(\\d{1,10}?)\\d{2,4}$">>,
|
||||
?assertEqual(<<"+7******3210">>, redact(<<"+79876543210">>, P1)),
|
||||
?assertEqual( <<"+1*11">>, redact(<<"+1111">>, P1)).
|
||||
|
||||
-endif.
|
||||
|
@ -64,7 +64,10 @@
|
||||
cancel_payment_ok_test/1,
|
||||
capture_payment_ok_test/1,
|
||||
|
||||
create_payment_tool_token_ok_test/1,
|
||||
create_visa_payment_resource_ok_test/1,
|
||||
create_nspkmir_payment_resource_ok_test/1,
|
||||
create_euroset_payment_resource_ok_test/1,
|
||||
create_qw_payment_resource_ok_test/1,
|
||||
|
||||
get_my_party_ok_test/1,
|
||||
suspend_my_party_ok_test/1,
|
||||
@ -167,7 +170,7 @@ invoice_access_token_tests() ->
|
||||
get_payment_by_id_ok_test,
|
||||
cancel_payment_ok_test,
|
||||
capture_payment_ok_test,
|
||||
create_payment_tool_token_ok_test
|
||||
{group, payment_resources}
|
||||
].
|
||||
|
||||
customer_access_token_tests() ->
|
||||
@ -190,6 +193,14 @@ groups() ->
|
||||
woody_unknown_test
|
||||
]
|
||||
},
|
||||
{payment_resources, [],
|
||||
[
|
||||
create_visa_payment_resource_ok_test,
|
||||
create_nspkmir_payment_resource_ok_test,
|
||||
create_euroset_payment_resource_ok_test,
|
||||
create_qw_payment_resource_ok_test
|
||||
]
|
||||
},
|
||||
{operations_by_base_api_token, [],
|
||||
[
|
||||
create_invoice_ok_test,
|
||||
@ -757,9 +768,9 @@ capture_payment_ok_test(Config) ->
|
||||
mock_services([{invoicing, fun('CapturePayment', _) -> {ok, ok} end}], Config),
|
||||
ok = capi_client_payments:capture_payment(?config(context, Config), ?STRING, ?STRING, ?STRING).
|
||||
|
||||
-spec create_payment_tool_token_ok_test(_) ->
|
||||
-spec create_visa_payment_resource_ok_test(_) ->
|
||||
_.
|
||||
create_payment_tool_token_ok_test(Config) ->
|
||||
create_visa_payment_resource_ok_test(Config) ->
|
||||
mock_services([
|
||||
{cds_storage, fun
|
||||
('PutCardData', [#'CardData'{pan = <<"411111", _:6/binary, Mask:4/binary>>}]) ->
|
||||
@ -771,7 +782,30 @@ create_payment_tool_token_ok_test(Config) ->
|
||||
masked_pan = Mask
|
||||
},
|
||||
session_id = ?STRING
|
||||
}};
|
||||
}}
|
||||
end}
|
||||
], Config),
|
||||
ClientInfo = #{<<"fingerprint">> => <<"test fingerprint">>},
|
||||
{ok, #{<<"paymentToolDetails">> := #{
|
||||
<<"detailsType">> := <<"PaymentToolDetailsBankCard">>,
|
||||
<<"paymentSystem">> := <<"visa">>,
|
||||
<<"cardNumberMask">> := <<"1111">>
|
||||
}}} = capi_client_tokens:create_payment_resource(?config(context, Config), #{
|
||||
<<"paymentTool">> => #{
|
||||
<<"paymentToolType">> => <<"CardData">>,
|
||||
<<"cardNumber">> => <<"4111111111111111">>,
|
||||
<<"cardHolder">> => <<"Alexander Weinerschnitzel">>,
|
||||
<<"expDate">> => <<"08/27">>,
|
||||
<<"cvv">> => <<"232">>
|
||||
},
|
||||
<<"clientInfo">> => ClientInfo
|
||||
}).
|
||||
|
||||
-spec create_nspkmir_payment_resource_ok_test(_) ->
|
||||
_.
|
||||
create_nspkmir_payment_resource_ok_test(Config) ->
|
||||
mock_services([
|
||||
{cds_storage, fun
|
||||
('PutCardData', [#'CardData'{pan = <<"22001111", _:6/binary, Mask:2/binary>>}]) ->
|
||||
{ok, #'PutCardDataResult'{
|
||||
bank_card = #domain_BankCard{
|
||||
@ -784,27 +818,51 @@ create_payment_tool_token_ok_test(Config) ->
|
||||
}}
|
||||
end}
|
||||
], Config),
|
||||
PaymentTool = #{
|
||||
<<"paymentToolType">> => <<"CardData">>,
|
||||
<<"cardHolder">> => <<"Alexander Weinerschnitzel">>,
|
||||
<<"expDate">> => <<"08/27">>,
|
||||
<<"cvv">> => <<"232">>
|
||||
},
|
||||
ClientInfo = #{<<"fingerprint">> => <<"test fingerprint">>},
|
||||
{ok, #{<<"paymentToolDetails">> := #{
|
||||
<<"detailsType">> := <<"PaymentToolDetailsBankCard">>,
|
||||
<<"paymentSystem">> := <<"visa">>,
|
||||
<<"cardNumberMask">> := <<"1111">>
|
||||
}}} = capi_client_tokens:create_payment_resource(?config(context, Config), #{
|
||||
<<"paymentTool">> => PaymentTool#{<<"cardNumber">> => <<"4111111111111111">>},
|
||||
<<"clientInfo">> => ClientInfo
|
||||
}),
|
||||
{ok, #{<<"paymentToolDetails">> := #{
|
||||
<<"detailsType">> := <<"PaymentToolDetailsBankCard">>,
|
||||
<<"paymentSystem">> := <<"nspkmir">>,
|
||||
<<"cardNumberMask">> := <<"11">>
|
||||
}}} = capi_client_tokens:create_payment_resource(?config(context, Config), #{
|
||||
<<"paymentTool">> => PaymentTool#{<<"cardNumber">> => <<"2200111111111111">>},
|
||||
<<"paymentTool">> => #{
|
||||
<<"paymentToolType">> => <<"CardData">>,
|
||||
<<"cardNumber">> => <<"2200111111111111">>,
|
||||
<<"cardHolder">> => <<"Alexander Weinerschnitzel">>,
|
||||
<<"expDate">> => <<"08/27">>,
|
||||
<<"cvv">> => <<"232">>
|
||||
},
|
||||
<<"clientInfo">> => ClientInfo
|
||||
}).
|
||||
|
||||
-spec create_euroset_payment_resource_ok_test(_) ->
|
||||
_.
|
||||
create_euroset_payment_resource_ok_test(Config) ->
|
||||
ClientInfo = #{<<"fingerprint">> => <<"test fingerprint">>},
|
||||
{ok, #{<<"paymentToolDetails">> := #{
|
||||
<<"detailsType">> := <<"PaymentToolDetailsPaymentTerminal">>,
|
||||
<<"provider">> := <<"euroset">>
|
||||
}}} = capi_client_tokens:create_payment_resource(?config(context, Config), #{
|
||||
<<"paymentTool">> => #{
|
||||
<<"paymentToolType">> => <<"PaymentTerminalData">>,
|
||||
<<"provider">> => <<"euroset">>
|
||||
},
|
||||
<<"clientInfo">> => ClientInfo
|
||||
}).
|
||||
|
||||
-spec create_qw_payment_resource_ok_test(_) ->
|
||||
_.
|
||||
create_qw_payment_resource_ok_test(Config) ->
|
||||
ClientInfo = #{<<"fingerprint">> => <<"test fingerprint">>},
|
||||
{ok, #{<<"paymentToolDetails">> := #{
|
||||
<<"detailsType">> := <<"PaymentToolDetailsDigitalWallet">>,
|
||||
<<"digitalWalletDetailsType">> := <<"DigitalWalletDetailsQIWI">>,
|
||||
<<"phoneNumberMask">> := <<"+7******3210">>
|
||||
}}} = capi_client_tokens:create_payment_resource(?config(context, Config), #{
|
||||
<<"paymentTool">> => #{
|
||||
<<"paymentToolType">> => <<"DigitalWalletData">>,
|
||||
<<"digitalWalletType">> => <<"DigitalWalletQIWI">>,
|
||||
<<"phoneNumber">> => <<"+79876543210">>
|
||||
},
|
||||
<<"clientInfo">> => ClientInfo
|
||||
}).
|
||||
|
||||
|
@ -62,6 +62,11 @@
|
||||
{git, "https://github.com/danielwhite/cowboy_cors.git",
|
||||
{branch, "master"}
|
||||
}
|
||||
},
|
||||
{cowboy_access_log,
|
||||
{git, "git@github.com:rbkmoney/cowboy_access_log.git",
|
||||
{branch, "master"}
|
||||
}
|
||||
}
|
||||
]}.
|
||||
|
||||
|
10
rebar.lock
10
rebar.lock
@ -2,6 +2,10 @@
|
||||
[{<<"base64url">>,{pkg,<<"base64url">>,<<"0.0.1">>},0},
|
||||
{<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},1},
|
||||
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},0},
|
||||
{<<"cowboy_access_log">>,
|
||||
{git,"git@github.com:rbkmoney/cowboy_access_log.git",
|
||||
{ref,"a06c299a9b05f6868c48f40b1aeb7c00b775ce12"}},
|
||||
0},
|
||||
{<<"cowboy_cors">>,
|
||||
{git,"https://github.com/danielwhite/cowboy_cors.git",
|
||||
{ref,"392f5804b63fff2bd0fda67671d5b2fbe0badd37"}},
|
||||
@ -9,7 +13,7 @@
|
||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},1},
|
||||
{<<"dmsl">>,
|
||||
{git,"git@github.com:rbkmoney/damsel.git",
|
||||
{ref,"07fbb3057e78e37611e642160a7201fe31d6ff69"}},
|
||||
{ref,"4ca2329c564b3730dbd742ae370be9919905b9de"}},
|
||||
0},
|
||||
{<<"genlib">>,
|
||||
{git,"https://github.com/rbkmoney/genlib.git",
|
||||
@ -32,7 +36,7 @@
|
||||
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1},
|
||||
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.0.2">>},1},
|
||||
{<<"parse_trans">>,
|
||||
{git,"git@github.com:rbkmoney/parse_trans.git",
|
||||
{git,"https://github.com/rbkmoney/parse_trans.git",
|
||||
{ref,"5ee45f5bfa6c04329bea3281977b774f04c89f11"}},
|
||||
0},
|
||||
{<<"pooler">>,{pkg,<<"pooler">>,<<"1.5.0">>},0},
|
||||
@ -49,7 +53,7 @@
|
||||
1},
|
||||
{<<"woody">>,
|
||||
{git,"git@github.com:rbkmoney/woody_erlang.git",
|
||||
{ref,"2d00bda10454534e230d452b7338debafaf0a869"}},
|
||||
{ref,"ad1e91050c36d8de15f1c7d8dd8a2c682d2d158c"}},
|
||||
0},
|
||||
{<<"woody_user_identity">>,
|
||||
{git,"git@github.com:rbkmoney/woody_erlang_user_identity.git",
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 963e8f2933bc71710e81c4a18416ff18fede9549
|
||||
Subproject commit 8d4b00cd075196f364f25beb6d0093cb741b97a3
|
Loading…
Reference in New Issue
Block a user