From eb0cc760cd83aad0f6d6bb9e3edbe8498cee0779 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 26 Nov 2025 12:42:55 +0300 Subject: [PATCH 1/9] Adds thrift handler for invoice-template creation w/ access token --- apps/capi/src/capi.app.src | 1 + .../src/capi_handler_invoice_templates.erl | 191 +++++++++++++++++- apps/capi/src/capi_sup.erl | 25 ++- apps/capi_extensions/include/.gitignore | 2 + .../proto/capi_extensions.thrift | 46 +++++ apps/capi_extensions/rebar.config | 18 ++ apps/capi_extensions/rebar.lock | 1 + apps/capi_extensions/src/.gitignore | 3 + .../src/capi_extensions.app.src | 10 + config/sys.config | 5 + elvis.config | 1 + rebar.config | 2 +- rebar.lock | 2 +- 13 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 apps/capi_extensions/include/.gitignore create mode 100644 apps/capi_extensions/proto/capi_extensions.thrift create mode 100644 apps/capi_extensions/rebar.config create mode 100644 apps/capi_extensions/rebar.lock create mode 100644 apps/capi_extensions/src/.gitignore create mode 100644 apps/capi_extensions/src/capi_extensions.app.src diff --git a/apps/capi/src/capi.app.src b/apps/capi/src/capi.app.src index 4ce6d78..b49942a 100644 --- a/apps/capi/src/capi.app.src +++ b/apps/capi/src/capi.app.src @@ -30,6 +30,7 @@ bouncer_client, token_keeper_client, party_client, + capi_extensions, opentelemetry_api, opentelemetry_exporter, opentelemetry diff --git a/apps/capi/src/capi_handler_invoice_templates.erl b/apps/capi/src/capi_handler_invoice_templates.erl index 68f221c..d27b4ee 100644 --- a/apps/capi/src/capi_handler_invoice_templates.erl +++ b/apps/capi/src/capi_handler_invoice_templates.erl @@ -3,11 +3,14 @@ -include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). -include_lib("damsel/include/dmsl_payproc_thrift.hrl"). +-include_lib("capi_extensions/include/capi_ext_thrift.hrl"). -behaviour(capi_handler). - -export([prepare/3]). +-behaviour(woody_server_thrift_handler). +-export([handle_function/4]). + -import(capi_handler_utils, [general_error/2, logic_error/2, conflict_error/1, map_service_result/1]). -spec prepare( @@ -212,6 +215,45 @@ prepare('GetInvoicePaymentMethodsByTemplateID' = OperationID, Req, Context) -> prepare(_OperationID, _Req, _Context) -> {error, noimpl}. +-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), _) -> + {ok, term()} | no_return(). +handle_function('Create', {InvoiceTemplateParams}, WoodyContext, _Opts) -> + scoper:scope( + invoice_templating, + fun() -> + try + %% NOTE Use same operation ID as the original in swagger/JSON API + InvoiceTemplateID = generate_thrift_invoice_template_id( + 'CreateInvoiceTemplate', InvoiceTemplateParams, WoodyContext + ), + CallArgs = {encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, InvoiceTemplateParams)}, + capi_woody_client:call_service(invoice_templating, 'Create', CallArgs, WoodyContext) + of + {ok, InvoiceTpl} -> + {ok, make_thrift_invoice_tpl_and_token(InvoiceTpl, WoodyContext)}; + {exception, #base_InvalidRequest{errors = Errors}} -> + woody_error:raise(business, #ext_InvalidRequest{errors = Errors}); + {exception, #payproc_PartyNotFound{}} -> + woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Party not found">>]}); + {exception, #payproc_ShopNotFound{}} -> + woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Shop not found">>]}); + {exception, #payproc_InvalidPartyStatus{}} -> + woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Invalid party status">>]}); + {exception, #payproc_InvalidShopStatus{}} -> + woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Invalid shop status">>]}) + catch + throw:invoice_cart_empty -> + woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Wrong size. Path to item: cart">>]}); + throw:zero_invoice_lifetime -> + woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Lifetime cannot be zero">>]}); + throw:{external_id_conflict, _ID, _UsedExternalID, _Schema} -> + woody_error:raise(business, #ext_InvalidRequest{ + errors = [<<"This 'externalID' has been used by another request">>] + }) + end + end + ). + mask_invoice_template_notfound(Resolution) -> % ED-206 % When bouncer says "forbidden" we can't really tell the difference between "forbidden because @@ -246,7 +288,118 @@ generate_invoice_template_id(OperationID, TemplateParams, PartyID, #{woody_conte 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) -> +generate_thrift_invoice_template_id( + OperationID, + #ext_InvoiceTemplateCreateParams{external_id = ExternalID, party_id = #domain_PartyConfigRef{id = PartyID}} = + TemplateParams, + WoodyContext +) -> + IdempKey = {OperationID, PartyID, ExternalID}, + Identity = capi_bender:make_identity( + capi_feature_schemas:invoice_template(), + decode_to_feature_container(TemplateParams) + ), + capi_bender:gen_snowflake(IdempKey, Identity, WoodyContext). + +decode_to_feature_container(#ext_InvoiceTemplateCreateParams{ + shop_id = #domain_ShopConfigRef{id = ShopID}, + invoice_lifetime = #domain_LifetimeInterval{days = DD, months = MM, years = YY}, + details = Details +}) -> + #{ + <<"shopID">> => ShopID, + <<"lifetime">> => #{<<"days">> => DD, <<"months">> => MM, <<"years">> => YY}, + <<"details">> => encode_details_to_feature_container(Details) + }. + +encode_details_to_feature_container( + {product, #domain_InvoiceTemplateProduct{ + product = Product, + price = Price, + metadata = Metadata + }} +) -> + genlib_map:compact(#{ + <<"templateType">> => <<"InvoiceTemplateSingleLine">>, + <<"product">> => Product, + <<"price">> => encode_price_to_feature_container(Price), + <<"taxMode">> => encode_tax_metadata_to_feature_container(Metadata) + }); +encode_details_to_feature_container({cart, #domain_InvoiceCart{lines = Lines}}) -> + {Cart, Currency} = encode_cart_lines_to_feature_container(Lines), + #{ + <<"templateType">> => <<"InvoiceTemplateMultiLine">>, + <<"currency">> => Currency, + <<"cart">> => Cart + }. + +encode_cart_lines_to_feature_container([]) -> + throw(invoice_cart_empty); +encode_cart_lines_to_feature_container(Lines) -> + {Currency, Cart} = lists:foldl( + fun( + #domain_InvoiceLine{ + product = Product, + quantity = Quantity, + price = #domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}, + metadata = Metadata + }, + {_, Items} + ) -> + {Currency, [ + genlib_map:compact(#{ + <<"product">> => Product, + <<"quantity">> => Quantity, + <<"price">> => Amount, + <<"taxMode">> => encode_tax_metadata_to_feature_container(Metadata) + }) + | Items + ]} + end, + {[], undefined}, + Lines + ), + {Currency, lists:reverse(Cart)}. + +encode_price_to_feature_container({unlim, #domain_InvoiceTemplateCostUnlimited{}}) -> + #{<<"costType">> => <<"InvoiceTemplateLineCostUnlim">>}; +encode_price_to_feature_container( + {fixed, #domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}} +) -> + #{ + <<"costType">> => <<"InvoiceTemplateLineCostFixed">>, + <<"amount">> => Amount, + <<"currency">> => Currency + }; +encode_price_to_feature_container( + {range, #domain_CashRange{ + lower = {_, #domain_Cash{currency = #domain_CurrencyRef{symbolic_code = Currency}}} = LowerBound, + upper = UpperBound + }} +) -> + #{ + <<"costType">> => <<"InvoiceTemplateLineCostRange">>, + <<"currency">> => Currency, + <<"range">> => #{ + <<"lowerBound">> => encode_bound_to_feature_container(LowerBound, 1), + <<"upperBound">> => encode_bound_to_feature_container(UpperBound, -1) + } + }. + +encode_bound_to_feature_container({inclusive, #domain_Cash{amount = Bound}}, _Delta) -> + Bound; +encode_bound_to_feature_container({exclusive, #domain_Cash{amount = Bound}}, Delta) -> + Bound + Delta. + +encode_tax_metadata_to_feature_container(#{<<"TaxMode">> := {str, TM}}) -> + #{ + <<"type">> => <<"InvoiceLineTaxVAT">>, + <<"rate">> => TM + }; +encode_tax_metadata_to_feature_container(#{}) -> + undefined. + +encode_invoice_tpl_create_params(InvoiceTemplateID, PartyID, Params) when is_map(Params) -> Details = encode_invoice_tpl_details(genlib_map:get(<<"details">>, Params)), Product = get_product_from_tpl_details(Details), #payproc_InvoiceTemplateCreateParams{ @@ -281,6 +434,40 @@ make_invoice_tpl_and_token(InvoiceTpl, ProcessingContext) -> <<"invoiceTemplateAccessToken">> => capi_handler_utils:issue_access_token(InvoiceTpl, ProcessingContext) }. +encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, #ext_InvoiceTemplateCreateParams{ + party_id = PartyID, + shop_id = ShopID, + invoice_lifetime = InvoiceLifetime, + name = Name, + description = Description, + details = Details, + context = Context +}) -> + Product = get_product_from_tpl_details(Details), + #payproc_InvoiceTemplateCreateParams{ + template_id = InvoiceTemplateID, + party_id = PartyID, + shop_id = ShopID, + invoice_lifetime = InvoiceLifetime, + product = Product, + name = Name, + description = Description, + details = Details, + context = Context + }. + +make_thrift_invoice_tpl_and_token(InvoiceTpl, WoodyContext) -> + TokenSpec = #{ + party => InvoiceTpl#domain_InvoiceTemplate.party_ref#domain_PartyConfigRef.id, + scope => {invoice_template, InvoiceTpl#domain_InvoiceTemplate.id}, + shop => InvoiceTpl#domain_InvoiceTemplate.shop_ref#domain_ShopConfigRef.id + }, + TokenPayload = capi_auth:issue_access_token(TokenSpec, WoodyContext), + #ext_InvoiceTemplateAndToken{ + invoice_template = InvoiceTpl, + invoice_template_access_token = #ext_AccessToken{payload = TokenPayload} + }. + encode_invoice_tpl_details(#{<<"templateType">> := <<"InvoiceTemplateSingleLine">>} = Details) -> {product, encode_invoice_tpl_product(Details)}; encode_invoice_tpl_details(#{<<"templateType">> := <<"InvoiceTemplateMultiLine">>} = Details) -> diff --git a/apps/capi/src/capi_sup.erl b/apps/capi/src/capi_sup.erl index 72d0575..b977807 100644 --- a/apps/capi/src/capi_sup.erl +++ b/apps/capi/src/capi_sup.erl @@ -32,12 +32,35 @@ init([]) -> AdditionalRoutes = [{'_', [erl_health_handle:get_route(HealthCheck), get_prometheus_route()]}], SwaggerHandlerOpts = genlib_app:env(?APP, swagger_handler_opts, #{}), SwaggerSpec = capi_swagger_server:child_spec(AdditionalRoutes, LogicHandler, SwaggerHandlerOpts), + WoodyChildSPec = get_woody_child_spec(), {ok, { {one_for_all, 0, 1}, - [LechiffreSpec, SwaggerSpec, PartyClientSpec] + [LechiffreSpec, SwaggerSpec, PartyClientSpec, WoodyChildSPec] }}. +get_woody_child_spec() -> + {ok, IP} = inet:parse_address(genlib_app:env(capi_woody_server, ip, "::")), + EventHandlerOpts = genlib_app:env(capi_woody_server, scoper_event_handler_options, #{}), + woody_server:child_spec( + ?MODULE, + #{ + ip => IP, + port => genlib_app:env(capi_woody_server, port, 8022), + transport_opts => genlib_app:env(capi_woody_server, transport_opts, #{}), + protocol_opts => genlib_app:env(capi_woody_server, protocol_opts, #{}), + event_handler => {scoper_woody_event_handler, EventHandlerOpts}, + handlers => [ + %% TODO Proper path + {"/v2/extensions/invoice_templating", { + {capi_ext_thrift, 'InvoiceTemplating'}, {capi_handler_invoice_templates, #{}} + }} + ], + additional_routes => [], + shutdown_timeout => genlib_app:env(?MODULE, shutdown_timeout, 0) + } + ). + -spec get_logic_handler_info(capi_handler:handler_opts()) -> {Handler :: swag_server:logic_handler(_), [Spec :: supervisor:child_spec()] | []}. get_logic_handler_info(HandlerOpts) -> diff --git a/apps/capi_extensions/include/.gitignore b/apps/capi_extensions/include/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/apps/capi_extensions/include/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/apps/capi_extensions/proto/capi_extensions.thrift b/apps/capi_extensions/proto/capi_extensions.thrift new file mode 100644 index 0000000..f99ddbe --- /dev/null +++ b/apps/capi_extensions/proto/capi_extensions.thrift @@ -0,0 +1,46 @@ +// NOTE "proto/base.thrift" is ambiguous, because both damsel and bouncer-proto +// have such thrift-file. +// include "proto/base.thrift" +include "proto/domain.thrift" +include "proto/payment_processing.thrift" + +namespace java dev.vality.capi +namespace erlang capi.ext + +// NOTE Stolen from damsel/proto/base.thrift +exception InvalidRequest { + 1: required list errors +} + +// Based on `payment_processing.InvoiceTemplateCreateParams` +struct InvoiceTemplateCreateParams { + 1: optional string external_id + 2: required domain.PartyConfigRef party_id + 3: required domain.ShopConfigRef shop_id + 4: required domain.LifetimeInterval invoice_lifetime + 5: optional string name + 6: optional string description + 7: required domain.InvoiceTemplateDetails details + 8: required domain.InvoiceContext context +} + +struct AccessToken { + 1: required string payload +} + +struct InvoiceTemplateAndToken { + 1: required domain.InvoiceTemplate invoice_template + 2: required AccessToken invoice_template_access_token +} + +service InvoiceTemplating { + + InvoiceTemplateAndToken Create (1: InvoiceTemplateCreateParams params) + throws ( + 1: payment_processing.PartyNotFound ex1, + 2: payment_processing.InvalidPartyStatus ex2, + 3: payment_processing.ShopNotFound ex3, + 4: payment_processing.InvalidShopStatus ex4, + 5: InvalidRequest ex5 + ) +} diff --git a/apps/capi_extensions/rebar.config b/apps/capi_extensions/rebar.config new file mode 100644 index 0000000..5061715 --- /dev/null +++ b/apps/capi_extensions/rebar.config @@ -0,0 +1,18 @@ +{plugins, [ + {rebar3_thrift_compiler, + {git, "https://github.com/valitydev/rebar3-thrift-compiler.git", {tag, "0.4"}}} +]}. + +{provider_hooks, [ + {pre, [ + {compile, {thrift, compile}}, + {clean, {thrift, clean}} + ]} +]}. + +{thrift_compiler_opts, [ + {in_dir, "proto"}, + {include_dirs, []}, + {in_files, all}, + {gen, "erlang:app_namespaces"} +]}. diff --git a/apps/capi_extensions/rebar.lock b/apps/capi_extensions/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/apps/capi_extensions/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/apps/capi_extensions/src/.gitignore b/apps/capi_extensions/src/.gitignore new file mode 100644 index 0000000..2ac90e6 --- /dev/null +++ b/apps/capi_extensions/src/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!capi_extensions.app.src \ No newline at end of file diff --git a/apps/capi_extensions/src/capi_extensions.app.src b/apps/capi_extensions/src/capi_extensions.app.src new file mode 100644 index 0000000..ced8c1b --- /dev/null +++ b/apps/capi_extensions/src/capi_extensions.app.src @@ -0,0 +1,10 @@ +{application, capi_extensions, [ + {description, "CAPI extensions definitions"}, + {vsn, "1"}, + {applications, [ + kernel, + stdlib, + damsel + ]}, + {maintainers, []} +]}. diff --git a/config/sys.config b/config/sys.config index 32052d3..32bb418 100644 --- a/config/sys.config +++ b/config/sys.config @@ -83,6 +83,11 @@ }} ]}, + {capi_woody_server, [ + {ip, "::"}, + {port, 8022} + ]}, + {capi_woody_client, [ {services, #{ party_management => <<"http://hellgate:8022/v1/processing/partymgmt">>, diff --git a/elvis.config b/elvis.config index 7e974ae..5ac8919 100644 --- a/elvis.config +++ b/elvis.config @@ -40,6 +40,7 @@ capi_feature_schemas ] }}, + {elvis_style, private_data_types, disable}, {elvis_style, no_import, disable}, {elvis_style, no_throw, disable}, {elvis_style, export_used_types, disable} diff --git a/rebar.config b/rebar.config index 82325bf..e05d89a 100644 --- a/rebar.config +++ b/rebar.config @@ -36,7 +36,7 @@ {cowboy_draining_server, {git, "https://github.com/valitydev/cowboy_draining_server.git", {branch, "master"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, {woody_user_identity, {git, "https://github.com/valitydev/woody_erlang_user_identity.git", {tag, "v1.1.0"}}}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.17"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.18"}}}, {bender_proto, {git, "https://github.com/valitydev/bender-proto.git", {branch, "master"}}}, {bender_client, {git, "https://github.com/valitydev/bender-client-erlang.git", {tag, "v1.1.0"}}}, {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.3"}}}, diff --git a/rebar.lock b/rebar.lock index 61fc5a6..ce1f5ce 100644 --- a/rebar.lock +++ b/rebar.lock @@ -41,7 +41,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"f831d3aa5fdfd0338b41af44d1eeffe810ca9708"}}, + {ref,"368f05b67c5bf0543856895e71da5c019aba51a1"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", From 33788e3e7caa2aa78ea3ed7673fa265ea15e61b4 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 26 Nov 2025 14:07:26 +0300 Subject: [PATCH 2/9] Adds test case w/ idempotency check for thrift version of invoice template creation --- .../src/capi_handler_invoice_templates.erl | 29 ++++---- .../test/capi_idempotency_tests_SUITE.erl | 73 +++++++++++++++++++ 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/apps/capi/src/capi_handler_invoice_templates.erl b/apps/capi/src/capi_handler_invoice_templates.erl index d27b4ee..edf6ffd 100644 --- a/apps/capi/src/capi_handler_invoice_templates.erl +++ b/apps/capi/src/capi_handler_invoice_templates.erl @@ -336,30 +336,33 @@ encode_details_to_feature_container({cart, #domain_InvoiceCart{lines = Lines}}) encode_cart_lines_to_feature_container([]) -> throw(invoice_cart_empty); encode_cart_lines_to_feature_container(Lines) -> - {Currency, Cart} = lists:foldl( + {Cart, Currency} = lists:foldl( fun( #domain_InvoiceLine{ product = Product, quantity = Quantity, - price = #domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}, + price = #domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Curr}}, metadata = Metadata }, - {_, Items} + {Items, _} ) -> - {Currency, [ - genlib_map:compact(#{ - <<"product">> => Product, - <<"quantity">> => Quantity, - <<"price">> => Amount, - <<"taxMode">> => encode_tax_metadata_to_feature_container(Metadata) - }) - | Items - ]} + { + [ + genlib_map:compact(#{ + <<"product">> => Product, + <<"quantity">> => Quantity, + <<"price">> => Amount, + <<"taxMode">> => encode_tax_metadata_to_feature_container(Metadata) + }) + | Items + ], + Curr + } end, {[], undefined}, Lines ), - {Currency, lists:reverse(Cart)}. + {lists:reverse(Cart), Currency}. encode_price_to_feature_container({unlim, #domain_InvoiceTemplateCostUnlimited{}}) -> #{<<"costType">> => <<"InvoiceTemplateLineCostUnlim">>}; diff --git a/apps/capi/test/capi_idempotency_tests_SUITE.erl b/apps/capi/test/capi_idempotency_tests_SUITE.erl index d09890d..5f3f277 100644 --- a/apps/capi/test/capi_idempotency_tests_SUITE.erl +++ b/apps/capi/test/capi_idempotency_tests_SUITE.erl @@ -7,6 +7,7 @@ -include_lib("damsel/include/dmsl_payproc_thrift.hrl"). -include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("capi_extensions/include/capi_ext_thrift.hrl"). -export([all/0]). -export([groups/0]). @@ -30,6 +31,7 @@ -export([create_invoice_idemp_cart_fail_test/1]). -export([create_invoice_idemp_bank_account_fail_test/1]). -export([create_invoice_template_ok_test/1]). +-export([create_invoice_template_with_woody_ok_test/1]). -export([create_invoice_template_fail_test/1]). -export([create_invoice_with_template_ok_test/1]). -export([create_invoice_with_template_fail_test/1]). @@ -78,6 +80,7 @@ groups() -> ]}, {invoice_template_creation, [], [ create_invoice_template_ok_test, + create_invoice_template_with_woody_ok_test, create_invoice_template_fail_test ]}, {invoice_with_template_creation, [], [ @@ -463,6 +466,76 @@ create_invoice_template_ok_test(Config) -> UnusedParams1 ). +-spec create_invoice_template_with_woody_ok_test(config()) -> _. +create_invoice_template_with_woody_ok_test(Config) -> + BenderKey = <<"create_invoice_template_with_woody_ok_test_bender_key">>, + CreateParams = #ext_InvoiceTemplateCreateParams{ + external_id = genlib:unique(), + party_id = #domain_PartyConfigRef{id = <<"2">>}, + shop_id = #domain_ShopConfigRef{id = <<"1">>}, + invoice_lifetime = #domain_LifetimeInterval{days = ?INTEGER, months = ?INTEGER, years = ?INTEGER}, + description = <<"Sample text">>, + details = + {cart, #domain_InvoiceCart{ + lines = [ + #domain_InvoiceLine{ + product = ?STRING, + quantity = ?INTEGER, + price = ?CASH, + metadata = #{?STRING => {obj, #{}}} + }, + #domain_InvoiceLine{ + product = ?STRING, + quantity = ?INTEGER, + price = ?CASH, + metadata = #{<<"TaxMode">> => {str, <<"18%">>}} + } + ] + }}, + context = ?CONTENT + }, + Result = capi_ct_helper_bender:with_storage( + fun(StorageID) -> + _ = capi_ct_helper:mock_services( + [ + {invoice_templating, fun( + 'Create', + {#payproc_InvoiceTemplateCreateParams{template_id = TemplateID}} + ) -> + {ok, ?INVOICE_TPL(TemplateID)} + end}, + {bender, fun('GenerateID', {_Key, _, CtxMsgPack}) -> + capi_ct_helper_bender:get_internal_id(StorageID, BenderKey, CtxMsgPack) + end} + ], + Config + ), + [ + with_feature_storage(fun() -> + woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ + url => "http://localhost:8022/v2/extensions/invoice_templating", + event_handler => scoper_woody_event_handler + }) + end) + || Params <- [ + CreateParams, + CreateParams#ext_InvoiceTemplateCreateParams{description = <<"whatever">>} + ] + ] + end + ), + [ + {{ok, #ext_InvoiceTemplateAndToken{invoice_template = Template1}}, UnusedParams1}, + {{ok, #ext_InvoiceTemplateAndToken{invoice_template = Template2}}, UnusedParams2} + ] = Result, + ?assertEqual(Template1, Template2), + ?assertEqual(UnusedParams1, UnusedParams2), + %% NOTE Since thrift parameters are decoded into the feature set only with + %% fields present in the schema, no other fields should be tracked as unused + %% by `capi_ct_features_reader_event_handler` and its storage. + %% NOTE See `capi_handler_invoice_templates:decode_to_feature_container/1`. + ?assertEqual([], UnusedParams1). + -spec create_invoice_template_fail_test(config()) -> _. create_invoice_template_fail_test(Config) -> BenderKey = <<"create_invoice_template_fail_test_bender_key">>, From fddc641a1dff6546b2cd99b0009105ebbbf56a31 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 26 Nov 2025 14:11:14 +0300 Subject: [PATCH 3/9] Fixes formatting --- apps/capi_extensions/rebar.config | 5 +-- config/sys.config | 68 +++++++++++++++++-------------- rebar.config | 8 +++- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/apps/capi_extensions/rebar.config b/apps/capi_extensions/rebar.config index 5061715..e4b4d61 100644 --- a/apps/capi_extensions/rebar.config +++ b/apps/capi_extensions/rebar.config @@ -1,6 +1,5 @@ {plugins, [ - {rebar3_thrift_compiler, - {git, "https://github.com/valitydev/rebar3-thrift-compiler.git", {tag, "0.4"}}} + {rebar3_thrift_compiler, {git, "https://github.com/valitydev/rebar3-thrift-compiler.git", {tag, "0.4"}}} ]}. {provider_hooks, [ @@ -12,7 +11,5 @@ {thrift_compiler_opts, [ {in_dir, "proto"}, - {include_dirs, []}, - {in_files, all}, {gen, "erlang:app_namespaces"} ]}. diff --git a/config/sys.config b/config/sys.config index 32bb418..c39d1a1 100644 --- a/config/sys.config +++ b/config/sys.config @@ -40,7 +40,9 @@ %% {oops_bodies, #{ %% 500 => "oops_bodies/500_body" %% }}, - {graceful_shutdown_timeout, 5000}, % non_neg_integer() | infinity + + % non_neg_integer() | infinity + {graceful_shutdown_timeout, 5000}, {bouncer_ruleset_id, <<"service/authz/api">>}, {access_conf, #{ jwt => #{ @@ -63,13 +65,14 @@ } }}, {health_check, #{ - disk => {erl_health, disk , ["/", 99]}, - memory => {erl_health, cg_memory, [99]}, - service => {erl_health, service , [<<"capi">>]} + disk => {erl_health, disk, ["/", 99]}, + memory => {erl_health, cg_memory, [99]}, + service => {erl_health, service, [<<"capi">>]} }}, - {max_request_deadline, 60000}, % milliseconds + % milliseconds + {max_request_deadline, 60000}, {default_processing_deadline, <<"30m">>}, - {lechiffre_opts, #{ + {lechiffre_opts, #{ encryption_key_path => <<"path/to/key1.secret">>, decryption_key_paths => [<<"path/to/key1.secret">>] }}, @@ -90,35 +93,38 @@ {capi_woody_client, [ {services, #{ - party_management => <<"http://hellgate:8022/v1/processing/partymgmt">>, - invoicing => <<"http://hellgate:8022/v1/processing/invoicing">>, - invoice_templating => <<"http://hellgate:8022/v1/processing/invoice_templating">>, - webhook_manager => <<"http://hooker:8022/hook">> + party_management => <<"http://hellgate:8022/v1/processing/partymgmt">>, + invoicing => <<"http://hellgate:8022/v1/processing/invoicing">>, + invoice_templating => <<"http://hellgate:8022/v1/processing/invoice_templating">>, + webhook_manager => <<"http://hooker:8022/hook">> }}, {service_deadlines, #{ - party_management => 5000 % milliseconds + % milliseconds + party_management => 5000 }}, {service_retries, #{ - party_management => #{ - % function => retry strategy - % '_' work as "any" - % default value is 'finish' - % for more info look genlib_retry :: strategy() - % https://github.com/valitydev/genlib/blob/master/src/genlib_retry.erl#L19 - 'Get' => {linear, 3, 1000}, - '_' => finish + party_management => #{ + % function => retry strategy + % '_' work as "any" + % default value is 'finish' + % for more info look genlib_retry :: strategy() + % https://github.com/valitydev/genlib/blob/master/src/genlib_retry.erl#L19 + 'Get' => {linear, 3, 1000}, + '_' => finish } }} ]}, {dmt_client, [ - {cache_update_interval, 30000}, % milliseconds + % milliseconds + {cache_update_interval, 30000}, {max_cache_size, #{ elements => 20, - memory => 52428800 % 50Mb + % 50Mb + memory => 52428800 }}, {service_urls, #{ - 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, + 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, 'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">> }} ]}, @@ -128,16 +134,18 @@ 'party_management' => "http://hellgate:8022/v1/processing/partymgmt" }}, {woody, #{ - cache_mode => safe, % disabled | safe | aggressive + % disabled | safe | aggressive + cache_mode => safe, options => #{ woody_client => #{ - event_handler => {scoper_woody_event_handler, #{ - event_handler_opts => #{ - formatter_opts => #{ - max_length => 1000 + event_handler => + {scoper_woody_event_handler, #{ + event_handler_opts => #{ + formatter_opts => #{ + max_length => 1000 + } } - } - }} + }} } }, %retries => #{ @@ -208,7 +216,7 @@ ]}, {snowflake, [ - % {machine_id, 42} + % {machine_id, 42} ]}, {prometheus, [ diff --git a/rebar.config b/rebar.config index e05d89a..ec9b66b 100644 --- a/rebar.config +++ b/rebar.config @@ -126,7 +126,13 @@ {erlfmt, [ {print_width, 120}, - {files, ["apps/capi*/{src,include,test}/*.{hrl,erl}", "rebar.config", "elvis.config"]} + {files, [ + "apps/capi*/{src,include,test}/*.{hrl,erl}", + "apps/capi*/rebar.config", + "rebar.config", + "elvis.config", + "config/sys.config" + ]} ]}. {covertool, [ From 995ffbba7d3a7a53bcfb3d8f68ee292feefbb9c1 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 26 Nov 2025 14:12:13 +0300 Subject: [PATCH 4/9] Fixes thrift proto' indentation --- apps/capi_extensions/proto/capi_extensions.thrift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/capi_extensions/proto/capi_extensions.thrift b/apps/capi_extensions/proto/capi_extensions.thrift index f99ddbe..533385f 100644 --- a/apps/capi_extensions/proto/capi_extensions.thrift +++ b/apps/capi_extensions/proto/capi_extensions.thrift @@ -25,12 +25,12 @@ struct InvoiceTemplateCreateParams { } struct AccessToken { - 1: required string payload + 1: required string payload } struct InvoiceTemplateAndToken { - 1: required domain.InvoiceTemplate invoice_template - 2: required AccessToken invoice_template_access_token + 1: required domain.InvoiceTemplate invoice_template + 2: required AccessToken invoice_template_access_token } service InvoiceTemplating { From f2454c6fce1a653bb93741b68eb69ba751bcc1c2 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 26 Nov 2025 16:09:15 +0300 Subject: [PATCH 5/9] Extends idempotency testcase w/ assertion against result of equivalent swag operation --- .../test/capi_idempotency_tests_SUITE.erl | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/apps/capi/test/capi_idempotency_tests_SUITE.erl b/apps/capi/test/capi_idempotency_tests_SUITE.erl index 5f3f277..2662e1a 100644 --- a/apps/capi/test/capi_idempotency_tests_SUITE.erl +++ b/apps/capi/test/capi_idempotency_tests_SUITE.erl @@ -469,8 +469,9 @@ create_invoice_template_ok_test(Config) -> -spec create_invoice_template_with_woody_ok_test(config()) -> _. create_invoice_template_with_woody_ok_test(Config) -> BenderKey = <<"create_invoice_template_with_woody_ok_test_bender_key">>, + ExternalID = genlib:unique(), CreateParams = #ext_InvoiceTemplateCreateParams{ - external_id = genlib:unique(), + external_id = ExternalID, party_id = #domain_PartyConfigRef{id = <<"2">>}, shop_id = #domain_ShopConfigRef{id = <<"1">>}, invoice_lifetime = #domain_LifetimeInterval{days = ?INTEGER, months = ?INTEGER, years = ?INTEGER}, @@ -494,6 +495,18 @@ create_invoice_template_with_woody_ok_test(Config) -> }}, context = ?CONTENT }, + SwagReq = #{ + <<"externalID">> => ExternalID, + <<"shopID">> => <<"1">>, + <<"lifetime">> => #{ + <<"days">> => ?INTEGER, + <<"months">> => ?INTEGER, + <<"years">> => ?INTEGER + }, + <<"partyID">> => <<"2">>, + <<"details">> => ?INVOICE_TMPL_DETAILS_PARAMS, + <<"description">> => <<"Sample text">> + }, Result = capi_ct_helper_bender:with_storage( fun(StorageID) -> _ = capi_ct_helper:mock_services( @@ -510,6 +523,7 @@ create_invoice_template_with_woody_ok_test(Config) -> ], Config ), + %% Two thrift calls [ with_feature_storage(fun() -> woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ @@ -521,20 +535,40 @@ create_invoice_template_with_woody_ok_test(Config) -> CreateParams, CreateParams#ext_InvoiceTemplateCreateParams{description = <<"whatever">>} ] - ] + ] ++ + %% And one swag request + [ + with_feature_storage(fun() -> + capi_client_invoice_templates:create(?config(context, Config), SwagReq) + end) + ] end ), - [ - {{ok, #ext_InvoiceTemplateAndToken{invoice_template = Template1}}, UnusedParams1}, - {{ok, #ext_InvoiceTemplateAndToken{invoice_template = Template2}}, UnusedParams2} - ] = Result, - ?assertEqual(Template1, Template2), - ?assertEqual(UnusedParams1, UnusedParams2), %% NOTE Since thrift parameters are decoded into the feature set only with %% fields present in the schema, no other fields should be tracked as unused %% by `capi_ct_features_reader_event_handler` and its storage. %% NOTE See `capi_handler_invoice_templates:decode_to_feature_container/1`. - ?assertEqual([], UnusedParams1). + ?assertMatch( + [ + { + {ok, #ext_InvoiceTemplateAndToken{invoice_template = #domain_InvoiceTemplate{id = ID} = Template}}, + [] = UnusedParams + }, + { + {ok, #ext_InvoiceTemplateAndToken{invoice_template = Template}}, + UnusedParams + }, + { + {ok, #{<<"invoiceTemplate">> := #{<<"id">> := ID}}}, + [ + [<<"description">>], + [<<"externalID">>], + [<<"partyID">>] + ] = _OtherUnusedParams + } + ], + Result + ). -spec create_invoice_template_fail_test(config()) -> _. create_invoice_template_fail_test(Config) -> From 238449bbc39e3d7bf2b86e115fe6c929bfcd68e2 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 27 Nov 2025 12:12:16 +0300 Subject: [PATCH 6/9] Adds get update and delete functions to invoice-templating handler --- .../src/capi_handler_invoice_templates.erl | 84 ++++++++++++++----- .../proto/capi_extensions.thrift | 32 +++++++ 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/apps/capi/src/capi_handler_invoice_templates.erl b/apps/capi/src/capi_handler_invoice_templates.erl index edf6ffd..7ef033e 100644 --- a/apps/capi/src/capi_handler_invoice_templates.erl +++ b/apps/capi/src/capi_handler_invoice_templates.erl @@ -217,31 +217,32 @@ prepare(_OperationID, _Req, _Context) -> -spec handle_function(woody:func(), woody:args(), woody_context:ctx(), _) -> {ok, term()} | no_return(). -handle_function('Create', {InvoiceTemplateParams}, WoodyContext, _Opts) -> +handle_function(Function, Args, WoodyContext, Opts) -> scoper:scope( invoice_templating, fun() -> - try - %% NOTE Use same operation ID as the original in swagger/JSON API - InvoiceTemplateID = generate_thrift_invoice_template_id( - 'CreateInvoiceTemplate', InvoiceTemplateParams, WoodyContext - ), - CallArgs = {encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, InvoiceTemplateParams)}, - capi_woody_client:call_service(invoice_templating, 'Create', CallArgs, WoodyContext) - of - {ok, InvoiceTpl} -> - {ok, make_thrift_invoice_tpl_and_token(InvoiceTpl, WoodyContext)}; + try handle_function_(Function, Args, WoodyContext, Opts) of + {ok, _} = Result -> + Result; + {exception, #payproc_InvoiceTemplateNotFound{} = Exception} -> + woody_error:raise(business, Exception); + {exception, #payproc_InvoiceTemplateRemoved{} = Exception} -> + woody_error:raise(business, Exception); + {exception, #payproc_PartyNotFound{} = Exception} -> + woody_error:raise(business, Exception); + {exception, #payproc_ShopNotFound{} = Exception} -> + woody_error:raise(business, Exception); + {exception, #payproc_InvalidPartyStatus{} = Exception} -> + woody_error:raise(business, Exception); + {exception, #payproc_InvalidShopStatus{} = Exception} -> + woody_error:raise(business, Exception); {exception, #base_InvalidRequest{errors = Errors}} -> - woody_error:raise(business, #ext_InvalidRequest{errors = Errors}); - {exception, #payproc_PartyNotFound{}} -> - woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Party not found">>]}); - {exception, #payproc_ShopNotFound{}} -> - woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Shop not found">>]}); - {exception, #payproc_InvalidPartyStatus{}} -> - woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Invalid party status">>]}); - {exception, #payproc_InvalidShopStatus{}} -> - woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Invalid shop status">>]}) + woody_error:raise(business, #ext_InvalidRequest{errors = Errors}) catch + throw:(#payproc_InvoiceTemplateNotFound{} = Exception) -> + woody_error:raise(business, Exception); + throw:(#payproc_InvoiceTemplateRemoved{} = Exception) -> + woody_error:raise(business, Exception); throw:invoice_cart_empty -> woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Wrong size. Path to item: cart">>]}); throw:zero_invoice_lifetime -> @@ -254,6 +255,26 @@ handle_function('Create', {InvoiceTemplateParams}, WoodyContext, _Opts) -> end ). +handle_function_('Create', {InvoiceTemplateParams}, WoodyContext, _Opts) -> + %% NOTE Use same operation ID as the original in swagger/JSON API + InvoiceTemplateID = generate_thrift_invoice_template_id( + 'CreateInvoiceTemplate', InvoiceTemplateParams, WoodyContext + ), + CallArgs = {encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, InvoiceTemplateParams)}, + case capi_woody_client:call_service(invoice_templating, 'Create', CallArgs, WoodyContext) of + {ok, InvoiceTpl} -> + {ok, make_thrift_invoice_tpl_and_token(InvoiceTpl, WoodyContext)}; + Passthrough -> + Passthrough + end; +handle_function_('Get', {InvoiceTemplateID}, WoodyContext, _Opts) -> + capi_woody_client:call_service(invoice_templating, 'Get', {InvoiceTemplateID}, WoodyContext); +handle_function_('Update', {InvoiceTemplateID, InvoiceTemplateParams}, WoodyContext, _Opts) -> + Params = encode_thrift_invoice_tpl_update_params(InvoiceTemplateParams), + capi_woody_client:call_service(invoice_templating, 'Update', {InvoiceTemplateID, Params}, WoodyContext); +handle_function_('Delete', {InvoiceTemplateID}, WoodyContext, _Opts) -> + capi_woody_client:call_service(invoice_templating, 'Delete', {InvoiceTemplateID}, WoodyContext). + mask_invoice_template_notfound(Resolution) -> % ED-206 % When bouncer says "forbidden" we can't really tell the difference between "forbidden because @@ -431,6 +452,29 @@ encode_invoice_tpl_update_params(Params) -> mutations = capi_mutation:encode_amount_randomization_params(genlib_map:get(<<"randomizeAmount">>, Params)) }. +encode_thrift_invoice_tpl_update_params(#ext_InvoiceTemplateUpdateParams{ + invoice_lifetime = InvoiceLifetime, + name = Name, + description = Description, + details = Details, + context = Context +}) -> + ok = assert_cart_is_not_empty(Details), + Product = get_product_from_tpl_details(Details), + #payproc_InvoiceTemplateUpdateParams{ + invoice_lifetime = InvoiceLifetime, + product = Product, + name = Name, + description = Description, + details = Details, + context = Context + }. + +assert_cart_is_not_empty({cart, #domain_InvoiceCart{lines = []}}) -> + throw(invoice_cart_empty); +assert_cart_is_not_empty(_) -> + ok. + make_invoice_tpl_and_token(InvoiceTpl, ProcessingContext) -> #{ <<"invoiceTemplate">> => decode_invoice_tpl(InvoiceTpl), diff --git a/apps/capi_extensions/proto/capi_extensions.thrift b/apps/capi_extensions/proto/capi_extensions.thrift index 533385f..1101e46 100644 --- a/apps/capi_extensions/proto/capi_extensions.thrift +++ b/apps/capi_extensions/proto/capi_extensions.thrift @@ -24,6 +24,15 @@ struct InvoiceTemplateCreateParams { 8: required domain.InvoiceContext context } +// Based on `payment_processing.InvoiceTemplateUpdateParams` +struct InvoiceTemplateUpdateParams { + 1: optional domain.LifetimeInterval invoice_lifetime + 2: optional string name + 3: optional string description + 4: optional domain.InvoiceTemplateDetails details + 5: optional domain.InvoiceContext context +} + struct AccessToken { 1: required string payload } @@ -43,4 +52,27 @@ service InvoiceTemplating { 4: payment_processing.InvalidShopStatus ex4, 5: InvalidRequest ex5 ) + + domain.InvoiceTemplate Get (2: domain.InvoiceTemplateID id) + throws ( + 1: payment_processing.InvoiceTemplateNotFound ex1, + 2: payment_processing.InvoiceTemplateRemoved ex2 + ) + + domain.InvoiceTemplate Update (2: domain.InvoiceTemplateID id, 3: InvoiceTemplateUpdateParams params) + throws ( + 1: payment_processing.InvoiceTemplateNotFound ex1, + 2: payment_processing.InvoiceTemplateRemoved ex2, + 3: payment_processing.InvalidPartyStatus ex3, + 4: payment_processing.InvalidShopStatus ex4, + 5: InvalidRequest ex5 + ) + + void Delete (2: domain.InvoiceTemplateID id) + throws ( + 1: payment_processing.InvoiceTemplateNotFound ex1, + 2: payment_processing.InvoiceTemplateRemoved ex2, + 3: payment_processing.InvalidPartyStatus ex3, + 4: payment_processing.InvalidShopStatus ex4 + ) } From 3b97d31c6d6639a80c66658ef6656ac71b8ef90a Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Mon, 1 Dec 2025 12:07:49 +0300 Subject: [PATCH 7/9] Adds invoice-templating woody testsuite --- .../capi_invoice_template_woody_SUITE.erl | 233 ++++++++++++++++++ .../keys/local/jwk.priv.json | 10 + .../keys/local/jwk.publ.json | 9 + .../keys/local/private.pem | 9 + 4 files changed, 261 insertions(+) create mode 100644 apps/capi/test/capi_invoice_template_woody_SUITE.erl create mode 100644 apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.priv.json create mode 100644 apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.publ.json create mode 100644 apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/private.pem diff --git a/apps/capi/test/capi_invoice_template_woody_SUITE.erl b/apps/capi/test/capi_invoice_template_woody_SUITE.erl new file mode 100644 index 0000000..524598e --- /dev/null +++ b/apps/capi/test/capi_invoice_template_woody_SUITE.erl @@ -0,0 +1,233 @@ +-module(capi_invoice_template_woody_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). + +-include_lib("capi_dummy_data.hrl"). +-include_lib("damsel/include/dmsl_payproc_thrift.hrl"). +%% -include_lib("damsel/include/dmsl_base_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("capi_extensions/include/capi_ext_thrift.hrl"). + +-export([all/0]). +-export([groups/0]). +-export([init_per_suite/1]). +-export([end_per_suite/1]). +-export([init_per_group/2]). +-export([end_per_group/2]). +-export([init_per_testcase/2]). +-export([end_per_testcase/2]). + +-export([init/1]). + +-export([create_invoice_template_ok_test/1]). +-export([update_invoice_template_ok_test/1]). +-export([get_invoice_template_ok_test/1]). +-export([delete_invoice_template_ok_test/1]). + +-type test_case_name() :: atom(). +-type config() :: [{atom(), any()}]. +-type group_name() :: atom(). + +-behaviour(supervisor). + +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +init([]) -> + {ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}. + +-spec all() -> [{group, test_case_name()}]. +all() -> + [ + {group, default} + ]. + +-spec groups() -> [{group_name(), list(), [test_case_name()]}]. +groups() -> + [ + %% NOTE Sequential execution due to mocks. + {default, [sequence], [ + create_invoice_template_ok_test, + update_invoice_template_ok_test, + get_invoice_template_ok_test, + delete_invoice_template_ok_test + ]} + ]. + +%% +%% starting/stopping +%% +-spec init_per_suite(config()) -> config(). +init_per_suite(Config0) -> + capi_ct_helper:init_suite(?MODULE, Config0). + +-spec end_per_suite(config()) -> _. +end_per_suite(C) -> + _ = capi_ct_helper:stop_mocked_service_sup(?config(suite_test_sup, C)), + _ = [application:stop(App) || App <- proplists:get_value(apps, C)], + ok. + +-spec init_per_group(group_name(), config()) -> config(). +init_per_group(_GroupName, Config) -> + SupPid = capi_ct_helper:start_mocked_service_sup(?MODULE), + Apps1 = capi_ct_helper_token_keeper:mock_user_session_token(SupPid), + [ + {group_apps, Apps1}, + {group_test_sup, SupPid} + | Config + ]. + +-spec end_per_group(group_name(), config()) -> _. +end_per_group(_Group, C) -> + _ = capi_utils:'maybe'(?config(group_test_sup, C), fun capi_ct_helper:stop_mocked_service_sup/1), + ok. + +-spec init_per_testcase(test_case_name(), config()) -> config(). +init_per_testcase(_Name, C) -> + [{test_sup, capi_ct_helper:start_mocked_service_sup(?MODULE)} | C]. + +-spec end_per_testcase(test_case_name(), config()) -> _. +end_per_testcase(_Name, C) -> + capi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)), + ok. + +%% TESTS + +-spec create_invoice_template_ok_test(config()) -> _. +create_invoice_template_ok_test(Config) -> + Params = #ext_InvoiceTemplateCreateParams{ + external_id = ?STRING, + party_id = #domain_PartyConfigRef{id = <<"2">>}, + shop_id = #domain_ShopConfigRef{id = <<"1">>}, + invoice_lifetime = #domain_LifetimeInterval{days = ?INTEGER, months = ?INTEGER, years = ?INTEGER}, + description = <<"Sample text">>, + details = + {cart, #domain_InvoiceCart{ + lines = [ + #domain_InvoiceLine{ + product = ?STRING, + quantity = ?INTEGER, + price = ?CASH, + metadata = #{?STRING => {obj, #{}}} + }, + #domain_InvoiceLine{ + product = ?STRING, + quantity = ?INTEGER, + price = ?CASH, + metadata = #{<<"TaxMode">> => {str, <<"18%">>}} + } + ] + }}, + context = ?CONTENT + }, + InvoiceTemplateID = genlib:unique(), + _ = capi_ct_helper:mock_services( + [ + {invoice_templating, fun( + 'Create', + {#payproc_InvoiceTemplateCreateParams{template_id = TemplateID}} + ) -> + {ok, ?INVOICE_TPL(TemplateID)} + end}, + {bender, fun('GenerateID', {_Key, _, _Ctx}) -> + {ok, capi_ct_helper_bender:get_result(InvoiceTemplateID)} + end} + ], + Config + ), + ?assertMatch( + {ok, #ext_InvoiceTemplateAndToken{ + invoice_template = #domain_InvoiceTemplate{id = InvoiceTemplateID}, + invoice_template_access_token = #ext_AccessToken{payload = ?API_TOKEN} + }}, + woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ + url => "http://localhost:8022/v2/extensions/invoice_templating", + event_handler => scoper_woody_event_handler + }) + ). + +-spec update_invoice_template_ok_test(config()) -> _. +update_invoice_template_ok_test(Config) -> + Params = #ext_InvoiceTemplateUpdateParams{ + invoice_lifetime = #domain_LifetimeInterval{days = ?INTEGER, months = ?INTEGER, years = ?INTEGER}, + description = <<"Sample text">>, + details = + {cart, #domain_InvoiceCart{ + lines = [ + #domain_InvoiceLine{ + product = ?STRING, + quantity = ?INTEGER, + price = ?CASH, + metadata = #{?STRING => {obj, #{}}} + }, + #domain_InvoiceLine{ + product = ?STRING, + quantity = ?INTEGER, + price = ?CASH, + metadata = #{<<"TaxMode">> => {str, <<"18%">>}} + } + ] + }}, + context = ?CONTENT + }, + InvoiceTemplateID = genlib:unique(), + _ = capi_ct_helper:mock_services( + [ + {invoice_templating, fun( + 'Update', + {TemplateID, #payproc_InvoiceTemplateUpdateParams{ + invoice_lifetime = _, + description = _, + details = _, + context = ?CONTENT + }} + ) -> + {ok, ?INVOICE_TPL(TemplateID)} + end} + ], + Config + ), + ?assertMatch( + {ok, #domain_InvoiceTemplate{id = InvoiceTemplateID}}, + woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Update', {InvoiceTemplateID, Params}}, #{ + url => "http://localhost:8022/v2/extensions/invoice_templating", + event_handler => scoper_woody_event_handler + }) + ). + +-spec get_invoice_template_ok_test(config()) -> _. +get_invoice_template_ok_test(Config) -> + InvoiceTemplateID = genlib:unique(), + _ = capi_ct_helper:mock_services( + [ + {invoice_templating, fun('Get', {TemplateID}) -> + {ok, ?INVOICE_TPL(TemplateID)} + end} + ], + Config + ), + ?assertMatch( + {ok, #domain_InvoiceTemplate{id = InvoiceTemplateID}}, + woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Get', {InvoiceTemplateID}}, #{ + url => "http://localhost:8022/v2/extensions/invoice_templating", + event_handler => scoper_woody_event_handler + }) + ). + +-spec delete_invoice_template_ok_test(config()) -> _. +delete_invoice_template_ok_test(Config) -> + InvoiceTemplateID = genlib:unique(), + _ = capi_ct_helper:mock_services( + [ + {invoice_templating, fun('Delete', {TemplateID}) when TemplateID =:= InvoiceTemplateID -> + {ok, ok} + end} + ], + Config + ), + ?assertMatch( + {ok, ok}, + woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Delete', {InvoiceTemplateID}}, #{ + url => "http://localhost:8022/v2/extensions/invoice_templating", + event_handler => scoper_woody_event_handler + }) + ). diff --git a/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.priv.json b/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.priv.json new file mode 100644 index 0000000..e7d6557 --- /dev/null +++ b/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.priv.json @@ -0,0 +1,10 @@ +{ + "use": "enc", + "kty": "EC", + "kid": "kxdD0orVPGoAxWrqAMTeQ0U5MRoK47uZxWiSJdgo0t0", + "crv": "P-256", + "alg": "ECDH-ES", + "x": "nHi7TCgBwfrPuNTf49bGvJMczk6WZOI-mCKAghbrOlM", + "y": "_8kiXGOIWkfz57m8K5dmTfbYzCJVYHZZZisCfbYicr0", + "d": "i45qDiARZ5qbS_uzeT-CiKnPUe64qHitKaVdAvcN6TI" +} \ No newline at end of file diff --git a/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.publ.json b/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.publ.json new file mode 100644 index 0000000..00b7002 --- /dev/null +++ b/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/jwk.publ.json @@ -0,0 +1,9 @@ +{ + "use": "enc", + "kty": "EC", + "kid": "kxdD0orVPGoAxWrqAMTeQ0U5MRoK47uZxWiSJdgo0t0", + "crv": "P-256", + "alg": "ECDH-ES", + "x": "nHi7TCgBwfrPuNTf49bGvJMczk6WZOI-mCKAghbrOlM", + "y": "_8kiXGOIWkfz57m8K5dmTfbYzCJVYHZZZisCfbYicr0" +} \ No newline at end of file diff --git a/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/private.pem b/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/private.pem new file mode 100644 index 0000000..4e6d12c --- /dev/null +++ b/apps/capi/test/capi_invoice_template_woody_SUITE_data/keys/local/private.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAK9fx7qOJT7Aoseu7KKgaLagBh3wvDzg7F/ZMtGbPFikJnnvRWvF +B5oEGbMPblvtF0/fjqfu+eqjP3Z1tUSn7TkCAwEAAQJABUY5KIgr4JZEjwLYxQ9T +9uIbLP1Xe/E7yqoqmBk2GGhSrPY0OeRkYnUVLcP96UPQhF63iuG8VF6uZ7oAPsq+ +gQIhANZy3jSCzPjXYHRU1kRqQzpt2S+OqoEiqQ6YG1HrC/VxAiEA0Vq6JlQK2tOX +37SS00dK0Qog4Qi8dN73GliFQNP18EkCIQC4epSA48zkfJMzQBAbRraSuxDNApPX +BzQbo+pMrEDbYQIgY4AncQgIkLB4Qk5kah48JNYXglzQlQtTjiX8Ty9ueGECIQCM +GD3UbQKiA0gf5plBA24I4wFVKxxa4wXbW/7SfP6XmQ== +-----END RSA PRIVATE KEY----- From 5d8eaa5e8ce27393eb2c9c83bc859261019ae762 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Tue, 2 Dec 2025 11:20:18 +0300 Subject: [PATCH 8/9] Moves API extensions to damsel --- apps/capi/src/capi.app.src | 1 - .../src/capi_handler_invoice_templates.erl | 22 +++--- apps/capi/src/capi_sup.erl | 2 +- .../test/capi_idempotency_tests_SUITE.erl | 12 +-- .../capi_invoice_template_woody_SUITE.erl | 18 ++--- apps/capi_extensions/include/.gitignore | 2 - .../proto/capi_extensions.thrift | 78 ------------------- apps/capi_extensions/rebar.config | 15 ---- apps/capi_extensions/rebar.lock | 1 - apps/capi_extensions/src/.gitignore | 3 - .../src/capi_extensions.app.src | 10 --- rebar.config | 2 +- rebar.lock | 2 +- 13 files changed, 29 insertions(+), 139 deletions(-) delete mode 100644 apps/capi_extensions/include/.gitignore delete mode 100644 apps/capi_extensions/proto/capi_extensions.thrift delete mode 100644 apps/capi_extensions/rebar.config delete mode 100644 apps/capi_extensions/rebar.lock delete mode 100644 apps/capi_extensions/src/.gitignore delete mode 100644 apps/capi_extensions/src/capi_extensions.app.src diff --git a/apps/capi/src/capi.app.src b/apps/capi/src/capi.app.src index b49942a..4ce6d78 100644 --- a/apps/capi/src/capi.app.src +++ b/apps/capi/src/capi.app.src @@ -30,7 +30,6 @@ bouncer_client, token_keeper_client, party_client, - capi_extensions, opentelemetry_api, opentelemetry_exporter, opentelemetry diff --git a/apps/capi/src/capi_handler_invoice_templates.erl b/apps/capi/src/capi_handler_invoice_templates.erl index 7ef033e..b1ccc8d 100644 --- a/apps/capi/src/capi_handler_invoice_templates.erl +++ b/apps/capi/src/capi_handler_invoice_templates.erl @@ -3,7 +3,7 @@ -include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). -include_lib("damsel/include/dmsl_payproc_thrift.hrl"). --include_lib("capi_extensions/include/capi_ext_thrift.hrl"). +-include_lib("damsel/include/dmsl_api_ext_thrift.hrl"). -behaviour(capi_handler). -export([prepare/3]). @@ -237,18 +237,18 @@ handle_function(Function, Args, WoodyContext, Opts) -> {exception, #payproc_InvalidShopStatus{} = Exception} -> woody_error:raise(business, Exception); {exception, #base_InvalidRequest{errors = Errors}} -> - woody_error:raise(business, #ext_InvalidRequest{errors = Errors}) + woody_error:raise(business, #base_InvalidRequest{errors = Errors}) catch throw:(#payproc_InvoiceTemplateNotFound{} = Exception) -> woody_error:raise(business, Exception); throw:(#payproc_InvoiceTemplateRemoved{} = Exception) -> woody_error:raise(business, Exception); throw:invoice_cart_empty -> - woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Wrong size. Path to item: cart">>]}); + woody_error:raise(business, #base_InvalidRequest{errors = [<<"Wrong size. Path to item: cart">>]}); throw:zero_invoice_lifetime -> - woody_error:raise(business, #ext_InvalidRequest{errors = [<<"Lifetime cannot be zero">>]}); + woody_error:raise(business, #base_InvalidRequest{errors = [<<"Lifetime cannot be zero">>]}); throw:{external_id_conflict, _ID, _UsedExternalID, _Schema} -> - woody_error:raise(business, #ext_InvalidRequest{ + woody_error:raise(business, #base_InvalidRequest{ errors = [<<"This 'externalID' has been used by another request">>] }) end @@ -311,7 +311,7 @@ generate_invoice_template_id(OperationID, TemplateParams, PartyID, #{woody_conte generate_thrift_invoice_template_id( OperationID, - #ext_InvoiceTemplateCreateParams{external_id = ExternalID, party_id = #domain_PartyConfigRef{id = PartyID}} = + #api_ext_InvoiceTemplateCreateParams{external_id = ExternalID, party_id = #domain_PartyConfigRef{id = PartyID}} = TemplateParams, WoodyContext ) -> @@ -322,7 +322,7 @@ generate_thrift_invoice_template_id( ), capi_bender:gen_snowflake(IdempKey, Identity, WoodyContext). -decode_to_feature_container(#ext_InvoiceTemplateCreateParams{ +decode_to_feature_container(#api_ext_InvoiceTemplateCreateParams{ shop_id = #domain_ShopConfigRef{id = ShopID}, invoice_lifetime = #domain_LifetimeInterval{days = DD, months = MM, years = YY}, details = Details @@ -452,7 +452,7 @@ encode_invoice_tpl_update_params(Params) -> mutations = capi_mutation:encode_amount_randomization_params(genlib_map:get(<<"randomizeAmount">>, Params)) }. -encode_thrift_invoice_tpl_update_params(#ext_InvoiceTemplateUpdateParams{ +encode_thrift_invoice_tpl_update_params(#api_ext_InvoiceTemplateUpdateParams{ invoice_lifetime = InvoiceLifetime, name = Name, description = Description, @@ -481,7 +481,7 @@ make_invoice_tpl_and_token(InvoiceTpl, ProcessingContext) -> <<"invoiceTemplateAccessToken">> => capi_handler_utils:issue_access_token(InvoiceTpl, ProcessingContext) }. -encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, #ext_InvoiceTemplateCreateParams{ +encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, #api_ext_InvoiceTemplateCreateParams{ party_id = PartyID, shop_id = ShopID, invoice_lifetime = InvoiceLifetime, @@ -510,9 +510,9 @@ make_thrift_invoice_tpl_and_token(InvoiceTpl, WoodyContext) -> shop => InvoiceTpl#domain_InvoiceTemplate.shop_ref#domain_ShopConfigRef.id }, TokenPayload = capi_auth:issue_access_token(TokenSpec, WoodyContext), - #ext_InvoiceTemplateAndToken{ + #api_ext_InvoiceTemplateAndToken{ invoice_template = InvoiceTpl, - invoice_template_access_token = #ext_AccessToken{payload = TokenPayload} + invoice_template_access_token = #api_ext_AccessToken{payload = TokenPayload} }. encode_invoice_tpl_details(#{<<"templateType">> := <<"InvoiceTemplateSingleLine">>} = Details) -> diff --git a/apps/capi/src/capi_sup.erl b/apps/capi/src/capi_sup.erl index b977807..9714c10 100644 --- a/apps/capi/src/capi_sup.erl +++ b/apps/capi/src/capi_sup.erl @@ -53,7 +53,7 @@ get_woody_child_spec() -> handlers => [ %% TODO Proper path {"/v2/extensions/invoice_templating", { - {capi_ext_thrift, 'InvoiceTemplating'}, {capi_handler_invoice_templates, #{}} + {dmsl_api_ext_thrift, 'InvoiceTemplating'}, {capi_handler_invoice_templates, #{}} }} ], additional_routes => [], diff --git a/apps/capi/test/capi_idempotency_tests_SUITE.erl b/apps/capi/test/capi_idempotency_tests_SUITE.erl index 2662e1a..f37442f 100644 --- a/apps/capi/test/capi_idempotency_tests_SUITE.erl +++ b/apps/capi/test/capi_idempotency_tests_SUITE.erl @@ -7,7 +7,7 @@ -include_lib("damsel/include/dmsl_payproc_thrift.hrl"). -include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). --include_lib("capi_extensions/include/capi_ext_thrift.hrl"). +-include_lib("damsel/include/dmsl_api_ext_thrift.hrl"). -export([all/0]). -export([groups/0]). @@ -470,7 +470,7 @@ create_invoice_template_ok_test(Config) -> create_invoice_template_with_woody_ok_test(Config) -> BenderKey = <<"create_invoice_template_with_woody_ok_test_bender_key">>, ExternalID = genlib:unique(), - CreateParams = #ext_InvoiceTemplateCreateParams{ + CreateParams = #api_ext_InvoiceTemplateCreateParams{ external_id = ExternalID, party_id = #domain_PartyConfigRef{id = <<"2">>}, shop_id = #domain_ShopConfigRef{id = <<"1">>}, @@ -526,14 +526,14 @@ create_invoice_template_with_woody_ok_test(Config) -> %% Two thrift calls [ with_feature_storage(fun() -> - woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ + woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ url => "http://localhost:8022/v2/extensions/invoice_templating", event_handler => scoper_woody_event_handler }) end) || Params <- [ CreateParams, - CreateParams#ext_InvoiceTemplateCreateParams{description = <<"whatever">>} + CreateParams#api_ext_InvoiceTemplateCreateParams{description = <<"whatever">>} ] ] ++ %% And one swag request @@ -551,11 +551,11 @@ create_invoice_template_with_woody_ok_test(Config) -> ?assertMatch( [ { - {ok, #ext_InvoiceTemplateAndToken{invoice_template = #domain_InvoiceTemplate{id = ID} = Template}}, + {ok, #api_ext_InvoiceTemplateAndToken{invoice_template = #domain_InvoiceTemplate{id = ID} = Template}}, [] = UnusedParams }, { - {ok, #ext_InvoiceTemplateAndToken{invoice_template = Template}}, + {ok, #api_ext_InvoiceTemplateAndToken{invoice_template = Template}}, UnusedParams }, { diff --git a/apps/capi/test/capi_invoice_template_woody_SUITE.erl b/apps/capi/test/capi_invoice_template_woody_SUITE.erl index 524598e..7b08cc6 100644 --- a/apps/capi/test/capi_invoice_template_woody_SUITE.erl +++ b/apps/capi/test/capi_invoice_template_woody_SUITE.erl @@ -7,7 +7,7 @@ -include_lib("damsel/include/dmsl_payproc_thrift.hrl"). %% -include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). --include_lib("capi_extensions/include/capi_ext_thrift.hrl"). +-include_lib("damsel/include/dmsl_api_ext_thrift.hrl"). -export([all/0]). -export([groups/0]). @@ -94,7 +94,7 @@ end_per_testcase(_Name, C) -> -spec create_invoice_template_ok_test(config()) -> _. create_invoice_template_ok_test(Config) -> - Params = #ext_InvoiceTemplateCreateParams{ + Params = #api_ext_InvoiceTemplateCreateParams{ external_id = ?STRING, party_id = #domain_PartyConfigRef{id = <<"2">>}, shop_id = #domain_ShopConfigRef{id = <<"1">>}, @@ -135,11 +135,11 @@ create_invoice_template_ok_test(Config) -> Config ), ?assertMatch( - {ok, #ext_InvoiceTemplateAndToken{ + {ok, #api_ext_InvoiceTemplateAndToken{ invoice_template = #domain_InvoiceTemplate{id = InvoiceTemplateID}, - invoice_template_access_token = #ext_AccessToken{payload = ?API_TOKEN} + invoice_template_access_token = #api_ext_AccessToken{payload = ?API_TOKEN} }}, - woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ + woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ url => "http://localhost:8022/v2/extensions/invoice_templating", event_handler => scoper_woody_event_handler }) @@ -147,7 +147,7 @@ create_invoice_template_ok_test(Config) -> -spec update_invoice_template_ok_test(config()) -> _. update_invoice_template_ok_test(Config) -> - Params = #ext_InvoiceTemplateUpdateParams{ + Params = #api_ext_InvoiceTemplateUpdateParams{ invoice_lifetime = #domain_LifetimeInterval{days = ?INTEGER, months = ?INTEGER, years = ?INTEGER}, description = <<"Sample text">>, details = @@ -188,7 +188,7 @@ update_invoice_template_ok_test(Config) -> ), ?assertMatch( {ok, #domain_InvoiceTemplate{id = InvoiceTemplateID}}, - woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Update', {InvoiceTemplateID, Params}}, #{ + woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'Update', {InvoiceTemplateID, Params}}, #{ url => "http://localhost:8022/v2/extensions/invoice_templating", event_handler => scoper_woody_event_handler }) @@ -207,7 +207,7 @@ get_invoice_template_ok_test(Config) -> ), ?assertMatch( {ok, #domain_InvoiceTemplate{id = InvoiceTemplateID}}, - woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Get', {InvoiceTemplateID}}, #{ + woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'Get', {InvoiceTemplateID}}, #{ url => "http://localhost:8022/v2/extensions/invoice_templating", event_handler => scoper_woody_event_handler }) @@ -226,7 +226,7 @@ delete_invoice_template_ok_test(Config) -> ), ?assertMatch( {ok, ok}, - woody_client:call({{capi_ext_thrift, 'InvoiceTemplating'}, 'Delete', {InvoiceTemplateID}}, #{ + woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'Delete', {InvoiceTemplateID}}, #{ url => "http://localhost:8022/v2/extensions/invoice_templating", event_handler => scoper_woody_event_handler }) diff --git a/apps/capi_extensions/include/.gitignore b/apps/capi_extensions/include/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/apps/capi_extensions/include/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/apps/capi_extensions/proto/capi_extensions.thrift b/apps/capi_extensions/proto/capi_extensions.thrift deleted file mode 100644 index 1101e46..0000000 --- a/apps/capi_extensions/proto/capi_extensions.thrift +++ /dev/null @@ -1,78 +0,0 @@ -// NOTE "proto/base.thrift" is ambiguous, because both damsel and bouncer-proto -// have such thrift-file. -// include "proto/base.thrift" -include "proto/domain.thrift" -include "proto/payment_processing.thrift" - -namespace java dev.vality.capi -namespace erlang capi.ext - -// NOTE Stolen from damsel/proto/base.thrift -exception InvalidRequest { - 1: required list errors -} - -// Based on `payment_processing.InvoiceTemplateCreateParams` -struct InvoiceTemplateCreateParams { - 1: optional string external_id - 2: required domain.PartyConfigRef party_id - 3: required domain.ShopConfigRef shop_id - 4: required domain.LifetimeInterval invoice_lifetime - 5: optional string name - 6: optional string description - 7: required domain.InvoiceTemplateDetails details - 8: required domain.InvoiceContext context -} - -// Based on `payment_processing.InvoiceTemplateUpdateParams` -struct InvoiceTemplateUpdateParams { - 1: optional domain.LifetimeInterval invoice_lifetime - 2: optional string name - 3: optional string description - 4: optional domain.InvoiceTemplateDetails details - 5: optional domain.InvoiceContext context -} - -struct AccessToken { - 1: required string payload -} - -struct InvoiceTemplateAndToken { - 1: required domain.InvoiceTemplate invoice_template - 2: required AccessToken invoice_template_access_token -} - -service InvoiceTemplating { - - InvoiceTemplateAndToken Create (1: InvoiceTemplateCreateParams params) - throws ( - 1: payment_processing.PartyNotFound ex1, - 2: payment_processing.InvalidPartyStatus ex2, - 3: payment_processing.ShopNotFound ex3, - 4: payment_processing.InvalidShopStatus ex4, - 5: InvalidRequest ex5 - ) - - domain.InvoiceTemplate Get (2: domain.InvoiceTemplateID id) - throws ( - 1: payment_processing.InvoiceTemplateNotFound ex1, - 2: payment_processing.InvoiceTemplateRemoved ex2 - ) - - domain.InvoiceTemplate Update (2: domain.InvoiceTemplateID id, 3: InvoiceTemplateUpdateParams params) - throws ( - 1: payment_processing.InvoiceTemplateNotFound ex1, - 2: payment_processing.InvoiceTemplateRemoved ex2, - 3: payment_processing.InvalidPartyStatus ex3, - 4: payment_processing.InvalidShopStatus ex4, - 5: InvalidRequest ex5 - ) - - void Delete (2: domain.InvoiceTemplateID id) - throws ( - 1: payment_processing.InvoiceTemplateNotFound ex1, - 2: payment_processing.InvoiceTemplateRemoved ex2, - 3: payment_processing.InvalidPartyStatus ex3, - 4: payment_processing.InvalidShopStatus ex4 - ) -} diff --git a/apps/capi_extensions/rebar.config b/apps/capi_extensions/rebar.config deleted file mode 100644 index e4b4d61..0000000 --- a/apps/capi_extensions/rebar.config +++ /dev/null @@ -1,15 +0,0 @@ -{plugins, [ - {rebar3_thrift_compiler, {git, "https://github.com/valitydev/rebar3-thrift-compiler.git", {tag, "0.4"}}} -]}. - -{provider_hooks, [ - {pre, [ - {compile, {thrift, compile}}, - {clean, {thrift, clean}} - ]} -]}. - -{thrift_compiler_opts, [ - {in_dir, "proto"}, - {gen, "erlang:app_namespaces"} -]}. diff --git a/apps/capi_extensions/rebar.lock b/apps/capi_extensions/rebar.lock deleted file mode 100644 index 57afcca..0000000 --- a/apps/capi_extensions/rebar.lock +++ /dev/null @@ -1 +0,0 @@ -[]. diff --git a/apps/capi_extensions/src/.gitignore b/apps/capi_extensions/src/.gitignore deleted file mode 100644 index 2ac90e6..0000000 --- a/apps/capi_extensions/src/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!capi_extensions.app.src \ No newline at end of file diff --git a/apps/capi_extensions/src/capi_extensions.app.src b/apps/capi_extensions/src/capi_extensions.app.src deleted file mode 100644 index ced8c1b..0000000 --- a/apps/capi_extensions/src/capi_extensions.app.src +++ /dev/null @@ -1,10 +0,0 @@ -{application, capi_extensions, [ - {description, "CAPI extensions definitions"}, - {vsn, "1"}, - {applications, [ - kernel, - stdlib, - damsel - ]}, - {maintainers, []} -]}. diff --git a/rebar.config b/rebar.config index ec9b66b..be10d03 100644 --- a/rebar.config +++ b/rebar.config @@ -36,7 +36,7 @@ {cowboy_draining_server, {git, "https://github.com/valitydev/cowboy_draining_server.git", {branch, "master"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, {woody_user_identity, {git, "https://github.com/valitydev/woody_erlang_user_identity.git", {tag, "v1.1.0"}}}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.18"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "ft/thrift-capi-extensions"}}}, {bender_proto, {git, "https://github.com/valitydev/bender-proto.git", {branch, "master"}}}, {bender_client, {git, "https://github.com/valitydev/bender-client-erlang.git", {tag, "v1.1.0"}}}, {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.3"}}}, diff --git a/rebar.lock b/rebar.lock index ce1f5ce..7b37209 100644 --- a/rebar.lock +++ b/rebar.lock @@ -41,7 +41,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"368f05b67c5bf0543856895e71da5c019aba51a1"}}, + {ref,"4996ada9e4f1be2a53b5d3463d6a345ee1fecff7"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", From 1cfe0da91de43a7c1e88bdd74cc6038680ddbf13 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Tue, 2 Dec 2025 11:32:13 +0300 Subject: [PATCH 9/9] Bumps damsel --- rebar.config | 2 +- rebar.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index be10d03..0267d7d 100644 --- a/rebar.config +++ b/rebar.config @@ -36,7 +36,7 @@ {cowboy_draining_server, {git, "https://github.com/valitydev/cowboy_draining_server.git", {branch, "master"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, {woody_user_identity, {git, "https://github.com/valitydev/woody_erlang_user_identity.git", {tag, "v1.1.0"}}}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "ft/thrift-capi-extensions"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.21"}}}, {bender_proto, {git, "https://github.com/valitydev/bender-proto.git", {branch, "master"}}}, {bender_client, {git, "https://github.com/valitydev/bender-client-erlang.git", {tag, "v1.1.0"}}}, {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.3"}}}, diff --git a/rebar.lock b/rebar.lock index 7b37209..e42f5f8 100644 --- a/rebar.lock +++ b/rebar.lock @@ -41,7 +41,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"4996ada9e4f1be2a53b5d3463d6a345ee1fecff7"}}, + {ref,"0ac1d9c85e61a3f7428e1a6bc56330116e5a8129"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git",