merchant

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

commit ebe98bd39046e88bb6b2605670cc38e425ccfe4e
parent 3a86481b5fa7935cf01e48ae4d638e5bac630527
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Mon, 12 Jan 2026 08:15:28 +0900

fix misc. minor issues, add FIXMEs:

Diffstat:
Msrc/backend/taler-merchant-httpd_get-templates-ID.c | 7++++---
Msrc/backend/taler-merchant-httpd_helper.c | 6+++++-
Msrc/backend/taler-merchant-httpd_post-using-templates.c | 87++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/backend/taler-merchant-httpd_private-post-products.c | 6++++++
4 files changed, 86 insertions(+), 20 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-templates-ID.c b/src/backend/taler-merchant-httpd_get-templates-ID.c @@ -436,9 +436,10 @@ handle_get_templates_inventory ( template_contract = json_deep_copy (tp->template_contract); GNUNET_assert (NULL != template_contract); - json_object_set_new (template_contract, - "inventory_payload", - inventory_payload); + GNUNET_assert (0 == + json_object_set_new (template_contract, + "inventory_payload", + inventory_payload)); { MHD_RESULT ret; diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c @@ -475,10 +475,14 @@ TMH_validate_unit_price_array (const struct TALER_Amount *prices, { for (size_t j = i + 1; j < prices_len; j++) { - if (GNUNET_OK == + if (GNUNET_YES == TALER_amount_cmp_currency (&prices[i], &prices[j])) + { + /* duplicate currency, not allowed! */ + GNUNET_break_op (0); return GNUNET_SYSERR; + } } } return GNUNET_OK; diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2022-2023 Taler Systems SA + (C) 2022-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -30,6 +30,12 @@ #include "taler-merchant-httpd_exchanges.h" #include <taler/taler_json_lib.h> +// FIXME: introduce PHASES (like we do in other handlers) +// and break up the gigantic (and sometimes redundant) +// handlers into clear parts, like parsing the request, +// fetching template, checking inventory/computing price, +// returning errors, etc. + /** * Our context. @@ -246,6 +252,7 @@ struct InventoryTemplateContext /** * Amount provided by the client. + * FIXME: I don't see it in the spec that the client MUST provide this amount. */ struct TALER_Amount amount; @@ -256,6 +263,10 @@ struct InventoryTemplateContext /** * Total amount for the requested currency (includes tip when present). + * + * FIXME: this makes no sense, we MAY still have multiple choices + * (and thus different totals!) *if* the user didn't specify a tip + * and there are products in multiple currencies! */ struct TALER_Amount total; @@ -316,6 +327,13 @@ cleanup_inventory_template_context (void *cls) /** * Compute totals for all currencies shared across selected products. * + * FIXME: this is wild. If the client _already_ provided an "amount" + * this is pointless: we know the currency the payment will be made in. + * (but should still check the total, alas below you just 'skip' + * if the currency matches the client-given amount); + * However, if the client did NOT give us an amount, we need to compute + * per-currency totals. + * * @param[in,out] ctx inventory template context * @return #GNUNET_OK on success */ @@ -325,7 +343,10 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) struct TALER_Amount line_total; if (0 == ctx->items_len) + { + GNUNET_break (0); return GNUNET_SYSERR; + } for (size_t i = 0; i < ctx->items[0].pd.price_array_length; i++) { @@ -337,16 +358,15 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) if (! TMH_test_exchange_configured_for_currency (currency)) continue; - GNUNET_array_append (ctx->currencies, ctx->currencies_len, (struct InventoryTemplateCurrency) { .currency = GNUNET_strdup (currency) }); - if (GNUNET_OK != - TALER_amount_set_zero (currency, - &ctx->currencies[ctx->currencies_len - 1].total)) - return GNUNET_SYSERR; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + &ctx->currencies[ctx->currencies_len + - 1].total)); } if (0 == ctx->currencies_len) @@ -373,6 +393,7 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) } if (NULL == unit_price) { + // FIXME: some logging would be nice... GNUNET_free (ctx->currencies[c].currency); ctx->currencies[c] = ctx->currencies[ctx->currencies_len - 1]; ctx->currencies_len--; @@ -394,7 +415,7 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) } return (0 == ctx->currencies_len) - ? GNUNET_SYSERR + ? GNUNET_SYSERR // FIXME: at least some logging would be nice... : GNUNET_OK; } @@ -483,6 +504,12 @@ handle_using_templates_inventory (struct MHD_Connection *connection, GNUNET_JSON_spec_string ("summary", &summary), NULL), + // FIXME: spec clearly says "amount" is *optional*! + // The (very common!) new inventory-cart case is one + // where the client did NOT specify any currency yet + // (unless tipping, I guess), and we should still + // generate a v1 order with choices for different + // currencies. TALER_JSON_spec_amount_any ("amount", &itc.amount), GNUNET_JSON_spec_mark_optional ( @@ -562,6 +589,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if ( (NULL != summary) && (NULL != tsummary) ) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -572,6 +600,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if ( (NULL == summary) && (NULL == tsummary) ) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -583,6 +612,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if (! no_tip && ! request_tip) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -593,6 +623,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if (! TMH_test_exchange_configured_for_currency (itc.amount.currency)) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -607,6 +638,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, TALER_amount_cmp_currency (&itc.amount, &itc.tip)) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -618,6 +650,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if (0 == json_array_size (inventory_selection)) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -628,6 +661,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if (choose_one && (1 != json_array_size (inventory_selection))) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -807,6 +841,9 @@ handle_using_templates_inventory (struct MHD_Connection *connection, "calculation of currency totals failed"); } + // FIXME: itc.amount MAY not have been specified by the client here, + // we MAY still be in a multi-currency scenario. You cannot just + // use itc.amount if (GNUNET_OK != compute_inventory_total (&itc, itc.amount.currency, @@ -825,10 +862,12 @@ handle_using_templates_inventory (struct MHD_Connection *connection, { // TODO: HMMMMM... If we have tip, we can't really go for another choice... // or we will have to indicate it somehow in the wallet, when user changes the choice... - if (GNUNET_OK != + // FIXME: yes, so if we have a tip, only generate that exact choice! + if (GNUNET_YES != TALER_amount_cmp_currency (&itc.tip, &itc.total)) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); cleanup_inventory_template_context (&itc); return TALER_MHD_reply_with_error ( @@ -842,12 +881,13 @@ handle_using_templates_inventory (struct MHD_Connection *connection, &itc.total, &itc.tip)) { + GNUNET_break (0); GNUNET_JSON_parse_free (spec); cleanup_inventory_template_context (&itc); return TALER_MHD_reply_with_error ( connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, "tip"); } tip_products = json_array (); @@ -866,6 +906,7 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if (0 != TALER_amount_cmp (&itc.amount, &itc.total)) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); json_decref (tip_products); cleanup_inventory_template_context (&itc); @@ -902,7 +943,8 @@ handle_using_templates_inventory (struct MHD_Connection *connection, json_array_append_new (choices, GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", - &itc.total)))); + &itc.total)) + )); for (size_t i = 0; i < itc.currencies_len; i++) { @@ -1063,6 +1105,7 @@ handle_using_templates_fixed (struct MHD_Connection *connection, if ( (! no_amount) && (! no_tamount) ) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -1076,6 +1119,7 @@ handle_using_templates_fixed (struct MHD_Connection *connection, (0 != strcmp (tcurrency, amount.currency)) ) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -1086,6 +1130,7 @@ handle_using_templates_fixed (struct MHD_Connection *connection, if (no_amount && no_tamount) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -1099,9 +1144,11 @@ handle_using_templates_fixed (struct MHD_Connection *connection, const struct TALER_Amount *total_amount; total_amount = no_amount ? &tamount : &amount; - if (GNUNET_OK != TALER_amount_cmp_currency (&tip, - total_amount)) + if (GNUNET_YES != + TALER_amount_cmp_currency (&tip, + total_amount)) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); json_decref (tip_products); return TALER_MHD_reply_with_error ( @@ -1115,6 +1162,7 @@ handle_using_templates_fixed (struct MHD_Connection *connection, if ( (NULL != summary) && (NULL != tsummary) ) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -1126,6 +1174,7 @@ handle_using_templates_fixed (struct MHD_Connection *connection, if ( (NULL == summary) && (NULL == tsummary) ) { + GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, @@ -1140,6 +1189,7 @@ handle_using_templates_fixed (struct MHD_Connection *connection, if (! no_tip) { tip_products = json_array (); + GNUNET_assert (NULL != tip_products); GNUNET_assert (0 == json_array_append_new ( tip_products, @@ -1239,6 +1289,8 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, { enum GNUNET_DB_QueryStatus qs; + // FIXME: include template type in request, fully parse + // request before even going into the DB. qs = TMH_db->lookup_template (TMH_db->cls, mi->settings.id, template_id, @@ -1274,6 +1326,8 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, break; } /* End of the switch */ } + // FIXME: share more logic between these, just skip (or run) individual + // phases depending on the template type! switch (TMH_template_type_from_contract (uc->etp.template_contract)) { case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: @@ -1301,9 +1355,10 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: break; } + GNUNET_break (0); return TALER_MHD_reply_with_error ( connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "template_contract"); + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "template_contract has unexpected type"); } diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c @@ -162,6 +162,7 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh, if (0 != TALER_amount_cmp (&price, &pd.price_array[0])) { + GNUNET_break_op (0); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, @@ -173,6 +174,7 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh, TMH_validate_unit_price_array (pd.price_array, pd.price_array_length)) { + GNUNET_break_op (0); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, @@ -184,6 +186,7 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh, { if (price_missing) { + GNUNET_break_op (0); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, @@ -200,6 +203,7 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh, { if (unit_precision_level > TMH_MAX_FRACTIONAL_PRECISION_LEVEL) { + GNUNET_break_op (0); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, @@ -234,6 +238,7 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh, pd.fractional_precision_level = unit_precision_level; { const char *eparam; + if (GNUNET_OK != TALER_MERCHANT_vk_process_quantity_inputs ( TALER_MERCHANT_VK_STOCK, @@ -246,6 +251,7 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh, &pd.total_stock_frac, &eparam)) { + GNUNET_break_op (0); ret = TALER_MHD_reply_with_error ( connection, MHD_HTTP_BAD_REQUEST,