merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit ecffbd075a8a36de47a21fdb749e98cb0f5bc79b
parent 200aae1502376b78a8ddb98e07f230382070faac
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Sat, 17 Jan 2026 23:55:00 +0100

putting template parsers to util

Diffstat:
Msrc/backend/taler-merchant-httpd_get-templates-ID.c | 2+-
Msrc/backend/taler-merchant-httpd_helper.c | 210-------------------------------------------------------------------------------
Msrc/backend/taler-merchant-httpd_helper.h | 43+++++++------------------------------------
Msrc/backend/taler-merchant-httpd_post-using-templates.c | 164+++++++++++++------------------------------------------------------------------
Msrc/backend/taler-merchant-httpd_private-patch-templates-ID.c | 2+-
Msrc/backend/taler-merchant-httpd_private-post-templates.c | 2+-
Msrc/include/taler_merchant_util.h | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/lib/merchant_api_post_templates.c | 22++++++++++++++++++++--
Msrc/util/Makefile.am | 1+
Asrc/util/template_parse.c | 303+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 497 insertions(+), 404 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-templates-ID.c b/src/backend/taler-merchant-httpd_get-templates-ID.c @@ -525,7 +525,7 @@ TMH_get_templates_ID ( { MHD_RESULT ret; - switch (TMH_template_type_from_contract (tp.template_contract)) + switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract)) { case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: ret = handle_get_templates_inventory (connection, diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c @@ -490,216 +490,6 @@ TMH_validate_unit_price_array (const struct TALER_Amount *prices, } -enum TALER_MERCHANT_TemplateType -TMH_template_type_from_contract (const json_t *template_contract) -{ - const json_t *type_val; - - if (NULL == template_contract) - return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; - type_val = json_object_get (template_contract, - "template_type"); - if (! json_is_string (type_val)) - return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; - return TALER_MERCHANT_template_type_from_string ( - json_string_value (type_val)); -} - - -const char * -TMH_template_type_to_string (enum TALER_MERCHANT_TemplateType template_type) -{ - switch (template_type) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - return "fixed-order"; - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - return "inventory-cart"; - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - return "paivana"; - case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - break; - } - return NULL; -} - - -static bool -inventory_template_contract_valid (const json_t *template_contract) -{ - bool selected_all = false; - bool choose_one = false; - bool request_tip = false; - const json_t *selected_categories = NULL; - const json_t *selected_products = NULL; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("request_tip", - &request_tip), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("selected_all", - &selected_all), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_categories", - &selected_categories), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_products", - &selected_products), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("choose_one", - &choose_one), - NULL), - GNUNET_JSON_spec_end () - }; - const char *ename; - unsigned int eline; - - if (GNUNET_OK != - GNUNET_JSON_parse (template_contract, - ispec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid inventory template_contract for field %s\n", - ename); - return false; - } - if (NULL != selected_categories) - { - const json_t *entry; - size_t idx; - - json_array_foreach ((json_t *) selected_categories, idx, entry) - { - if (! json_is_integer (entry)) - { - GNUNET_break_op (0); - return false; - } - if (0 > json_integer_value (entry)) - { - GNUNET_break_op (0); - return false; - } - } - } - if (NULL != selected_products) - { - const json_t *entry; - size_t idx; - - json_array_foreach ((json_t *) selected_products, idx, entry) - { - if (! json_is_string (entry)) - { - GNUNET_break_op (0); - return false; - } - } - } - return true; -} - - -static bool -paivana_template_contract_valid (const json_t *template_contract) -{ - const char *paivana_id; - struct GNUNET_JSON_Specification pspec[] = { - GNUNET_JSON_spec_string ("paivana_id", - &paivana_id), - GNUNET_JSON_spec_end () - }; - const char *ename; - unsigned int eline; - - if (GNUNET_OK != - GNUNET_JSON_parse (template_contract, - pspec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid paivana template_contract for field %s\n", - ename); - return false; - } - (void) paivana_id; - /* TODO: PAIVANA validate paivana-specific fields beyond presence. */ - return true; -} - - -bool -TMH_template_contract_valid (const json_t *template_contract) -{ - const char *summary; - const char *currency; - struct TALER_Amount amount = { .value = 0}; - uint32_t minimum_age = 0; - struct GNUNET_TIME_Relative pay_duration = { 0 }; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &summary), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("currency", - &currency), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &amount), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("minimum_age", - &minimum_age), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_relative_time ("pay_duration", - &pay_duration), - NULL), - GNUNET_JSON_spec_end () - }; - const char *ename; - unsigned int eline; - enum TALER_MERCHANT_TemplateType template_type; - - if (GNUNET_OK != - GNUNET_JSON_parse (template_contract, - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid template_contract for field %s\n", - ename); - return false; - } - template_type = TMH_template_type_from_contract (template_contract); - switch (template_type) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - /* no further fields */ - return true; - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - return inventory_template_contract_valid (template_contract); - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - return paivana_template_contract_valid (template_contract); - case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - break; - } - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid template_type\n"); - return false; -} - - bool TMH_category_set_contains (const struct TMH_CategorySet *set, uint64_t id) diff --git a/src/backend/taler-merchant-httpd_helper.h b/src/backend/taler-merchant-httpd_helper.h @@ -87,23 +87,6 @@ enum GNUNET_GenericReturnValue TMH_validate_unit_price_array (const struct TALER_Amount *prices, size_t prices_len); -/** - * Determine template type from a template contract. - * - * @param template_contract contract JSON - * @return template type (defaults to fixed order) - */ -enum TALER_MERCHANT_TemplateType -TMH_template_type_from_contract (const json_t *template_contract); - -/** - * Convert template type to its string representation. - * - * @param template_type template type to convert - * @return string name or NULL for invalid types - */ -const char * -TMH_template_type_to_string (enum TALER_MERCHANT_TemplateType template_type); /** * Set of category IDs. @@ -222,18 +205,6 @@ TMH_unit_defaults_for_instance (const struct TMH_MerchantInstance *mi, /** - * Check if @a template_contract is a valid template_contract object in the sense of Taler's API - * definition. - * - * @param template_contract object to check - * @return true if @a template_location is an object - * representing a template_location. - */ -bool -TMH_template_contract_valid (const json_t *template_contract); - - -/** * Setup new wire method for the given @ payto_uri. * * @param payto_uri already validated payto URI @@ -373,13 +344,13 @@ TMH_make_order_status_url (struct MHD_Connection *con, * @param hr a `TALER_EXCHANGE_HttpResponse` */ #define TMH_pack_exchange_reply(hr) \ - GNUNET_JSON_pack_uint64 ("exchange_code", (hr)->ec), \ - GNUNET_JSON_pack_uint64 ("exchange_http_status", (hr)->http_status), \ - GNUNET_JSON_pack_uint64 ("exchange_ec", (hr)->ec), /* LEGACY */ \ - GNUNET_JSON_pack_uint64 ("exchange_hc", (hr)->http_status), /* LEGACY */ \ - GNUNET_JSON_pack_allow_null ( \ - GNUNET_JSON_pack_object_incref ("exchange_reply", (json_t *) (hr)-> \ - reply)) + GNUNET_JSON_pack_uint64 ("exchange_code", (hr)->ec), \ + GNUNET_JSON_pack_uint64 ("exchange_http_status", (hr)->http_status), \ + GNUNET_JSON_pack_uint64 ("exchange_ec", (hr)->ec), /* LEGACY */ \ + GNUNET_JSON_pack_uint64 ("exchange_hc", (hr)->http_status), /* LEGACY */ \ + GNUNET_JSON_pack_allow_null ( \ + GNUNET_JSON_pack_object_incref ("exchange_reply", (json_t *) (hr)-> \ + reply)) /** diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c @@ -28,6 +28,7 @@ #include "taler-merchant-httpd_private-post-orders.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_exchanges.h" +#include "taler_merchant_util.h" #include <taler/taler_json_lib.h> /** @@ -239,74 +240,7 @@ struct UseContext /** * Information set in the #USE_PHASE_PARSE_TEMPLATE phase. */ - struct - { - - /** - * Summary from the template contract. - */ - const char *tsummary; - - /** - * Minimum age required by the template. - */ - uint32_t min_age; - - /** - * How long does the customer have to pay for the - * order. 0 if not specified (use instance default). - */ - struct GNUNET_TIME_Relative pay_duration; - - /** - * Currency from the template contract. - */ - const char *tcurrency; - - /** - * Amount from the template contract. - */ - struct TALER_Amount tamount; - - /** - * True if @e tamount was not provided. - */ - bool no_tamount; - - /** - * Parsed fields for inventory templates. - */ - struct - { - - /** - * Selected categories from the template contract. - */ - const json_t *selected_categories; - - /** - * Selected products from the template contract. - */ - const json_t *selected_products; - - /** - * Whether all products are selectable. - */ - bool selected_all; - - /** - * Template requires exactly one selection. - */ - bool choose_one; - - /** - * Template allows tips. - */ - bool request_tip; - - } inventory; - - } parse_template; + struct TALER_MERCHANT_TemplateContract template_contract; /** * Information set in the #USE_PHASE_COMPUTE_PRICE phase. @@ -653,7 +587,7 @@ handle_phase_lookup_template ( break; } if (uc->template_type != - TMH_template_type_from_contract ( + TALER_MERCHANT_template_type_from_contract ( uc->lookup_template.etp.template_contract)) { GNUNET_break_op (0); @@ -677,61 +611,15 @@ handle_phase_lookup_template ( * @param[in,out] uc use context */ static void -handle_phase_parse_template (struct UseContext *uc) +handle_phase_template_contract (struct UseContext *uc) { - struct GNUNET_JSON_Specification tcspec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &uc->parse_template.tsummary), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("currency", - &uc->parse_template.tcurrency), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &uc->parse_template.tamount), - &uc->parse_template.no_tamount), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("minimum_age", - &uc->parse_template.min_age), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_relative_time ("pay_duration", - &uc->parse_template.pay_duration), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ( - "selected_categories", - &uc->parse_template.inventory.selected_categories), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ( - "selected_products", - &uc->parse_template.inventory.selected_products), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("choose_one", - &uc->parse_template.inventory.choose_one), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("request_tip", - &uc->parse_template.inventory.request_tip), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("selected_all", - &uc->parse_template.inventory.selected_all), - NULL), - GNUNET_JSON_spec_end () - }; const char *err_name; - unsigned int err_line; enum GNUNET_GenericReturnValue res; - res = GNUNET_JSON_parse (uc->lookup_template.etp.template_contract, - tcspec, - &err_name, - &err_line); + res = TALER_MERCHANT_template_contract_parse ( + uc->lookup_template.etp.template_contract, + &uc->template_contract, + &err_name); if (GNUNET_OK != res) { GNUNET_break (0); @@ -902,7 +790,7 @@ category_allowed (const json_t *allowed_categories, static enum GNUNET_GenericReturnValue verify_using_templates_inventory (struct UseContext *uc) { - if (uc->parse_template.inventory.choose_one && + if (uc->template_contract.inventory.choose_one && (1 != uc->parse_request.inventory.items_len)) { GNUNET_break_op (0); @@ -912,7 +800,7 @@ verify_using_templates_inventory (struct UseContext *uc) "inventory_selection"); return GNUNET_SYSERR; } - if (uc->parse_template.inventory.selected_all) + if (uc->template_contract.inventory.selected_all) return GNUNET_OK; for (unsigned int i = 0; i < uc->parse_request.inventory.items_len; @@ -960,11 +848,11 @@ verify_using_templates_inventory (struct UseContext *uc) struct InventoryTemplateItemContext *item = &uc->parse_request.inventory.items[i]; - if (product_id_allowed (uc->parse_template.inventory.selected_products, + if (product_id_allowed (uc->template_contract.inventory.selected_products, item->product_id)) continue; if (category_allowed ( - uc->parse_template.inventory.selected_categories, + uc->template_contract.inventory.selected_categories, item->num_categories, item->categories)) continue; @@ -993,7 +881,7 @@ verify_using_templates_fixed ( struct UseContext *uc) { if ( (! uc->parse_request.no_amount) && - (! uc->parse_template.no_tamount) ) + (! uc->template_contract.no_amount) ) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1003,7 +891,7 @@ verify_using_templates_fixed ( return GNUNET_SYSERR; } if (uc->parse_request.no_amount && - uc->parse_template.no_tamount) + uc->template_contract.no_amount) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1044,7 +932,7 @@ handle_phase_verify ( enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; if ( (NULL != uc->parse_request.summary) && - (NULL != uc->parse_template.tsummary) ) + (NULL != uc->template_contract.summary) ) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1054,7 +942,7 @@ handle_phase_verify ( return; } if ( (NULL == uc->parse_request.summary) && - (NULL == uc->parse_template.tsummary) ) + (NULL == uc->template_contract.summary) ) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1064,15 +952,15 @@ handle_phase_verify ( return; } if ( (! uc->parse_request.no_amount) && - (NULL != uc->parse_template.tcurrency) && - (0 != strcasecmp (uc->parse_template.tcurrency, + (NULL != uc->template_contract.currency) && + (0 != strcasecmp (uc->template_contract.currency, uc->parse_request.amount.currency)) ) { GNUNET_break_op (0); use_reply_with_error (uc, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - uc->parse_template.tcurrency); + uc->template_contract.currency); return; } switch (uc->template_type) @@ -1355,13 +1243,13 @@ handle_phase_compute_price (struct UseContext *uc) = 1; if (uc->parse_request.no_amount) { - GNUNET_assert (! uc->parse_template.no_tamount); + GNUNET_assert (! uc->template_contract.no_amount); *uc->compute_price.totals - = uc->parse_template.tamount; + = uc->template_contract.amount; } else { - GNUNET_assert (uc->parse_template.no_tamount); + GNUNET_assert (uc->template_contract.no_amount); *uc->compute_price.totals = uc->parse_request.amount; } @@ -1373,7 +1261,7 @@ handle_phase_compute_price (struct UseContext *uc) case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: GNUNET_assert (0); } - primary_currency = uc->parse_template.tcurrency; + primary_currency = uc->template_contract.currency; if (! uc->parse_request.no_amount) primary_currency = uc->parse_request.amount.currency; if (! uc->parse_request.no_tip) @@ -1609,7 +1497,7 @@ create_using_templates_inventory (struct UseContext *uc) choices), GNUNET_JSON_pack_string ("summary", NULL == uc->parse_request.summary - ? uc->parse_template.tsummary + ? uc->template_contract.summary : uc->parse_request.summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", @@ -1648,7 +1536,7 @@ create_using_templates_fixed (struct UseContext *uc) GNUNET_JSON_pack_string ( "summary", NULL == uc->parse_request.summary - ? uc->parse_template.tsummary + ? uc->template_contract.summary : uc->parse_request.summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", @@ -1729,7 +1617,7 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, handle_phase_lookup_template (uc); break; case USE_PHASE_PARSE_TEMPLATE: - handle_phase_parse_template (uc); + handle_phase_template_contract (uc); break; case USE_PHASE_DB_FETCH: handle_phase_db_fetch (uc); diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c @@ -133,7 +133,7 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh, : MHD_NO; } - if (! TMH_template_contract_valid (tp.template_contract)) + if (! TALER_MERCHANT_template_contract_valid (tp.template_contract)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); diff --git a/src/backend/taler-merchant-httpd_private-post-templates.c b/src/backend/taler-merchant-httpd_private-post-templates.c @@ -101,7 +101,7 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh, : MHD_NO; } } - if (! TMH_template_contract_valid (tp.template_contract)) + if (! TALER_MERCHANT_template_contract_valid (tp.template_contract)) { GNUNET_break_op (0); json_dumpf (tp.template_contract, diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h @@ -224,22 +224,144 @@ enum TALER_MERCHANT_TemplateType * @param template_type string value (NULL means fixed-order) * @return template type (defaults to fixed order) */ -static inline enum TALER_MERCHANT_TemplateType -TALER_MERCHANT_template_type_from_string (const char *template_type) +enum TALER_MERCHANT_TemplateType +TALER_MERCHANT_template_type_from_string (const char *template_type); + +/** + * Convert template type to its string representation. + * + * @param template_type template type to convert + * @return string name or NULL for invalid types + */ +const char * +TALER_MERCHANT_template_type_to_string ( + enum TALER_MERCHANT_TemplateType template_type); + +/** + * Determine template type from a template contract. + * + * @param template_contract contract JSON + * @return template type (defaults to fixed order) + */ +enum TALER_MERCHANT_TemplateType +TALER_MERCHANT_template_type_from_contract (const json_t *template_contract); + +/** + * Template contract fields for inventory templates. + */ +struct TALER_MERCHANT_TemplateContractInventory +{ + /** + * Selected categories from the template contract. + */ + const json_t *selected_categories; + + /** + * Selected products from the template contract. + */ + const json_t *selected_products; + + /** + * Whether all products are selectable. + */ + bool selected_all; + + /** + * Template requires exactly one selection. + */ + bool choose_one; + + /** + * Template allows tips. + */ + bool request_tip; +}; + +/** + * Template contract fields for paivana templates. + */ +struct TALER_MERCHANT_TemplateContractPaivana { - if (NULL == template_type) - return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; - if (0 == strcmp (template_type, - "fixed-order")) - return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; - if (0 == strcmp (template_type, - "inventory-cart")) - return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART; - if (0 == strcmp (template_type, - "paivana")) - return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA; - return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; -} + /** + * Paivana reference ID. + */ + const char *paivana_id; +}; + +/** + * Parsed template contract. + */ +struct TALER_MERCHANT_TemplateContract +{ + /** + * Template type. + */ + enum TALER_MERCHANT_TemplateType type; + + /** + * Summary from the template contract. + */ + const char *summary; + + /** + * Currency from the template contract. + */ + const char *currency; + + /** + * Amount from the template contract. + */ + struct TALER_Amount amount; + + /** + * True if @e amount was not provided. + */ + bool no_amount; + + /** + * Minimum age required by the template. + */ + uint32_t minimum_age; + + /** + * How long does the customer have to pay for the order. + * 0 if not specified (use instance default). + */ + struct GNUNET_TIME_Relative pay_duration; + + /** + * Parsed fields for inventory templates. + */ + struct TALER_MERCHANT_TemplateContractInventory inventory; + + /** + * Parsed fields for paivana templates. + */ + struct TALER_MERCHANT_TemplateContractPaivana paivana; +}; + +/** + * Parse template contract JSON into @a out. + * + * @param template_contract JSON object containing the template contract + * @param[out] out parsed template contract + * @param[out] error_name pointer to the name of the failed field, or NULL + * @return #GNUNET_SYSERR if @a template_contract is malformed; #GNUNET_OK otherwise + */ +enum GNUNET_GenericReturnValue +TALER_MERCHANT_template_contract_parse ( + const json_t *template_contract, + struct TALER_MERCHANT_TemplateContract *out, + const char **error_name); + +/** + * Check if @a template_contract is valid. + * + * @param template_contract template contract to validate + * @return true if @a template_contract is valid + */ +bool +TALER_MERCHANT_template_contract_valid (const json_t *template_contract); /** diff --git a/src/lib/merchant_api_post_templates.c b/src/lib/merchant_api_post_templates.c @@ -34,6 +34,24 @@ #include <taler/taler_curl_lib.h> #include "taler_merchant_util.h" +/* FIXME: Bohdan is to stupid to figure out how util can be used here */ +static enum TALER_MERCHANT_TemplateType +template_type_from_string (const char *template_type) +{ + if (NULL == template_type) + return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; + if (0 == strcmp (template_type, + "fixed-order")) + return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; + if (0 == strcmp (template_type, + "inventory-cart")) + return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART; + if (0 == strcmp (template_type, + "paivana")) + return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA; + return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; +} + /** * Validate a fixed-order template contract. @@ -360,8 +378,7 @@ test_template_contract_valid (const json_t *template_contract) return false; } - template_type_enum = - TALER_MERCHANT_template_type_from_string (template_type); + template_type_enum = template_type_from_string (template_type); if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == template_type_enum) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -370,6 +387,7 @@ test_template_contract_valid (const json_t *template_contract) return false; } + /* FIXME: Bohdan understands that links have to be changed, but worried, that can crash something */ switch (template_type_enum) { case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: diff --git a/src/util/Makefile.am b/src/util/Makefile.am @@ -45,6 +45,7 @@ libtalermerchantutil_la_SOURCES = \ json.c \ mfa.c \ os_installation.c \ + template_parse.c \ value_kinds.c \ validators.c libtalermerchantutil_la_LIBADD = \ diff --git a/src/util/template_parse.c b/src/util/template_parse.c @@ -0,0 +1,303 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file util/template_parse.c + * @brief shared logic for template contract parsing + * @author Bohdan Potuzhnyi + */ +#include "platform.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <string.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> +#include "taler_merchant_util.h" + + +enum TALER_MERCHANT_TemplateType +TALER_MERCHANT_template_type_from_contract (const json_t *template_contract) +{ + const json_t *type_val; + + if (NULL == template_contract) + return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; + + type_val = json_object_get (template_contract, + "template_type"); + + if (! json_is_string (type_val)) + return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; + + return TALER_MERCHANT_template_type_from_string ( + json_string_value (type_val)); +} + + +enum TALER_MERCHANT_TemplateType +TALER_MERCHANT_template_type_from_string (const char *template_type) +{ + if (NULL == template_type) + return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; + if (0 == strcmp (template_type, + "fixed-order")) + return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; + if (0 == strcmp (template_type, + "inventory-cart")) + return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART; + if (0 == strcmp (template_type, + "paivana")) + return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA; + return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; +} + + +const char * +TALER_MERCHANT_template_type_to_string ( + enum TALER_MERCHANT_TemplateType template_type) +{ + switch (template_type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + return "fixed-order"; + case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: + return "inventory-cart"; + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + return "paivana"; + case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: + break; + } + return NULL; +} + + +/** + * Parse inventory-specific fields from a template contract. + * + * @param template_contract json + * @param[out] out where to write parsed fields + * @param[out] error_name error description + * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure + */ +static enum GNUNET_GenericReturnValue +parse_template_inventory (const json_t *template_contract, + struct TALER_MERCHANT_TemplateContract *out, + const char **error_name) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("request_tip", + &out->inventory.request_tip), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("selected_all", + &out->inventory.selected_all), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("selected_categories", + &out->inventory.selected_categories), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("selected_products", + &out->inventory.selected_products), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("choose_one", + &out->inventory.choose_one), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse ((json_t *) template_contract, + spec, + error_name, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid inventory template_contract for field %s\n", + *error_name); + return GNUNET_SYSERR; + } + + if (NULL != out->inventory.selected_categories) + { + const json_t *entry; + size_t idx; + + json_array_foreach ((json_t *) out->inventory.selected_categories, idx, + entry) + { + if ( (! json_is_integer (entry)) || + (0 > json_integer_value (entry)) ) + { + GNUNET_break_op (0); + *error_name = "selected_categories"; + return GNUNET_SYSERR; + } + } + } + + if (NULL != out->inventory.selected_products) + { + const json_t *entry; + size_t idx; + + json_array_foreach ((json_t *) out->inventory.selected_products, idx, entry) + { + if (! json_is_string (entry)) + { + GNUNET_break_op (0); + *error_name = "selected_products"; + return GNUNET_SYSERR; + } + } + } + return GNUNET_OK; +} + + +/** + * Parse paivana-specific fields from a template contract. + * + * @param template_contract json + * @param[out] out where to write parsed fields + * @param[out] error_name error description + * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure + */ +static enum GNUNET_GenericReturnValue +parse_template_paivana (const json_t *template_contract, + struct TALER_MERCHANT_TemplateContract *out, + const char **error_name) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("paivana_id", + &out->paivana.paivana_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse ((json_t *) template_contract, + spec, + error_name, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid paivana template_contract for field %s\n", + *error_name); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_MERCHANT_template_contract_parse ( + const json_t *template_contract, + struct TALER_MERCHANT_TemplateContract *out, + const char **error_name) +{ + const char *template_type_str = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("template_type", + &template_type_str), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("summary", + &out->summary), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("currency", + &out->currency), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("amount", + &out->amount), + &out->no_amount), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &out->minimum_age), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_relative_time ("pay_duration", + &out->pay_duration), + NULL), + GNUNET_JSON_spec_end () + }; + + if (NULL == template_contract) + { + *error_name = "template_contract is NULL"; + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_JSON_parse ((json_t *) template_contract, + spec, + error_name, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid input for field %s\n", + *error_name); + return GNUNET_SYSERR; + } + + out->type = TALER_MERCHANT_template_type_from_string (template_type_str); + if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid template_type used '%s'\n", + template_type_str); + *error_name = "Invalid template_type used"; + return GNUNET_SYSERR; + } + + /* Parse additional fields for each specific type */ + switch (out->type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + return GNUNET_OK; + case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: + return parse_template_inventory (template_contract, + out, + error_name); + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + return parse_template_paivana (template_contract, + out, + error_name); + case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: + break; + } + + /* I think we are never supposed to reach it */ + GNUNET_break_op (0); + *error_name = "template_type"; + return GNUNET_SYSERR; +} + + +bool +TALER_MERCHANT_template_contract_valid (const json_t *template_contract) +{ + struct TALER_MERCHANT_TemplateContract tmp; + + return (GNUNET_OK == + TALER_MERCHANT_template_contract_parse (template_contract, + &tmp, + NULL)); +}