mirror of
https://github.com/valitydev/capi-v2.git
synced 2024-11-06 01:55:20 +00:00
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:
parent
dddb3a6e6c
commit
e6b02e716c
@ -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}) ->
|
||||
|
@ -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, #{}).
|
@ -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.
|
||||
|
@ -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) ->
|
||||
|
@ -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).
|
||||
|
@ -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{}.
|
||||
|
@ -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 = #{
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user