mirror of
https://github.com/valitydev/capi-v2.git
synced 2024-11-06 01:55:20 +00:00
TD-288: Stop responding w/ payment tool tokens (#11)
* Bump to valitydev/swag-payments@c39e50b * Also drop legacy idemp features handling * Simplify bender client module * Increase test coverage * Disentangle conflict error and `logic_error` * Deduplicate testcases code
This commit is contained in:
parent
d4fe7e16bd
commit
7f28b21d25
@ -1,9 +0,0 @@
|
||||
-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.
|
||||
-define(discriminator, -1).
|
||||
-define(difference, -1).
|
||||
|
||||
-endif.
|
@ -1,7 +1,6 @@
|
||||
-module(capi_bender).
|
||||
|
||||
-include_lib("bender_proto/include/bender_thrift.hrl").
|
||||
-include_lib("bender_proto/include/msgpack_thrift.hrl").
|
||||
|
||||
-type id() :: binary().
|
||||
-type idempotent_key_prefix() :: binary() | atom().
|
||||
@ -9,17 +8,11 @@
|
||||
-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}.
|
||||
%% TODO(ED-287): remove identity_request() from below
|
||||
-opaque identity() :: {identity, identity_features(), identity_schema(), identity_request()}.
|
||||
|
||||
-opaque identity() :: {identity, identity_features(), identity_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_schema() :: feat:schema().
|
||||
-type identity_request() :: feat:request().
|
||||
-type woody_context() :: woody_context:ctx().
|
||||
-type context_data() :: #{binary() => term()}.
|
||||
@ -34,6 +27,8 @@
|
||||
-type external_id_conflict() :: {external_id_conflict, id(), difference(), identity_schema()}.
|
||||
-type generation_error() :: external_id_conflict().
|
||||
|
||||
-type throws(_T) :: no_return().
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([external_id/0]).
|
||||
-export_type([issuer_id/0]).
|
||||
@ -50,50 +45,32 @@
|
||||
|
||||
-export([gen_snowflake/3]).
|
||||
-export([gen_snowflake/4]).
|
||||
-export([try_gen_snowflake/3]).
|
||||
-export([try_gen_snowflake/4]).
|
||||
-export([gen_sequence/4]).
|
||||
-export([gen_sequence/5]).
|
||||
-export([gen_sequence/6]).
|
||||
-export([try_gen_sequence/6]).
|
||||
-export([gen_constant/4]).
|
||||
-export([gen_constant/5]).
|
||||
-export([try_gen_constant/4]).
|
||||
-export([try_gen_constant/5]).
|
||||
-export([make_identity/2]).
|
||||
-export([get_internal_id/2]).
|
||||
|
||||
-define(BENDER_NAMESPACE, <<"capi">>).
|
||||
|
||||
%% 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()}.
|
||||
id() | throws(generation_error()).
|
||||
gen_snowflake(IdempotentKey, Identity, WoodyContext) ->
|
||||
Context = #{},
|
||||
gen_snowflake(IdempotentKey, Identity, WoodyContext, Context).
|
||||
|
||||
-spec gen_snowflake(idempotent_key_params() | undefined, identity(), woody_context(), context_data()) ->
|
||||
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
|
||||
id() | throws(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(), 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(), 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(), sequence_id(), woody_context()) ->
|
||||
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
|
||||
id() | throws(generation_error()).
|
||||
gen_sequence(IdempotentKey, Identity, SequenceID, WoodyContext) ->
|
||||
SequenceParams = #{},
|
||||
gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext).
|
||||
@ -104,7 +81,7 @@ gen_sequence(IdempotentKey, Identity, SequenceID, WoodyContext) ->
|
||||
sequence_id(),
|
||||
sequence_params(),
|
||||
woody_context()
|
||||
) -> {ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
|
||||
) -> id() | throws(generation_error()).
|
||||
gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext) ->
|
||||
Context = #{},
|
||||
gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext, Context).
|
||||
@ -116,61 +93,27 @@ gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext)
|
||||
sequence_params(),
|
||||
woody_context(),
|
||||
context_data()
|
||||
) -> {ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
|
||||
) -> id() | throws(generation_error()).
|
||||
gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext, Context) ->
|
||||
IdSchema = build_sequence_schema(SequenceID, SequenceParams),
|
||||
generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, Context).
|
||||
|
||||
-spec try_gen_sequence(
|
||||
idempotent_key_params() | undefined,
|
||||
identity(),
|
||||
sequence_id(),
|
||||
sequence_params(),
|
||||
woody_context(),
|
||||
context_data()
|
||||
) -> id() | no_return().
|
||||
try_gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyContext, ContextData) ->
|
||||
IdSchema = build_sequence_schema(SequenceID, SequenceParams),
|
||||
try_generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, ContextData).
|
||||
try_generate_id(IdSchema, IdempotentKey, Identity, WoodyContext, Context).
|
||||
|
||||
-spec gen_constant(idempotent_key_params(), identity(), constant_id(), woody_context()) ->
|
||||
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
|
||||
id() | throws(generation_error()).
|
||||
gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext) ->
|
||||
Context = #{},
|
||||
gen_constant(IdempotentKey, Identity, ConstantID, WoodyContext, Context).
|
||||
|
||||
-spec gen_constant(idempotent_key_params(), identity(), constant_id(), woody_context(), context_data()) ->
|
||||
{ok, id()} | {ok, id(), context_data()} | {error, generation_error()}.
|
||||
id() | throws(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(), 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(), 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_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().
|
||||
Features = feat:read(Schema, Data),
|
||||
{identity, Features, Schema}.
|
||||
|
||||
-spec get_internal_id(idempotent_key_params(), woody_context()) ->
|
||||
{ok, binary(), context_data()} | {error, internal_id_not_found}.
|
||||
@ -208,14 +151,8 @@ try_generate_id(BenderIdSchema, IdempotentKey, Identity, WoodyContext, CtxData)
|
||||
case generate_id(BenderIdSchema, IdempotentKey, Identity, WoodyContext, CtxData) of
|
||||
{ok, ID} ->
|
||||
ID;
|
||||
{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,
|
||||
{error, {external_id_conflict, ID, Difference, Schema}} ->
|
||||
ReadableDiff = feat:list_diff_fields(Schema, Difference),
|
||||
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})
|
||||
@ -241,19 +178,11 @@ make_idempotent_key({Prefix, PartyID, ExternalID}) ->
|
||||
bender_client:get_idempotent_key(?BENDER_NAMESPACE, Prefix, PartyID, ExternalID).
|
||||
|
||||
bender_generate_id(BenderIdSchema, IdempKey, Identity, WoodyContext, CtxData) ->
|
||||
{identity, Features, Schema, _Data} = Identity,
|
||||
{identity, Features, Schema} = Identity,
|
||||
BenderCtx = build_bender_ctx(Features, CtxData),
|
||||
case bender_client:gen_id(IdempKey, BenderIdSchema, WoodyContext, BenderCtx) of
|
||||
{ok, ID} ->
|
||||
{ok, ID};
|
||||
{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.
|
||||
@ -283,20 +212,6 @@ check_idempotent_conflict(ID, Features, SavedBenderCtx, Schema) ->
|
||||
{error, {external_id_conflict, ID, Difference, Schema}}
|
||||
end.
|
||||
|
||||
%% Deprecated idempotent context
|
||||
|
||||
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) ->
|
||||
maps:get(<<"context_data">>, Context, #{}).
|
||||
|
@ -1,984 +0,0 @@
|
||||
-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.
|
@ -6,7 +6,13 @@
|
||||
|
||||
-export([prepare/3]).
|
||||
|
||||
-import(capi_handler_utils, [general_error/2, logic_error/1, logic_error/2, map_service_result/1]).
|
||||
-import(capi_handler_utils, [
|
||||
general_error/2,
|
||||
logic_error/1,
|
||||
logic_error/2,
|
||||
conflict_error/1,
|
||||
map_service_result/1
|
||||
]).
|
||||
|
||||
-spec prepare(
|
||||
OperationID :: capi_handler:operation_id(),
|
||||
@ -45,7 +51,7 @@ prepare('CreateCustomer' = OperationID, Req, Context) ->
|
||||
end
|
||||
catch
|
||||
throw:{external_id_conflict, ID, UsedExternalID, _Schema} ->
|
||||
{ok, logic_error('externalIDConflict', {ID, UsedExternalID})}
|
||||
{ok, conflict_error({ID, UsedExternalID})}
|
||||
end
|
||||
end,
|
||||
{ok, #{authorize => Authorize, process => Process}};
|
||||
@ -165,7 +171,7 @@ prepare('CreateBinding' = OperationID, Req, Context) ->
|
||||
{error, invalid_payment_session} ->
|
||||
{ok, logic_error('invalidPaymentSession', <<"Specified payment session is invalid">>)};
|
||||
{error, {external_id_conflict, ID, UsedExternalID, _Schema}} ->
|
||||
{ok, logic_error('externalIDConflict', {ID, UsedExternalID})}
|
||||
{ok, conflict_error({ID, UsedExternalID})}
|
||||
end
|
||||
end,
|
||||
{ok, #{authorize => Authorize, process => Process}};
|
||||
@ -290,8 +296,8 @@ mask_customer_notfound(Resolution) ->
|
||||
generate_customer_id(OperationID, PartyID, CustomerParams, #{woody_context := WoodyContext}) ->
|
||||
ExternalID = maps:get(<<"externalID">>, CustomerParams, undefined),
|
||||
IdempKey = {OperationID, PartyID, ExternalID},
|
||||
Identity = capi_bender:make_identity(customer, CustomerParams),
|
||||
capi_bender:try_gen_snowflake(IdempKey, Identity, WoodyContext).
|
||||
Identity = capi_bender:make_identity(capi_feature_schemas:customer(), CustomerParams),
|
||||
capi_bender:gen_snowflake(IdempKey, Identity, WoodyContext).
|
||||
|
||||
encode_customer_params(CustomerID, PartyID, Params) ->
|
||||
#payproc_CustomerParams{
|
||||
@ -323,15 +329,18 @@ generate_binding_ids(OperationID, CustomerBindingParams, Context = #{woody_conte
|
||||
CustomerBindingParams
|
||||
),
|
||||
|
||||
Identity = capi_bender:make_identity(customer_binding, CustomerBindingParamsEncrypted),
|
||||
Identity = capi_bender:make_identity(
|
||||
capi_feature_schemas:customer_binding(),
|
||||
CustomerBindingParamsEncrypted
|
||||
),
|
||||
|
||||
OperationIDBin = erlang:atom_to_binary(OperationID),
|
||||
CustomerBindingID = capi_bender:try_gen_snowflake(
|
||||
CustomerBindingID = capi_bender:gen_snowflake(
|
||||
{<<OperationIDBin/binary, "+CustomerBindingID">>, UserID, ExternalID},
|
||||
Identity,
|
||||
WoodyContext
|
||||
),
|
||||
RecPaymentToolID = capi_bender:try_gen_snowflake(
|
||||
RecPaymentToolID = capi_bender:gen_snowflake(
|
||||
{<<OperationIDBin/binary, "+RecPaymentToolID">>, UserID, ExternalID},
|
||||
Identity,
|
||||
WoodyContext
|
||||
|
@ -183,11 +183,9 @@ decode_payer(
|
||||
customer_id = ID
|
||||
}}
|
||||
) ->
|
||||
PaymentToolSwag = capi_handler_decoder_party:decode_payment_tool(PaymentTool),
|
||||
#{
|
||||
<<"payerType">> => <<"CustomerPayer">>,
|
||||
<<"customerID">> => ID,
|
||||
<<"paymentToolToken">> => capi_handler_decoder_party:wrap_payment_tool_token(PaymentToolSwag),
|
||||
<<"paymentToolDetails">> => capi_handler_decoder_party:decode_payment_tool_details(PaymentTool)
|
||||
};
|
||||
decode_payer(
|
||||
@ -197,10 +195,8 @@ decode_payer(
|
||||
contact_info = ContactInfo
|
||||
}}
|
||||
) ->
|
||||
PaymentToolSwag = capi_handler_decoder_party:decode_payment_tool(PaymentTool),
|
||||
#{
|
||||
<<"payerType">> => <<"RecurrentPayer">>,
|
||||
<<"paymentToolToken">> => capi_handler_decoder_party:wrap_payment_tool_token(PaymentToolSwag),
|
||||
<<"paymentToolDetails">> => capi_handler_decoder_party:decode_payment_tool_details(PaymentTool),
|
||||
<<"contactInfo">> => capi_handler_decoder_party:decode_contact_info(ContactInfo),
|
||||
<<"recurrentParentPayment">> => decode_recurrent_parent(RecurrentParent)
|
||||
|
@ -19,8 +19,6 @@
|
||||
-export([decode_payment_tool/1]).
|
||||
-export([decode_payment_tool_details/1]).
|
||||
|
||||
-export([wrap_payment_tool_token/1]).
|
||||
|
||||
%%
|
||||
|
||||
-spec decode_shop_location(capi_handler_encoder:encode_data()) -> capi_handler_decoder_utils:decode_data().
|
||||
@ -177,74 +175,18 @@ decode_payment_tool({mobile_commerce, MobileCommerce}) ->
|
||||
decode_payment_tool({crypto_currency, CryptoCurrency}) ->
|
||||
decode_crypto_wallet(CryptoCurrency).
|
||||
|
||||
-spec wrap_payment_tool_token(capi_handler_decoder_utils:decode_data()) -> binary().
|
||||
wrap_payment_tool_token(#{<<"type">> := <<"bank_card">>} = BankCard) ->
|
||||
Fields = [
|
||||
<<"token">>,
|
||||
<<"payment_system">>,
|
||||
<<"bin">>,
|
||||
<<"masked_pan">>,
|
||||
<<"token_provider">>,
|
||||
<<"issuer_country">>,
|
||||
<<"bank_name">>,
|
||||
<<"metadata">>,
|
||||
<<"is_cvv_empty">>
|
||||
],
|
||||
BankCard1 = maps:with(Fields, BankCard),
|
||||
capi_utils:map_to_base64url(BankCard1);
|
||||
wrap_payment_tool_token(#{<<"type">> := <<"payment_terminal">>} = PaymentTerminal) ->
|
||||
capi_utils:map_to_base64url(PaymentTerminal);
|
||||
wrap_payment_tool_token(#{<<"type">> := <<"digital_wallet">>} = DigitalWallet) ->
|
||||
capi_utils:map_to_base64url(DigitalWallet);
|
||||
wrap_payment_tool_token(#{<<"type">> := <<"crypto_currency">>} = CryptoCurrency) ->
|
||||
capi_utils:map_to_base64url(CryptoCurrency);
|
||||
wrap_payment_tool_token(#{<<"type">> := <<"mobile_commerce">>} = MobileCommerce) ->
|
||||
capi_utils:map_to_base64url(MobileCommerce).
|
||||
|
||||
decode_bank_card(#domain_BankCard{
|
||||
'token' = Token,
|
||||
'payment_system' = PaymentSystem,
|
||||
'bin' = Bin,
|
||||
'last_digits' = LastDigits,
|
||||
'payment_token' = BankCardTokenServiceRef,
|
||||
'issuer_country' = IssuerCountry,
|
||||
'bank_name' = BankName,
|
||||
'metadata' = Metadata,
|
||||
'is_cvv_empty' = IsCVVEmpty,
|
||||
'exp_date' = ExpDate,
|
||||
'cardholder_name' = CardHolder
|
||||
% 'tokenization_method' = TokenizationMethod
|
||||
'exp_date' = ExpDate
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
<<"type">> => <<"bank_card">>,
|
||||
<<"token">> => Token,
|
||||
<<"payment_system">> => capi_handler_decoder_utils:decode_payment_system_ref(PaymentSystem),
|
||||
<<"bin">> => Bin,
|
||||
<<"masked_pan">> => LastDigits,
|
||||
<<"token_provider">> => capi_utils:maybe(
|
||||
BankCardTokenServiceRef,
|
||||
fun capi_handler_decoder_utils:decode_bank_card_token_service_ref/1
|
||||
),
|
||||
<<"issuer_country">> => IssuerCountry,
|
||||
<<"bank_name">> => BankName,
|
||||
<<"metadata">> => decode_bank_card_metadata(Metadata),
|
||||
<<"is_cvv_empty">> => decode_bank_card_cvv_flag(IsCVVEmpty),
|
||||
<<"exp_date">> => ExpDate,
|
||||
<<"cardholder_name">> => CardHolder
|
||||
% TODO: Uncomment or delete this when we negotiate deploying non-breaking changes
|
||||
% <<"tokenization_method">> => TokenizationMethod
|
||||
<<"exp_date">> => ExpDate
|
||||
}).
|
||||
|
||||
decode_bank_card_cvv_flag(undefined) ->
|
||||
undefined;
|
||||
decode_bank_card_cvv_flag(CVVFlag) when is_atom(CVVFlag) ->
|
||||
erlang:atom_to_binary(CVVFlag, utf8).
|
||||
|
||||
decode_bank_card_metadata(undefined) ->
|
||||
undefined;
|
||||
decode_bank_card_metadata(Meta) ->
|
||||
maps:map(fun(_, Data) -> capi_msgp_marshalling:unmarshal(Data) end, Meta).
|
||||
|
||||
decode_payment_terminal(#domain_PaymentTerminal{payment_service = PaymentService}) ->
|
||||
#{
|
||||
<<"type">> => <<"payment_terminal">>,
|
||||
@ -345,15 +287,13 @@ decode_digital_wallet_details(#domain_DigitalWallet{payment_service = Provider},
|
||||
|
||||
-spec decode_disposable_payment_resource(capi_handler_encoder:encode_data()) ->
|
||||
capi_handler_decoder_utils:decode_data().
|
||||
decode_disposable_payment_resource(Resource) ->
|
||||
#domain_DisposablePaymentResource{payment_tool = PaymentTool, payment_session_id = SessionID} = Resource,
|
||||
ClientInfo = decode_client_info(Resource#domain_DisposablePaymentResource.client_info),
|
||||
PaymentToolSwag = decode_payment_tool(PaymentTool),
|
||||
decode_disposable_payment_resource(#domain_DisposablePaymentResource{
|
||||
payment_tool = PaymentTool,
|
||||
client_info = ClientInfo
|
||||
}) ->
|
||||
#{
|
||||
<<"paymentToolToken">> => wrap_payment_tool_token(PaymentToolSwag),
|
||||
<<"paymentSession">> => capi_handler_utils:wrap_payment_session(ClientInfo, SessionID),
|
||||
<<"paymentToolDetails">> => decode_payment_tool_details(PaymentTool),
|
||||
<<"clientInfo">> => ClientInfo
|
||||
<<"clientInfo">> => decode_client_info(ClientInfo)
|
||||
}.
|
||||
|
||||
decode_client_info(undefined) ->
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
-export([prepare/3]).
|
||||
|
||||
-import(capi_handler_utils, [general_error/2, logic_error/2, map_service_result/1]).
|
||||
-import(capi_handler_utils, [general_error/2, logic_error/2, conflict_error/1, map_service_result/1]).
|
||||
|
||||
-spec prepare(
|
||||
OperationID :: capi_handler:operation_id(),
|
||||
@ -53,7 +53,7 @@ prepare('CreateInvoiceTemplate' = OperationID, Req, Context) ->
|
||||
throw:zero_invoice_lifetime ->
|
||||
{ok, logic_error('invalidRequest', <<"Lifetime cannot be zero">>)};
|
||||
throw:{external_id_conflict, ID, UsedExternalID, _Schema} ->
|
||||
{ok, logic_error('externalIDConflict', {ID, UsedExternalID})}
|
||||
{ok, conflict_error({ID, UsedExternalID})}
|
||||
end
|
||||
end,
|
||||
{ok, #{authorize => Authorize, process => Process}};
|
||||
@ -185,7 +185,7 @@ prepare('CreateInvoiceWithTemplate' = OperationID, Req, Context) ->
|
||||
throw:{bad_invoice_params, amount_no_currency} ->
|
||||
{ok, logic_error('invalidRequest', <<"Currency is required for the amount">>)};
|
||||
throw:{external_id_conflict, InvoiceID, ExternalID, _Schema} ->
|
||||
{ok, logic_error('externalIDConflict', {InvoiceID, ExternalID})}
|
||||
{ok, conflict_error({InvoiceID, ExternalID})}
|
||||
end
|
||||
end,
|
||||
{ok, #{authorize => Authorize, process => Process}};
|
||||
@ -245,8 +245,8 @@ create_invoice(PartyID, InvoiceTplID, InvoiceParams, Context, BenderPrefix) ->
|
||||
ExternalID = maps:get(<<"externalID">>, InvoiceParams, undefined),
|
||||
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
|
||||
InvoiceParamsWithTemplate = maps:put(<<"invoiceTemplateID">>, InvoiceTplID, InvoiceParams),
|
||||
Identity = capi_bender:make_identity(invoice, InvoiceParamsWithTemplate),
|
||||
InvoiceID = capi_bender:try_gen_snowflake(IdempotentKey, Identity, WoodyCtx),
|
||||
Identity = capi_bender:make_identity(capi_feature_schemas:invoice(), InvoiceParamsWithTemplate),
|
||||
InvoiceID = capi_bender:gen_snowflake(IdempotentKey, Identity, WoodyCtx),
|
||||
CallArgs = {encode_invoice_params_with_tpl(InvoiceID, InvoiceTplID, InvoiceParams)},
|
||||
Call = {invoicing, 'CreateWithTemplate', CallArgs},
|
||||
capi_handler_utils:service_call_with([user_info], Call, Context).
|
||||
@ -258,8 +258,8 @@ 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 = capi_bender:make_identity(invoice_template, TemplateParams),
|
||||
capi_bender:try_gen_snowflake(IdempKey, Identity, WoodyContext).
|
||||
Identity = capi_bender:make_identity(capi_feature_schemas:invoice_template(), TemplateParams),
|
||||
capi_bender:gen_snowflake(IdempKey, Identity, WoodyContext).
|
||||
|
||||
encode_invoice_tpl_create_params(InvoiceTemplateID, PartyID, Params) ->
|
||||
Details = encode_invoice_tpl_details(genlib_map:get(<<"details">>, Params)),
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
-export([prepare/3]).
|
||||
|
||||
-import(capi_handler_utils, [general_error/2, logic_error/2, map_service_result/1]).
|
||||
-import(capi_handler_utils, [general_error/2, logic_error/2, conflict_error/1, map_service_result/1]).
|
||||
|
||||
-spec prepare(
|
||||
OperationID :: capi_handler:operation_id(),
|
||||
@ -61,7 +61,7 @@ prepare('CreateInvoice' = OperationID, Req, Context) ->
|
||||
throw:invalid_invoice_cost ->
|
||||
{ok, logic_error('invalidInvoiceCost', <<"Invalid invoice amount">>)};
|
||||
throw:{external_id_conflict, InvoiceID, ExternalID, _Schema} ->
|
||||
{ok, logic_error('externalIDConflict', {InvoiceID, ExternalID})};
|
||||
{ok, conflict_error({InvoiceID, ExternalID})};
|
||||
throw:allocation_wrong_cart ->
|
||||
{ok, logic_error('invalidAllocation', <<"Wrong cart">>)};
|
||||
throw:allocation_duplicate ->
|
||||
@ -274,8 +274,8 @@ create_invoice(PartyID, InvoiceParams, Context, BenderPrefix) ->
|
||||
#{woody_context := WoodyCtx} = Context,
|
||||
ExternalID = maps:get(<<"externalID">>, InvoiceParams, undefined),
|
||||
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
|
||||
Identity = capi_bender:make_identity(invoice, InvoiceParams),
|
||||
InvoiceID = capi_bender:try_gen_snowflake(IdempotentKey, Identity, WoodyCtx),
|
||||
Identity = capi_bender:make_identity(capi_feature_schemas:invoice(), InvoiceParams),
|
||||
InvoiceID = capi_bender:gen_snowflake(IdempotentKey, Identity, WoodyCtx),
|
||||
Call = {invoicing, 'Create', {encode_invoice_params(InvoiceID, PartyID, InvoiceParams)}},
|
||||
capi_handler_utils:service_call_with([user_info], Call, Context).
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
-export([prepare/3]).
|
||||
|
||||
-import(capi_handler_utils, [general_error/2, logic_error/1, logic_error/2]).
|
||||
-import(capi_handler_utils, [general_error/2, logic_error/1, logic_error/2, conflict_error/1]).
|
||||
|
||||
-define(DEFAULT_PROCESSING_DEADLINE, <<"30m">>).
|
||||
|
||||
@ -60,7 +60,7 @@ prepare(OperationID = 'CreatePayment', Req, Context) ->
|
||||
throw:invalid_processing_deadline ->
|
||||
{ok, logic_error('invalidProcessingDeadline', <<"Specified processing deadline is invalid">>)};
|
||||
throw:{external_id_conflict, PaymentID, ExternalID, _Schema} ->
|
||||
{ok, logic_error('externalIDConflict', {PaymentID, ExternalID})}
|
||||
{ok, conflict_error({PaymentID, ExternalID})}
|
||||
end
|
||||
end,
|
||||
{ok, #{authorize => Authorize, process => Process}};
|
||||
@ -307,7 +307,7 @@ prepare(OperationID = 'CreateRefund', Req, Context) ->
|
||||
throw:invoice_cart_empty ->
|
||||
{ok, logic_error('invalidInvoiceCart', <<"Wrong size. Path to item: cart">>)};
|
||||
throw:{external_id_conflict, RefundID, ExternalID, _Schema} ->
|
||||
{ok, logic_error('externalIDConflict', {RefundID, ExternalID})};
|
||||
{ok, conflict_error({RefundID, ExternalID})};
|
||||
throw:allocation_duplicate ->
|
||||
{ok, logic_error('invalidAllocation', <<"Duplicate shop">>)};
|
||||
throw:allocation_wrong_cart ->
|
||||
@ -500,12 +500,12 @@ create_payment_id(Invoice, PaymentParams0, Context, OperationID, PaymentToolThri
|
||||
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
|
||||
|
||||
SequenceID = InvoiceID,
|
||||
Identity = capi_bender:make_identity(payment, PaymentParams),
|
||||
Identity = capi_bender:make_identity(capi_feature_schemas: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
|
||||
CtxData = #{<<"invoice_id">> => InvoiceID},
|
||||
capi_bender:try_gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyCtx, CtxData).
|
||||
capi_bender:gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyCtx, CtxData).
|
||||
|
||||
find_payment_by_id(PaymentID, #payproc_Invoice{payments = Payments}) ->
|
||||
Fun = fun(#payproc_InvoicePayment{payment = #domain_InvoicePayment{id = ID}}) ->
|
||||
@ -706,13 +706,13 @@ create_refund(InvoiceID, PaymentID, RefundParams0, Context, BenderPrefix) ->
|
||||
|
||||
ExternalID = maps:get(<<"externalID">>, RefundParams, undefined),
|
||||
IdempotentKey = {BenderPrefix, PartyID, ExternalID},
|
||||
Identity = capi_bender:make_identity(refund, RefundParams),
|
||||
Identity = capi_bender:make_identity(capi_feature_schemas:refund(), RefundParams),
|
||||
SequenceID = create_sequence_id([InvoiceID, PaymentID], BenderPrefix),
|
||||
SequenceParams = #{minimum => 100},
|
||||
#{woody_context := WoodyCtx} = Context,
|
||||
%% We put `invoice_id` and `payment_id` in a context here because `get_refund_by_external_id/2` needs it to work
|
||||
CtxData = #{<<"invoice_id">> => InvoiceID, <<"payment_id">> => PaymentID},
|
||||
RefundID = capi_bender:try_gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyCtx, CtxData),
|
||||
RefundID = capi_bender:gen_sequence(IdempotentKey, Identity, SequenceID, SequenceParams, WoodyCtx, CtxData),
|
||||
refund_payment(RefundID, InvoiceID, PaymentID, RefundParams, Context).
|
||||
|
||||
refund_payment(RefundID, InvoiceID, PaymentID, RefundParams, Context) ->
|
||||
|
@ -222,7 +222,6 @@ decode_stat_payer(
|
||||
) ->
|
||||
#{
|
||||
<<"payerType">> => <<"CustomerPayer">>,
|
||||
<<"paymentToolToken">> => decode_stat_payment_tool_token(PaymentTool),
|
||||
<<"paymentToolDetails">> => decode_stat_payment_tool_details(PaymentTool),
|
||||
<<"customerID">> => ID
|
||||
};
|
||||
@ -236,7 +235,6 @@ decode_stat_payer(
|
||||
) ->
|
||||
#{
|
||||
<<"payerType">> => <<"RecurrentPayer">>,
|
||||
<<"paymentToolToken">> => decode_stat_payment_tool_token(PaymentTool),
|
||||
<<"paymentToolDetails">> => decode_stat_payment_tool_details(PaymentTool),
|
||||
<<"contactInfo">> => genlib_map:compact(#{
|
||||
<<"phoneNumber">> => PhoneNumber,
|
||||
@ -256,7 +254,6 @@ decode_stat_payer(
|
||||
) ->
|
||||
genlib_map:compact(#{
|
||||
<<"payerType">> => <<"PaymentResourcePayer">>,
|
||||
<<"paymentToolToken">> => decode_stat_payment_tool_token(PaymentTool),
|
||||
<<"paymentToolDetails">> => decode_stat_payment_tool_details(PaymentTool),
|
||||
<<"paymentSession">> => PaymentSession,
|
||||
<<"clientInfo">> => genlib_map:compact(#{
|
||||
@ -296,80 +293,6 @@ decode_stat_payment_status({Status, StatusInfo}, Context) ->
|
||||
<<"error">> => Error
|
||||
}.
|
||||
|
||||
decode_stat_payment_tool_token({bank_card, BankCard}) ->
|
||||
decode_bank_card(BankCard);
|
||||
decode_stat_payment_tool_token({payment_terminal, PaymentTerminal}) ->
|
||||
decode_payment_terminal(PaymentTerminal);
|
||||
decode_stat_payment_tool_token({digital_wallet, DigitalWallet}) ->
|
||||
decode_digital_wallet(DigitalWallet);
|
||||
decode_stat_payment_tool_token({crypto_currency, CryptoCurrency}) ->
|
||||
decode_crypto_wallet(CryptoCurrency);
|
||||
decode_stat_payment_tool_token({mobile_commerce, MobileCommerce}) ->
|
||||
decode_mobile_commerce(MobileCommerce).
|
||||
|
||||
decode_bank_card(#merchstat_BankCard{
|
||||
'token' = Token,
|
||||
'payment_system' = PaymentSystem,
|
||||
'bin' = Bin,
|
||||
'masked_pan' = MaskedPan,
|
||||
'payment_token' = BankCardTokenServiceRef
|
||||
}) ->
|
||||
capi_utils:map_to_base64url(
|
||||
genlib_map:compact(#{
|
||||
<<"type">> => <<"bank_card">>,
|
||||
<<"token">> => Token,
|
||||
<<"payment_system">> => capi_handler_decoder_utils:decode_payment_system_ref(PaymentSystem),
|
||||
<<"bin">> => Bin,
|
||||
<<"masked_pan">> => MaskedPan,
|
||||
<<"token_provider">> => capi_utils:maybe(
|
||||
BankCardTokenServiceRef,
|
||||
fun capi_handler_decoder_utils:decode_bank_card_token_service_ref/1
|
||||
),
|
||||
<<"issuer_country">> => undefined,
|
||||
<<"bank_name">> => undefined,
|
||||
<<"metadata">> => undefined
|
||||
})
|
||||
).
|
||||
|
||||
decode_payment_terminal(#merchstat_PaymentTerminal{
|
||||
terminal_type = Type
|
||||
}) ->
|
||||
capi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"payment_terminal">>,
|
||||
<<"terminal_type">> => Type
|
||||
}).
|
||||
|
||||
decode_digital_wallet(#merchstat_DigitalWallet{
|
||||
provider = Provider,
|
||||
id = ID
|
||||
}) ->
|
||||
capi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"digital_wallet">>,
|
||||
<<"provider">> => atom_to_binary(Provider, utf8),
|
||||
<<"id">> => ID
|
||||
}).
|
||||
|
||||
decode_crypto_wallet(CryptoCurrency) ->
|
||||
capi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"crypto_wallet">>,
|
||||
<<"crypto_currency">> => capi_handler_decoder_utils:convert_crypto_currency_to_swag(CryptoCurrency)
|
||||
}).
|
||||
|
||||
decode_mobile_commerce(MobileCommerce) ->
|
||||
#merchstat_MobileCommerce{
|
||||
operator = Operator,
|
||||
phone = #merchstat_MobilePhone{
|
||||
cc = Cc,
|
||||
ctn = Ctn
|
||||
}
|
||||
} = MobileCommerce,
|
||||
Phone = #{<<"cc">> => Cc, <<"ctn">> => Ctn},
|
||||
capi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"mobile_commerce">>,
|
||||
<<"phone">> => Phone,
|
||||
<<"operator">> => atom_to_binary(Operator, utf8)
|
||||
}).
|
||||
|
||||
decode_stat_payment_tool_details({bank_card, V}) ->
|
||||
decode_bank_card_details(V, #{<<"detailsType">> => <<"PaymentToolDetailsBankCard">>});
|
||||
decode_stat_payment_tool_details({payment_terminal, V}) ->
|
||||
|
@ -3,6 +3,7 @@
|
||||
-include_lib("damsel/include/dmsl_payment_processing_thrift.hrl").
|
||||
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
|
||||
|
||||
-export([conflict_error/1]).
|
||||
-export([general_error/2]).
|
||||
-export([logic_error/1]).
|
||||
-export([logic_error/2]).
|
||||
@ -49,6 +50,21 @@
|
||||
| dmsl_domain_thrift:'InvoiceTemplate'().
|
||||
-type token_source() :: capi_auth:token_spec() | entity().
|
||||
|
||||
-spec conflict_error(binary() | {binary(), binary()}) -> response().
|
||||
conflict_error({ID, ExternalID}) ->
|
||||
Data = #{
|
||||
<<"externalID">> => ExternalID,
|
||||
<<"id">> => ID,
|
||||
<<"message">> => <<"This 'externalID' has been used by another request">>
|
||||
},
|
||||
create_error_resp(409, Data);
|
||||
conflict_error(ExternalID) ->
|
||||
Data = #{
|
||||
<<"externalID">> => ExternalID,
|
||||
<<"message">> => <<"This 'externalID' has been used by another request">>
|
||||
},
|
||||
create_error_resp(409, Data).
|
||||
|
||||
-spec general_error(cowboy:http_status(), binary()) -> response().
|
||||
general_error(Code, Message) ->
|
||||
create_error_resp(Code, #{<<"message">> => genlib:to_binary(Message)}).
|
||||
@ -57,24 +73,7 @@ general_error(Code, Message) ->
|
||||
logic_error('invalidPaymentToolToken') ->
|
||||
logic_error('invalidPaymentToolToken', <<"Specified payment tool token is invalid">>).
|
||||
|
||||
-spec logic_error
|
||||
(term(), io_lib:chars() | binary()) -> response();
|
||||
(term(), {binary(), binary() | undefined}) -> response().
|
||||
logic_error('externalIDConflict', {ID, undefined}) ->
|
||||
logic_error('externalIDConflict', {ID, <<"undefined">>});
|
||||
logic_error('externalIDConflict', {ID, ExternalID}) ->
|
||||
Data = #{
|
||||
<<"externalID">> => ExternalID,
|
||||
<<"id">> => ID,
|
||||
<<"message">> => <<"This 'externalID' has been used by another request">>
|
||||
},
|
||||
create_error_resp(409, Data);
|
||||
logic_error('externalIDConflict', ExternalID) ->
|
||||
Data = #{
|
||||
<<"externalID">> => ExternalID,
|
||||
<<"message">> => <<"This 'externalID' has been used by another request">>
|
||||
},
|
||||
create_error_resp(409, Data);
|
||||
-spec logic_error(term(), iodata()) -> response().
|
||||
logic_error(Code, Message) ->
|
||||
Data = #{<<"code">> => genlib:to_binary(Code), <<"message">> => genlib:to_binary(Message)},
|
||||
create_error_resp(400, Data).
|
||||
|
@ -1,247 +0,0 @@
|
||||
-module(capi_idemp_features_legacy).
|
||||
|
||||
-include("capi_feature_schemas_legacy.hrl").
|
||||
|
||||
-type request_key() :: binary().
|
||||
-type request_value() :: integer() | binary() | request() | [request()].
|
||||
-type request() :: #{request_key() := request_value()}.
|
||||
|
||||
-type feature_name() :: integer().
|
||||
-type feature_value() :: integer() | features() | [feature_value()] | undefined.
|
||||
-type features() :: #{feature_name() := feature_value()}.
|
||||
-type schema() ::
|
||||
#{
|
||||
feature_name() := [request_key() | schema() | {set, schema()}]
|
||||
}
|
||||
| #{
|
||||
?discriminator := [request_key()],
|
||||
feature_name() := schema()
|
||||
}.
|
||||
|
||||
-type difference() :: features().
|
||||
|
||||
-type event() ::
|
||||
{invalid_schema_fragment, feature_name(), request()}
|
||||
| {request_visited, {request, request()}}
|
||||
| {request_key_index_visit, integer()}
|
||||
| {request_key_index_visited, integer()}
|
||||
| {request_key_visit, {key, integer(), request()}}
|
||||
| {request_key_visited, {key, integer()}}.
|
||||
|
||||
-type event_handler() :: {module(), options()} | undefined.
|
||||
-type options() :: term().
|
||||
|
||||
-export_type([event_handler/0]).
|
||||
-export_type([event/0]).
|
||||
-export_type([schema/0]).
|
||||
-export_type([request/0]).
|
||||
-export_type([difference/0]).
|
||||
-export_type([features/0]).
|
||||
-export_type([feature_name/0]).
|
||||
-export_type([feature_value/0]).
|
||||
-export_type([options/0]).
|
||||
|
||||
-export([read/2, read/3]).
|
||||
-export([compare/2]).
|
||||
-export([hash/1]).
|
||||
-export([list_diff_fields/2]).
|
||||
|
||||
-callback handle_event(event(), options()) -> ok.
|
||||
|
||||
-spec read(schema(), request()) -> features().
|
||||
read(Schema, Request) ->
|
||||
read(get_event_handler(), Schema, Request).
|
||||
|
||||
-spec read(event_handler(), schema(), request()) -> features().
|
||||
read(Handler, Schema, Request) ->
|
||||
handle_event(get_event_handler(Handler), {request_visited, {request, Request}}),
|
||||
read_(Schema, Request, Handler).
|
||||
|
||||
read_(Schema, Request, Handler) ->
|
||||
Result = maps:fold(
|
||||
fun
|
||||
(Name, Fs, Acc) when is_map(Fs) ->
|
||||
Value = read_(Fs, Request, Handler),
|
||||
Acc#{Name => Value};
|
||||
(Name, Accessor, Acc) when is_list(Accessor) ->
|
||||
FeatureValue = read_request_value(Accessor, Request, Handler),
|
||||
Acc#{Name => FeatureValue};
|
||||
(_Name, 'reserved', Acc) ->
|
||||
Acc
|
||||
end,
|
||||
#{},
|
||||
Schema
|
||||
),
|
||||
Result.
|
||||
|
||||
read_request_value([], undefined, _) ->
|
||||
undefined;
|
||||
read_request_value([], Value, _) ->
|
||||
hash(Value);
|
||||
read_request_value([Schema = #{}], Request = #{}, Handler) ->
|
||||
read_(Schema, Request, Handler);
|
||||
read_request_value([{set, Schema = #{}}], List, Handler) when is_list(List) ->
|
||||
{_, ListIndex} = lists:foldl(fun(Item, {N, Acc}) -> {N + 1, [{N, Item} | Acc]} end, {0, []}, List),
|
||||
ListSorted = lists:keysort(2, ListIndex),
|
||||
lists:foldl(
|
||||
fun({Index, Req}, Acc) ->
|
||||
handle_event(get_event_handler(Handler), {request_key_index_visit, Index}),
|
||||
Value = read_(Schema, Req, Handler),
|
||||
handle_event(get_event_handler(Handler), {request_key_index_visited, Index}),
|
||||
[[Index, Value] | Acc]
|
||||
end,
|
||||
[],
|
||||
ListSorted
|
||||
);
|
||||
read_request_value([Key | Rest], Request = #{}, Handler) when is_binary(Key) ->
|
||||
SubRequest = maps:get(Key, Request, undefined),
|
||||
handle_event(get_event_handler(Handler), {request_key_visit, {key, Key, SubRequest}}),
|
||||
Value = read_request_value(Rest, SubRequest, Handler),
|
||||
handle_event(get_event_handler(Handler), {request_key_visited, {key, Key}}),
|
||||
Value;
|
||||
read_request_value(_, undefined, _) ->
|
||||
undefined;
|
||||
read_request_value(Key, Request, Handler) ->
|
||||
handle_event(get_event_handler(Handler), {invalid_schema_fragment, Key, Request}).
|
||||
|
||||
handle_event(undefined, {invalid_schema_fragment, Key, Request}) ->
|
||||
logger:warning("Unable to extract idemp feature with schema: ~p from client request subset: ~p", [Key, Request]),
|
||||
undefined;
|
||||
handle_event(undefined, _Event) ->
|
||||
ok;
|
||||
handle_event({Mod, Opts}, Event) ->
|
||||
Mod:handle_event(Event, Opts).
|
||||
|
||||
get_event_handler() ->
|
||||
genlib_app:env(capi, idempotence_event_handler).
|
||||
|
||||
get_event_handler({Mod, Options}) ->
|
||||
{Mod, Options};
|
||||
get_event_handler(undefined) ->
|
||||
undefined.
|
||||
|
||||
-spec hash(term()) -> integer().
|
||||
hash(V) ->
|
||||
erlang:phash2(V).
|
||||
|
||||
-spec list_diff_fields(schema(), difference()) -> [binary()].
|
||||
list_diff_fields(Schema, Diff) ->
|
||||
{ConvertedDiff, _} = list_diff_fields_(Diff, Schema, {[], []}),
|
||||
lists:foldl(
|
||||
fun(Keys, AccIn) ->
|
||||
KeysBin = lists:map(fun genlib:to_binary/1, Keys),
|
||||
Item = list_to_binary(lists:join(<<".">>, KeysBin)),
|
||||
case lists:member(Item, AccIn) of
|
||||
false ->
|
||||
[Item | AccIn];
|
||||
_ ->
|
||||
AccIn
|
||||
end
|
||||
end,
|
||||
[],
|
||||
ConvertedDiff
|
||||
).
|
||||
|
||||
list_diff_fields_(Diffs, {set, Schema}, Acc) when is_map(Schema) ->
|
||||
maps:fold(
|
||||
fun(I, Diff, {PathsAcc, PathRev}) ->
|
||||
list_diff_fields_(Diff, Schema, {PathsAcc, [I | PathRev]})
|
||||
end,
|
||||
Acc,
|
||||
Diffs
|
||||
);
|
||||
list_diff_fields_(Diff, Schema, Acc) when is_map(Schema) ->
|
||||
zipfold(
|
||||
fun
|
||||
(_Feature, ?difference, [Key | _SchemaPart], {PathsAcc, PathRev}) ->
|
||||
Path = lists:reverse([Key | PathRev]),
|
||||
{[Path | PathsAcc], PathRev};
|
||||
(_Feature, DiffPart, SchemaPart, {_PathsAcc, PathRev} = AccIn) ->
|
||||
{NewPathsAcc, _NewPathRev} = list_diff_fields_(DiffPart, SchemaPart, AccIn),
|
||||
{NewPathsAcc, PathRev}
|
||||
end,
|
||||
Acc,
|
||||
Diff,
|
||||
Schema
|
||||
);
|
||||
list_diff_fields_(Diff, [Schema], Acc) ->
|
||||
list_diff_fields_(Diff, Schema, Acc);
|
||||
list_diff_fields_(Diff, [Key | Schema], {PathsAcc, PathRev}) ->
|
||||
list_diff_fields_(Diff, Schema, {PathsAcc, [Key | PathRev]}).
|
||||
|
||||
-spec compare(features(), features()) -> true | {false, difference()}.
|
||||
compare(Features, FeaturesWith) ->
|
||||
case compare_features(Features, FeaturesWith) of
|
||||
Diff when map_size(Diff) > 0 ->
|
||||
{false, Diff};
|
||||
_ ->
|
||||
true
|
||||
end.
|
||||
|
||||
compare_features(Fs, FsWith) ->
|
||||
zipfold(
|
||||
fun
|
||||
(Key, Values, ValuesWith, Diff) when is_list(ValuesWith), is_list(Values) ->
|
||||
compare_list_features(Key, Values, ValuesWith, Diff);
|
||||
(Key, Value, ValueWith, Diff) when is_map(ValueWith) and is_map(Value) ->
|
||||
compare_features_(Key, Value, ValueWith, Diff);
|
||||
%% We expect that clients may _at any time_ change their implementation and start
|
||||
%% sending information they were not sending beforehand, so this is not considered a
|
||||
%% conflict. Yet, we DO NOT expect them to do the opposite, to stop sending
|
||||
%% information they were sending, this is still a conflict.
|
||||
(_Key, _Value, undefined, Diff) ->
|
||||
Diff;
|
||||
(_Key, Value, Value, Diff) ->
|
||||
Diff;
|
||||
(Key, Value, ValueWith, Diff) when Value =/= ValueWith ->
|
||||
Diff#{Key => ?difference}
|
||||
end,
|
||||
#{},
|
||||
Fs,
|
||||
FsWith
|
||||
).
|
||||
|
||||
compare_list_features(Key, L1, L2, Diff) when length(L1) =/= length(L2) ->
|
||||
Diff#{Key => ?difference};
|
||||
compare_list_features(Key, L1, L2, Acc) ->
|
||||
case compare_list_features_(L1, L2, #{}) of
|
||||
Diff when map_size(Diff) > 0 ->
|
||||
Acc#{Key => Diff};
|
||||
#{} ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
compare_list_features_([], [], Diff) ->
|
||||
Diff;
|
||||
compare_list_features_([[Index, V1] | Values], [[_, V2] | ValuesWith], Acc) ->
|
||||
Diff = compare_features_(Index, V1, V2, Acc),
|
||||
compare_list_features_(Values, ValuesWith, Diff).
|
||||
|
||||
compare_features_(Key, Value, ValueWith, Diff) when is_map(Value) and is_map(ValueWith) ->
|
||||
case compare_features(Value, ValueWith) of
|
||||
ValueWith ->
|
||||
% different everywhere
|
||||
Diff#{Key => ?difference};
|
||||
#{?discriminator := _} ->
|
||||
% Different with regard to discriminator, semantically same as different everywhere.
|
||||
Diff#{Key => ?difference};
|
||||
Diff1 when map_size(Diff1) > 0 ->
|
||||
Diff#{Key => Diff1};
|
||||
#{} ->
|
||||
% no notable differences
|
||||
Diff
|
||||
end.
|
||||
|
||||
zipfold(Fun, Acc, M1, M2) ->
|
||||
maps:fold(
|
||||
fun(Key, V1, AccIn) ->
|
||||
case maps:find(Key, M2) of
|
||||
{ok, V2} ->
|
||||
Fun(Key, V1, V2, AccIn);
|
||||
error ->
|
||||
AccIn
|
||||
end
|
||||
end,
|
||||
Acc,
|
||||
M1
|
||||
).
|
@ -52,3 +52,53 @@ unmarshal({obj, Object}) ->
|
||||
maps:fold(fun(K, V, Acc) -> maps:put(unmarshal(K), unmarshal(V), Acc) end, #{}, Object);
|
||||
unmarshal({arr, Array}) ->
|
||||
lists:map(fun unmarshal/1, Array).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-spec test() -> _.
|
||||
|
||||
-define(INSTANCES, [
|
||||
{undefined, {nl, #msgpack_Nil{}}},
|
||||
{42, {i, 42}},
|
||||
{false, {b, false}},
|
||||
{
|
||||
#{
|
||||
3.1415 => 1.337,
|
||||
<<"there">> => [<<"be">>, {bin, <<"🐲"/utf8>>}, <<"dragons">>],
|
||||
false => #{<<"is">> => true}
|
||||
},
|
||||
{obj, #{
|
||||
{flt, 3.1415} => {flt, 1.337},
|
||||
{str, <<"there">>} => {arr, [{str, <<"be">>}, {bin, <<"🐲"/utf8>>}, {str, <<"dragons">>}]},
|
||||
{b, false} => {obj, #{{str, <<"is">>} => {b, true}}}
|
||||
}}
|
||||
}
|
||||
]).
|
||||
|
||||
-spec marshalling_test_() -> _.
|
||||
marshalling_test_() ->
|
||||
[?_assertEqual(marshal(Instance), Marshalled) || {Instance, Marshalled} <- ?INSTANCES].
|
||||
|
||||
-spec unmarshalling_test_() -> _.
|
||||
unmarshalling_test_() ->
|
||||
[?_assertEqual(Instance, unmarshal(Marshalled)) || {Instance, Marshalled} <- ?INSTANCES].
|
||||
|
||||
-spec symmetric_marshalling_test_() -> _.
|
||||
symmetric_marshalling_test_() ->
|
||||
[?_assertEqual(Instance, unmarshal(marshal(Instance))) || {Instance, _} <- ?INSTANCES].
|
||||
|
||||
-spec symmetric_unmarshalling_test_() -> _.
|
||||
symmetric_unmarshalling_test_() ->
|
||||
[?_assertEqual(Marshalled, marshal(unmarshal(Marshalled))) || {_, Marshalled} <- ?INSTANCES].
|
||||
|
||||
-spec thrift_serialize_test_() -> _.
|
||||
thrift_serialize_test_() ->
|
||||
[?_test(serialize_thrift(marshal(Instance))) || {Instance, _} <- ?INSTANCES].
|
||||
|
||||
serialize_thrift(Term) ->
|
||||
C1 = thrift_strict_binary_codec:new(),
|
||||
{ok, C2} = thrift_strict_binary_codec:write(C1, {struct, union, {dmsl_msgpack_thrift, 'Value'}}, Term),
|
||||
thrift_strict_binary_codec:close(C2).
|
||||
|
||||
-endif.
|
||||
|
@ -2075,7 +2075,7 @@ get_payment_revenue_stats_ok_test(Config) ->
|
||||
{offset, 2},
|
||||
{from_time, {{2015, 08, 11}, {19, 42, 36}}},
|
||||
{to_time, {{2020, 08, 11}, {19, 42, 36}}},
|
||||
{split_unit, minute},
|
||||
{split_unit, hour},
|
||||
{split_size, 1}
|
||||
],
|
||||
{ok, _} = capi_client_analytics:get_payment_revenue_stats(?config(context, Config), ?STRING, Query).
|
||||
@ -2094,7 +2094,7 @@ get_payment_geo_stats_ok_test(Config) ->
|
||||
{offset, 0},
|
||||
{from_time, {{2015, 08, 11}, {19, 42, 37}}},
|
||||
{to_time, {{2020, 08, 11}, {19, 42, 37}}},
|
||||
{split_unit, minute},
|
||||
{split_unit, day},
|
||||
{split_size, 1}
|
||||
],
|
||||
{ok, _} = capi_client_analytics:get_payment_geo_stats(?config(context, Config), ?STRING, Query).
|
||||
@ -2113,7 +2113,7 @@ get_payment_rate_stats_ok_test(Config) ->
|
||||
{offset, 0},
|
||||
{from_time, {{2015, 08, 11}, {19, 42, 38}}},
|
||||
{to_time, {{2020, 08, 11}, {19, 42, 38}}},
|
||||
{split_unit, minute},
|
||||
{split_unit, week},
|
||||
{split_size, 1}
|
||||
],
|
||||
{ok, _} = capi_client_analytics:get_payment_rate_stats(?config(context, Config), ?STRING, Query).
|
||||
@ -2132,7 +2132,7 @@ get_payment_method_stats_ok_test(Config) ->
|
||||
{offset, 0},
|
||||
{from_time, {{2015, 08, 11}, {19, 42, 35}}},
|
||||
{to_time, {{2020, 08, 11}, {19, 42, 35}}},
|
||||
{split_unit, minute},
|
||||
{split_unit, month},
|
||||
{split_size, 1},
|
||||
{'paymentMethod', <<"bankCard">>}
|
||||
],
|
||||
|
@ -16,6 +16,7 @@
|
||||
-define(TEST_USER_REALM, <<"external">>).
|
||||
-define(TEST_RULESET_ID, <<"test/api">>).
|
||||
-define(API_TOKEN, <<"letmein">>).
|
||||
-define(EMAIL, <<"test@test.ru">>).
|
||||
|
||||
-define(RATIONAL, #'Rational'{p = ?INTEGER, q = ?INTEGER}).
|
||||
|
||||
@ -198,6 +199,7 @@
|
||||
last_digits = <<"411111******1111">>
|
||||
}).
|
||||
|
||||
-define(BANK_CARD(PS, ExpDate), ?BANK_CARD(PS, ExpDate, <<"CARD HODLER">>)).
|
||||
-define(BANK_CARD(PS, ExpDate, CardHolder), ?BANK_CARD(PS, ExpDate, CardHolder, undefined)).
|
||||
-define(BANK_CARD(PS, ExpDate, CardHolder, Category), #domain_BankCard{
|
||||
token = PS,
|
||||
@ -215,9 +217,19 @@
|
||||
token = Token
|
||||
}).
|
||||
|
||||
-define(MOBILE_COMMERCE(Operator, CC, CTN), #domain_MobileCommerce{
|
||||
operator = #domain_MobileOperatorRef{id = Operator},
|
||||
phone = #domain_MobilePhone{
|
||||
cc = CC,
|
||||
ctn = CTN
|
||||
}
|
||||
}).
|
||||
|
||||
-define(CRYPTO_CURRENCY_BTC, #domain_CryptoCurrencyRef{id = <<"bitcoin">>}).
|
||||
|
||||
-define(CONTACT_INFO, #domain_ContactInfo{
|
||||
phone_number = ?STRING,
|
||||
email = <<"test@test.ru">>
|
||||
email = ?EMAIL
|
||||
}).
|
||||
|
||||
-define(EXP_DATE(Month, Year), #domain_BankCardExpDate{
|
||||
@ -1220,7 +1232,7 @@
|
||||
}}
|
||||
},
|
||||
#domain_PaymentMethodRef{
|
||||
id = {crypto_currency, #domain_CryptoCurrencyRef{id = <<"bitcoin">>}}
|
||||
id = {crypto_currency, ?CRYPTO_CURRENCY_BTC}
|
||||
},
|
||||
#domain_PaymentMethodRef{
|
||||
id = {crypto_currency, #domain_CryptoCurrencyRef{id = <<"bitcoin_cash">>}}
|
||||
|
@ -23,8 +23,6 @@
|
||||
-export([second_request_with_idempotent_feature_test/1]).
|
||||
-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]).
|
||||
@ -44,8 +42,6 @@
|
||||
-type config() :: [{atom(), any()}].
|
||||
-type group_name() :: atom().
|
||||
|
||||
-define(DIFFERENCE, -1).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
@ -77,8 +73,6 @@ 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,
|
||||
@ -216,9 +210,6 @@ create_payment_ok_test(Config) ->
|
||||
[<<"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">>]
|
||||
@ -324,63 +315,6 @@ 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">>,
|
||||
ExternalID = <<"merch_id">>,
|
||||
Req = invoice_params(ExternalID),
|
||||
Unused = [
|
||||
[<<"description">>],
|
||||
[<<"externalID">>],
|
||||
[<<"metadata">>, <<"invoice_dummy_metadata">>]
|
||||
],
|
||||
Req2 = Req#{<<"product">> => <<"test_product2">>},
|
||||
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, Invoice1}, _} = create_invoice_(Req, Config),
|
||||
#{<<"invoice">> := #{<<"id">> := InvoiceID}} = Invoice1,
|
||||
{Response, Unused2} = create_invoice_(Req2, Config),
|
||||
?assertEqual(Unused, Unused2),
|
||||
?assertEqual(response_error(409, ExternalID, InvoiceID), Response).
|
||||
|
||||
-spec create_invoice_fail_test(config()) -> _.
|
||||
create_invoice_fail_test(Config) ->
|
||||
BenderKey = <<"bender_key">>,
|
||||
@ -703,8 +637,6 @@ create_customer_binding_ok_test(Config) ->
|
||||
?assertMatch(
|
||||
{{ok, _}, [
|
||||
[<<"externalID">>],
|
||||
[<<"paymentResource">>, <<"paymentTool">>, <<"bin">>],
|
||||
[<<"paymentResource">>, <<"paymentTool">>, <<"masked_pan">>],
|
||||
[<<"paymentResource">>, <<"paymentTool">>, <<"payment_system">>]
|
||||
]},
|
||||
BindingResult1
|
||||
|
@ -25,8 +25,9 @@
|
||||
create_payment_ok_test/1,
|
||||
create_payment_expired_test/1,
|
||||
create_payment_qiwi_access_token_ok_test/1,
|
||||
create_payment_crypto_ok_test/1,
|
||||
create_payment_mobile_commerce_ok_test/1,
|
||||
create_payment_with_empty_cvv_ok_test/1,
|
||||
create_payment_with_googlepay_encrypt_ok_test/1,
|
||||
get_payments_ok_test/1,
|
||||
get_payment_by_id_ok_test/1,
|
||||
get_payment_by_id_trx_ok_test/1,
|
||||
@ -73,9 +74,10 @@ invoice_access_token_tests() ->
|
||||
create_payment_ok_test,
|
||||
create_payment_expired_test,
|
||||
create_payment_with_empty_cvv_ok_test,
|
||||
create_payment_with_googlepay_encrypt_ok_test,
|
||||
get_payments_ok_test,
|
||||
create_payment_qiwi_access_token_ok_test,
|
||||
create_payment_crypto_ok_test,
|
||||
create_payment_mobile_commerce_ok_test,
|
||||
create_first_recurrent_payment_ok_test,
|
||||
create_second_recurrent_payment_ok_test
|
||||
].
|
||||
@ -297,7 +299,7 @@ create_payment_ok_test(Config) ->
|
||||
?STRING,
|
||||
Config
|
||||
),
|
||||
PaymentToolToken = get_encrypted_token(<<"visa">>, ?EXP_DATE(2, 2020)),
|
||||
PaymentToolToken = encrypt_payment_tool({bank_card, ?BANK_CARD(<<"visa">>, ?EXP_DATE(2, 2020))}),
|
||||
Req = ?PAYMENT_PARAMS(ExternalID, PaymentToolToken),
|
||||
{ok, #{
|
||||
<<"id">> := BenderKey,
|
||||
@ -347,61 +349,30 @@ create_payment_expired_test(Config) ->
|
||||
|
||||
-spec create_payment_with_empty_cvv_ok_test(config()) -> _.
|
||||
create_payment_with_empty_cvv_ok_test(Config) ->
|
||||
_ = capi_ct_helper:mock_services(
|
||||
[
|
||||
{invoicing, fun
|
||||
('Get', _) ->
|
||||
{ok, ?PAYPROC_INVOICE};
|
||||
(
|
||||
'StartPayment',
|
||||
{
|
||||
_UserInfo,
|
||||
_InvoiceID,
|
||||
#payproc_InvoicePaymentParams{
|
||||
payer =
|
||||
{payment_resource, #payproc_PaymentResourcePayerParams{
|
||||
resource = #domain_DisposablePaymentResource{
|
||||
payment_tool = {
|
||||
bank_card,
|
||||
#domain_BankCard{is_cvv_empty = true}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
) ->
|
||||
{ok, ?PAYPROC_PAYMENT}
|
||||
end},
|
||||
{generator, fun('GenerateID', _) -> capi_ct_helper_bender:generate_id(<<"bender_key">>) end}
|
||||
],
|
||||
Config
|
||||
),
|
||||
_ = capi_ct_helper_bouncer:mock_assert_invoice_op_ctx(
|
||||
<<"CreatePayment">>,
|
||||
?STRING,
|
||||
?STRING,
|
||||
?STRING,
|
||||
Config
|
||||
),
|
||||
PaymentToolToken = get_encrypted_token(<<"visa">>, ?EXP_DATE(1, 2020), true),
|
||||
Req2 = #{
|
||||
<<"flow">> => #{<<"type">> => <<"PaymentFlowInstant">>},
|
||||
<<"payer">> => #{
|
||||
<<"payerType">> => <<"PaymentResourcePayer">>,
|
||||
<<"paymentSession">> => ?TEST_PAYMENT_SESSION,
|
||||
<<"paymentToolToken">> => PaymentToolToken,
|
||||
<<"contactInfo">> => #{
|
||||
<<"email">> => <<"bla@bla.ru">>
|
||||
}
|
||||
}
|
||||
},
|
||||
{ok, _} = capi_client_payments:create_payment(?config(context, Config), Req2, ?STRING).
|
||||
BankCard = ?BANK_CARD(<<"visa">>, ?EXP_DATE(1, 2020), <<"CARD HODLER">>),
|
||||
BankCardNoCVV = BankCard#domain_BankCard{is_cvv_empty = true},
|
||||
{ok, _} = create_payment_w_payment_tool({bank_card, BankCardNoCVV}, Config).
|
||||
|
||||
-spec create_payment_qiwi_access_token_ok_test(_) -> _.
|
||||
create_payment_qiwi_access_token_ok_test(Config) ->
|
||||
Provider = <<"qiwi">>,
|
||||
WalletID = <<"+79876543210">>,
|
||||
Token = <<"blarg">>,
|
||||
DigitalWallet = ?DIGITAL_WALLET(Provider, WalletID, Token),
|
||||
{ok, _} = create_payment_w_payment_tool({digital_wallet, DigitalWallet}, Config).
|
||||
|
||||
-spec create_payment_crypto_ok_test(_) -> _.
|
||||
create_payment_crypto_ok_test(Config) ->
|
||||
{ok, _} = create_payment_w_payment_tool({crypto_currency, ?CRYPTO_CURRENCY_BTC}, Config).
|
||||
|
||||
-spec create_payment_mobile_commerce_ok_test(_) -> _.
|
||||
create_payment_mobile_commerce_ok_test(Config) ->
|
||||
MobileCommerce = ?MOBILE_COMMERCE(<<"mts">>, <<"123">>, <<"4567890">>),
|
||||
{ok, _} = create_payment_w_payment_tool({mobile_commerce, MobileCommerce}, Config).
|
||||
|
||||
-spec create_payment_w_payment_tool(PaymentTool, config()) -> _ when
|
||||
PaymentTool :: dmsl_domain_thrift:'PaymentTool'().
|
||||
create_payment_w_payment_tool(PaymentTool, Config) ->
|
||||
_ = capi_ct_helper:mock_services(
|
||||
[
|
||||
{invoicing, fun
|
||||
@ -411,93 +382,30 @@ create_payment_qiwi_access_token_ok_test(Config) ->
|
||||
?assertMatch(
|
||||
{payment_resource, #payproc_PaymentResourcePayerParams{
|
||||
resource = #domain_DisposablePaymentResource{
|
||||
payment_tool = {
|
||||
digital_wallet,
|
||||
?DIGITAL_WALLET(Provider, WalletID, Token)
|
||||
}
|
||||
payment_tool = PaymentTool
|
||||
}
|
||||
}},
|
||||
Params#payproc_InvoicePaymentParams.payer
|
||||
),
|
||||
{ok, ?PAYPROC_PAYMENT}
|
||||
end},
|
||||
{generator, fun('GenerateID', _) -> capi_ct_helper_bender:generate_id(<<"bender_key">>) end}
|
||||
{generator, fun('GenerateID', _) ->
|
||||
capi_ct_helper_bender:generate_id(?STRING)
|
||||
end}
|
||||
],
|
||||
Config
|
||||
),
|
||||
_ = capi_ct_helper_bouncer:mock_assert_invoice_op_ctx(
|
||||
<<"CreatePayment">>,
|
||||
?STRING,
|
||||
?STRING,
|
||||
?STRING,
|
||||
Config
|
||||
),
|
||||
PaymentToolToken = get_encrypted_token({Provider, WalletID, Token}),
|
||||
_ = capi_ct_helper_bouncer:mock_assert_invoice_op_ctx(<<"CreatePayment">>, ?STRING, ?STRING, ?STRING, Config),
|
||||
Req = #{
|
||||
<<"flow">> => #{<<"type">> => <<"PaymentFlowInstant">>},
|
||||
<<"payer">> => #{
|
||||
<<"payerType">> => <<"PaymentResourcePayer">>,
|
||||
<<"paymentSession">> => ?TEST_PAYMENT_SESSION,
|
||||
<<"paymentToolToken">> => PaymentToolToken,
|
||||
<<"contactInfo">> => #{<<"email">> => <<"bla@bla.ru">>}
|
||||
<<"paymentToolToken">> => encrypt_payment_tool(PaymentTool),
|
||||
<<"contactInfo">> => #{<<"email">> => ?EMAIL}
|
||||
}
|
||||
},
|
||||
{ok, _} = capi_client_payments:create_payment(?config(context, Config), Req, ?STRING).
|
||||
|
||||
-spec create_payment_with_googlepay_encrypt_ok_test(_) -> _.
|
||||
create_payment_with_googlepay_encrypt_ok_test(Config) ->
|
||||
_ = capi_ct_helper:mock_services(
|
||||
[
|
||||
{invoicing, fun
|
||||
('Get', _) ->
|
||||
{ok, ?PAYPROC_INVOICE};
|
||||
(
|
||||
'StartPayment',
|
||||
{
|
||||
_UserInfo,
|
||||
_InvoiceID,
|
||||
#payproc_InvoicePaymentParams{
|
||||
payer =
|
||||
{payment_resource, #payproc_PaymentResourcePayerParams{
|
||||
resource = #domain_DisposablePaymentResource{
|
||||
payment_tool = {
|
||||
bank_card,
|
||||
#domain_BankCard{
|
||||
is_cvv_empty = undefined,
|
||||
payment_system = #domain_PaymentSystemRef{id = <<"mastercard">>}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
) ->
|
||||
{ok, ?PAYPROC_PAYMENT}
|
||||
end},
|
||||
{generator, fun('GenerateID', _) -> capi_ct_helper_bender:generate_id(<<"bender_key">>) end}
|
||||
],
|
||||
Config
|
||||
),
|
||||
_ = capi_ct_helper_bouncer:mock_assert_invoice_op_ctx(
|
||||
<<"CreatePayment">>,
|
||||
?STRING,
|
||||
?STRING,
|
||||
?STRING,
|
||||
Config
|
||||
),
|
||||
PaymentToolToken = get_encrypted_token(<<"mastercard">>, ?EXP_DATE(1, 2020)),
|
||||
Req2 = #{
|
||||
<<"flow">> => #{<<"type">> => <<"PaymentFlowInstant">>},
|
||||
<<"payer">> => #{
|
||||
<<"payerType">> => <<"PaymentResourcePayer">>,
|
||||
<<"paymentSession">> => ?TEST_PAYMENT_SESSION,
|
||||
<<"paymentToolToken">> => PaymentToolToken,
|
||||
<<"contactInfo">> => #{
|
||||
<<"email">> => <<"bla@bla.ru">>
|
||||
}
|
||||
}
|
||||
},
|
||||
{ok, _} = capi_client_payments:create_payment(?config(context, Config), Req2, ?STRING).
|
||||
capi_client_payments:create_payment(?config(context, Config), Req, ?STRING).
|
||||
|
||||
-spec get_payments_ok_test(config()) -> _.
|
||||
get_payments_ok_test(Config) ->
|
||||
@ -665,7 +573,7 @@ create_first_recurrent_payment_ok_test(Config) ->
|
||||
],
|
||||
Config
|
||||
),
|
||||
PaymentToolToken = get_encrypted_token(<<"visa">>, ?EXP_DATE(1, 2020)),
|
||||
PaymentToolToken = encrypt_payment_tool({bank_card, ?BANK_CARD(<<"visa">>, ?EXP_DATE(1, 2020))}),
|
||||
Req2 = #{
|
||||
<<"flow">> => #{<<"type">> => <<"PaymentFlowInstant">>},
|
||||
<<"makeRecurrent">> => true,
|
||||
@ -768,31 +676,6 @@ get_failed_payment_with_invalid_cvv(Config) ->
|
||||
% mock_services([{invoicing, fun('GetPayment', _) -> {ok, ?PAYPROC_PAYMENT} end}], Config),
|
||||
capi_client_payments:get_payment_by_id(?config(context, Config), ?STRING, ?STRING).
|
||||
|
||||
get_encrypted_token({Provider, ID, TokenID}) ->
|
||||
PaymentTool =
|
||||
{digital_wallet, #domain_DigitalWallet{
|
||||
payment_service = #domain_PaymentServiceRef{id = Provider},
|
||||
id = ID,
|
||||
token = TokenID
|
||||
}},
|
||||
encrypt_payment_tool(PaymentTool).
|
||||
|
||||
get_encrypted_token(PS, ExpDate) ->
|
||||
get_encrypted_token(PS, ExpDate, undefined).
|
||||
|
||||
get_encrypted_token(PS, ExpDate, IsCvvEmpty) ->
|
||||
PaymentTool =
|
||||
{bank_card, #domain_BankCard{
|
||||
token = ?TEST_PAYMENT_TOKEN,
|
||||
payment_system = #domain_PaymentSystemRef{id = PS},
|
||||
bin = <<"411111">>,
|
||||
last_digits = <<"1111">>,
|
||||
exp_date = ExpDate,
|
||||
cardholder_name = <<"Degus Degusovich">>,
|
||||
is_cvv_empty = IsCvvEmpty
|
||||
}},
|
||||
encrypt_payment_tool(PaymentTool).
|
||||
|
||||
encrypt_payment_tool(PaymentTool) ->
|
||||
encrypt_payment_tool(PaymentTool, undefined).
|
||||
|
||||
|
@ -37,8 +37,7 @@
|
||||
{elvis_style, macro_names, #{
|
||||
ignore => [
|
||||
% Abuses lowercase macros too much
|
||||
capi_feature_schemas,
|
||||
capi_feature_schemas_legacy
|
||||
capi_feature_schemas
|
||||
]
|
||||
}}
|
||||
]
|
||||
|
@ -119,11 +119,11 @@
|
||||
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},2},
|
||||
{<<"swag_client">>,
|
||||
{git,"https://github.com/valitydev/swag-payments",
|
||||
{ref,"03520bda80db540cfe2f431bc035e9c9c165fb99"}},
|
||||
{ref,"187da507554108df60a317582ec8a8d40433c9e5"}},
|
||||
0},
|
||||
{<<"swag_server">>,
|
||||
{git,"https://github.com/valitydev/swag-payments",
|
||||
{ref,"347f081b2823c17c390fcedefb58bad9de35034f"}},
|
||||
{ref,"04149a7aad1b9fad8d1992bc29bffa0de5fbd498"}},
|
||||
0},
|
||||
{<<"thrift">>,
|
||||
{git,"https://github.com/valitydev/thrift_erlang.git",
|
||||
|
Loading…
Reference in New Issue
Block a user