Add getPaymentByExternalID (#349)

* CAPI-357: Add GetPaymentByExternalID

* CAPI-357: Add tests, reworked code

* CAPI-357: Move test to another suite

* CAPI-357: Type in spec

* CAPI-357: Fix typo in spec

* Update apps/capi/src/capi_bender.erl

Co-Authored-By: Andrew Mayorov <encube.ul@gmail.com>

* CAPI-357: Store invoice_id whitin bender context

* CAPI-357: Simplify code

* CAPI-357: More code cleanup

* CAPI-357: Add test for missing invoice in getPaymentByExternalId

* CAPI-357: Don't mess flow

* CAPI-357: Move helper function to test
This commit is contained in:
Sergey Elin 2019-06-11 16:36:47 +03:00 committed by GitHub
parent dddb3a6e6c
commit e6b02e716c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 214 additions and 10 deletions

View File

@ -198,6 +198,8 @@ get_operation_access('GetPayments' , #{'invoiceID' := ID}) ->
[{[{invoices, ID}, payments], read}];
get_operation_access('GetPaymentByID' , #{'invoiceID' := ID1, paymentID := ID2}) ->
[{[{invoices, ID1}, {payments, ID2}], read}];
get_operation_access('GetPaymentByExternalID' , _) ->
[{[invoices, payments], read}];
get_operation_access('CancelPayment' , #{'invoiceID' := ID1, paymentID := ID2}) ->
[{[{invoices, ID1}, {payments, ID2}], write}];
get_operation_access('CapturePayment' , #{'invoiceID' := ID1, paymentID := ID2}) ->

View File

@ -4,11 +4,21 @@
-include_lib("bender_proto/include/msgpack_thrift.hrl").
-type woody_context() :: woody_context:ctx().
-type context_data() :: #{binary() => term()}.
-type bender_context() :: #{binary() => term()}.
-export_type([
bender_context/0,
context_data/0
]).
-export([gen_by_snowflake/4]).
-export([gen_by_snowflake/3]).
-export([gen_by_sequence/4]).
-export([gen_by_sequence/5]).
-export([gen_by_constant/4]).
-export([gen_by_constant/5]).
-export([get_idempotent_key/3]).
-export([get_internal_id/2]).
-define(SCHEMA_VER1, 1).
@ -17,19 +27,33 @@
{error, {external_id_conflict, binary()}}.
gen_by_snowflake(IdempotentKey, Hash, WoodyContext) ->
gen_by_snowflake(IdempotentKey, Hash, WoodyContext, #{}).
-spec gen_by_snowflake(binary(), integer(), woody_context(), context_data()) ->
{ok, binary()} |
{error, {external_id_conflict, binary()}}.
gen_by_snowflake(IdempotentKey, Hash, WoodyContext, CtxData) ->
Snowflake = {snowflake, #bender_SnowflakeSchema{}},
generate_id(IdempotentKey, Snowflake, Hash, WoodyContext).
generate_id(IdempotentKey, Snowflake, Hash, WoodyContext, CtxData).
-spec gen_by_sequence(binary(), binary(), integer(), woody_context()) ->
{ok, binary()} |
{error, {external_id_conflict, binary()}}.
gen_by_sequence(IdempotentKey, SequenceID, Hash, WoodyContext) ->
gen_by_sequence(IdempotentKey, SequenceID, Hash, WoodyContext, #{}).
-spec gen_by_sequence(binary(), binary(), integer(), woody_context(), context_data()) ->
{ok, binary()} |
{error, {external_id_conflict, binary()}}.
gen_by_sequence(IdempotentKey, SequenceID, Hash, WoodyContext, CtxData) ->
Sequence = {sequence, #bender_SequenceSchema{
sequence_id = SequenceID,
minimum = 100
}},
generate_id(IdempotentKey, Sequence, Hash, WoodyContext).
generate_id(IdempotentKey, Sequence, Hash, WoodyContext, CtxData).
-spec gen_by_constant(binary(), binary(), integer(), woody_context()) ->
@ -37,8 +61,15 @@ gen_by_sequence(IdempotentKey, SequenceID, Hash, WoodyContext) ->
{error, {external_id_conflict, binary()}}.
gen_by_constant(IdempotentKey, ConstantID, Hash, WoodyContext) ->
gen_by_constant(IdempotentKey, ConstantID, Hash, WoodyContext, #{}).
-spec gen_by_constant(binary(), binary(), integer(), woody_context(), context_data()) ->
{ok, binary()} |
{error, {external_id_conflict, binary()}}.
gen_by_constant(IdempotentKey, ConstantID, Hash, WoodyContext, CtxData) ->
Constant = {constant, #bender_ConstantSchema{internal_id = ConstantID}},
generate_id(IdempotentKey, Constant, Hash, WoodyContext).
generate_id(IdempotentKey, Constant, Hash, WoodyContext, CtxData).
-spec get_idempotent_key(atom() | binary(), binary(), binary() | undefined) ->
binary().
@ -50,15 +81,31 @@ get_idempotent_key(Prefix, PartyID, undefined) ->
get_idempotent_key(Prefix, PartyID, ExternalID) ->
<<"capi/", Prefix/binary, "/", PartyID/binary, "/", ExternalID/binary>>.
-spec get_internal_id(binary(), woody_context()) ->
{ok, binary(), context_data()} | {error, internal_id_not_found}.
get_internal_id(ExternalID, WoodyContext) ->
case capi_woody_client:call_service(bender, 'GetInternalID', [ExternalID], WoodyContext) of
{ok, #bender_GetInternalIDResult{
internal_id = InternalID,
context = Context
}} ->
UnmarshaledCtx = capi_msgp_marshalling:unmarshal(Context),
{ok, InternalID, get_context_data(UnmarshaledCtx)};
{exception, #bender_InternalIDNotFound{}} ->
{error, internal_id_not_found}
end.
%% Internal
gen_external_id() ->
genlib:unique().
generate_id(Key, BenderSchema, Hash, WoodyContext) ->
generate_id(Key, BenderSchema, Hash, WoodyContext, CtxData) ->
Context = capi_msgp_marshalling:marshal(#{
<<"version">> => ?SCHEMA_VER1,
<<"params_hash">> => Hash
<<"params_hash">> => Hash,
<<"context_data">> => CtxData
}),
Args = [Key, BenderSchema, Context],
Result = case capi_woody_client:call_service(bender, 'GenerateID', Args, WoodyContext) of
@ -72,3 +119,8 @@ generate_id(Key, BenderSchema, Hash, WoodyContext) ->
{ok, ID, Hash} -> {ok, ID};
{ok, ID, _Other} -> {error, {external_id_conflict, ID}}
end.
-spec get_context_data(bender_context()) -> undefined | context_data().
get_context_data(Context) ->
maps:get(<<"context_data">>, Context, #{}).

View File

@ -112,6 +112,26 @@ process_request('GetPaymentByID', Req, Context) ->
end
end;
process_request('GetPaymentByExternalID', Req, Context) ->
ExternalID = maps:get(externalID, Req),
case get_payment_by_external_id(ExternalID, Context) of
{ok, InvoiceID, Payment} ->
{ok, {200, [], decode_invoice_payment(InvoiceID, Payment, Context)}};
{error, internal_id_not_found} ->
{ok, general_error(404, <<"Payment not found">>)};
{error, invoice_not_found} ->
{ok, general_error(404, <<"Invoice not found">>)};
{exception, Exception} ->
case Exception of
#payproc_InvoicePaymentNotFound{} ->
{ok, general_error(404, <<"Payment not found">>)};
#payproc_InvalidUser{} ->
{ok, general_error(404, <<"Invoice not found">>)};
#payproc_InvoiceNotFound{} ->
{ok, general_error(404, <<"Invoice not found">>)}
end
end;
process_request('CancelPayment', Req, Context) ->
CallArgs = [maps:get(invoiceID, Req), maps:get(paymentID, Req), maps:get(<<"reason">>, maps:get('Reason', Req))],
Call = {invoicing, 'CancelPayment', CallArgs},
@ -306,7 +326,8 @@ create_payment(InvoiceID, PartyID, PaymentParams, #{woody_context := WoodyCtx} =
ExternalID = maps:get(<<"externalID">>, PaymentParams, undefined),
IdempotentKey = capi_bender:get_idempotent_key(BenderPrefix, PartyID, ExternalID),
Hash = erlang:phash2(PaymentParams),
case capi_bender:gen_by_sequence(IdempotentKey, InvoiceID, Hash, WoodyCtx) of
CtxData = #{<<"invoice_id">> => InvoiceID},
case capi_bender:gen_by_sequence(IdempotentKey, InvoiceID, Hash, WoodyCtx, CtxData) of
{ok, ID} ->
Params = encode_invoice_payment_params(ID, ExternalID, PaymentParams),
Call = {invoicing, 'StartPayment', [InvoiceID, Params]},
@ -391,3 +412,27 @@ encode_optional_cash(_, _, _, _) ->
decode_invoice_payment(InvoiceID, #payproc_InvoicePayment{payment = Payment}, Context) ->
capi_handler_decoder_invoicing:decode_payment(InvoiceID, Payment, Context).
-spec get_payment_by_external_id(binary(), capi_handler:processing_context()) ->
woody:result().
get_payment_by_external_id(ExternalID, #{woody_context := WoodyContext} = Context) ->
PartyID = capi_handler_utils:get_party_id(Context),
PaymentKey = capi_bender:get_idempotent_key('CreatePayment', PartyID, ExternalID),
case capi_bender:get_internal_id(PaymentKey, WoodyContext) of
{ok, PaymentID, CtxData} ->
InvoiceID = maps:get(<<"invoice_id">>, CtxData, undefined),
get_payment(InvoiceID, PaymentID, Context);
Error ->
Error
end.
get_payment(undefined, _, _) ->
{error, invoice_not_found};
get_payment(InvoiceID, PaymentID, Context) ->
case capi_handler_utils:get_payment_by_id(InvoiceID, PaymentID, Context) of
{ok, Payment} ->
{ok, InvoiceID, Payment};
Error ->
Error
end.

View File

@ -96,7 +96,11 @@
get_payment_institutions/1,
get_payment_institution_by_ref/1,
get_payment_institution_payment_terms/1,
get_payment_institution_payout_terms/1
get_payment_institution_payout_terms/1,
check_no_payment_by_external_id_test/1,
check_no_internal_id_for_external_id_test/1,
retrieve_payment_by_external_id_test/1,
check_no_invoice_by_external_id_test/1
]).
-define(CAPI_PORT , 8080).
@ -196,7 +200,11 @@ groups() ->
get_payment_institution_by_ref,
get_payment_institution_payment_terms,
get_payment_institution_payout_terms,
delete_customer_ok_test
delete_customer_ok_test,
check_no_payment_by_external_id_test,
check_no_internal_id_for_external_id_test,
retrieve_payment_by_external_id_test,
check_no_invoice_by_external_id_test
]
}
].
@ -1209,6 +1217,69 @@ get_category_by_ref_ok_test(Config) ->
get_schedule_by_ref_ok_test(Config) ->
{ok, _} = capi_client_payouts:get_schedule_by_ref(?config(context, Config), ?INTEGER).
-spec check_no_payment_by_external_id_test(config()) ->
_.
check_no_payment_by_external_id_test(Config) ->
ExternalID = capi_ct_helper:unique_id(),
BenderContext = capi_msgp_marshalling:marshal(#{<<"context_data">> => #{<<"invoice_id">> => <<"123">>}}),
capi_ct_helper:mock_services([
{invoicing, fun('GetPayment', _) -> throw(#payproc_InvoicePaymentNotFound{}) end},
{bender, fun('GetInternalID', _) ->
InternalKey = capi_ct_helper:unique_id(),
{ok, capi_ct_helper_bender:get_internal_id_result(InternalKey, BenderContext)} end}
], Config),
{error, {404, #{
<<"message">> := <<"Payment not found">>
}}} =
capi_client_payments:get_payment_by_external_id(?config(context, Config), ExternalID).
-spec check_no_invoice_by_external_id_test(config()) ->
_.
check_no_invoice_by_external_id_test(Config) ->
ExternalID = capi_ct_helper:unique_id(),
BenderContext = capi_msgp_marshalling:marshal(#{}),
capi_ct_helper:mock_services([
{bender, fun('GetInternalID', _) ->
InternalKey = capi_ct_helper:unique_id(),
{ok, capi_ct_helper_bender:get_internal_id_result(InternalKey, BenderContext)} end}
], Config),
{error, {404, #{
<<"message">> := <<"Invoice not found">>
}}} =
capi_client_payments:get_payment_by_external_id(?config(context, Config), ExternalID).
-spec check_no_internal_id_for_external_id_test(config()) ->
_.
check_no_internal_id_for_external_id_test(Config) ->
ExternalID = capi_ct_helper:unique_id(),
capi_ct_helper:mock_services([
{bender, fun('GetInternalID', _) -> throw(capi_ct_helper_bender:no_internal_id()) end}
], Config),
{error, {404, #{
<<"message">> := <<"Payment not found">>
}}} =
capi_client_payments:get_payment_by_external_id(?config(context, Config), ExternalID).
-spec retrieve_payment_by_external_id_test(config()) ->
_.
retrieve_payment_by_external_id_test(Config) ->
PaymentID = capi_ct_helper:unique_id(),
ExternalID = capi_ct_helper:unique_id(),
BenderContext = capi_msgp_marshalling:marshal(#{<<"context_data">> => #{<<"invoice_id">> => <<"123">>}}),
capi_ct_helper:mock_services([
{invoicing, fun('GetPayment', _) -> {ok, ?PAYPROC_PAYMENT(PaymentID, ExternalID)} end},
{bender, fun('GetInternalID', _) ->
InternalKey = capi_ct_helper:unique_id(),
{ok, capi_ct_helper_bender:get_internal_id_result(InternalKey, BenderContext)} end}
], Config),
{ok, #{
<<"externalID">> := ExternalID
}} =
capi_client_payments:get_payment_by_external_id(?config(context, Config), ExternalID).
-spec get_payment_institutions(config()) ->
_.
get_payment_institutions(Config) ->

View File

@ -19,6 +19,7 @@
-export([mock_services/2]).
-export([mock_services_/2]).
-export([get_lifetime/0]).
-export([unique_id/0]).
-define(CAPI_IP , "::").
-define(CAPI_PORT , 8080).
@ -232,3 +233,9 @@ get_lifetime(YY, MM, DD) ->
<<"months">> => MM,
<<"days">> => DD
}.
-spec unique_id() -> binary().
unique_id() ->
<<ID:64>> = snowflake:new(),
genlib_format:format_int_base(ID, 62).

View File

@ -4,9 +4,15 @@
-export([get_result/1]).
-export([get_result/2]).
-export([get_internal_id_result/2]).
-export([no_internal_id/0]).
-spec get_result(binary()) -> bender_thrift:bender_GenerationResult().
-spec get_result(binary(), msgpack_thrift:'Value'() | undefined) -> bender_thrift:bender_GenerationResult().
-spec get_internal_id_result(binary(), msgpack_thrift:'Value'() | undefined) ->
bender_thrift:bender_GetInternalIDResult().
-spec no_internal_id() -> bender_thrift:'InternalIDNotFound'().
get_result(ID) ->
get_result(ID, undefined).
@ -16,3 +22,12 @@ get_result(ID, Context) ->
internal_id = ID,
context = Context
}.
get_internal_id_result(ID, Ctx) ->
#bender_GetInternalIDResult{
internal_id = ID,
context = Ctx
}.
no_internal_id() ->
#bender_InternalIDNotFound{}.

View File

@ -1,6 +1,7 @@
-module(capi_client_payments).
-export([get_payment_by_id/3]).
-export([get_payment_by_external_id/2]).
-export([get_payments/2]).
-export([create_payment/3]).
-export([cancel_payment/4]).
@ -34,6 +35,17 @@ get_payment_by_id(Context, InvoiceID, PaymentID) ->
Response = swag_client_payments_api:get_payment_by_id(Url, PreparedParams, Opts),
capi_client_lib:handle_response(Response).
-spec get_payment_by_external_id(context(), binary()) -> {ok, term()} | {error, term()}.
get_payment_by_external_id(Context, ExternalID) ->
Params = #{
qs_val => #{
<<"externalID">> => ExternalID
}
},
{Url, PreparedParams, Opts} = capi_client_lib:make_request(Context, Params),
Response = swag_client_payments_api:get_payment_by_external_id(Url, PreparedParams, Opts),
capi_client_lib:handle_response(Response).
-spec create_payment(context(), map(), binary()) -> {ok, term()} | {error, term()}.
create_payment(Context, Request, InvoiceID) ->
Params = #{

View File

@ -3,7 +3,7 @@
{<<"bear">>,{pkg,<<"bear">>,<<"0.8.7">>},2},
{<<"bender_proto">>,
{git,"git@github.com:rbkmoney/bender-proto.git",
{ref,"70f25cc97512522d37a40ee6b9aedb039150fca0"}},
{ref,"d765b9dfeb89d6eefccb947356dab85fbff592a9"}},
0},
{<<"binbase_proto">>,
{git,"git@github.com:rbkmoney/binbase-proto.git",

@ -1 +1 @@
Subproject commit cd79255d11af3e1eccf1990943119c94dda1c853
Subproject commit c2f02b36edc7070874d0fc3f670927999c1606b3