diff --git a/apps/capi/src/capi_handler_decoder_invoicing.erl b/apps/capi/src/capi_handler_decoder_invoicing.erl index 6b78c37..56559cc 100644 --- a/apps/capi/src/capi_handler_decoder_invoicing.erl +++ b/apps/capi/src/capi_handler_decoder_invoicing.erl @@ -729,7 +729,9 @@ make_invoice_and_token(Invoice, UrlParams, ProcessingContext) -> #{ <<"invoice">> => decode_invoice(Invoice), <<"invoiceAccessToken">> => InvoiceAccessToken, - <<"invoiceUrl">> => capi_handler_utils:create_checkout_url(Invoice, AccessToken, UrlParams, ProcessingContext) + <<"invoiceUrl">> => #{ + <<"url">> => capi_handler_utils:create_checkout_url(Invoice, AccessToken, UrlParams, ProcessingContext) + } }. %% diff --git a/apps/capi/src/capi_handler_invoice_templates.erl b/apps/capi/src/capi_handler_invoice_templates.erl index 5d58166..4240f9b 100644 --- a/apps/capi/src/capi_handler_invoice_templates.erl +++ b/apps/capi/src/capi_handler_invoice_templates.erl @@ -16,6 +16,7 @@ logic_error/2, conflict_error/1, invalid_url_params_error/1, + invalid_url_params_message/1, map_service_result/1 ]). @@ -34,7 +35,9 @@ prepare('CreateInvoiceTemplate' = OperationID, Req, Context) -> {ok, Resolution} end, Process = fun() -> + UrlParams = maps:get(<<"urlParams">>, InvoiceTemplateParams, #{}), try + ok = validate_checkout_url_params(UrlParams), InvoiceTemplateID = generate_invoice_template_id(OperationID, InvoiceTemplateParams, PartyID, Context), CallArgs = {encode_invoice_tpl_create_params(InvoiceTemplateID, PartyID, InvoiceTemplateParams)}, capi_handler_utils:service_call( @@ -43,7 +46,7 @@ prepare('CreateInvoiceTemplate' = OperationID, Req, Context) -> ) of {ok, InvoiceTpl} -> - {ok, {201, #{}, make_invoice_tpl_and_token(InvoiceTpl, Context)}}; + {ok, {201, #{}, make_invoice_tpl_and_token(InvoiceTpl, UrlParams, Context)}}; {exception, #base_InvalidRequest{errors = Errors}} -> FormattedErrors = capi_handler_utils:format_request_errors(Errors), {ok, logic_error('invalidRequest', FormattedErrors)}; @@ -56,6 +59,8 @@ prepare('CreateInvoiceTemplate' = OperationID, Req, Context) -> {exception, #payproc_InvalidShopStatus{}} -> {ok, logic_error('invalidShopStatus', <<"Invalid shop status">>)} catch + throw:{invalid_url_params, Reason} -> + {ok, invalid_url_params_error(Reason)}; throw:invoice_cart_empty -> {ok, logic_error('invalidInvoiceCart', <<"Wrong size. Path to item: cart">>)}; throw:zero_invoice_lifetime -> @@ -228,7 +233,7 @@ prepare('GetInvoicePaymentMethodsByTemplateID' = OperationID, Req, Context) -> prepare(_OperationID, _Req, _Context) -> {error, noimpl}. --spec handle_function(woody:func(), woody:args(), woody_context:ctx(), _) -> +-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), #{party_client := party_client:client()}) -> {ok, term()} | no_return(). handle_function(Function, Args, WoodyContext, Opts) -> scoper:scope( @@ -252,6 +257,8 @@ handle_function(Function, Args, WoodyContext, Opts) -> {exception, #base_InvalidRequest{errors = Errors}} -> woody_error:raise(business, #base_InvalidRequest{errors = Errors}) catch + throw:{invalid_url_params, Reason} -> + woody_error:raise(business, #base_InvalidRequest{errors = [invalid_url_params_message(Reason)]}); throw:(#payproc_InvoiceTemplateNotFound{} = Exception) -> woody_error:raise(business, Exception); throw:(#payproc_InvoiceTemplateRemoved{} = Exception) -> @@ -268,15 +275,17 @@ handle_function(Function, Args, WoodyContext, Opts) -> end ). -handle_function_('Create', {InvoiceTemplateParams}, WoodyContext, _Opts) -> +handle_function_('Create', {InvoiceTemplateParams}, WoodyContext, Opts) -> + #api_ext_InvoiceTemplateCreateParams{url_params = UrlParams} = InvoiceTemplateParams, + ok = validate_checkout_url_params(UrlParams), %% NOTE Use same operation ID as the original in swagger/JSON API - InvoiceTemplateID = generate_thrift_invoice_template_id( - 'CreateInvoiceTemplate', InvoiceTemplateParams, WoodyContext - ), + OperationID = 'CreateInvoiceTemplate', + ProcessingContext = create_processing_context(OperationID, WoodyContext, Opts), + InvoiceTemplateID = generate_thrift_invoice_template_id(OperationID, 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)}; + {ok, make_thrift_invoice_tpl_and_token(InvoiceTpl, UrlParams, ProcessingContext)}; Passthrough -> Passthrough end; @@ -286,7 +295,29 @@ handle_function_('Update', {InvoiceTemplateID, InvoiceTemplateParams}, WoodyCont 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). + capi_woody_client:call_service(invoice_templating, 'Delete', {InvoiceTemplateID}, WoodyContext); +handle_function_('CreateUrl', {InvoiceTemplateID, UrlParams}, WoodyContext, Opts) -> + ok = validate_checkout_url_params(UrlParams), + OperationID = 'CreateInvoiceTemplateUrl', + ProcessingContext = create_processing_context(OperationID, WoodyContext, Opts), + case capi_woody_client:call_service(invoice_templating, 'Get', {InvoiceTemplateID}, WoodyContext) of + {ok, InvoiceTpl} -> + {_, InvoiceTemplateUrl} = make_thrift_invoice_tpl_token_and_url(InvoiceTpl, UrlParams, ProcessingContext), + {ok, InvoiceTemplateUrl}; + Passthrough -> + Passthrough + end. + +create_processing_context(OperationID, WoodyContext, Opts) -> + %% Creates processing context w/o swagger context to reuse it in existing + %% code passes for url generation + #{ + operation_id => OperationID, + woody_context => WoodyContext, + swagger_context => #{}, + party_client_context => party_client:create_context(#{woody_context => WoodyContext}), + party_client => maps:get(party_client, Opts) + }. mask_invoice_template_notfound(Resolution) -> % ED-206 @@ -299,6 +330,8 @@ mask_invoice_template_notfound(Resolution) -> %% +validate_checkout_url_params(undefined) -> + ok; validate_checkout_url_params(UrlParams) -> case capi_handler_utils:validate_checkout_url_params(UrlParams) of ok -> ok; @@ -494,10 +527,17 @@ assert_cart_is_not_empty({cart, #domain_InvoiceCart{lines = []}}) -> assert_cart_is_not_empty(_) -> ok. -make_invoice_tpl_and_token(InvoiceTpl, ProcessingContext) -> +make_invoice_tpl_and_token(InvoiceTpl, UrlParams, ProcessingContext) -> + #{<<"payload">> := AccessToken} = + InvoiceTemplateAccessToken = capi_handler_utils:issue_access_token(InvoiceTpl, ProcessingContext), #{ <<"invoiceTemplate">> => decode_invoice_tpl(InvoiceTpl), - <<"invoiceTemplateAccessToken">> => capi_handler_utils:issue_access_token(InvoiceTpl, ProcessingContext) + <<"invoiceTemplateAccessToken">> => InvoiceTemplateAccessToken, + <<"invoiceTemplateUrl">> => #{ + <<"url">> => capi_handler_utils:create_checkout_url( + InvoiceTpl, AccessToken, UrlParams, ProcessingContext + ) + } }. encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, #api_ext_InvoiceTemplateCreateParams{ @@ -522,16 +562,35 @@ encode_thrift_invoice_tpl_create_params(InvoiceTemplateID, #api_ext_InvoiceTempl context = Context }. -make_thrift_invoice_tpl_and_token(InvoiceTpl, WoodyContext) -> +make_thrift_invoice_tpl_token_and_url( + #domain_InvoiceTemplate{ + id = InvoiceTemplateID, + party_ref = #domain_PartyConfigRef{id = PartyID}, + shop_ref = #domain_ShopConfigRef{id = ShopID} + } = InvoiceTpl, + UrlParams, + #{woody_context := WoodyContext} = ProcessingContext +) -> 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 + scope => {invoice_template, InvoiceTemplateID}, + party => PartyID, + shop => ShopID }, TokenPayload = capi_auth:issue_access_token(TokenSpec, WoodyContext), + InvoiceTemplateAccessToken = #api_ext_AccessToken{payload = TokenPayload}, + Url = capi_handler_utils:create_checkout_url( + InvoiceTpl, TokenPayload, genlib:define(UrlParams, #{}), ProcessingContext + ), + InvoiceTemplateUrl = #api_ext_InvoiceTemplateUrl{url = Url}, + {InvoiceTemplateAccessToken, InvoiceTemplateUrl}. + +make_thrift_invoice_tpl_and_token(InvoiceTpl, UrlParams, ProcessingContext) -> + {InvoiceTemplateAccessToken, InvoiceTemplateUrl} = + make_thrift_invoice_tpl_token_and_url(InvoiceTpl, UrlParams, ProcessingContext), #api_ext_InvoiceTemplateAndToken{ invoice_template = InvoiceTpl, - invoice_template_access_token = #api_ext_AccessToken{payload = TokenPayload} + invoice_template_access_token = InvoiceTemplateAccessToken, + invoice_template_url = InvoiceTemplateUrl }. encode_invoice_tpl_details(#{<<"templateType">> := <<"InvoiceTemplateSingleLine">>} = Details) -> diff --git a/apps/capi/src/capi_handler_invoices.erl b/apps/capi/src/capi_handler_invoices.erl index c4f4a32..9e68f27 100644 --- a/apps/capi/src/capi_handler_invoices.erl +++ b/apps/capi/src/capi_handler_invoices.erl @@ -115,7 +115,9 @@ prepare('CreateInvoiceUrl' = OperationID, Req, Context) -> capi_handler:respond_if_undefined(ResultInvoice, general_error(404, <<"Invoice not found">>)), Invoice = ResultInvoice#payproc_Invoice.invoice, #{<<"payload">> := AccessToken} = capi_handler_utils:issue_access_token(Invoice, Context), - Response = capi_handler_utils:create_checkout_url(Invoice, AccessToken, UrlParams, Context), + Response = #{ + <<"url">> => capi_handler_utils:create_checkout_url(Invoice, AccessToken, UrlParams, Context) + }, {ok, {201, #{}, Response}}; {error, Reason} -> {ok, invalid_url_params_error(Reason)} diff --git a/apps/capi/src/capi_handler_utils.erl b/apps/capi/src/capi_handler_utils.erl index a770470..6f7255b 100644 --- a/apps/capi/src/capi_handler_utils.erl +++ b/apps/capi/src/capi_handler_utils.erl @@ -11,6 +11,7 @@ -export([server_error/1]). -export([format_request_errors/1]). -export([invalid_url_params_error/1]). +-export([invalid_url_params_message/1]). -export([assert_party_accessible/2]). -export([run_if_party_accessible/3]). @@ -94,7 +95,11 @@ format_request_errors([]) -> <<>>; format_request_errors(Errors) -> genlib_string:join(<<"\n">>, Errors). -spec invalid_url_params_error(term()) -> response(). -invalid_url_params_error({bad_keys, [_ | _] = BadKeys, Whitelist}) when is_list(BadKeys) andalso is_list(Whitelist) -> +invalid_url_params_error(Reason) -> + logic_error('invalidUrlParams', invalid_url_params_message(Reason)). + +-spec invalid_url_params_message(term()) -> binary(). +invalid_url_params_message({bad_keys, [_ | _] = BadKeys, Whitelist}) when is_list(BadKeys) andalso is_list(Whitelist) -> Message = [ <<"Bad keys: ">>, genlib_string:join(<<", ">>, BadKeys), @@ -102,9 +107,9 @@ invalid_url_params_error({bad_keys, [_ | _] = BadKeys, Whitelist}) when is_list( <<"Allowed keys: ">>, genlib_string:join(<<", ">>, Whitelist) ], - logic_error('invalidUrlParams', genlib_string:join(<<>>, Message)); -invalid_url_params_error(_Reason) -> - logic_error('invalidUrlParams', <<"Bad URL params">>). + genlib_string:join(<<>>, Message); +invalid_url_params_message(_Reason) -> + <<"Bad URL params">>. %%% @@ -163,26 +168,43 @@ validate_checkout_url_params(Params0) -> end. -spec create_checkout_url( - dmsl_domain_thrift:'Invoice'(), + dmsl_domain_thrift:'Invoice'() | dmsl_domain_thrift:'InvoiceTemplate'(), token_keeper_client:token(), url_params(), processing_context() -) -> map() | no_return(). -create_checkout_url(Invoice, AccessToken, Params0, ProcessingContext) -> +) -> binary() | no_return(). +create_checkout_url(#domain_Invoice{id = InvoiceID} = Invoice, AccessToken, Params0, ProcessingContext) -> UrlGenOpts = genlib_app:env(capi, checkout_url_generation), + BaseUrl = get_base_url(Invoice, UrlGenOpts, ProcessingContext), + BaseParams = #{ + <<"invoiceID">> => InvoiceID, + <<"invoiceAccessToken">> => AccessToken + }, + create_checkout_url_(BaseUrl, BaseParams, Params0, UrlGenOpts); +create_checkout_url( + #domain_InvoiceTemplate{id = InvoiceTemplateID} = InvoiceTemplate, + AccessToken, + Params0, + ProcessingContext +) -> + UrlGenOpts = genlib_app:env(capi, checkout_url_generation), + BaseUrl = get_base_url(InvoiceTemplate, UrlGenOpts, ProcessingContext), + BaseParams = #{ + <<"invoiceTemplateID">> => InvoiceTemplateID, + <<"invoiceTemplateAccessToken">> => AccessToken + }, + create_checkout_url_(BaseUrl, BaseParams, Params0, UrlGenOpts). + +create_checkout_url_(BaseUrl, BaseParams, Params0, UrlGenOpts) -> Params1 = maps:with(maps:get(params_whitelist, UrlGenOpts, []), Params0), %% TODO Warn if params filtered out - Params2 = maps:merge(Params1, #{ - <<"invoiceID">> => Invoice#domain_Invoice.id, - <<"invoiceAccessToken">> => AccessToken - }), - BaseUrl = get_base_url(Invoice, UrlGenOpts, ProcessingContext), + Params2 = maps:merge(Params1, BaseParams), %% TODO Sanitize params? case uri_string:compose_query(maps:to_list(Params2), [{encoding, utf8}]) of {error, Error, Term} -> erlang:throw({Error, Term}); EncodedParams -> - #{<<"url">> => <>} + <> end. -define(CHECKOUT_BASE_URL(BaseUrl), #domain_CheckoutLocations{ @@ -190,7 +212,21 @@ create_checkout_url(Invoice, AccessToken, Params0, ProcessingContext) -> }). get_base_url( - #domain_Invoice{party_ref = #domain_PartyConfigRef{id = PartyID}, shop_ref = #domain_ShopConfigRef{id = ShopID}}, + #domain_Invoice{party_ref = PartyRef, shop_ref = ShopRef}, + UrlGenOpts, + ProcessingContext +) -> + get_base_url_(PartyRef, ShopRef, UrlGenOpts, ProcessingContext); +get_base_url( + #domain_InvoiceTemplate{party_ref = PartyRef, shop_ref = ShopRef}, + UrlGenOpts, + ProcessingContext +) -> + get_base_url_(PartyRef, ShopRef, UrlGenOpts, ProcessingContext). + +get_base_url_( + #domain_PartyConfigRef{id = PartyID}, + #domain_ShopConfigRef{id = ShopID}, #{default_base_url := Default}, ProcessingContext ) -> diff --git a/apps/capi/src/capi_sup.erl b/apps/capi/src/capi_sup.erl index 9714c10..49cf203 100644 --- a/apps/capi/src/capi_sup.erl +++ b/apps/capi/src/capi_sup.erl @@ -32,14 +32,14 @@ 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(), + WoodyChildSPec = get_woody_child_spec(PartyClient), {ok, { {one_for_all, 0, 1}, [LechiffreSpec, SwaggerSpec, PartyClientSpec, WoodyChildSPec] }}. -get_woody_child_spec() -> +get_woody_child_spec(PartyClient) -> {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( @@ -53,7 +53,8 @@ get_woody_child_spec() -> handlers => [ %% TODO Proper path {"/v2/extensions/invoice_templating", { - {dmsl_api_ext_thrift, 'InvoiceTemplating'}, {capi_handler_invoice_templates, #{}} + {dmsl_api_ext_thrift, 'InvoiceTemplating'}, + {capi_handler_invoice_templates, #{party_client => PartyClient}} }} ], additional_routes => [], diff --git a/apps/capi/test/capi_base_api_token_tests_SUITE.erl b/apps/capi/test/capi_base_api_token_tests_SUITE.erl index 5d53ff8..031c7d2 100644 --- a/apps/capi/test/capi_base_api_token_tests_SUITE.erl +++ b/apps/capi/test/capi_base_api_token_tests_SUITE.erl @@ -34,6 +34,7 @@ create_invoice_url_ok_test/1, create_invoice_url_not_allowed_test/1, create_invoice_template_ok_test/1, + create_invoice_template_bad_keys_test/1, create_invoice_template_w_randomization_ok_test/1, create_invoice_with_template_test/1, create_invoice_with_template_bad_keys_test/1, @@ -138,6 +139,7 @@ groups() -> create_invoice_url_ok_test, create_invoice_url_not_allowed_test, create_invoice_template_ok_test, + create_invoice_template_bad_keys_test, create_invoice_template_w_randomization_ok_test, create_invoice_template_autorization_error_test, create_invoice_with_template_test, @@ -497,11 +499,16 @@ create_invoice_template_ok_test(Config) -> Config ), _ = capi_ct_helper_bouncer:mock_assert_shop_op_ctx(<<"CreateInvoiceTemplate">>, ?STRING, ?STRING, Config), + UrlParams = #{ + <<"theme">> => ?STRING, + <<"locale">> => ?STRING + }, Req = #{ <<"shopID">> => ?STRING, <<"lifetime">> => capi_ct_helper:get_lifetime(), <<"description">> => <<"test_invoice_template_description">>, - <<"metadata">> => #{<<"invoice_template_dummy_metadata">> => <<"test_value">>} + <<"metadata">> => #{<<"invoice_template_dummy_metadata">> => <<"test_value">>}, + <<"urlParams">> => UrlParams }, Details0 = #{ <<"templateType">> => <<"InvoiceTemplateSingleLine">>, @@ -513,9 +520,34 @@ create_invoice_template_ok_test(Config) -> } }, {ok, _} = capi_client_invoice_templates:create(?config(context, Config), Req#{<<"details">> => Details0}), - {ok, _} = capi_client_invoice_templates:create(?config(context, Config), Req#{ - <<"details">> => ?INVOICE_TMPL_DETAILS_PARAMS - }). + {ok, #{<<"invoiceTemplate">> := _, <<"invoiceTemplateUrl">> := InvoiceTemplateUrl}} = + capi_client_invoice_templates:create(?config(context, Config), Req#{ + <<"details">> => ?INVOICE_TMPL_DETAILS_PARAMS + }), + assert_invoice_template_url(?STRING, ?CHECKOUT_URL, UrlParams, InvoiceTemplateUrl). + +-spec create_invoice_template_bad_keys_test(config()) -> _. +create_invoice_template_bad_keys_test(Config) -> + _ = capi_ct_helper_bouncer:mock_assert_shop_op_ctx(<<"CreateInvoiceTemplate">>, ?STRING, ?STRING, Config), + UrlParams = #{ + <<"theme">> => ?STRING, + <<"locale">> => ?STRING, + <<"not-whitelisted">> => ?STRING + }, + Req = #{ + <<"shopID">> => ?STRING, + <<"lifetime">> => capi_ct_helper:get_lifetime(), + <<"description">> => <<"test_invoice_template_description">>, + <<"metadata">> => #{<<"invoice_template_dummy_metadata">> => <<"test_value">>}, + <<"urlParams">> => UrlParams + }, + ?assertMatch( + {error, + {400, #{<<"code">> := <<"invalidUrlParams">>, <<"message">> := <<"Bad keys: not-whitelisted", _/binary>>}}}, + capi_client_invoice_templates:create(?config(context, Config), Req#{ + <<"details">> => ?INVOICE_TMPL_DETAILS_PARAMS + }) + ). -spec create_invoice_template_w_randomization_ok_test(config()) -> _. create_invoice_template_w_randomization_ok_test(Config) -> @@ -1948,3 +1980,15 @@ assert_invoice_url(InvoiceID, BaseUrl, Params0, InvoiceUrl) -> ), Expected = <>, ?assertMatch(#{<<"url">> := Expected}, InvoiceUrl). + +-spec assert_invoice_template_url(binary(), binary(), map(), map()) -> ok | no_return(). +assert_invoice_template_url(InvoiceTemplateID, BaseUrl, Params0, InvoiceTemplateUrl) -> + EncodedParams = uri_string:compose_query( + maps:to_list(Params0#{ + <<"invoiceTemplateAccessToken">> => ?API_TOKEN, + <<"invoiceTemplateID">> => InvoiceTemplateID + }), + [{encoding, utf8}] + ), + Expected = <>, + ?assertMatch(#{<<"url">> := Expected}, InvoiceTemplateUrl). diff --git a/apps/capi/test/capi_invoice_template_woody_SUITE.erl b/apps/capi/test/capi_invoice_template_woody_SUITE.erl index 7b08cc6..50dee24 100644 --- a/apps/capi/test/capi_invoice_template_woody_SUITE.erl +++ b/apps/capi/test/capi_invoice_template_woody_SUITE.erl @@ -5,7 +5,6 @@ -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("damsel/include/dmsl_api_ext_thrift.hrl"). @@ -21,9 +20,12 @@ -export([init/1]). -export([create_invoice_template_ok_test/1]). +-export([create_invoice_template_bad_keys_test/1]). -export([update_invoice_template_ok_test/1]). -export([get_invoice_template_ok_test/1]). -export([delete_invoice_template_ok_test/1]). +-export([create_invoice_template_url_ok_test/1]). +-export([create_invoice_template_url_bad_keys_test/1]). -type test_case_name() :: atom(). -type config() :: [{atom(), any()}]. @@ -47,9 +49,12 @@ groups() -> %% NOTE Sequential execution due to mocks. {default, [sequence], [ create_invoice_template_ok_test, + create_invoice_template_bad_keys_test, update_invoice_template_ok_test, get_invoice_template_ok_test, - delete_invoice_template_ok_test + delete_invoice_template_ok_test, + create_invoice_template_url_ok_test, + create_invoice_template_url_bad_keys_test ]} ]. @@ -92,33 +97,40 @@ end_per_testcase(_Name, C) -> %% TESTS +-define(INVOICE_TPL_CREATE_PARAMS(UrlParams), #api_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, + url_params = UrlParams +}). + -spec create_invoice_template_ok_test(config()) -> _. create_invoice_template_ok_test(Config) -> - Params = #api_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 + UrlParams = #{ + <<"theme">> => ?STRING, + <<"locale">> => ?STRING }, + Params = ?INVOICE_TPL_CREATE_PARAMS(UrlParams), InvoiceTemplateID = genlib:unique(), _ = capi_ct_helper:mock_services( [ @@ -134,10 +146,12 @@ create_invoice_template_ok_test(Config) -> ], Config ), + ExpectedUrl = make_invoice_url(InvoiceTemplateID, ?CHECKOUT_URL, UrlParams), ?assertMatch( {ok, #api_ext_InvoiceTemplateAndToken{ invoice_template = #domain_InvoiceTemplate{id = InvoiceTemplateID}, - invoice_template_access_token = #api_ext_AccessToken{payload = ?API_TOKEN} + invoice_template_access_token = #api_ext_AccessToken{payload = ?API_TOKEN}, + invoice_template_url = #api_ext_InvoiceTemplateUrl{url = ExpectedUrl} }}, woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'Create', {Params}}, #{ url => "http://localhost:8022/v2/extensions/invoice_templating", @@ -145,6 +159,22 @@ create_invoice_template_ok_test(Config) -> }) ). +-spec create_invoice_template_bad_keys_test(config()) -> _. +create_invoice_template_bad_keys_test(_Config) -> + UrlParams = #{ + <<"theme">> => ?STRING, + <<"locale">> => ?STRING, + <<"not-whitelisted">> => ?STRING + }, + Params = ?INVOICE_TPL_CREATE_PARAMS(UrlParams), + ?assertMatch( + {exception, #base_InvalidRequest{errors = [<<"Bad keys: not-whitelisted", _/binary>>]}}, + woody_client:call({{dmsl_api_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 = #api_ext_InvoiceTemplateUpdateParams{ @@ -231,3 +261,55 @@ delete_invoice_template_ok_test(Config) -> event_handler => scoper_woody_event_handler }) ). + +-spec create_invoice_template_url_ok_test(config()) -> _. +create_invoice_template_url_ok_test(Config) -> + UrlParams = #{ + <<"theme">> => ?STRING, + <<"locale">> => ?STRING + }, + InvoiceTemplateID = genlib:unique(), + _ = capi_ct_helper:mock_services( + [ + {invoice_templating, fun('Get', {TemplateID}) -> + {ok, ?INVOICE_TPL(TemplateID)} + end} + ], + Config + ), + ExpectedUrl = make_invoice_url(InvoiceTemplateID, ?CHECKOUT_URL, UrlParams), + ?assertMatch( + {ok, #api_ext_InvoiceTemplateUrl{url = ExpectedUrl}}, + woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'CreateUrl', {InvoiceTemplateID, UrlParams}}, #{ + url => "http://localhost:8022/v2/extensions/invoice_templating", + event_handler => scoper_woody_event_handler + }) + ). + +-spec create_invoice_template_url_bad_keys_test(config()) -> _. +create_invoice_template_url_bad_keys_test(_Config) -> + UrlParams = #{ + <<"theme">> => ?STRING, + <<"locale">> => ?STRING, + <<"not-whitelisted">> => ?STRING + }, + InvoiceTemplateID = genlib:unique(), + ?assertMatch( + {exception, #base_InvalidRequest{errors = [<<"Bad keys: not-whitelisted", _/binary>>]}}, + woody_client:call({{dmsl_api_ext_thrift, 'InvoiceTemplating'}, 'CreateUrl', {InvoiceTemplateID, UrlParams}}, #{ + url => "http://localhost:8022/v2/extensions/invoice_templating", + event_handler => scoper_woody_event_handler + }) + ). + +%% + +make_invoice_url(InvoiceTemplateID, BaseUrl, Params0) -> + EncodedParams = uri_string:compose_query( + maps:to_list(Params0#{ + <<"invoiceTemplateAccessToken">> => ?API_TOKEN, + <<"invoiceTemplateID">> => InvoiceTemplateID + }), + [{encoding, utf8}] + ), + <>. diff --git a/rebar.config b/rebar.config index 96325de..4ebf233 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.39"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.42"}}}, {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 653552e..1191a2b 100644 --- a/rebar.lock +++ b/rebar.lock @@ -23,7 +23,7 @@ {git,"https://github.com/rbkmoney/cg_mon.git", {ref,"5a87a37694e42b6592d3b4164ae54e0e87e24e18"}}, 1}, - {<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.15.1">>},2}, + {<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.16.0">>},2}, {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.12.0">>},0}, {<<"cowboy_access_log">>, {git,"https://github.com/valitydev/cowboy_access_log.git", @@ -41,7 +41,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"25e75a53bbdc2cdbda7da266d05ae626fa3fa69b"}}, + {ref,"99c91c97a06145846edde0ed763ba15d885826ad"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -65,7 +65,7 @@ {ref,"d2324089afbbd9630e85fac554620f1de0b33dfe"}}, 0}, {<<"gproc">>,{pkg,<<"gproc">>,<<"0.9.0">>},1}, - {<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.17.1">>},1}, + {<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.18.0">>},1}, {<<"gun">>, {git,"https://github.com/ninenines/gun.git", {ref,"e7dd9f227e46979d8073e71c683395a809b78cb4"}}, @@ -126,18 +126,18 @@ {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},2}, {<<"swag_client">>, {git,"https://github.com/valitydev/swag-payments", - {ref,"fc4d014dd6d1a9b86bb1efa9fd36b2a718ee755d"}}, + {ref,"e34951eef8c78688a7dba5d531ff546d33eadebe"}}, 0}, {<<"swag_server">>, {git,"https://github.com/valitydev/swag-payments", - {ref,"1e0ffc210a419c42fa10955b16a2405399a2cc70"}}, + {ref,"7d87588593d79d7d3de6e81b93610461144a6c8b"}}, 0}, {<<"thrift">>, {git,"https://github.com/valitydev/thrift_erlang.git", {ref,"3a60e5dc5bbd709495024f26e100b041c3547fd9"}}, 1}, {<<"tls_certificate_check">>, - {pkg,<<"tls_certificate_check">>,<<"1.31.0">>}, + {pkg,<<"tls_certificate_check">>,<<"1.33.0">>}, 1}, {<<"token_keeper_client">>, {git,"https://github.com/valitydev/token-keeper-client.git", @@ -162,13 +162,13 @@ {<<"acceptor_pool">>, <<"D88C2E8A0BE9216CF513FBCD3E5A4BEB36BEE3FF4168E85D6152C6F899359CDB">>}, {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, {<<"certifi">>, <<"DBAB8E5E155A0763EEA978C913CA280A6B544BFA115633FA20249C3D396D9493">>}, - {<<"chatterbox">>, <<"5CAC4D15DD7AD61FC3C4415CE4826FC563D4643DEE897A558EC4EA0B1C835C9C">>}, + {<<"chatterbox">>, <<"9D062F566235B6DEB5FF94C4A4EB332B7CFF28D9CCF281B91DE10FE74E1F59FE">>}, {<<"cowboy">>, <<"F276D521A1FF88B2B9B4C54D0E753DA6C66DD7BE6C9FCA3D9418B561828A3731">>}, {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, {<<"ctx">>, <<"8FF88B70E6400C4DF90142E7F130625B82086077A45364A78D208ED3ED53C7FE">>}, {<<"email_validator">>, <<"7E09A862E9AA99AE2CA6FD2A718D2B94360E32940A1339B53DFEE6B774BCDB03">>}, {<<"gproc">>, <<"853CCB7805E9ADA25D227A157BA966F7B34508F386A3E7E21992B1B484230699">>}, - {<<"grpcbox">>, <<"6E040AB3EF16FE699FFB513B0EF8E2E896DA7B18931A1EF817143037C454BCCE">>}, + {<<"grpcbox">>, <<"CCC14EC546509E0952D406EF626C5856B68D36A40BE6040FA444B375B33BD14F">>}, {<<"hackney">>, <<"99DA4674592504D3FB0CFEF0DB84C3BA02B4508BAE2DFF8C0108BAA0D6E0977C">>}, {<<"hpack">>, <<"2461899CC4AB6A0EF8E970C1661C5FC6A52D3C25580BC6DD204F84CE94669926">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, @@ -185,20 +185,20 @@ {<<"quantile_estimator">>, <<"EF50A361F11B5F26B5F16D0696E46A9E4661756492C981F7B2229EF42FF1CD15">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, - {<<"tls_certificate_check">>, <<"9A910B54D8CB96CC810CABF4C0129F21360F82022B20180849F1442A25CCBB04">>}, + {<<"tls_certificate_check">>, <<"01E0822A2EBB0B207C4964E8D32AD8B1B8CE1CAF58F446E53F816D06DB953DE4">>}, {<<"unicode_util_compat">>, <<"A48703A25C170EEDADCA83B11E88985AF08D35F37C6F664D6DCFB106A97782FC">>}]}, {pkg_hash_ext,[ {<<"accept">>, <<"CA69388943F5DAD2E7232A5478F16086E3C872F48E32B88B378E1885A59F5649">>}, {<<"acceptor_pool">>, <<"F172F3D74513E8EDD445C257D596FC84DBDD56D2C6FA287434269648AE5A421E">>}, {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, {<<"certifi">>, <<"524C97B4991B3849DD5C17A631223896272C6B0AF446778BA4675A1DFF53BB7E">>}, - {<<"chatterbox">>, <<"4F75B91451338BC0DA5F52F3480FA6EF6E3A2AEECFC33686D6B3D0A0948F31AA">>}, + {<<"chatterbox">>, <<"34C145C702F3A8D22F49A189EB34579EF3DB68F9A98A82D19B5CF6E390AAD54F">>}, {<<"cowboy">>, <<"8A7ABE6D183372CEB21CAA2709BEC928AB2B72E18A3911AA1771639BEF82651E">>}, {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, {<<"ctx">>, <<"A14ED2D1B67723DBEBBE423B28D7615EB0BDCBA6FF28F2D1F1B0A7E1D4AA5FC2">>}, {<<"email_validator">>, <<"2B1E6DF7BB14155C8D7D131F1C95CF4676200BC056EEBA82123396833FF94DA2">>}, {<<"gproc">>, <<"587E8AF698CCD3504CF4BA8D90F893EDE2B0F58CABB8A916E2BF9321DE3CF10B">>}, - {<<"grpcbox">>, <<"4A3B5D7111DAABC569DC9CBD9B202A3237D81C80BF97212FBC676832CB0CEB17">>}, + {<<"grpcbox">>, <<"5EC9F8FE664AB51201B32C117A61511A1F9D6316771E3891BA8A88D289A732AB">>}, {<<"hackney">>, <<"DE16FF4996556C8548D512F4DBE22DD58A587BF3332E7FD362430A7EF3986B16">>}, {<<"hpack">>, <<"D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, @@ -215,6 +215,6 @@ {<<"quantile_estimator">>, <<"282A8A323CA2A845C9E6F787D166348F776C1D4A41EDE63046D72D422E3DA946">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, - {<<"tls_certificate_check">>, <<"9D2B41B128D5507BD8AD93E1A998E06D0AB2F9A772AF343F4C00BF76C6BE1532">>}, + {<<"tls_certificate_check">>, <<"CAB9A7439E2DBFE91B38104F2D8A4B6D61DBC4D3A5AD59AC364713A88C6CFD9B">>}, {<<"unicode_util_compat">>, <<"B3A917854CE3AE233619744AD1E0102E05673136776FB2FA76234F3E03B23642">>}]} ].