ED-282/deps: Migrate to feat for idempotency feats (#578)

* ED-282/deps: Migrate to feat for idempotency feats

* ED-282/ref: Fix dialyzer warning

* ED-282/ref: Fix dialyzer warnings

* ED-282/fix: Remove leftover

* ED-282/ref: Fix legacy module naming

* ED-282/ref: Fix headers module naming

* ED-282/fix: Fix v2 legacy fallback

* ED-282/fix: Fix v2 compativility code

* ED-282/ref: Fix dialyzer and linter warnings

* ED-282/fix: Fix bug and dialyzer warnings

* ED-282/test: Return create_invoice_legacy_fail

* ED-282/ref: Rewrite feat CT reader

Make it simpler and more explicit

* ED-282/ref: Fix formatting

* ED-282/fix: Remove debug leftovers

* ED-282/fix: Fix CT feat reader

* ED-282/test: Fix unused fields

* ED-282/ref: Refactor capi_bender

* ED-282/ref: Refactor arg naming in capi_bender

* ED-282/fix: Fix capi_bender list_diff for legacy

* ED-282/test: Add success test and fix impl

* ED-282/fix: Fix types for consistency

* ED-282/ref: Tweak elvis to ignore idemp tests

* ED-282/ref: Simplify capi_bender API

* ED-282/deps: Switch capi_bender to bender_client

* ED-282/ref: Remove direct bender call leftovers

* ED-282/fix: Return spec for get_context_data

* ED-282/ref: Rename bender capi namespace const

* ED-282/ref: Remove leftover

* ED-282/ref: Remove read_schema redundant clauses

* ED-282/ref: Rename bender namespace var

* ED-282/ref: Remove leftovers and fix formatting
This commit is contained in:
Jarosław Rogov 2021-11-02 15:24:00 +03:00 committed by GitHub
parent 4e39a79083
commit 8381da653a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1590 additions and 561 deletions

View File

@ -1,5 +1,5 @@
-ifndef(__capi_feature_schemas__).
-define(__capi_feature_schemas__, 42).
-ifndef(__capi_feature_schemas_legacy__).
-define(__capi_feature_schemas_legacy__, 42).
% Marking some feature as `discriminator` will make featureset comparator consider two sets with different
% `discriminator` values as _different everywhere_ which usually helps with diff readability.

View File

@ -9,6 +9,7 @@
public_key,
snowflake,
genlib,
feat,
woody,
capi_woody_client,
damsel,

View File

@ -9,35 +9,35 @@
-type issuer_id() :: dmsl_domain_thrift:'PartyID'() | dmsl_payment_processing_thrift:'UserID'().
-type idempotent_key() :: binary().
-type idempotent_key_params() :: {idempotent_key_prefix(), issuer_id(), external_id() | undefined}.
-type identity() :: {identity, identity_hash(), identity_features(), identity_schema()}.
-type identity_params() ::
{schema, identity_schema(), capi_idemp_features:request()}
| {schema, identity_schema(), capi_idemp_features:request(), HashedRequest :: capi_idemp_features:request()}
| identity().
%% TODO(ED-287): remove identity_request() from below
-opaque identity() :: {identity, identity_features(), identity_schema(), identity_request()}.
-type identity_hash() :: non_neg_integer().
-type identity_features() :: capi_idemp_features:features().
-type identity_schema() :: capi_idemp_features:schema().
-type identity_features() :: feat:features().
%% TODO(ED-287): switch back to passing schema by value (`schemas:schema()`)
%% and not by name (`schema`) after V2 is removed
%% -type identity_schema() :: feat:schema().
-type identity_schema_name() :: atom().
-type identity_schema() :: identity_schema_name().
-type identity_request() :: feat:request().
-type woody_context() :: woody_context:ctx().
-type context_data() :: #{binary() => term()}.
-type bender_context() :: #{binary() => term()}.
-type sequence_id() :: binary().
-type sequence_params() :: #{minimum => integer()}.
-type difference() :: capi_idemp_features:difference().
-type difference() :: feat:difference().
-type constant_id() :: binary().
-type external_id_conflict_v1() :: {external_id_conflict, id(), undefined, identity_schema()}.
-type external_id_conflict_v2() :: {external_id_conflict, id(), difference(), identity_schema()}.
-type external_id_conflict() :: external_id_conflict_v1() | external_id_conflict_v2().
-type external_id_conflict() :: {external_id_conflict, id(), difference(), identity_schema()}.
-type generation_error() :: external_id_conflict().
-export_type([id/0]).
-export_type([external_id/0]).
-export_type([issuer_id/0]).
-export_type([idempotent_key_params/0]).
-export_type([identity_params/0]).
-export_type([context_data/0]).
-export_type([woody_context/0]).
-export_type([difference/0]).
@ -46,6 +46,7 @@
-export_type([constant_id/0]).
-export_type([external_id_conflict/0]).
-export_type([bender_context/0]).
-export_type([identity/0]).
-export([gen_snowflake/3]).
-export([gen_snowflake/4]).
@ -59,37 +60,39 @@
-export([gen_constant/5]).
-export([try_gen_constant/4]).
-export([try_gen_constant/5]).
-export([make_identity/1]).
-export([make_identity/2]).
-export([get_internal_id/2]).
%% deprecated
-define(SCHEMA_VER1, 1).
-define(SCHEMA_VER2, 2).
-define(BENDER_NAMESPACE, <<"capi">>).
-spec gen_snowflake(idempotent_key_params() | undefined, identity_params(), woody_context()) ->
%% deprecated
-define(SCHEMA_VER2, 2).
-define(SCHEMA_VER3, 3).
-spec gen_snowflake(idempotent_key_params() | undefined, identity(), woody_context()) ->
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
gen_snowflake(IdempotentKey, Identity, WoodyContext) ->
Context = #{},
gen_snowflake(IdempotentKey, Identity, WoodyContext, Context).
-spec gen_snowflake(idempotent_key_params() | undefined, identity_params(), woody_context(), context_data()) ->
-spec gen_snowflake(idempotent_key_params() | undefined, identity(), woody_context(), context_data()) ->
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
gen_snowflake(IdempotentKey, Identity, WoodyContext, Context) ->
IdSchema = {snowflake, #bender_SnowflakeSchema{}},
generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, Context).
-spec try_gen_snowflake(idempotent_key_params() | undefined, identity_params(), woody_context()) -> id().
-spec try_gen_snowflake(idempotent_key_params() | undefined, identity(), woody_context()) -> id().
try_gen_snowflake(IdempotentKey, Identity, WoodyContext) ->
Context = #{},
try_gen_snowflake(IdempotentKey, Identity, WoodyContext, Context).
-spec try_gen_snowflake(idempotent_key_params() | undefined, identity_params(), woody_context(), context_data()) ->
-spec try_gen_snowflake(idempotent_key_params() | undefined, identity(), woody_context(), context_data()) ->
id().
try_gen_snowflake(IdempotentKey, Identity, WoodyContext, Context) ->
IdSchema = {snowflake, #bender_SnowflakeSchema{}},
try_generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, Context).
-spec gen_sequence(idempotent_key_params() | undefined, identity_params(), sequence_id(), woody_context()) ->
-spec gen_sequence(idempotent_key_params() | undefined, identity(), sequence_id(), woody_context()) ->
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
gen_sequence(IdempotentKey, Identity, SequenceID, WoodyContext) ->
SequenceParams = #{},
@ -97,7 +100,7 @@ gen_sequence(IdempotentKey, Identity, SequenceID, WoodyContext) ->
-spec gen_sequence(
idempotent_key_params() | undefined,
identity_params(),
identity(),
sequence_id(),
sequence_params(),
woody_context()
@ -108,7 +111,7 @@ gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext)
-spec gen_sequence(
idempotent_key_params() | undefined,
identity_params(),
identity(),
sequence_id(),
sequence_params(),
woody_context(),
@ -120,7 +123,7 @@ gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext,
-spec try_gen_sequence(
idempotent_key_params() | undefined,
identity_params(),
identity(),
sequence_id(),
sequence_params(),
woody_context(),
@ -130,54 +133,54 @@ try_gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyConte
IdSchema = build_sequence_schema(SequenceID, SequenceParams),
try_generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, ContextData).
-spec gen_constant(idempotent_key_params(), identity_params(), constant_id(), woody_context()) ->
-spec gen_constant(idempotent_key_params(), identity(), constant_id(), woody_context()) ->
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext) ->
Context = #{},
gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext, Context).
-spec gen_constant(idempotent_key_params(), identity_params(), constant_id(), woody_context(), context_data()) ->
-spec gen_constant(idempotent_key_params(), identity(), constant_id(), woody_context(), context_data()) ->
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext, Context) ->
IdSchema = {constant, #bender_ConstantSchema{internal_id = ConstantID}},
generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, Context).
-spec try_gen_constant(idempotent_key_params(), identity_params(), constant_id(), woody_context()) -> id().
-spec try_gen_constant(idempotent_key_params(), identity(), constant_id(), woody_context()) -> id().
try_gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext) ->
Context = #{},
try_gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext, Context).
-spec try_gen_constant(idempotent_key_params(), identity_params(), constant_id(), woody_context(), context_data()) ->
-spec try_gen_constant(idempotent_key_params(), identity(), constant_id(), woody_context(), context_data()) ->
id().
try_gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext, Context) ->
IdSchema = {constant, #bender_ConstantSchema{internal_id = ConstantID}},
try_generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, Context).
-spec make_identity(identity_params()) -> identity().
make_identity({schema, Schema, Data}) ->
Hash = erlang:phash2(Data),
Features = capi_idemp_features:read(Schema, Data),
{identity, Hash, Features, Schema};
make_identity({schema, Schema, Data, HashedData}) ->
Hash = erlang:phash2(HashedData),
Features = capi_idemp_features:read(Schema, Data),
{identity, Hash, Features, Schema};
make_identity(Identity = {identity, _Hash, _Features, _Schema}) ->
Identity.
-spec make_identity(identity_schema(), identity_request()) -> identity().
make_identity(Schema, Data) ->
Features = feat:read(read_schema(Schema), Data),
{identity, Features, Schema, Data}.
transform_identity_to_deprecated_v2({identity, _NewFeatures, Schema, Data}) ->
LegacyFeatures = capi_idemp_features_legacy:read(read_schema_deprecated_v2(Schema), Data),
{identity, LegacyFeatures, Schema, Data}.
%% TODO(ED-287): (see above)
read_schema(SchemaName) when is_atom(SchemaName) ->
capi_feature_schemas:SchemaName().
read_schema_deprecated_v2(SchemaName) when is_atom(SchemaName) ->
capi_feature_schemas_legacy:SchemaName().
-spec get_internal_id(idempotent_key_params(), woody_context()) ->
{ok, binary(), context_data()} | {error, internal_id_not_found}.
get_internal_id(IdempotentKeyParams, WoodyContext) ->
IdempotentKey = make_idempotent_key(IdempotentKeyParams),
case capi_woody_client:call_service(bender, 'GetInternalID', {IdempotentKey}, 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}
case bender_client:get_internal_id(IdempotentKey, WoodyContext) of
{ok, InternalID, Context} ->
{ok, InternalID, get_context_data(Context)};
{error, _} = Error ->
Error
end.
%%%
@ -193,7 +196,7 @@ build_sequence_schema(SequenceID, SequenceParams) ->
build_bender_ctx(Features, Ctx) ->
#{
<<"version">> => ?SCHEMA_VER2,
<<"version">> => ?SCHEMA_VER3,
<<"features">> => Features,
<<"context_data">> => Ctx
}.
@ -205,22 +208,27 @@ try_generate_id(BenderIdSchema, IdempotentKey, Identity, WoodyContext, CtxData)
case generate_id(BenderIdSchema, IdempotentKey, Identity, WoodyContext, CtxData) of
{ok, ID} ->
ID;
{error, {external_id_conflict, ID, undefined, Schema}} ->
logger:warning("This externalID: ~p, used in another request.~n", [ID]),
SourceID = get_external_id(IdempotentKey),
throw({external_id_conflict, ID, SourceID, Schema});
{error, {external_id_conflict, ID, Difference, Schema}} ->
ReadableDiff = capi_idemp_features:list_diff_fields(Schema, Difference),
{error, {Err, ID, Difference, Schema}} when Err == external_id_conflict; Err == external_id_conflict_legacy ->
ReadableDiff =
case Err of
external_id_conflict ->
feat:list_diff_fields(read_schema(Schema), Difference);
external_id_conflict_legacy ->
capi_idemp_features_legacy:list_diff_fields(read_schema_deprecated_v2(Schema), Difference)
end,
logger:warning("This externalID: ~p, used in another request.~nDifference: ~p", [ID, ReadableDiff]),
SourceID = get_external_id(IdempotentKey),
throw({external_id_conflict, ID, SourceID, Schema})
end.
generate_id(BenderIdSchema, IdempKeyParams, IdempIdentity, WoodyContext, CtxData) ->
generate_id(BenderIdSchema, IdempKeyParams, Identity, WoodyContext, CtxData) ->
IdempKey = make_idempotent_key(IdempKeyParams),
case IdempKey of
undefined -> generator_generate_id(BenderIdSchema, WoodyContext);
IdempKey -> bender_generate_id(BenderIdSchema, IdempKey, IdempIdentity, WoodyContext, CtxData)
undefined ->
ID = generator_generate_id(BenderIdSchema, WoodyContext),
{ok, ID};
IdempKey ->
bender_generate_id(BenderIdSchema, IdempKey, Identity, WoodyContext, CtxData)
end.
-spec make_idempotent_key(idempotent_key_params()) -> idempotent_key() | undefined.
@ -230,49 +238,45 @@ make_idempotent_key({_Prefix, _PartyID, undefined}) ->
%% If external ID is undefined, no reason to generate it: noone can really use it
undefined;
make_idempotent_key({Prefix, PartyID, ExternalID}) ->
<<"capi/", Prefix/binary, "/", PartyID/binary, "/", ExternalID/binary>>.
bender_client:get_idempotent_key(?BENDER_NAMESPACE, Prefix, PartyID, ExternalID).
bender_generate_id(BenderIdSchema, IdempKey, IdempIdentity, WoodyContext, CtxData) ->
{identity, Hash, Features, Schema} = make_identity(IdempIdentity),
bender_generate_id(BenderIdSchema, IdempKey, Identity, WoodyContext, CtxData) ->
{identity, Features, Schema, _Data} = Identity,
BenderCtx = build_bender_ctx(Features, CtxData),
Args = {IdempKey, BenderIdSchema, capi_msgp_marshalling:marshal(BenderCtx)},
Result =
case capi_woody_client:call_service(bender, 'GenerateID', Args, WoodyContext) of
{ok, #bender_GenerationResult{internal_id = InternalID, context = undefined}} ->
{ok, InternalID};
{ok, #bender_GenerationResult{internal_id = InternalID, context = Ctx}} ->
{ok, InternalID, capi_msgp_marshalling:unmarshal(Ctx)}
end,
case Result of
case bender_client:gen_id(IdempKey, BenderIdSchema, WoodyContext, BenderCtx) of
{ok, ID} ->
{ok, ID};
{ok, ID, #{<<"version">> := ?SCHEMA_VER1} = DeprecatedCtx} ->
check_idempotent_conflict_deprecated(ID, Hash, DeprecatedCtx, Schema);
{ok, ID, #{<<"version">> := ?SCHEMA_VER2} = SavedBenderCtx} ->
{identity, FeaturesDeprecated, Schema, _} = transform_identity_to_deprecated_v2(Identity),
check_idempotent_conflict_deprecated_v2(
ID,
FeaturesDeprecated,
SavedBenderCtx,
Schema
);
{ok, ID, #{<<"version">> := ?SCHEMA_VER3} = SavedBenderCtx} ->
check_idempotent_conflict(ID, Features, SavedBenderCtx, Schema)
end.
generator_generate_id(BenderIDSchema, WoodyContext) ->
case BenderIDSchema of
{snowflake, #bender_SnowflakeSchema{}} ->
{ok, {ID, _}} = bender_generator_client:gen_snowflake(WoodyContext),
{ok, ID};
bender_generator_client:gen_snowflake(WoodyContext);
{sequence, #bender_SequenceSchema{
sequence_id = SequenceID,
minimum = Minimum
}} ->
{ok, {ID, _}} = bender_generator_client:gen_sequence(SequenceID, WoodyContext, #{minimum => Minimum}),
{ok, ID};
bender_generator_client:gen_sequence(SequenceID, WoodyContext, #{minimum => Minimum});
{constant, #bender_ConstantSchema{internal_id = InternalID}} ->
{ok, InternalID}
InternalID
end.
check_idempotent_conflict(ID, Features, SavedBenderCtx, Schema) ->
#{
<<"version">> := ?SCHEMA_VER2,
<<"version">> := ?SCHEMA_VER3,
<<"features">> := OtherFeatures
} = SavedBenderCtx,
case capi_idemp_features:compare(Features, OtherFeatures) of
case feat:compare(Features, OtherFeatures) of
true ->
{ok, ID};
{false, Difference} ->
@ -281,10 +285,17 @@ check_idempotent_conflict(ID, Features, SavedBenderCtx, Schema) ->
%% Deprecated idempotent context
check_idempotent_conflict_deprecated(ID, Hash, #{<<"params_hash">> := Hash}, _schema) ->
{ok, ID};
check_idempotent_conflict_deprecated(ID, _Hash, #{<<"params_hash">> := _OtherHash}, Schema) ->
{error, {external_id_conflict, ID, undefined, Schema}}.
check_idempotent_conflict_deprecated_v2(ID, Features, SavedBenderCtx, Schema) ->
#{
<<"version">> := ?SCHEMA_VER2,
<<"features">> := OtherFeatures
} = SavedBenderCtx,
case capi_idemp_features_legacy:compare(Features, OtherFeatures) of
true ->
{ok, ID};
{false, Difference} ->
{error, {external_id_conflict_legacy, ID, Difference, Schema}}
end.
-spec get_context_data(bender_context()) -> undefined | context_data().
get_context_data(Context) ->

View File

@ -1,8 +1,8 @@
-module(capi_feature_schemas).
-type schema() :: capi_idemp_features:schema().
-type schema() :: feat:schema().
-include("capi_feature_schemas.hrl").
-include_lib("feat/include/feat.hrl").
-define(id, 1).
-define(invoice_id, 2).
@ -56,7 +56,6 @@
-define(contact_info, 50).
-define(email, 51).
-define(phone_number, 52).
-define(allocation, 53).
-define(target, 54).
-define(total, 55).
@ -64,6 +63,11 @@
-define(share, 57).
-define(matisse, 58).
-define(exponent, 59).
-define(instant, 60).
-define(hold, 61).
-define(vat, 62).
-define(unlimited, 63).
-define(shop, 64).
-export([payment/0]).
-export([invoice/0]).
@ -75,148 +79,183 @@
-spec payment() -> schema().
payment() ->
#{
?invoice_id => [<<"invoiceID">>],
?make_recurrent => [<<"makeRecurrent">>],
?flow => [
?invoice_id => <<"invoiceID">>,
?make_recurrent => <<"makeRecurrent">>,
?flow => {
<<"flow">>,
#{
?discriminator => [<<"type">>],
?hold_exp => [<<"onHoldExpiration">>]
}
],
?payer => [
{union, <<"type">>, #{
<<"PaymentFlowInstant">> => {?instant, #{}},
<<"PaymentFlowHold">> =>
{?hold, #{
?hold_exp => <<"onHoldExpiration">>
}}
}}
},
?payer => {
<<"payer">>,
#{
?discriminator => [<<"payerType">>],
?payment_tool => [<<"paymentTool">>, payment_tool_schema()],
?customer => [<<"customerID">>],
?recurrent => [
<<"recurrentParentPayment">>,
#{
?invoice => [<<"invoiceID">>],
?payment => [<<"paymentID">>]
{union, <<"payerType">>, #{
<<"CustomerPayer">> =>
{?customer, #{
?customer => <<"customerID">>
}},
<<"RecurrentPayer">> => {
?recurrent, #{
?recurrent => {
<<"recurrentParentPayment">>,
#{
?invoice => <<"invoiceID">>,
?payment => <<"paymentID">>
}
}
}
]
}
]
},
<<"PaymentResourcePayer">> =>
{?payment_resource, #{
?payment_tool => {<<"paymentTool">>, payment_tool_schema()}
}}
}}
}
}.
-spec invoice() -> schema().
invoice() ->
#{
?shop_id => [<<"shopID">>],
?amount => [<<"amount">>],
?currency => [<<"currency">>],
?product => [<<"product">>],
?due_date => [<<"dueDate">>],
?cart => [<<"cart">>, {set, cart_line_schema()}],
?bank_account => [<<"bankAccount">>, bank_account_schema()],
?invoice_template_id => [<<"invoiceTemplateID">>],
?allocation => [<<"allocation">>, {set, allocation_transaction()}]
?shop_id => <<"shopID">>,
?amount => <<"amount">>,
?currency => <<"currency">>,
?product => <<"product">>,
?due_date => <<"dueDate">>,
?cart => {<<"cart">>, {set, cart_line_schema()}},
?bank_account => {<<"bankAccount">>, bank_account_schema()},
?invoice_template_id => <<"invoiceTemplateID">>,
?allocation => {<<"allocation">>, {set, allocation_transaction()}}
}.
-spec invoice_template() -> schema().
invoice_template() ->
#{
?shop_id => [<<"shopID">>],
?lifetime => [<<"lifetime">>, lifetime_schema()],
?details => [<<"details">>, invoice_template_details_schema()]
?shop_id => <<"shopID">>,
?lifetime => {<<"lifetime">>, lifetime_schema()},
?details => {<<"details">>, invoice_template_details_schema()}
}.
-spec invoice_template_details_schema() -> schema().
invoice_template_details_schema() ->
#{
?discriminator => [<<"templateType">>],
?single_line => #{
?product => [<<"product">>],
?price => [<<"price">>, invoice_template_line_cost()],
?tax => [<<"taxMode">>, tax_mode_schema()]
},
?multiline => #{
?currency => [<<"currency">>],
?cart => [<<"cart">>, {set, cart_line_schema()}]
}
}.
{union, <<"templateType">>, #{
<<"InvoiceTemplateSingleLine">> =>
{?single_line, #{
?product => <<"product">>,
?price => {<<"price">>, invoice_template_line_cost()},
?tax => {<<"taxMode">>, tax_mode_schema()}
}},
<<"InvoiceTemplateMultiLine">> =>
{?multiline, #{
?currency => <<"currency">>,
?cart => {<<"cart">>, {set, cart_line_schema()}}
}}
}}.
-spec refund() -> schema().
refund() ->
#{
?amount => [<<"amount">>],
?currency => [<<"currency">>],
?cart => [<<"cart">>, {set, cart_line_schema()}],
?allocation => [<<"allocation">>, {set, allocation_transaction()}]
?amount => <<"amount">>,
?currency => <<"currency">>,
?cart => {<<"cart">>, {set, cart_line_schema()}},
?allocation => {<<"allocation">>, {set, allocation_transaction()}}
}.
-spec customer() -> schema().
customer() ->
#{
?shop_id => [<<"shopID">>],
?contact_info => [<<"contactInfo">>, contact_info_schema()]
?shop_id => <<"shopID">>,
?contact_info => {<<"contactInfo">>, contact_info_schema()}
}.
-spec customer_binding() -> schema().
customer_binding() ->
#{
?payment_resource => [
?payment_resource => {
<<"paymentResource">>,
#{
?payment_session => [<<"paymentSession">>],
?payment_tool => [<<"paymentTool">>, payment_tool_schema()]
?payment_session => <<"paymentSession">>,
?payment_tool => {<<"paymentTool">>, payment_tool_schema()}
}
]
}
}.
-spec payment_tool_schema() -> schema().
payment_tool_schema() ->
#{
?discriminator => [<<"type">>],
?bank_card => #{
?token => [<<"token">>],
?exp_date => [<<"exp_date">>]
},
?terminal => #{
?discriminator => [<<"terminal_type">>]
},
?wallet => #{
?provider => [<<"provider">>],
?id => [<<"id">>],
?token => [<<"token">>]
},
?crypto => #{
?currency => [<<"currency">>]
},
?mobile_commerce => #{
?operator => [<<"operator">>],
?phone => [<<"phone">>]
}
}.
{union, <<"type">>, #{
<<"bank_card">> =>
{?bank_card, #{
?token => <<"token">>,
?exp_date => <<"exp_date">>
}},
<<"payment_terminal">> => {?terminal, #{?terminal_type => <<"terminal_type">>}},
<<"digital_wallet">> =>
{?wallet, #{
?provider => <<"provider">>,
?id => <<"id">>,
?token => <<"token">>
}},
<<"crypto_wallet">> =>
{?crypto, #{
?currency => <<"currency">>
}},
<<"mobile_commerce">> =>
{?mobile_commerce, #{
?operator => <<"operator">>,
?phone => <<"phone">>
}}
}}.
-spec allocation_transaction() -> schema().
allocation_transaction() ->
#{
?target => [<<"target">>, allocation_target()],
?discriminator => [<<"allocationBodyType">>],
?amount => [<<"amount">>],
?total => [<<"total">>],
?currency => [<<"currency">>],
?fee => [
<<"fee">>,
#{
?target => [<<"target">>, allocation_target()],
?discriminator => [<<"allocationFeeType">>],
?amount => [<<"amount">>],
?share => [<<"share">>, decimal()]
}
],
?cart => [<<"cart">>, {set, cart_line_schema()}]
?target => {<<"target">>, allocation_target()},
?cart => {<<"cart">>, {set, cart_line_schema()}},
?allocation =>
{union, <<"allocationBodyType">>, #{
<<"AllocationBodyAmount">> =>
{?amount, #{
?currency => <<"currency">>,
?amount => <<"amount">>
}},
<<"AllocationBodyTotal">> =>
{?total, #{
?total => <<"total">>,
?currency => <<"currency">>,
?fee => {<<"fee">>, allocation_fee()}
}}
}}
}.
-spec allocation_fee() -> schema().
allocation_fee() ->
#{
?target => {<<"target">>, allocation_target()},
?fee =>
{union, <<"allocationFeeType">>, #{
<<"AllocationFeeFixed">> =>
{?fixed, #{?amount => <<"amount">>}},
<<"AllocationFeeShare">> => {
?share, #{
?amount => <<"amount">>,
?share => {<<"share">>, decimal()}
}
}
}}
}.
-spec allocation_target() -> schema().
allocation_target() ->
#{
?discriminator => [<<"allocationTargetType">>],
?shop_id => [<<"shopID">>]
}.
{union, <<"allocationTargetType">>, #{
<<"AllocationTargetShop">> =>
{?shop, #{
?shop_id => <<"shopID">>
}}
}}.
-spec decimal() -> schema().
decimal() ->
@ -228,61 +267,64 @@ decimal() ->
-spec cart_line_schema() -> schema().
cart_line_schema() ->
#{
?product => [<<"product">>],
?quantity => [<<"quantity">>],
?price => [<<"price">>],
?tax => [<<"taxMode">>, tax_mode_schema()]
?product => <<"product">>,
?quantity => <<"quantity">>,
?price => <<"price">>,
?tax => {<<"taxMode">>, tax_mode_schema()}
}.
-spec tax_mode_schema() -> schema().
tax_mode_schema() ->
#{
?discriminator => [<<"type">>],
?rate => [<<"rate">>]
}.
{union, <<"type">>, #{
<<"InvoiceLineTaxVAT">> => {?vat, #{?rate => <<"rate">>}}
}}.
-spec bank_account_schema() -> schema().
bank_account_schema() ->
#{
?discriminator => [<<"accountType">>],
?account => [<<"account">>],
?bank_bik => [<<"bankBik">>]
}.
{union, <<"accountType">>, #{
<<"InvoiceRussianBankAccount">> =>
{?bank_account, #{
?account => <<"account">>,
?bank_bik => <<"bankBik">>
}}
}}.
invoice_template_line_cost() ->
#{
?discriminator => [<<"costType">>],
?range => #{
?currency => [<<"currency">>],
?range => [<<"range">>, cost_amount_range()]
{union, <<"costType">>, #{
<<"InvoiceTemplateLineCostRange">> => {
?range, #{
?currency => <<"currency">>,
?range => {<<"range">>, cost_amount_range()}
}
},
?fixed => #{
?currency => [<<"currency">>],
?amount => [<<"amount">>]
}
%% Unlim has no params and is fully contained in discriminator
}.
<<"InvoiceTemplateLineCostFixed">> =>
{?fixed, #{
?currency => <<"currency">>,
?amount => <<"amount">>
}},
<<"InvoiceTemplateLineCostUnlim">> => {?unlimited, #{}}
}}.
-spec cost_amount_range() -> schema().
cost_amount_range() ->
#{
?upper_bound => [<<"upperBound">>],
?lower_bound => [<<"lowerBound">>]
?upper_bound => <<"upperBound">>,
?lower_bound => <<"lowerBound">>
}.
-spec lifetime_schema() -> schema().
lifetime_schema() ->
#{
?days => [<<"days">>],
?months => [<<"months">>],
?years => [<<"years">>]
?days => <<"days">>,
?months => <<"months">>,
?years => <<"years">>
}.
-spec contact_info_schema() -> schema().
contact_info_schema() ->
#{
?email => [<<"email">>],
?phone_number => [<<"phoneNumber">>]
?email => <<"email">>,
?phone_number => <<"phoneNumber">>
}.
-ifdef(TEST).
@ -306,16 +348,16 @@ deep_fetch(Map, Keys) ->
lists:foldl(fun(K, M) -> maps:get(K, M) end, Map, Keys).
hash(Term) ->
capi_idemp_features:hash(Term).
feat:hash(Term).
read(Schema, Request) ->
capi_idemp_features:read(Schema, Request).
feat:read(Schema, Request).
compare(Features1, Features2) ->
capi_idemp_features:compare(Features1, Features2).
feat:compare(Features1, Features2).
list_diff_fields(Schema, Diff) ->
capi_idemp_features:list_diff_fields(Schema, Diff).
feat:list_diff_fields(Schema, Diff).
-spec test() -> _.
@ -347,33 +389,20 @@ read_payment_features_test() ->
Payer = #{
?invoice_id => undefined,
?make_recurrent => undefined,
?flow => #{
?discriminator => hash(Flow),
?hold_exp => undefined
},
?payer => #{
?discriminator => hash(PayerType),
?customer => undefined,
?recurrent => undefined,
?payment_tool => #{
?discriminator => hash(ToolType),
?bank_card => #{
?exp_date => hash(ExpDate),
?token => hash(Token)
},
?crypto => #{?currency => undefined},
?mobile_commerce => #{
?operator => undefined,
?phone => undefined
},
?terminal => #{?discriminator => undefined},
?wallet => #{
?id => undefined,
?provider => undefined,
?token => hash(Token)
}
?flow => [?hold, #{?hold_exp => undefined}],
?payer => [
?payment_resource,
#{
?payment_tool =>
[
?bank_card,
#{
?exp_date => hash(ExpDate),
?token => hash(Token)
}
]
}
}
]
},
Features = read(payment(), Request),
?assertEqual(Payer, Features).
@ -397,7 +426,7 @@ compare_payment_bank_card_test() ->
-spec compare_different_payment_tool_test() -> _.
compare_different_payment_tool_test() ->
ToolType2 = <<"wallet">>,
ToolType2 = <<"digital_wallet">>,
Token2 = <<"wallet token">>,
PaymentTool1 = bank_card(),
PaymentTool2 = #{
@ -407,50 +436,7 @@ compare_different_payment_tool_test() ->
Request1 = payment_params(PaymentTool1),
Request2 = payment_params(PaymentTool2),
common_compare_tests(payment(), Request1, Request2, [<<"payer.paymentTool">>]).
-spec feature_multi_accessor_test() -> _.
feature_multi_accessor_test() ->
Request1 = #{
<<"payer">> => #{
<<"payerType">> => <<"PaymentResourcePayer">>,
<<"paymentTool">> => #{
<<"wrapper">> => bank_card()
}
}
},
Request2 = deep_merge(Request1, #{
<<"payer">> => #{
<<"paymentTool">> => #{
<<"wrapper">> => #{
<<"token">> => <<"cds token 2">>,
<<"cardholder_name">> => <<"Cake">>
}
}
}
}),
Schema = #{
<<"payer">> => [
<<"payer">>,
#{
<<"type">> => [<<"payerType">>],
<<"tool">> => [
<<"paymentTool">>,
<<"wrapper">>,
#{
<<"$type">> => [<<"type">>],
<<"bank_card">> => #{
<<"token">> => [<<"token">>],
<<"exp_date">> => [<<"exp_date">>]
}
}
]
}
]
},
common_compare_tests(Schema, Request1, Request2, [
<<"payer.paymentTool.wrapper.token">>
]).
common_compare_tests(payment(), Request1, Request2, [<<"payer">>]).
-spec read_payment_customer_features_value_test() -> _.
read_payment_customer_features_value_test() ->
@ -468,12 +454,7 @@ read_payment_customer_features_value_test() ->
?invoice_id => undefined,
?make_recurrent => undefined,
?flow => undefined,
?payer => #{
?discriminator => hash(PayerType),
?customer => hash(CustomerID),
?recurrent => undefined,
?payment_tool => undefined
}
?payer => [?customer, #{?customer => hash(CustomerID)}]
},
Features
).
@ -498,11 +479,13 @@ read_invoice_features_test() ->
?product => hash(Prod2),
?price => hash(Price2)
},
BankAccount = #{
?discriminator => hash(<<"InvoiceRussianBankAccount">>),
?account => hash(<<"12345678901234567890">>),
?bank_bik => hash(<<"123456789">>)
},
BankAccount = [
?bank_account,
#{
?account => hash(<<"12345678901234567890">>),
?bank_bik => hash(<<"123456789">>)
}
],
Invoice = #{
?amount => undefined,
?currency => hash(Cur),
@ -578,6 +561,7 @@ compare_invoices_features_test() ->
Product#{
<<"price">> => Price2,
<<"taxMode">> => #{
<<"type">> => <<"InvoiceLineTaxVAT">>,
<<"rate">> => <<"18%">>
}
}
@ -587,14 +571,7 @@ compare_invoices_features_test() ->
InvoiceWithFullCart = read(Schema, Request3),
?assertEqual(
{false, #{
?cart => #{
0 => #{
?price => ?difference,
?product => ?difference,
?quantity => ?difference,
?tax => ?difference
}
}
?cart => ?difference
}},
compare(Invoice2, Invoice1)
),
@ -611,7 +588,7 @@ compare_invoices_features_test() ->
{false, Diff} = compare(Invoice1, InvoiceChg1),
?assertEqual(
[<<"cart.0.price">>, <<"cart.0.taxMode.rate">>],
[<<"cart.0.price">>, <<"cart.0.taxMode">>],
list_diff_fields(Schema, Diff)
),
?assert(compare(Invoice1, Invoice1#{?cart => undefined})).
@ -651,11 +628,7 @@ compare_customer_features_test() ->
Request,
RequestSame,
RequestDifferent,
[
<<"shopID">>,
<<"contactInfo.email">>,
<<"contactInfo.phoneNumber">>
]
all
).
-spec read_customer_binding_features_test() -> _.
@ -666,29 +639,13 @@ read_customer_binding_features_test() ->
Features = #{
?payment_resource => #{
?payment_session => hash(Session),
?payment_tool => #{
?discriminator => hash(<<"bank_card">>),
?bank_card => #{
?payment_tool => [
?bank_card,
#{
?token => hash(<<"TOKEN">>),
?exp_date => hash(<<"12/2012">>)
},
?terminal => #{
?discriminator => undefined
},
?wallet => #{
?provider => undefined,
?id => undefined,
?token => hash(<<"TOKEN">>)
},
?crypto => #{
?currency => undefined
},
?mobile_commerce => #{
?operator => undefined,
?phone => undefined
}
}
]
}
},
@ -729,14 +686,9 @@ read_invoice_template_features_test() ->
?months => hash(2),
?years => hash(3)
},
?details => #{
?discriminator => hash(<<"InvoiceTemplateMultiLine">>),
?single_line => #{
?product => undefined,
?price => undefined,
?tax => undefined
},
?multiline => #{
?details => [
?multiline,
#{
?currency => hash(<<"RUB">>),
?cart => [
[
@ -745,7 +697,7 @@ read_invoice_template_features_test() ->
?product => hash(?STRING),
?quantity => hash(42),
?price => hash(?INTEGER),
?tax => #{?discriminator => hash(<<"InvoiceLineTaxVAT">>), ?rate => hash(<<"18%">>)}
?tax => [?vat, #{?rate => hash(<<"18%">>)}]
}
],
[
@ -759,7 +711,7 @@ read_invoice_template_features_test() ->
]
]
}
}
]
},
?assertEqual(
@ -791,34 +743,43 @@ compare_invoice_template_features_test() ->
common_compare_tests(invoice_template(), Request1, Request2, [
<<"shopID">>,
<<"lifetime.years">>,
<<"details.currency">>,
<<"details.cart">>
<<"details">>
]).
-spec read_allocation_transaction_test_() -> _.
read_allocation_transaction_test_() ->
Request1 = ?ALLOCATION_TRANSACTION_PARAMS,
Features1 = #{
?target => #{
?discriminator => hash(<<"AllocationTargetShop">>),
?shop_id => hash(?STRING)
},
?discriminator => hash(<<"AllocationBodyTotal">>),
?amount => undefined,
AllocationParams = #{
?total => hash(?INTEGER),
?currency => hash(?USD),
?fee => #{
?target => #{
?discriminator => hash(<<"AllocationTargetShop">>),
?shop_id => hash(?STRING)
},
?discriminator => hash(<<"AllocationFeeShare">>),
?amount => hash(?INTEGER),
?share => #{
?matisse => hash(?INTEGER),
?exponent => hash(?INTEGER)
}
},
?target =>
[
?shop,
#{
?shop_id => hash(?STRING)
}
],
?fee => [
?share,
#{
?amount => hash(?INTEGER),
?share => #{
?matisse => hash(?INTEGER),
?exponent => hash(?INTEGER)
}
}
]
}
},
Features1 = #{
?target =>
[
?shop,
#{
?shop_id => hash(?STRING)
}
],
?cart => [
[
0,
@ -829,7 +790,8 @@ read_allocation_transaction_test_() ->
?tax => undefined
}
]
]
],
?allocation => [?total, AllocationParams]
},
Request2 = Request1#{
<<"fee">> => #{
@ -839,15 +801,22 @@ read_allocation_transaction_test_() ->
}
},
Features2 = Features1#{
?fee => #{
?target => #{
?discriminator => hash(<<"AllocationTargetShop">>),
?shop_id => hash(?STRING)
},
?discriminator => hash(<<"AllocationFeeFixed">>),
?amount => hash(1024),
?share => undefined
}
?allocation =>
[
?total,
AllocationParams#{
?fee => #{
?target =>
[
?shop,
#{
?shop_id => hash(?STRING)
}
],
?fee => [?fixed, #{?amount => hash(1024)}]
}
}
]
},
[
?_assertEqual(Features1, read(allocation_transaction(), Request1)),
@ -867,15 +836,15 @@ compare_allocation_transaction_test() ->
<<"share">> => undefined
}
},
Request3 = #{
<<"target">> => ?ALLOCATION_TARGET#{<<"shopID">> => <<"SomeShop">>},
<<"allocationBodyType">> => <<"AllocationBodyAmount">>,
<<"amount">> => ?INTEGER,
<<"currency">> => ?RUB,
<<"cart">> => [
#{<<"product">> => ?STRING, <<"quantity">> => 1, <<"price">> => ?INTEGER}
]
},
%% Request3 = #{
%% <<"target">> => ?ALLOCATION_TARGET#{<<"shopID">> => <<"SomeShop">>},
%% <<"allocationBodyType">> => <<"AllocationBodyAmount">>,
%% <<"amount">> => ?INTEGER,
%% <<"currency">> => ?RUB,
%% <<"cart">> => [
%% #{<<"product">> => ?STRING, <<"quantity">> => 1, <<"price">> => ?INTEGER}
%% ]
%% },
Request4 = Request1#{
<<"fee">> => deep_merge(maps:get(<<"fee">>, Request1), #{
<<"amount">> => 1024,
@ -883,14 +852,10 @@ compare_allocation_transaction_test() ->
})
},
common_compare_tests(allocation_transaction(), Request1, Request2, [
<<"amount">>, <<"total">>, <<"fee">>
<<"fee">>, <<"total">>
]),
common_compare_tests(allocation_transaction(), Request1, Request3, [
<<"target.shopID">>, <<"allocationBodyType">>, <<"currency">>, <<"amount">>, <<"cart.0.quantity">>
]),
common_compare_tests(allocation_transaction(), Request1, Request4, [
<<"fee.amount">>, <<"fee.share.m">>, <<"fee.share.exp">>
]).
%% common_compare_tests(allocation_transaction(), Request1, Request3, all),
common_compare_tests(allocation_transaction(), Request1, Request4, [<<"fee">>]).
-spec demo_compare_allocation_transaction_test() -> _.
demo_compare_allocation_transaction_test() ->
@ -898,17 +863,16 @@ demo_compare_allocation_transaction_test() ->
Request2 = #{
<<"allocationBodyType">> => <<"AllocationBodyAmount">>
},
Request3 = #{
<<"fee">> => deep_merge(maps:get(<<"fee">>, Request1), #{
<<"allocationFeeType">> => <<"AllocationFeeFixed">>
})
},
common_compare_tests(allocation_transaction(), Request1, Request2, [
<<"allocationBodyType">>
]),
common_compare_tests(allocation_transaction(), Request1, Request3, [
<<"fee">>
]).
%% Request3 = #{
%% <<"fee">> => deep_merge(maps:get(<<"fee">>, Request1), #{
%% <<"allocationFeeType">> => <<"AllocationFeeFixed">>
%% })
%% },
common_compare_tests(allocation_transaction(), Request1, Request2, all)
%% common_compare_tests(allocation_transaction(), Request1, Request3, [
%% <<"fee">>
%% ])
.
%%
@ -920,18 +884,19 @@ payment_resource(Session, Tool) ->
}
}.
payment_params(ExternalID, MakeRecurrent) ->
payment_params(PaymentTool) ->
deep_merge(
payment_params(<<"EID">>, <<"Jwe">>, #{}, false),
#{<<"payer">> => #{<<"paymentTool">> => PaymentTool}}
).
payment_params(ExternalID, Jwe, ContactInfo, MakeRecurrent) ->
genlib_map:compact(#{
<<"externalID">> => ExternalID,
<<"flow">> => #{<<"type">> => <<"PaymentFlowInstant">>},
<<"makeRecurrent">> => MakeRecurrent,
<<"metadata">> => #{<<"bla">> => <<"*">>},
<<"processingDeadline">> => <<"5m">>
}).
payment_params(ExternalID, Jwe, ContactInfo, MakeRecurrent) ->
Params = payment_params(ExternalID, MakeRecurrent),
genlib_map:compact(Params#{
<<"processingDeadline">> => <<"5m">>,
<<"payer">> => #{
<<"payerType">> => <<"PaymentResourcePayer">>,
<<"paymentSession">> => <<"payment.session">>,
@ -940,11 +905,6 @@ payment_params(ExternalID, Jwe, ContactInfo, MakeRecurrent) ->
}
}).
payment_params(PaymentTool) ->
Params = payment_params(<<"EID">>, <<"Jwe">>, #{}, false),
PaymentParams = deep_merge(Params, #{<<"payer">> => #{<<"paymentTool">> => PaymentTool}}),
PaymentParams.
bank_card() ->
#{
<<"type">> => <<"bank_card">>,
@ -964,24 +924,38 @@ lifetime_dummy(Days, Months, Years) ->
<<"years">> => Years
}.
%% compare_equal_test(Schema, Request, AnotherRequest) ->
%% Features = read(Schema, Request),
%% AnotherFeatures = read(Schema, AnotherRequest),
%% ?assertEqual(true, compare(Features, AnotherFeatures)).
common_compare_tests(Schema, Request, RequestDifferent, DiffFeatures) ->
common_compare_tests(Schema, Request, Request, RequestDifferent, DiffFeatures).
common_compare_tests(Schema, Request, RequestWithIgnoredFields, RequestDifferent, DiffFeatures) ->
Features = read(Schema, Request),
FeaturesIgnored = read(Schema, RequestWithIgnoredFields),
FeaturesDifferent = read(Schema, RequestDifferent),
%% Equal to self
?assertEqual(true, compare(Features, Features)),
%% Equal to feature-wise same request
?assertEqual(true, compare(Features, FeaturesIgnored)),
case Request =:= RequestWithIgnoredFields of
true ->
ok;
false ->
FeaturesIgnored = read(Schema, RequestWithIgnoredFields),
?assertEqual(true, compare(Features, FeaturesIgnored))
end,
%% Has correct diff with different request
FeaturesDifferent = read(Schema, RequestDifferent),
Result = compare(Features, FeaturesDifferent),
?assertMatch({false, _}, Result),
{false, Diff} = Result,
?assertEqual(lists:sort(DiffFeatures), lists:sort(list_diff_fields(Schema, Diff))).
ActualDiffFeatures = list_diff_fields(Schema, Diff),
case ActualDiffFeatures =:= all orelse DiffFeatures =:= all of
true -> ?assertEqual(DiffFeatures, ActualDiffFeatures);
false -> ?assertEqual(lists:sort(DiffFeatures), lists:sort(ActualDiffFeatures))
end.
-endif.

View File

@ -0,0 +1,984 @@
-module(capi_feature_schemas_legacy).
-type schema() :: capi_idemp_features_legacy:schema().
-include("capi_feature_schemas_legacy.hrl").
-define(id, 1).
-define(invoice_id, 2).
-define(make_recurrent, 3).
-define(flow, 4).
-define(hold_exp, 5).
-define(payer, 6).
-define(payment_tool, 7).
-define(token, 8).
-define(bank_card, 9).
-define(exp_date, 10).
-define(terminal, 11).
-define(terminal_type, 12).
-define(wallet, 13).
-define(provider, 14).
-define(crypto, 15).
-define(currency, 16).
-define(mobile_commerce, 17).
-define(operator, 18).
-define(phone, 19).
-define(customer, 20).
-define(recurrent, 21).
-define(invoice, 22).
-define(payment, 23).
-define(shop_id, 24).
-define(amount, 25).
-define(product, 26).
-define(due_date, 27).
-define(cart, 28).
-define(quantity, 29).
-define(price, 30).
-define(tax, 31).
-define(rate, 32).
-define(bank_account, 33).
-define(account, 34).
-define(bank_bik, 35).
-define(payment_resource, 36).
-define(payment_session, 37).
-define(lifetime, 38).
-define(details, 39).
-define(days, 40).
-define(months, 41).
-define(years, 42).
-define(single_line, 43).
-define(multiline, 44).
-define(range, 45).
-define(fixed, 46).
-define(lower_bound, 47).
-define(upper_bound, 48).
-define(invoice_template_id, 49).
-define(contact_info, 50).
-define(email, 51).
-define(phone_number, 52).
-define(allocation, 53).
-define(target, 54).
-define(total, 55).
-define(fee, 56).
-define(share, 57).
-define(matisse, 58).
-define(exponent, 59).
-export([payment/0]).
-export([invoice/0]).
-export([invoice_template/0]).
-export([refund/0]).
-export([customer_binding/0]).
-export([customer/0]).
-spec payment() -> schema().
payment() ->
#{
?invoice_id => [<<"invoiceID">>],
?make_recurrent => [<<"makeRecurrent">>],
?flow => [
<<"flow">>,
#{
?discriminator => [<<"type">>],
?hold_exp => [<<"onHoldExpiration">>]
}
],
?payer => [
<<"payer">>,
#{
?discriminator => [<<"payerType">>],
?payment_tool => [<<"paymentTool">>, payment_tool_schema()],
?customer => [<<"customerID">>],
?recurrent => [
<<"recurrentParentPayment">>,
#{
?invoice => [<<"invoiceID">>],
?payment => [<<"paymentID">>]
}
]
}
]
}.
-spec invoice() -> schema().
invoice() ->
#{
?shop_id => [<<"shopID">>],
?amount => [<<"amount">>],
?currency => [<<"currency">>],
?product => [<<"product">>],
?due_date => [<<"dueDate">>],
?cart => [<<"cart">>, {set, cart_line_schema()}],
?bank_account => [<<"bankAccount">>, bank_account_schema()],
?invoice_template_id => [<<"invoiceTemplateID">>],
?allocation => [<<"allocation">>, {set, allocation_transaction()}]
}.
-spec invoice_template() -> schema().
invoice_template() ->
#{
?shop_id => [<<"shopID">>],
?lifetime => [<<"lifetime">>, lifetime_schema()],
?details => [<<"details">>, invoice_template_details_schema()]
}.
-spec invoice_template_details_schema() -> schema().
invoice_template_details_schema() ->
#{
?discriminator => [<<"templateType">>],
?single_line => #{
?product => [<<"product">>],
?price => [<<"price">>, invoice_template_line_cost()],
?tax => [<<"taxMode">>, tax_mode_schema()]
},
?multiline => #{
?currency => [<<"currency">>],
?cart => [<<"cart">>, {set, cart_line_schema()}]
}
}.
-spec refund() -> schema().
refund() ->
#{
?amount => [<<"amount">>],
?currency => [<<"currency">>],
?cart => [<<"cart">>, {set, cart_line_schema()}],
?allocation => [<<"allocation">>, {set, allocation_transaction()}]
}.
-spec customer() -> schema().
customer() ->
#{
?shop_id => [<<"shopID">>],
?contact_info => [<<"contactInfo">>, contact_info_schema()]
}.
-spec customer_binding() -> schema().
customer_binding() ->
#{
?payment_resource => [
<<"paymentResource">>,
#{
?payment_session => [<<"paymentSession">>],
?payment_tool => [<<"paymentTool">>, payment_tool_schema()]
}
]
}.
-spec payment_tool_schema() -> schema().
payment_tool_schema() ->
#{
?discriminator => [<<"type">>],
?bank_card => #{
?token => [<<"token">>],
?exp_date => [<<"exp_date">>]
},
?terminal => #{
?discriminator => [<<"terminal_type">>]
},
?wallet => #{
?provider => [<<"provider">>],
?id => [<<"id">>],
?token => [<<"token">>]
},
?crypto => #{
?currency => [<<"currency">>]
},
?mobile_commerce => #{
?operator => [<<"operator">>],
?phone => [<<"phone">>]
}
}.
-spec allocation_transaction() -> schema().
allocation_transaction() ->
#{
?target => [<<"target">>, allocation_target()],
?discriminator => [<<"allocationBodyType">>],
?amount => [<<"amount">>],
?total => [<<"total">>],
?currency => [<<"currency">>],
?fee => [
<<"fee">>,
#{
?target => [<<"target">>, allocation_target()],
?discriminator => [<<"allocationFeeType">>],
?amount => [<<"amount">>],
?share => [<<"share">>, decimal()]
}
],
?cart => [<<"cart">>, {set, cart_line_schema()}]
}.
-spec allocation_target() -> schema().
allocation_target() ->
#{
?discriminator => [<<"allocationTargetType">>],
?shop_id => [<<"shopID">>]
}.
-spec decimal() -> schema().
decimal() ->
#{
?matisse => [<<"m">>],
?exponent => [<<"exp">>]
}.
-spec cart_line_schema() -> schema().
cart_line_schema() ->
#{
?product => [<<"product">>],
?quantity => [<<"quantity">>],
?price => [<<"price">>],
?tax => [<<"taxMode">>, tax_mode_schema()]
}.
-spec tax_mode_schema() -> schema().
tax_mode_schema() ->
#{
?discriminator => [<<"type">>],
?rate => [<<"rate">>]
}.
-spec bank_account_schema() -> schema().
bank_account_schema() ->
#{
?discriminator => [<<"accountType">>],
?account => [<<"account">>],
?bank_bik => [<<"bankBik">>]
}.
invoice_template_line_cost() ->
#{
?discriminator => [<<"costType">>],
?range => #{
?currency => [<<"currency">>],
?range => [<<"range">>, cost_amount_range()]
},
?fixed => #{
?currency => [<<"currency">>],
?amount => [<<"amount">>]
}
%% Unlim has no params and is fully contained in discriminator
}.
-spec cost_amount_range() -> schema().
cost_amount_range() ->
#{
?upper_bound => [<<"upperBound">>],
?lower_bound => [<<"lowerBound">>]
}.
-spec lifetime_schema() -> schema().
lifetime_schema() ->
#{
?days => [<<"days">>],
?months => [<<"months">>],
?years => [<<"years">>]
}.
-spec contact_info_schema() -> schema().
contact_info_schema() ->
#{
?email => [<<"email">>],
?phone_number => [<<"phoneNumber">>]
}.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-include_lib("capi_dummy_data.hrl").
deep_merge(M1, M2) ->
maps:fold(
fun
(K, V, MAcc) when is_map(V) ->
Value = deep_merge(maps:get(K, MAcc, #{}), V),
MAcc#{K => Value};
(K, V, MAcc) ->
MAcc#{K => V}
end,
M1,
M2
).
deep_fetch(Map, Keys) ->
lists:foldl(fun(K, M) -> maps:get(K, M) end, Map, Keys).
hash(Term) ->
capi_idemp_features_legacy:hash(Term).
read(Schema, Request) ->
capi_idemp_features_legacy:read(Schema, Request).
compare(Features1, Features2) ->
capi_idemp_features_legacy:compare(Features1, Features2).
list_diff_fields(Schema, Diff) ->
capi_idemp_features_legacy:list_diff_fields(Schema, Diff).
-spec test() -> _.
-spec read_payment_features_test() -> _.
read_payment_features_test() ->
PayerType = <<"PaymentResourcePayer">>,
ToolType = <<"bank_card">>,
Token = <<"cds token">>,
CardHolder = <<"0x42">>,
Category = <<"BUSINESS">>,
ExpDate = {exp_date, 02, 2022},
Flow = <<"PaymentFlowHold">>,
Request = #{
<<"flow">> => #{
<<"type">> => Flow
},
<<"payer">> => #{
<<"payerType">> => PayerType,
<<"paymentTool">> => #{
<<"type">> => ToolType,
<<"token">> => Token,
<<"exp_date">> => ExpDate,
<<"cardholder_name">> => CardHolder,
<<"category">> => Category
}
}
},
Payer = #{
?invoice_id => undefined,
?make_recurrent => undefined,
?flow => #{
?discriminator => hash(Flow),
?hold_exp => undefined
},
?payer => #{
?discriminator => hash(PayerType),
?customer => undefined,
?recurrent => undefined,
?payment_tool => #{
?discriminator => hash(ToolType),
?bank_card => #{
?exp_date => hash(ExpDate),
?token => hash(Token)
},
?crypto => #{?currency => undefined},
?mobile_commerce => #{
?operator => undefined,
?phone => undefined
},
?terminal => #{?discriminator => undefined},
?wallet => #{
?id => undefined,
?provider => undefined,
?token => hash(Token)
}
}
}
},
Features = read(payment(), Request),
?assertEqual(Payer, Features).
-spec compare_payment_bank_card_test() -> _.
compare_payment_bank_card_test() ->
Token2 = <<"cds token 2">>,
CardHolder2 = <<"Cake">>,
PaymentTool1 = bank_card(),
PaymentTool2 = PaymentTool1#{
<<"token">> => Token2,
<<"cardholder_name">> => CardHolder2
},
Request1 = payment_params(PaymentTool1),
Request2 = payment_params(PaymentTool2),
common_compare_tests(payment(), Request1, Request2, [
<<"payer.paymentTool.token">>
]).
-spec compare_different_payment_tool_test() -> _.
compare_different_payment_tool_test() ->
ToolType2 = <<"wallet">>,
Token2 = <<"wallet token">>,
PaymentTool1 = bank_card(),
PaymentTool2 = #{
<<"type">> => ToolType2,
<<"token">> => Token2
},
Request1 = payment_params(PaymentTool1),
Request2 = payment_params(PaymentTool2),
common_compare_tests(payment(), Request1, Request2, [<<"payer.paymentTool">>]).
-spec feature_multi_accessor_test() -> _.
feature_multi_accessor_test() ->
Request1 = #{
<<"payer">> => #{
<<"payerType">> => <<"PaymentResourcePayer">>,
<<"paymentTool">> => #{
<<"wrapper">> => bank_card()
}
}
},
Request2 = deep_merge(Request1, #{
<<"payer">> => #{
<<"paymentTool">> => #{
<<"wrapper">> => #{
<<"token">> => <<"cds token 2">>,
<<"cardholder_name">> => <<"Cake">>
}
}
}
}),
Schema = #{
<<"payer">> => [
<<"payer">>,
#{
<<"type">> => [<<"payerType">>],
<<"tool">> => [
<<"paymentTool">>,
<<"wrapper">>,
#{
<<"$type">> => [<<"type">>],
<<"bank_card">> => #{
<<"token">> => [<<"token">>],
<<"exp_date">> => [<<"exp_date">>]
}
}
]
}
]
},
common_compare_tests(Schema, Request1, Request2, [
<<"payer.paymentTool.wrapper.token">>
]).
-spec read_payment_customer_features_value_test() -> _.
read_payment_customer_features_value_test() ->
PayerType = <<"CustomerPayer">>,
CustomerID = <<"some customer id">>,
Request = #{
<<"payer">> => #{
<<"payerType">> => PayerType,
<<"customerID">> => CustomerID
}
},
Features = read(payment(), Request),
?assertEqual(
#{
?invoice_id => undefined,
?make_recurrent => undefined,
?flow => undefined,
?payer => #{
?discriminator => hash(PayerType),
?customer => hash(CustomerID),
?recurrent => undefined,
?payment_tool => undefined
}
},
Features
).
-spec read_invoice_features_test() -> _.
read_invoice_features_test() ->
ShopID = <<"shopus">>,
Cur = <<"XXX">>,
Prod1 = <<"yellow duck">>,
Prod2 = <<"blue duck">>,
DueDate = <<"2019-08-24T14:15:22Z">>,
Price1 = 10000,
Price2 = 20000,
Quantity = 1,
Product = #{
?product => hash(Prod1),
?quantity => hash(Quantity),
?price => hash(Price1),
?tax => undefined
},
Product2 = Product#{
?product => hash(Prod2),
?price => hash(Price2)
},
BankAccount = #{
?discriminator => hash(<<"InvoiceRussianBankAccount">>),
?account => hash(<<"12345678901234567890">>),
?bank_bik => hash(<<"123456789">>)
},
Invoice = #{
?amount => undefined,
?currency => hash(Cur),
?shop_id => hash(ShopID),
?product => undefined,
?due_date => hash(DueDate),
?bank_account => BankAccount,
?cart => [
[1, Product],
[0, Product2]
],
?invoice_template_id => undefined,
?allocation => undefined
},
Request = #{
<<"externalID">> => <<"externalID">>,
<<"dueDate">> => DueDate,
<<"shopID">> => ShopID,
<<"currency">> => Cur,
<<"description">> => <<"Wild birds.">>,
<<"bankAccount">> => #{
<<"accountType">> => <<"InvoiceRussianBankAccount">>,
<<"account">> => <<"12345678901234567890">>,
<<"bankBik">> => <<"123456789">>
},
<<"cart">> => [
#{<<"product">> => Prod2, <<"quantity">> => 1, <<"price">> => Price2},
#{<<"product">> => Prod1, <<"quantity">> => 1, <<"price">> => Price1, <<"not feature">> => <<"hmm">>}
],
<<"metadata">> => #{}
},
Features = read(invoice(), Request),
?assertEqual(Invoice, Features),
TemplateID = <<"42">>,
RequestWithTemplate = Request#{<<"invoiceTemplateID">> => TemplateID},
FeaturesWithTemplate = read(invoice(), RequestWithTemplate),
?assertEqual(hash(TemplateID), maps:get(?invoice_template_id, FeaturesWithTemplate)).
-spec compare_invoices_features_test() -> _.
compare_invoices_features_test() ->
ShopID = <<"shopus">>,
Cur = <<"RUB">>,
Prod1 = <<"yellow duck">>,
Prod2 = <<"blue duck">>,
Price1 = 10000,
Price2 = 20000,
Product = #{
<<"product">> => Prod1,
<<"quantity">> => 1,
<<"price">> => Price1,
<<"taxMode">> => #{
<<"type">> => <<"InvoiceLineTaxVAT">>,
<<"rate">> => <<"10%">>
}
},
Request1 = #{
<<"shopID">> => ShopID,
<<"currency">> => Cur,
<<"cart">> => [Product]
},
Request2 = deep_merge(Request1, #{
<<"cart">> => [#{<<"product">> => Prod2, <<"price">> => Price2}]
}),
Request3 = deep_merge(Request1, #{
<<"cart">> => [#{<<"product">> => Prod2, <<"price">> => Price2, <<"quantity">> => undefined}]
}),
Schema = invoice(),
Invoice1 = read(Schema, Request1),
InvoiceChg1 = read(Schema, Request1#{
<<"cart">> => [
Product#{
<<"price">> => Price2,
<<"taxMode">> => #{
<<"rate">> => <<"18%">>
}
}
]
}),
Invoice2 = read(Schema, Request2),
InvoiceWithFullCart = read(Schema, Request3),
?assertEqual(
{false, #{
?cart => #{
0 => #{
?price => ?difference,
?product => ?difference,
?quantity => ?difference,
?tax => ?difference
}
}
}},
compare(Invoice2, Invoice1)
),
?assert(compare(Invoice1, Invoice1)),
%% Feature was deleted
?assert(compare(InvoiceWithFullCart, Invoice2)),
%% Feature was add
?assert(compare(Invoice2, InvoiceWithFullCart)),
%% When second request didn't contain feature, this situation detected as conflict.
?assertEqual(
{false, #{?cart => ?difference}},
compare(Invoice1#{?cart => undefined}, Invoice1)
),
{false, Diff} = compare(Invoice1, InvoiceChg1),
?assertEqual(
[<<"cart.0.price">>, <<"cart.0.taxMode.rate">>],
list_diff_fields(Schema, Diff)
),
?assert(compare(Invoice1, Invoice1#{?cart => undefined})).
-spec read_customer_features_test() -> _.
read_customer_features_test() ->
Request = ?CUSTOMER_PARAMS,
Features = #{
?shop_id => hash(?STRING),
?contact_info => #{
?email => hash(<<"bla@bla.ru">>),
?phone_number => undefined
}
},
?assertEqual(
Features,
read(customer(), Request)
).
-spec compare_customer_features_test() -> _.
compare_customer_features_test() ->
Request = ?CUSTOMER_PARAMS,
RequestSame = Request#{
<<"partyID">> => <<"ANOTHER PARTY">>,
<<"metadata">> => #{<<"text">> => <<"sample text">>}
},
RequestDifferent = Request#{
<<"shopID">> => hash(<<"Another shop">>),
<<"contactInfo">> => #{
<<"email">> => hash(<<"bla@example.com">>),
<<"phoneNumber">> => <<"8-800-555-35-35">>
}
},
common_compare_tests(
customer(),
Request,
RequestSame,
RequestDifferent,
[
<<"shopID">>,
<<"contactInfo.email">>,
<<"contactInfo.phoneNumber">>
]
).
-spec read_customer_binding_features_test() -> _.
read_customer_binding_features_test() ->
Session = ?TEST_PAYMENT_SESSION(<<"Session">>),
Tool = ?TEST_PAYMENT_TOOL(visa, <<"TOKEN">>),
Request = payment_resource(Session, Tool),
Features = #{
?payment_resource => #{
?payment_session => hash(Session),
?payment_tool => #{
?discriminator => hash(<<"bank_card">>),
?bank_card => #{
?token => hash(<<"TOKEN">>),
?exp_date => hash(<<"12/2012">>)
},
?terminal => #{
?discriminator => undefined
},
?wallet => #{
?provider => undefined,
?id => undefined,
?token => hash(<<"TOKEN">>)
},
?crypto => #{
?currency => undefined
},
?mobile_commerce => #{
?operator => undefined,
?phone => undefined
}
}
}
},
?assertEqual(
Features,
read(customer_binding(), Request)
).
-spec compare_customer_binding_features_test() -> _.
compare_customer_binding_features_test() ->
Session1 = ?TEST_PAYMENT_SESSION(<<"Session1">>),
Tool1 = ?TEST_PAYMENT_TOOL(visa),
Request1 = payment_resource(Session1, Tool1),
Session2 = ?TEST_PAYMENT_SESSION(<<"Session2">>),
Tool2 = ?TEST_PAYMENT_TOOL(mastercard)#{<<"exp_date">> => <<"01/2020">>},
Request2 = payment_resource(Session2, Tool2),
common_compare_tests(customer_binding(), Request1, Request2, [
<<"paymentResource.paymentTool.exp_date">>,
<<"paymentResource.paymentSession">>
]).
%% Add invoice_template tests
-spec read_invoice_template_features_test() -> _.
read_invoice_template_features_test() ->
ShopID = <<"1">>,
Request = #{
<<"shopID">> => ShopID,
<<"lifetime">> => lifetime_dummy(1, 2, 3),
<<"details">> => ?INVOICE_TMPL_DETAILS_PARAMS(42)
},
Features = #{
?shop_id => hash(ShopID),
?lifetime => #{
?days => hash(1),
?months => hash(2),
?years => hash(3)
},
?details => #{
?discriminator => hash(<<"InvoiceTemplateMultiLine">>),
?single_line => #{
?product => undefined,
?price => undefined,
?tax => undefined
},
?multiline => #{
?currency => hash(<<"RUB">>),
?cart => [
[
1,
#{
?product => hash(?STRING),
?quantity => hash(42),
?price => hash(?INTEGER),
?tax => #{?discriminator => hash(<<"InvoiceLineTaxVAT">>), ?rate => hash(<<"18%">>)}
}
],
[
0,
#{
?product => hash(?STRING),
?quantity => hash(42),
?price => hash(?INTEGER),
?tax => undefined
}
]
]
}
}
},
?assertEqual(
Features,
read(invoice_template(), Request)
).
-spec compare_invoice_template_features_test() -> _.
compare_invoice_template_features_test() ->
ShopID1 = <<"1">>,
ShopID2 = <<"2">>,
Request1 = #{
<<"shopID">> => ShopID1,
<<"lifetime">> => lifetime_dummy(1, 2, 3),
<<"details">> => ?INVOICE_TMPL_DETAILS_PARAMS(42)
},
Request2 = deep_merge(
Request1,
#{
<<"shopID">> => ShopID2,
<<"lifetime">> => lifetime_dummy(1, 2, 42),
<<"details">> => #{
<<"currency">> => ?USD,
<<"cart">> => [hd(deep_fetch(Request1, [<<"details">>, <<"cart">>]))]
}
}
),
common_compare_tests(invoice_template(), Request1, Request2, [
<<"shopID">>,
<<"lifetime.years">>,
<<"details.currency">>,
<<"details.cart">>
]).
-spec read_allocation_transaction_test_() -> _.
read_allocation_transaction_test_() ->
Request1 = ?ALLOCATION_TRANSACTION_PARAMS,
Features1 = #{
?target => #{
?discriminator => hash(<<"AllocationTargetShop">>),
?shop_id => hash(?STRING)
},
?discriminator => hash(<<"AllocationBodyTotal">>),
?amount => undefined,
?total => hash(?INTEGER),
?currency => hash(?USD),
?fee => #{
?target => #{
?discriminator => hash(<<"AllocationTargetShop">>),
?shop_id => hash(?STRING)
},
?discriminator => hash(<<"AllocationFeeShare">>),
?amount => hash(?INTEGER),
?share => #{
?matisse => hash(?INTEGER),
?exponent => hash(?INTEGER)
}
},
?cart => [
[
0,
#{
?product => hash(?STRING),
?quantity => hash(?INTEGER),
?price => hash(?INTEGER),
?tax => undefined
}
]
]
},
Request2 = Request1#{
<<"fee">> => #{
<<"target">> => ?ALLOCATION_TARGET,
<<"allocationFeeType">> => <<"AllocationFeeFixed">>,
<<"amount">> => 1024
}
},
Features2 = Features1#{
?fee => #{
?target => #{
?discriminator => hash(<<"AllocationTargetShop">>),
?shop_id => hash(?STRING)
},
?discriminator => hash(<<"AllocationFeeFixed">>),
?amount => hash(1024),
?share => undefined
}
},
[
?_assertEqual(Features1, read(allocation_transaction(), Request1)),
?_assertEqual(Features2, read(allocation_transaction(), Request2))
].
-spec compare_allocation_transaction_test() -> _.
compare_allocation_transaction_test() ->
Request1 = ?ALLOCATION_TRANSACTION_PARAMS,
Request2 = ?ALLOCATION_TRANSACTION_PARAMS#{
<<"total">> => 1024,
<<"amount">> => 512,
<<"fee">> => #{
<<"target">> => ?ALLOCATION_TARGET,
<<"allocationFeeType">> => <<"AllocationFeeFixed">>,
<<"amount">> => ?INTEGER,
<<"share">> => undefined
}
},
Request3 = #{
<<"target">> => ?ALLOCATION_TARGET#{<<"shopID">> => <<"SomeShop">>},
<<"allocationBodyType">> => <<"AllocationBodyAmount">>,
<<"amount">> => ?INTEGER,
<<"currency">> => ?RUB,
<<"cart">> => [
#{<<"product">> => ?STRING, <<"quantity">> => 1, <<"price">> => ?INTEGER}
]
},
Request4 = Request1#{
<<"fee">> => deep_merge(maps:get(<<"fee">>, Request1), #{
<<"amount">> => 1024,
<<"share">> => #{<<"m">> => 1024, <<"exp">> => 1024}
})
},
common_compare_tests(allocation_transaction(), Request1, Request2, [
<<"amount">>, <<"total">>, <<"fee">>
]),
common_compare_tests(allocation_transaction(), Request1, Request3, [
<<"target.shopID">>, <<"allocationBodyType">>, <<"currency">>, <<"amount">>, <<"cart.0.quantity">>
]),
common_compare_tests(allocation_transaction(), Request1, Request4, [
<<"fee.amount">>, <<"fee.share.m">>, <<"fee.share.exp">>
]).
-spec demo_compare_allocation_transaction_test() -> _.
demo_compare_allocation_transaction_test() ->
Request1 = ?ALLOCATION_TRANSACTION_PARAMS,
Request2 = #{
<<"allocationBodyType">> => <<"AllocationBodyAmount">>
},
Request3 = #{
<<"fee">> => deep_merge(maps:get(<<"fee">>, Request1), #{
<<"allocationFeeType">> => <<"AllocationFeeFixed">>
})
},
common_compare_tests(allocation_transaction(), Request1, Request2, [
<<"allocationBodyType">>
]),
common_compare_tests(allocation_transaction(), Request1, Request3, [
<<"fee">>
]).
payment_resource(Session, Tool) ->
#{
<<"paymentResource">> => #{
<<"paymentSession">> => Session,
<<"paymentTool">> => Tool
}
}.
payment_params(ExternalID, MakeRecurrent) ->
genlib_map:compact(#{
<<"externalID">> => ExternalID,
<<"flow">> => #{<<"type">> => <<"PaymentFlowInstant">>},
<<"makeRecurrent">> => MakeRecurrent,
<<"metadata">> => #{<<"bla">> => <<"*">>},
<<"processingDeadline">> => <<"5m">>
}).
payment_params(ExternalID, Jwe, ContactInfo, MakeRecurrent) ->
Params = payment_params(ExternalID, MakeRecurrent),
genlib_map:compact(Params#{
<<"payer">> => #{
<<"payerType">> => <<"PaymentResourcePayer">>,
<<"paymentSession">> => <<"payment.session">>,
<<"paymentToolToken">> => Jwe,
<<"contactInfo">> => ContactInfo
}
}).
payment_params(PaymentTool) ->
Params = payment_params(<<"EID">>, <<"Jwe">>, #{}, false),
PaymentParams = deep_merge(Params, #{<<"payer">> => #{<<"paymentTool">> => PaymentTool}}),
PaymentParams.
bank_card() ->
#{
<<"type">> => <<"bank_card">>,
<<"token">> => <<"cds token">>,
<<"payment_system">> => <<"visa">>,
<<"bin">> => <<"411111">>,
<<"last_digits">> => <<"1111">>,
<<"exp_date">> => <<"2019-08-24T14:15:22Z">>,
<<"cardholder_name">> => <<"Degus Degusovich">>,
<<"is_cvv_empty">> => false
}.
lifetime_dummy(Days, Months, Years) ->
#{
<<"days">> => Days,
<<"months">> => Months,
<<"years">> => Years
}.
common_compare_tests(Schema, Request, RequestDifferent, DiffFeatures) ->
common_compare_tests(Schema, Request, Request, RequestDifferent, DiffFeatures).
common_compare_tests(Schema, Request, RequestWithIgnoredFields, RequestDifferent, DiffFeatures) ->
Features = read(Schema, Request),
FeaturesIgnored = read(Schema, RequestWithIgnoredFields),
FeaturesDifferent = read(Schema, RequestDifferent),
%% Equal to self
?assertEqual(true, compare(Features, Features)),
%% Equal to feature-wise same request
?assertEqual(true, compare(Features, FeaturesIgnored)),
%% Has correct diff with different request
Result = compare(Features, FeaturesDifferent),
?assertMatch({false, _}, Result),
{false, Diff} = Result,
?assertEqual(lists:sort(DiffFeatures), lists:sort(list_diff_fields(Schema, Diff))).
-endif.

View File

@ -301,7 +301,7 @@ mask_customer_notfound(Resolution) ->
generate_customer_id(OperationID, PartyID, CustomerParams, #{woody_context := WoodyContext}) ->
ExternalID = maps:get(<<"externalID">>, CustomerParams, undefined),
IdempKey = {OperationID, PartyID, ExternalID},
Identity = {schema, capi_feature_schemas:customer(), CustomerParams},
Identity = capi_bender:make_identity(customer, CustomerParams),
capi_bender:try_gen_snowflake(IdempKey, Identity, WoodyContext).
encode_customer_params(CustomerID, PartyID, Params) ->
@ -334,9 +334,7 @@ generate_binding_ids(OperationID, CustomerBindingParams, Context = #{woody_conte
CustomerBindingParams
),
Identity = capi_bender:make_identity(
{schema, capi_feature_schemas:customer_binding(), CustomerBindingParamsEncrypted}
),
Identity = capi_bender:make_identity(customer_binding, CustomerBindingParamsEncrypted),
OperationIDBin = erlang:atom_to_binary(OperationID),
CustomerBindingID = capi_bender:try_gen_snowflake(

View File

@ -257,7 +257,7 @@ create_invoice(PartyID, InvoiceTplID, InvoiceParams, Context, BenderPrefix) ->
ExternalID = maps:get(<<"externalID">>, InvoiceParams, undefined),
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
InvoiceParamsWithTemplate = maps:put(<<"invoiceTemplateID">>, InvoiceTplID, InvoiceParams),
Identity = {schema, capi_feature_schemas:invoice(), InvoiceParamsWithTemplate},
Identity = capi_bender:make_identity(invoice, InvoiceParamsWithTemplate),
InvoiceID = capi_bender:try_gen_snowflake(IdempotentKey, Identity, WoodyCtx),
CallArgs = {encode_invoice_params_with_tpl(InvoiceID, InvoiceTplID, InvoiceParams)},
Call = {invoicing, 'CreateWithTemplate', CallArgs},
@ -270,7 +270,7 @@ get_invoice_template(ID, Context) ->
generate_invoice_template_id(OperationID, TemplateParams, PartyID, #{woody_context := WoodyContext}) ->
ExternalID = maps:get(<<"externalID">>, TemplateParams, undefined),
IdempKey = {OperationID, PartyID, ExternalID},
Identity = {schema, capi_feature_schemas:invoice_template(), TemplateParams},
Identity = capi_bender:make_identity(invoice_template, TemplateParams),
capi_bender:try_gen_snowflake(IdempKey, Identity, WoodyContext).
encode_invoice_tpl_create_params(InvoiceTemplateID, PartyID, Params) ->

View File

@ -291,7 +291,7 @@ create_invoice(PartyID, InvoiceParams, Context, BenderPrefix) ->
#{woody_context := WoodyCtx} = Context,
ExternalID = maps:get(<<"externalID">>, InvoiceParams, undefined),
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
Identity = {schema, capi_feature_schemas:invoice(), InvoiceParams},
Identity = capi_bender:make_identity(invoice, InvoiceParams),
InvoiceID = capi_bender:try_gen_snowflake(IdempotentKey, Identity, WoodyCtx),
Call = {invoicing, 'Create', {encode_invoice_params(InvoiceID, PartyID, InvoiceParams)}},
capi_handler_utils:service_call_with([user_info], Call, Context).

View File

@ -19,7 +19,6 @@ prepare(OperationID = 'CreatePayment', Req, Context) ->
InvoiceID = maps:get(invoiceID, Req),
Invoice = get_invoice_by_id(InvoiceID, Context),
PaymentParams = maps:get('PaymentParams', Req),
PaymentToken = decode_payment_token(PaymentParams),
Authorize = fun() ->
Prototypes = [
{operation, #{id => OperationID, invoice => InvoiceID}},
@ -31,8 +30,7 @@ prepare(OperationID = 'CreatePayment', Req, Context) ->
try
capi_handler:respond_if_undefined(Invoice, general_error(404, <<"Invoice not found">>)),
DomainInvoice = Invoice#payproc_Invoice.invoice,
PaymentTool = capi_utils:maybe(PaymentToken, fun(#{payment_tool := V}) -> V end),
Result = create_payment(DomainInvoice, PaymentParams, Context, OperationID, PaymentTool),
Result = create_payment(DomainInvoice, PaymentParams, Context, OperationID),
case Result of
{ok, Payment} ->
{ok, {201, #{}, decode_invoice_payment(InvoiceID, Payment, Context)}};
@ -526,7 +524,10 @@ validate_refund(Params) ->
_ -> throw(refund_cart_conflict)
end.
create_payment(Invoice, PaymentParams, Context, OperationID, PaymentTool) ->
create_payment(Invoice, PaymentParams, Context, OperationID) ->
PaymentToken = decode_payment_token(PaymentParams),
PaymentTool = capi_utils:maybe(PaymentToken, fun(#{payment_tool := V}) -> V end),
InvoiceID = Invoice#domain_Invoice.id,
PaymentID = create_payment_id(Invoice, PaymentParams, Context, OperationID, PaymentTool),
ExternalID = maps:get(<<"externalID">>, PaymentParams, undefined),
@ -534,39 +535,28 @@ create_payment(Invoice, PaymentParams, Context, OperationID, PaymentTool) ->
Call = {invoicing, 'StartPayment', {InvoiceID, InvoicePaymentParams}},
capi_handler_utils:service_call_with([user_info], Call, Context).
create_payment_id(Invoice, PaymentParams, Context, OperationID, PaymentTool) ->
create_payment_id(Invoice, PaymentParams0, Context, OperationID, PaymentToolThrift) ->
InvoiceID = Invoice#domain_Invoice.id,
PartyID = Invoice#domain_Invoice.owner_id,
Payer = maps:get(<<"payer">>, PaymentParams),
Payer = maps:get(<<"payer">>, PaymentParams0),
PaymentTool = capi_utils:maybe(PaymentToolThrift, fun capi_handler_decoder_party:decode_payment_tool/1),
% Temprory decision was made for analytics
% Temporary decision for analytics team
% TODO: delete this after analytics research will be down
_ = log_payer_client_url(Payer, InvoiceID),
% TODO При наличии paymentToolToken заменяем его раскодированной структурой paymentTool
% В противном случае токены будут оказывать влияние на расчет hash2, удаление paymentToolToken
% не потребуется при удалении capi_bender:check_idempotent_conflict_deprecated
ClearPayer =
case PaymentTool of
undefined ->
Payer;
_ ->
Payer0 = maps:without([<<"paymentToolToken">>], Payer),
Payer0#{<<"paymentTool">> => capi_handler_decoder_party:decode_payment_tool(PaymentTool)}
end,
FullParams = PaymentParams#{
PaymentParams = PaymentParams0#{
% Требуется для последующей кодировки параметров плательщика
<<"invoiceID">> => InvoiceID,
% Заменяем на структуру без токена
<<"payer">> => ClearPayer
<<"payer">> => Payer#{<<"paymentTool">> => PaymentTool}
},
Identity = capi_bender:make_identity({schema, capi_feature_schemas:payment(), FullParams, PaymentParams}),
ExternalID = maps:get(<<"externalID">>, PaymentParams, undefined),
BenderPrefix = OperationID,
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
SequenceID = InvoiceID,
Identity = capi_bender:make_identity(payment, PaymentParams),
SequenceParams = #{},
#{woody_context := WoodyCtx} = Context,
%% We put `invoice_id` in a context here because `get_payment_by_external_id()` needs it to work
@ -780,12 +770,13 @@ encode_processing_deadline(Deadline) ->
default_processing_deadline() ->
genlib_app:env(capi, default_processing_deadline, ?DEFAULT_PROCESSING_DEADLINE).
create_refund(InvoiceID, PaymentID, RefundParams, Context, BenderPrefix) ->
create_refund(InvoiceID, PaymentID, RefundParams0, Context, BenderPrefix) ->
PartyID = capi_handler_utils:get_party_id(Context),
RefundParamsFull = RefundParams#{<<"invoiceID">> => InvoiceID, <<"paymentID">> => PaymentID},
RefundParams = RefundParams0#{<<"invoiceID">> => InvoiceID, <<"paymentID">> => PaymentID},
ExternalID = maps:get(<<"externalID">>, RefundParams, undefined),
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
Identity = {schema, capi_feature_schemas:refund(), RefundParamsFull, RefundParams},
Identity = capi_bender:make_identity(refund, RefundParams),
SequenceID = create_sequence_id([InvoiceID, PaymentID], BenderPrefix),
SequenceParams = #{minimum => 100},
#{woody_context := WoodyCtx} = Context,

View File

@ -1,6 +1,6 @@
-module(capi_idemp_features).
-module(capi_idemp_features_legacy).
-include("capi_feature_schemas.hrl").
-include("capi_feature_schemas_legacy.hrl").
-type request_key() :: binary().
-type request_value() :: integer() | binary() | request() | [request()].

View File

@ -7,7 +7,7 @@
%%
-spec create_storage() -> capi_idemp_features:event_handler().
-spec create_storage() -> feat:event_handler().
create_storage() ->
%% TODO delete named_table. Make opportunity for concurrent tests.
ets:new(?MODULE, [set, public, named_table]).
@ -18,105 +18,78 @@ delete_storage() ->
-spec get_unused_params() -> _.
get_unused_params() ->
Req = get_request(),
maps:keys(capi_ct_helper:map_to_flat(Req)).
get_req_paths().
-spec handle_event(capi_idemp_features:event(), capi_idemp_features:options()) -> ok.
handle_event({invalid_schema_fragment, Key, Request}, _Opts) ->
throw({extact_idemp_feature, Key, Request});
handle_event({request_visited, {request, Req}}, _Opts) ->
save_request(Req),
-spec handle_event(feat:event(), feat:options()) -> ok.
handle_event({request_visited, Req}, _Opts) ->
save_req_paths(unroll_request_to_paths(Req));
handle_event({request_key_visit, Key, _Value}, _Opts) ->
push_path(Key);
handle_event({request_key_visited, _Key, _Value}, _Opts) ->
delete_subpath(pop_path());
handle_event({request_index_visit, N, _Value}, _Opts) ->
push_path(N);
handle_event({request_index_visited, _N, _Value}, _Opts) ->
delete_subpath(pop_path());
handle_event({request_variant_visit, _FeatureName, _Variant, _Value}, _Opts) ->
ok;
handle_event({request_key_index_visit, N}, _Opts) ->
push_path({key_index, N}),
handle_event({request_variant_visited, _FeatureName, _Variant, _Value}, _Opts) ->
ok;
handle_event({request_key_index_visited, _N}, _Opts) ->
%% delete key_index from stack
pop_path(),
{Key, List} = pop_path(),
%% delete empty map from set
List2 = lists:foldl(
fun
(M, AccIn) when map_size(M) =:= 0 -> AccIn;
(M, AccIn) -> [M | AccIn]
handle_event(Error, _Opts) ->
throw(Error).
delete_subpath(Path) ->
save_req_paths(
lists:delete(
lists:reverse(Path),
get_req_paths()
)
).
unroll_request_to_paths(Req) when not is_map(Req), not is_list(Req); map_size(Req) == 0; length(Req) == 0 ->
[[]];
unroll_request_to_paths(Req) when is_map(Req); is_list(Req) ->
lists:flatmap(
fun({Key, Nested}) ->
lists:map(
fun(Rest) -> [Key | Rest] end,
unroll_request_to_paths(Nested)
)
end,
[],
List
),
push_path({Key, List2}),
ok;
handle_event({request_key_visit, {key, Key, SubReq}}, _Opts) ->
push_path({Key, SubReq}),
ok;
handle_event({request_key_visited, {key, Key}}, _Opts) ->
Path = get_path(),
[{Key, SubReq} | Tail] = Path,
delete_subpath(Key, SubReq, Tail),
ok.
req_to_list(Req)
).
delete_subpath(Key, SubReq, []) when is_map(SubReq), map_size(SubReq) > 0; is_list(SubReq), length(SubReq) > 0 ->
Request = get_request(),
Request2 = Request#{Key => SubReq},
save_request(Request2),
ok;
delete_subpath(Key, SubReq, [{K, Req} | T]) when
is_map(SubReq), map_size(SubReq) > 0; is_list(SubReq), length(SubReq) > 0
->
Req2 = Req#{Key => SubReq},
update_path([{K, Req2} | T]),
ok;
delete_subpath(Key, _SubReq, []) ->
Request = get_request(),
Request2 = maps:remove(Key, Request),
insert(path, []),
save_request(Request2);
delete_subpath(Key, _SubReq, [{key_index, Index} = KeyIndex | Tail]) ->
[{KeyList, SubReqList} | T] = Tail,
{_, SubReqList2} = lists:foldl(
fun
(SubReq, {I, AccIn}) when I == Index ->
SubReq2 = maps:remove(Key, SubReq),
{I + 1, [SubReq2 | AccIn]};
(_, {N, AccIn}) ->
{N + 1, AccIn}
end,
{0, []},
SubReqList
),
Path = [KeyIndex, {KeyList, lists:reverse(SubReqList2)}] ++ T,
update_path(Path),
ok;
delete_subpath(Key, _SubReq, [{K, Req} | T]) ->
Req2 = maps:remove(Key, Req),
update_path([{K, Req2} | T]),
ok.
req_to_list(Map) when is_map(Map) ->
maps:to_list(Map);
req_to_list(List) when is_list(List) ->
lists:zip(lists:seq(0, length(List) - 1), List).
save_request(Req) ->
insert(request, Req).
save_req_paths(Paths) ->
insert(paths, Paths).
get_request() ->
case ets:lookup(?MODULE, request) of
[] -> #{};
[{request, Req}] -> Req
end.
get_req_paths() ->
get(paths, []).
update_path(Path) ->
put_path(Path) ->
insert(path, Path).
get_path() ->
case ets:lookup(?MODULE, path) of
[] -> [];
[{path, Path}] -> Path
end.
get(path, []).
push_path({Key, Req}) ->
Path = get_path(),
insert(path, [{Key, Req} | Path]).
push_path(Item) ->
put_path([Item | get_path()]).
pop_path() ->
[Result | Path] = get_path(),
insert(path, Path),
Result.
Path = get_path(),
put_path(tl(Path)),
Path.
insert(Key, Value) ->
ets:insert(?MODULE, {Key, Value}).
ets:insert(?MODULE, {Key, Value}),
ok.
get(Key, Default) ->
case ets:lookup(?MODULE, Key) of
[] -> Default;
[{Key, Value}] -> Value
end.

View File

@ -18,6 +18,8 @@
-export([stop_mocked_service_sup/1]).
-export([mock_services/2]).
-export([mock_services_/2]).
-export([replace_env/1]).
-export([restore_env/1]).
-export([get_lifetime/0]).
-export([map_to_flat/1]).
@ -33,6 +35,7 @@
-type config() :: [{atom(), any()}].
-type app_name() :: atom().
-type sup_or_config() :: config() | pid().
-type replaces() :: #{App :: atom() => #{Key :: atom() => undefined | {value, term()}}}.
-export_type([config/0]).
-export_type([app_name/0]).
@ -174,6 +177,7 @@ mock_services(Services, SupOrConfig) ->
{BenderClientServices, WoodyServices} = lists:partition(
fun
({generator, _}) -> true;
({bender, _}) -> true;
(_) -> false
end,
Other
@ -182,6 +186,45 @@ mock_services(Services, SupOrConfig) ->
_ = start_bender_client(mock_services_(BenderClientServices, SupOrConfig)),
start_woody_client(mock_services_(WoodyServices, SupOrConfig)).
-spec replace_env(#{App :: atom() => #{Key :: atom() => Value :: term()}}) -> replaces().
replace_env(Env) ->
maps:fold(
fun(App, AppEnv, Acc) ->
AppReplaces =
maps:fold(
fun(Key, Value, AppAcc) ->
AccValue =
case application:get_env(App, Key) of
undefined -> undefined;
{ok, Original} -> {value, Original}
end,
application:set_env(App, Key, Value),
AppAcc#{Key => AccValue}
end,
#{},
AppEnv
),
Acc#{App => AppReplaces}
end,
#{},
Env
).
-spec restore_env(replaces()) -> ok.
restore_env(Replaces) ->
maps:foreach(
fun(App, AppReplaces) ->
maps:foreach(
fun
(Key, undefined) -> application:unset_env(App, Key);
(Key, {value, Original}) -> application:set_env(App, Key, Original)
end,
AppReplaces
)
end,
Replaces
).
start_party_client(Services) ->
start_app(party_client, [{services, Services}]).
@ -220,6 +263,8 @@ mock_services_(Services, SupPid) when is_pid(SupPid) ->
get_service_name({generator, _}) ->
'Generator';
get_service_name({bender, _}) ->
'Bender';
get_service_name({ServiceName, _Fun}) ->
ServiceName;
get_service_name({ServiceName, _WoodyService, _Fun}) ->
@ -227,6 +272,8 @@ get_service_name({ServiceName, _WoodyService, _Fun}) ->
mock_service_handler({generator, Fun}) ->
mock_service_handler('Generator', {bender_thrift, 'Generator'}, Fun);
mock_service_handler({bender, Fun}) ->
mock_service_handler('Bender', {bender_thrift, 'Bender'}, Fun);
mock_service_handler({party_management, Fun}) ->
mock_service_handler(party_management, {dmsl_payment_processing_thrift, 'PartyManagement'}, Fun);
mock_service_handler({ServiceName, Fun}) ->

View File

@ -24,6 +24,7 @@
-export([second_request_without_idempotent_feature_test/1]).
-export([create_invoice_ok_test/1]).
-export([create_invoice_legacy_fail_test/1]).
-export([create_invoice_legacy_ok_test/1]).
-export([create_invoice_fail_test/1]).
-export([create_invoice_idemp_cart_ok_test/1]).
-export([create_invoice_idemp_cart_fail_test/1]).
@ -77,6 +78,7 @@ groups() ->
{invoice_creation, [], [
create_invoice_ok_test,
create_invoice_legacy_fail_test,
create_invoice_legacy_ok_test,
create_invoice_fail_test,
create_invoice_idemp_cart_fail_test,
create_invoice_idemp_cart_ok_test,
@ -108,15 +110,19 @@ groups() ->
%% starting/stopping
%%
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
ExtraEnv = [{idempotence_event_handler, {capi_ct_features_reader_event_handler, #{}}}],
capi_ct_helper:init_suite(?MODULE, Config, ExtraEnv).
init_per_suite(Config0) ->
ReplacedEnv =
capi_ct_helper:replace_env(
#{feat => #{event_handler => {capi_ct_features_reader_event_handler, #{}}}}
),
Config = capi_ct_helper:init_suite(?MODULE, Config0),
[{replaced_env, ReplacedEnv} | Config].
-spec end_per_suite(config()) -> _.
end_per_suite(C) ->
_ = capi_ct_helper:stop_mocked_service_sup(?config(suite_test_sup, C)),
_ = [application:stop(App) || App <- proplists:get_value(apps, C)],
_ = application:unset_env(capi, idempotence_event_handler),
ok = capi_ct_helper:restore_env(?config(replaced_env, C)),
ok.
-spec init_per_group(group_name(), config()) -> config().
@ -207,12 +213,14 @@ create_payment_ok_test(Config) ->
?assertEqual(
[
[<<"externalID">>],
[<<"metadata">>, <<"bla">>],
[<<"metadata">>, <<"bla">>, 0],
[<<"payer">>, <<"contactInfo">>],
[<<"payer">>, <<"paymentSession">>],
[<<"payer">>, <<"paymentTool">>, <<"bin">>],
[<<"payer">>, <<"paymentTool">>, <<"cardholder_name">>],
[<<"payer">>, <<"paymentTool">>, <<"masked_pan">>],
[<<"payer">>, <<"paymentTool">>, <<"payment_system">>],
[<<"payer">>, <<"paymentToolToken">>],
[<<"processingDeadline">>]
],
Unused
@ -249,8 +257,10 @@ different_payment_tools_test(Config) ->
?assertEqual(
[
[<<"externalID">>],
[<<"metadata">>, <<"bla">>],
[<<"metadata">>, <<"bla">>, 0],
[<<"payer">>, <<"contactInfo">>],
[<<"payer">>, <<"paymentSession">>],
[<<"payer">>, <<"paymentToolToken">>],
[<<"processingDeadline">>]
],
Unused
@ -314,6 +324,33 @@ create_invoice_ok_test(Config) ->
?assertEqual(ExternalID, maps:get(<<"externalID">>, Invoice1)),
?assertEqual(Invoice1, Invoice2).
-spec create_invoice_legacy_ok_test(config()) -> _.
create_invoice_legacy_ok_test(Config) ->
BenderKey = <<"bender_key">>,
ExternalID = <<"ok_merch_id">>,
Req = invoice_params(ExternalID),
Unused = [
[<<"description">>],
[<<"externalID">>],
[<<"metadata">>, <<"invoice_dummy_metadata">>]
],
Ctx = capi_msgp_marshalling:marshal(#{
<<"version">> => 2,
<<"features">> => capi_idemp_features_legacy:read(capi_feature_schemas_legacy:invoice(), Req)
}),
_ = capi_ct_helper:mock_services(
[
{invoicing, fun('Create', {_UserInfo, #payproc_InvoiceParams{id = ID, external_id = EID}}) ->
{ok, ?PAYPROC_INVOICE_WITH_ID(ID, EID)}
end},
{bender, fun('GenerateID', _) -> {ok, capi_ct_helper_bender:get_result(BenderKey, Ctx)} end}
],
Config
),
{{ok, ActualInvoice}, ActualUnused} = create_invoice_(Req, Config),
?assertEqual(Unused, ActualUnused),
{{ok, ActualInvoice}, ActualUnused} = create_invoice_(Req, Config).
-spec create_invoice_legacy_fail_test(config()) -> _.
create_invoice_legacy_fail_test(Config) ->
BenderKey = <<"bender_key">>,
@ -325,7 +362,10 @@ create_invoice_legacy_fail_test(Config) ->
[<<"metadata">>, <<"invoice_dummy_metadata">>]
],
Req2 = Req#{<<"product">> => <<"test_product2">>},
Ctx = capi_msgp_marshalling:marshal(#{<<"version">> => 1, <<"params_hash">> => erlang:phash2(Req)}),
Ctx = capi_msgp_marshalling:marshal(#{
<<"version">> => 2,
<<"features">> => capi_idemp_features_legacy:read(capi_feature_schemas_legacy:invoice(), Req)
}),
_ = capi_ct_helper:mock_services(
[
{invoicing, fun('Create', {_UserInfo, #payproc_InvoiceParams{id = ID, external_id = EID}}) ->
@ -618,6 +658,7 @@ create_customer_ok_test(Config) ->
Req1 = ?CUSTOMER_PARAMS#{<<"externalID">> => genlib:unique()},
Req2 = Req1#{<<"externalID">> => genlib:unique()},
UnusedFeatures = [[<<"externalID">>], [<<"metadata">>, <<"text">>, 0], [<<"metadata">>, <<"text">>, 1]],
Result = create_customers(BenderKey, [Req1, Req2], Config),
[{{ok, #{<<"customer">> := Customer1}}, UnusedFeatures1}, {{ok, #{<<"customer">> := Customer2}}, UnusedFeatures2}] =
@ -625,7 +666,7 @@ create_customer_ok_test(Config) ->
?assertEqual(Customer1, Customer2),
?assertEqual(UnusedFeatures1, UnusedFeatures2),
?assertEqual(UnusedFeatures1, [[<<"externalID">>], [<<"metadata">>, <<"text">>]]).
?assertEqual(UnusedFeatures1, UnusedFeatures).
-spec create_customer_fail_test(config()) -> _.
create_customer_fail_test(Config) ->
@ -637,7 +678,9 @@ create_customer_fail_test(Config) ->
[CustomerResult1, CustomerResult2] = create_customers(BenderKey, [Req1, Req2], Config),
?assertMatch({{ok, _}, _}, CustomerResult1),
?assertEqual(
{response_error(409, ExternalID, BenderKey), [[<<"externalID">>], [<<"metadata">>, <<"text">>]]},
{response_error(409, ExternalID, BenderKey), [
[<<"externalID">>], [<<"metadata">>, <<"text">>, 0], [<<"metadata">>, <<"text">>, 1]
]},
CustomerResult2
).
@ -938,7 +981,7 @@ create_invoices_with_templates(BenderKey, Requests, Config) ->
).
with_feature_storage(Fun) ->
capi_ct_features_reader_event_handler:create_storage(),
_ = capi_ct_features_reader_event_handler:create_storage(),
Result = Fun(),
UnusedParams = capi_ct_features_reader_event_handler:get_unused_params(),
capi_ct_features_reader_event_handler:delete_storage(),

View File

@ -92,9 +92,7 @@ get_service_modname(webhook_manager) ->
get_service_modname(customer_management) ->
{dmsl_payment_processing_thrift, 'CustomerManagement'};
get_service_modname(party_management) ->
{dmsl_payment_processing_thrift, 'PartyManagement'};
get_service_modname(bender) ->
{bender_thrift, 'Bender'}.
{dmsl_payment_processing_thrift, 'PartyManagement'}.
get_service_deadline(ServiceName) ->
ServiceDeadlines = genlib_app:env(?MODULE, service_deadlines, #{}),

View File

@ -95,8 +95,7 @@
merchant_stat => <<"http://magista:8022/stat">>,
reporting => <<"http://reporter:8022/reports">>,
payouts => <<"http://payouter:8022/reports">>,
geo_ip_service => <<"http://columbus:8022/repo">>,
bender => <<"http://bender:8022/v1/bender">>
geo_ip_service => <<"http://columbus:8022/repo">>
}},
{service_deadlines, #{
party_management => 5000, % milliseconds

View File

@ -12,7 +12,12 @@
{elvis_style, macro_module_names},
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
{elvis_style, nesting_level, #{level => 4}},
{elvis_style, god_modules, #{limit => 30, ignore => [capi_base_api_token_tests_SUITE]}},
{elvis_style, god_modules, #{
limit => 30,
ignore => [
capi_base_api_token_tests_SUITE, capi_idempotency_tests_SUITE
]
}},
{elvis_style, no_if_expression},
{elvis_style, invalid_dynamic_call, #{ignore => [capi_domain]}},
{elvis_style, used_ignored_variable},

View File

@ -51,7 +51,8 @@
{token_keeper_client, {git, "https://github.com/rbkmoney/token-keeper-client.git", {branch, master}}},
{party_client, {git, "https://github.com/rbkmoney/party_client_erlang.git", {branch, master}}},
{payout_manager_proto, {git, "https://github.com/rbkmoney/payout-manager-proto.git", {branch, master}}},
{how_are_you, {git, "https://github.com/rbkmoney/how_are_you.git", {branch, master}}}
{how_are_you, {git, "https://github.com/rbkmoney/how_are_you.git", {branch, master}}},
{feat, {git, "https://github.com/rbkmoney/feat.git", {branch, master}}}
]}.
%% XRef checks

View File

@ -3,7 +3,7 @@
{<<"bear">>,{pkg,<<"bear">>,<<"0.9.0">>},2},
{<<"bender_client">>,
{git,"https://github.com/rbkmoney/bender_client_erlang.git",
{ref,"69024efc38167c515d1dc7b7c2bb52262ffe7d0d"}},
{ref,"29501d6f6425bc310ef6b37b62790126bdff356b"}},
0},
{<<"bender_proto">>,
{git,"https://github.com/rbkmoney/bender-proto.git",
@ -54,13 +54,17 @@
{git,"https://github.com/rbkmoney/erlang-health.git",
{ref,"5958e2f35cd4d09f40685762b82b82f89b4d9333"}},
0},
{<<"feat">>,
{git,"https://github.com/rbkmoney/feat.git",
{ref,"bf7dff68c822e58769da962e7f99c3e428a88551"}},
0},
{<<"folsom">>,
{git,"https://github.com/folsom-project/folsom.git",
{ref,"62fd0714e6f0b4e7833880afe371a9c882ea0fc2"}},
1},
{<<"genlib">>,
{git,"https://github.com/rbkmoney/genlib.git",
{ref,"3e1776536802739d8819351b15d54ec70568aba7"}},
{ref,"82c5ff3866e3019eb347c7f1d8f1f847bed28c10"}},
0},
{<<"gproc">>,{pkg,<<"gproc">>,<<"0.9.0">>},1},
{<<"gun">>,