Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/capi/src/capi_handler_decoder_invoicing.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}.

%%
Expand Down
89 changes: 74 additions & 15 deletions apps/capi/src/capi_handler_invoice_templates.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
logic_error/2,
conflict_error/1,
invalid_url_params_error/1,
invalid_url_params_message/1,
map_service_result/1
]).

Expand All @@ -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(
Expand All @@ -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)};
Expand All @@ -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 ->
Expand Down Expand Up @@ -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(
Expand All @@ -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) ->
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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{
Expand All @@ -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) ->
Expand Down
4 changes: 3 additions & 1 deletion apps/capi/src/capi_handler_invoices.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down
64 changes: 50 additions & 14 deletions apps/capi/src/capi_handler_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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]).
Expand Down Expand Up @@ -94,17 +95,21 @@ 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),
<<$\n>>,
<<"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">>.

%%%

Expand Down Expand Up @@ -163,34 +168,65 @@ 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">> => <<BaseUrl/binary, $?, EncodedParams/binary>>}
<<BaseUrl/binary, $?, EncodedParams/binary>>
end.

-define(CHECKOUT_BASE_URL(BaseUrl), #domain_CheckoutLocations{
locations = [#domain_CheckoutLocation{base_url = BaseUrl} | _]
}).

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
) ->
Expand Down
7 changes: 4 additions & 3 deletions apps/capi/src/capi_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 => [],
Expand Down
Loading
Loading