CAPI-397: QR code & crypto currency user interaction (#443)

* decode user interaction

* fix failing tests: invoice sessions and refunds

* update lockfile

* fix failing tests: event range

* CAPI-398: Fix refunds encoder (#444)

* CAPI-398: Fix refunds encoder

* Typo

* update tests

* add revision to getinvoicepaymentmethods

* add revision to getinvoicepaymentmethodsbytemplateid

* update swag

* explicit decimal points for crypto

* change unwrap to match on ok tuple

* rework crypto interaction decoder

* add unit test for crypto decoder, fix

* formatting fix

* extra test cases

* review updates

* shorten line

* typo fix
This commit is contained in:
Roman Pushkov 2019-11-29 12:27:26 +03:00 committed by GitHub
parent 9bd2cf3098
commit 5b7bd81e35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 119 additions and 19 deletions

View File

@ -204,7 +204,8 @@ process_request(_OperationID, _Req, _Context) ->
{error, noimpl}.
get_customer_by_id(CustomerID, Context) ->
capi_handler_utils:service_call({customer_management, 'Get', [CustomerID]}, Context).
EventRange = #payproc_EventRange{},
capi_handler_utils:service_call({customer_management, 'Get', [CustomerID, EventRange]}, Context).
encode_customer_params(PartyID, Params) ->
#payproc_CustomerParams{

View File

@ -37,6 +37,18 @@ decode_user_interaction({redirect, BrowserRequest}) ->
#{
<<"interactionType">> => <<"Redirect">>,
<<"request">> => decode_browser_request(BrowserRequest)
};
decode_user_interaction({qr_code_display_request, QrCodeDisplayRequest}) ->
#{
<<"interactionType">> => <<"QrCodeDisplayRequest">>,
<<"qrCode">> => decode_qr_code(QrCodeDisplayRequest)
};
decode_user_interaction({crypto_currency_transfer_request, CryptoCurrencyTransferRequest}) ->
#{
<<"interactionType">> => <<"CryptoCurrencyTransferRequest">>,
<<"cryptoAddress">> => CryptoCurrencyTransferRequest#'CryptoCurrencyTransferRequest'.crypto_address,
<<"symbolicCode">> => decode_crypto_symcode(CryptoCurrencyTransferRequest),
<<"cryptoAmount">> => decode_crypto_amount(CryptoCurrencyTransferRequest)
}.
decode_browser_request({get_request, #'BrowserGetRequest'{uri = UriTemplate}}) ->
@ -51,6 +63,55 @@ decode_browser_request({post_request, #'BrowserPostRequest'{uri = UriTemplate, f
<<"form">> => decode_user_interaction_form(UserInteractionForm)
}.
decode_qr_code(#'QrCodeDisplayRequest'{qr_code = QrCode}) ->
QrCode#'QrCode'.payload.
decode_crypto_symcode(#'CryptoCurrencyTransferRequest'{crypto_cash = Cash}) ->
Cash#'CryptoCash'.crypto_symbolic_code.
decode_crypto_amount(#'CryptoCurrencyTransferRequest'{crypto_cash = Cash}) ->
% apparently Q is always a power of ten
Amount = Cash#'CryptoCash'.crypto_amount,
ok = ensure_correct_exponent(Amount),
Integral = decode_integral_part(Amount),
Fractional = decode_fractional_part(Amount),
build_decoded_crypto_amount(Integral, Fractional).
ensure_correct_exponent(#'Rational'{q = Q}) ->
Log = math:log10(Q),
case Log - trunc(Log) of
0.0 -> ok;
_ -> error('expected a power of 10 denominator')
end.
decode_integral_part(#'Rational'{p = P, q = Q}) ->
erlang:integer_to_binary(P div Q).
decode_fractional_part(#'Rational'{p = P, q = Q}) ->
Exponent = get_exponent(Q),
build_fractional(P rem Q, Exponent).
get_exponent(Q) ->
erlang:trunc(math:log10(Q)).
build_fractional(_Fractional, _Exponent = 0) ->
<<>>;
build_fractional(Fractional, Exponent) ->
BinaryFractional = erlang:integer_to_binary(Fractional),
strip_trailing_zeroes(genlib_string:pad_numeric(BinaryFractional, Exponent)).
strip_trailing_zeroes(Fractional) ->
ByteSize = byte_size(Fractional) - 1,
case Fractional of
<<Prefix:ByteSize/bytes, "0">> -> strip_trailing_zeroes(Prefix);
Fractional -> Fractional
end.
build_decoded_crypto_amount(Integral, <<>>) ->
Integral;
build_decoded_crypto_amount(Integral, Fractional) ->
<<Integral/binary, ".", Fractional/binary>>.
-spec decode_user_interaction_form(map()) ->
capi_handler_decoder_utils:decode_data().
@ -408,3 +469,28 @@ make_invoice_and_token(Invoice, PartyID, ExtraProperties) ->
ExtraProperties
)
}.
%%
-ifdef(EUNIT).
-include_lib("eunit/include/eunit.hrl").
-spec test() -> _.
-spec crypto_amount_decoder_test() -> _.
crypto_amount_decoder_test() ->
?assertError('expected a power of 10 denominator', decode_crypto_amount(build_request(1, 2))),
?assertEqual(<<"1100000007" >>, decode_crypto_amount(build_request(1100000007, 1 ))),
?assertEqual(<< "1" >>, decode_crypto_amount(build_request(100000000 , 100000000))),
?assertEqual(<< "1.1" >>, decode_crypto_amount(build_request(110000000 , 100000000))),
?assertEqual(<<"11.00000007">>, decode_crypto_amount(build_request(1100000007, 100000000))),
?assertEqual(<< "0.11000007">>, decode_crypto_amount(build_request(11000007 , 100000000))),
?assertEqual(<< "0.110007" >>, decode_crypto_amount(build_request(11000700 , 100000000))).
build_request(P, Q) ->
Amount = #'Rational'{p = P, q = Q},
Cash = #'CryptoCash'{crypto_amount = Amount, crypto_symbolic_code = <<>>},
#'CryptoCurrencyTransferRequest'{crypto_address = <<>>, crypto_cash = Cash}.
-endif.

View File

@ -1,5 +1,6 @@
-module(capi_handler_invoice_templates).
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
-include_lib("damsel/include/dmsl_payment_processing_thrift.hrl").
-behaviour(capi_handler).
@ -151,13 +152,12 @@ process_request('CreateInvoiceWithTemplate' = OperationID, Req, Context) ->
end;
process_request('GetInvoicePaymentMethodsByTemplateID', Req, Context) ->
Result =
capi_handler_decoder_invoicing:construct_payment_methods(
invoice_templating,
[maps:get('invoiceTemplateID', Req), capi_utils:unwrap(rfc3339:format(erlang:system_time()))],
Context
),
case Result of
InvoiceTemplateID = maps:get('invoiceTemplateID', Req),
Timestamp = capi_utils:unwrap(rfc3339:format(erlang:system_time())),
{ok, Party} = capi_handler_utils:get_my_party(Context),
Revision = Party#domain_Party.revision,
Args = [InvoiceTemplateID, Timestamp, {revision, Revision}],
case capi_handler_decoder_invoicing:construct_payment_methods(invoice_templating, Args, Context) of
{ok, PaymentMethods0} when is_list(PaymentMethods0) ->
PaymentMethods = capi_utils:deduplicate_payment_methods(PaymentMethods0),
{ok, {200, #{}, PaymentMethods}};

View File

@ -163,7 +163,11 @@ process_request('GetInvoiceEvents', Req, Context) ->
end;
process_request('GetInvoicePaymentMethods', Req, Context) ->
case capi_handler_decoder_invoicing:construct_payment_methods(invoicing, [maps:get(invoiceID, Req)], Context) of
InvoiceID = maps:get(invoiceID, Req),
Party = capi_utils:unwrap(capi_handler_utils:get_my_party(Context)),
Revision = Party#domain_Party.revision,
Args = [InvoiceID, {revision, Revision}],
case capi_handler_decoder_invoicing:construct_payment_methods(invoicing, Args, Context) of
{ok, PaymentMethods0} when is_list(PaymentMethods0) ->
PaymentMethods = capi_utils:deduplicate_payment_methods(PaymentMethods0),
{ok, {200, #{}, PaymentMethods}};
@ -360,4 +364,4 @@ get_invoice_by_external_id(ExternalID, #{woody_context := WoodyContext} = Contex
capi_handler_utils:get_invoice_by_id(InvoiceID, Context);
Error ->
Error
end.
end.

View File

@ -318,7 +318,10 @@ process_request('CreateRefund' = OperationID, Req, Context) ->
process_request('GetRefunds', Req, Context) ->
case capi_handler_utils:get_payment_by_id(maps:get(invoiceID, Req), maps:get(paymentID, Req), Context) of
{ok, #payproc_InvoicePayment{refunds = Refunds}} ->
{ok, {200, #{}, [capi_handler_decoder_invoicing:decode_refund(R, Context) || R <- Refunds]}};
{ok, {200, #{}, [
capi_handler_decoder_invoicing:decode_refund(R, Context)
|| #payproc_InvoicePaymentRefund{refund = R} <- Refunds
]}};
{exception, Exception} ->
case Exception of
#payproc_InvalidUser{} ->

View File

@ -303,7 +303,9 @@ wrap_payment_session(ClientInfo, PaymentSession) ->
woody:result().
get_invoice_by_id(InvoiceID, Context) ->
service_call_with([user_info], {invoicing, 'Get', [InvoiceID]}, Context).
EventRange = #payproc_EventRange{},
Args = [InvoiceID, EventRange],
service_call_with([user_info], {invoicing, 'Get', Args}, Context).
-spec get_payment_by_id(binary(), binary(), processing_context()) ->
woody:result().

View File

@ -229,9 +229,11 @@
-define(RECURRENT_PAYMENT, ?RECURRENT_PAYMENT({pending, #domain_InvoicePaymentPending{}})).
-define(PAYPROC_PAYMENT(Payment, Refunds, Adjustments), #payproc_InvoicePayment{
payment = Payment,
refunds = Refunds,
adjustments = Adjustments
payment = Payment,
refunds = [#payproc_InvoicePaymentRefund{refund = R, sessions = []} || R <- Refunds],
sessions = [],
legacy_refunds = Refunds,
adjustments = Adjustments
}).
-define(PAYPROC_PAYMENT, ?PAYPROC_PAYMENT(?PAYMENT, [?REFUND], [?ADJUSTMENT])).

View File

@ -206,7 +206,8 @@ get_invoice_events_ok_test(Config) ->
-spec get_invoice_payment_methods_ok_test(config()) ->
_.
get_invoice_payment_methods_ok_test(Config) ->
capi_ct_helper:mock_services([{invoicing, fun('ComputeTerms', _) -> {ok, ?TERM_SET} end}], Config),
capi_ct_helper:mock_services([{invoicing, fun('ComputeTerms', _) -> {ok, ?TERM_SET} end},
{party_management, fun('Get', _) -> {ok, ?PARTY} end}], Config),
{ok, _} = capi_client_invoices:get_invoice_payment_methods(?config(context, Config), ?STRING).
-spec create_payment_ok_test(config()) ->

View File

@ -148,5 +148,6 @@ get_invoice_template_ok_test(Config) ->
-spec get_invoice_payment_methods_by_tpl_id_ok_test(config()) ->
_.
get_invoice_payment_methods_by_tpl_id_ok_test(Config) ->
capi_ct_helper:mock_services([{'invoice_templating', fun('ComputeTerms', _) -> {ok, ?TERM_SET} end}], Config),
capi_ct_helper:mock_services([{invoice_templating, fun('ComputeTerms', _) -> {ok, ?TERM_SET} end},
{party_management, fun('Get', _) -> {ok, ?PARTY} end}], Config),
{ok, _} = capi_client_invoice_templates:get_invoice_payment_methods(?config(context, Config), ?STRING).

View File

@ -23,7 +23,7 @@
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.6.0">>},1},
{<<"damsel">>,
{git,"git@github.com:rbkmoney/damsel.git",
{ref,"4339b6c286741fc9a4bc851a92eb58ebbcce81ab"}},
{ref,"99a0e83ac031635ae9fc9947e5c0191fcb8cce45"}},
0},
{<<"dmt_client">>,
{git,"git@github.com:rbkmoney/dmt_client.git",

@ -1 +1 @@
Subproject commit 8e7cd05c06b531f7548428b748db443d498c4b51
Subproject commit bb3cee35cfc9723fa120461a37b630fe9aaf1f9d