merchant

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

commit e0cf46fd0f699db3aa72dfc7f4118b80bb0993d8
parent 7d3cd38106c7bc3d0ac6c3455d9b6758ce05ae34
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 28 Dec 2025 20:07:21 +0100

implement money pots using an array of currencies

Diffstat:
Msrc/backend/taler-merchant-httpd_private-get-pot-ID.c | 15+++++++++++----
Msrc/backend/taler-merchant-httpd_private-get-pots.c | 14++++++++++----
Msrc/backend/taler-merchant-httpd_private-patch-pot-ID.c | 43+++++++++++++++----------------------------
Msrc/backend/taler-merchant-httpd_private-post-pots.c | 14--------------
Msrc/backenddb/merchant-0028.sql | 7++++---
Msrc/backenddb/pg_insert_money_pot.c | 11-----------
Msrc/backenddb/pg_insert_money_pot.h | 2--
Msrc/backenddb/pg_select_money_pot.c | 9++++++---
Msrc/backenddb/pg_select_money_pot.h | 6++++--
Msrc/backenddb/pg_select_money_pots.c | 22++++++++++++++++------
Msrc/backenddb/pg_update_money_pot.c | 20++++++++++++--------
Msrc/backenddb/pg_update_money_pot.h | 16++++++++++------
Msrc/backenddb/pg_update_money_pot.sql | 8++++----
Msrc/include/taler_merchantdb_plugin.h | 37+++++++++++++++++++++++--------------
14 files changed, 115 insertions(+), 109 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_private-get-pot-ID.c b/src/backend/taler-merchant-httpd_private-get-pot-ID.c @@ -34,7 +34,8 @@ TMH_private_get_pot (const struct TMH_RequestHandler *rh, unsigned long long pot_id; char *pot_name; char *description; - struct TALER_Amount pot_total; + size_t pot_total_len; + struct TALER_Amount *pot_totals; enum GNUNET_DB_QueryStatus qs; char dummy; @@ -55,7 +56,8 @@ TMH_private_get_pot (const struct TMH_RequestHandler *rh, pot_id, &pot_name, &description, - &pot_total); + &pot_total_len, + &pot_totals); if (qs < 0) { @@ -84,8 +86,13 @@ TMH_private_get_pot (const struct TMH_RequestHandler *rh, description), GNUNET_JSON_pack_string ("pot_name", pot_name), - TALER_JSON_pack_amount ("pot_total", - &pot_total)); + (0 == pot_total_len) + ? GNUNET_JSON_pack_array_steal ("pot_totals", + json_array ()) + : TALER_JSON_pack_amount_array ("pot_totals", + pot_total_len, + pot_totals)); + GNUNET_free (pot_totals); GNUNET_free (pot_name); GNUNET_free (description); return ret; diff --git a/src/backend/taler-merchant-httpd_private-get-pots.c b/src/backend/taler-merchant-httpd_private-get-pots.c @@ -38,13 +38,15 @@ * @param money_pot_id unique identifier of the pot * @param name name of the pot * @param description human-readable description (ignored for listing) - * @param pot_total current total amount in the pot + * @param pot_total_len length of the @a pot_totals array + * @param pot_totals current total amounts in the pot */ static void add_pot (void *cls, uint64_t money_pot_id, const char *name, - const struct TALER_Amount *pot_total) + size_t pot_total_len, + const struct TALER_Amount *pot_totals) { json_t *pots = cls; json_t *entry; @@ -54,8 +56,12 @@ add_pot (void *cls, money_pot_id), GNUNET_JSON_pack_string ("pot_name", name), - TALER_JSON_pack_amount ("pot_total", - pot_total)); + (0 == pot_total_len) + ? GNUNET_JSON_pack_array_steal ("pot_totals", + json_array ()) + : TALER_JSON_pack_amount_array ("pot_totals", + pot_total_len, + pot_totals)); GNUNET_assert (NULL != entry); GNUNET_assert (0 == json_array_append_new (pots, diff --git a/src/backend/taler-merchant-httpd_private-patch-pot-ID.c b/src/backend/taler-merchant-httpd_private-patch-pot-ID.c @@ -25,25 +25,6 @@ #include <taler/taler_json_lib.h> -/** - * ISSUE: The database API and REST API have a conflict handling mismatch. - * - * REST API spec says: - * - Return 409 Conflict if "the pot total did not match the expected total" - * - * Database API provides: - * - conflict parameter that is set if old_pot_total doesn't match - * - * However, there's a semantic problem: - * - The conflict parameter is about pot_name uniqueness in update_product_group - * - But here it's about pot total mismatch in update_money_pot - * - This is inconsistent terminology across the database API - * - * The implementation below assumes 'conflict' means "pot total mismatch" - * for money pots, but this should be clarified. - */ - - MHD_RESULT TMH_private_patch_pot (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, @@ -53,9 +34,11 @@ TMH_private_patch_pot (const struct TMH_RequestHandler *rh, unsigned long long pot_id; const char *pot_name; const char *description; - struct TALER_Amount expected_pot_total; + size_t expected_pot_total_len; + struct TALER_Amount *expected_pot_totals; bool no_expected_total; - struct TALER_Amount new_pot_total; + size_t new_pot_total_len; + struct TALER_Amount *new_pot_totals; bool no_new_total; enum GNUNET_DB_QueryStatus qs; bool conflict_total; @@ -66,12 +49,14 @@ TMH_private_patch_pot (const struct TMH_RequestHandler *rh, GNUNET_JSON_spec_string ("description", &description), GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("expected_pot_total", - &expected_pot_total), + TALER_JSON_spec_amount_any_array ("expected_pot_total", + &expected_pot_total_len, + &expected_pot_totals), &no_expected_total), GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("new_pot_total", - &new_pot_total), + TALER_JSON_spec_amount_any_array ("new_pot_totals", + &new_pot_total_len, + &new_pot_totals), &no_new_total), GNUNET_JSON_spec_end () }; @@ -113,15 +98,17 @@ TMH_private_patch_pot (const struct TMH_RequestHandler *rh, pot_id, pot_name, description, + expected_pot_total_len, no_expected_total ? NULL - : &expected_pot_total, + : expected_pot_totals, + new_pot_total_len, no_new_total ? NULL - : &new_pot_total, + : new_pot_totals, &conflict_total, &conflict_name); - + GNUNET_JSON_parse_free (spec); if (qs < 0) { GNUNET_break (0); diff --git a/src/backend/taler-merchant-httpd_private-post-pots.c b/src/backend/taler-merchant-httpd_private-post-pots.c @@ -32,7 +32,6 @@ TMH_private_post_pots (const struct TMH_RequestHandler *rh, { const char *pot_name; const char *description; - const char *currency; enum GNUNET_DB_QueryStatus qs; uint64_t pot_id; struct GNUNET_JSON_Specification spec[] = { @@ -40,8 +39,6 @@ TMH_private_post_pots (const struct TMH_RequestHandler *rh, &pot_name), GNUNET_JSON_spec_string ("description", &description), - GNUNET_JSON_spec_string ("currency", - &currency), GNUNET_JSON_spec_end () }; @@ -61,21 +58,10 @@ TMH_private_post_pots (const struct TMH_RequestHandler *rh, } } - /* Validate currency string */ - if (GNUNET_OK != - TALER_check_currency (currency)) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - currency); - } - qs = TMH_db->insert_money_pot (TMH_db->cls, hc->instance->settings.id, pot_name, description, - currency, &pot_id); if (qs < 0) diff --git a/src/backenddb/merchant-0028.sql b/src/backenddb/merchant-0028.sql @@ -94,7 +94,8 @@ CREATE TABLE merchant_money_pots REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE ,money_pot_name TEXT NOT NULL ,money_pot_description TEXT NOT NULL - ,pot_total taler_amount_currency NOT NULL + ,pot_totals taler_amount_currency[] NOT NULL + DEFAULT ARRAY[]::taler_amount_currency[] ,UNIQUE (merchant_serial,money_pot_name) ); COMMENT ON TABLE merchant_money_pots @@ -107,8 +108,8 @@ COMMENT ON COLUMN merchant_money_pots.money_pot_name IS 'Name for the money pot'; COMMENT ON COLUMN merchant_money_pots.money_pot_description IS 'Human-readable description for the money pot'; -COMMENT ON COLUMN merchant_money_pots.pot_total - IS 'Total amount in the pot'; +COMMENT ON COLUMN merchant_money_pots.pot_totals + IS 'Total amounts in the pot'; ALTER TABLE merchant_inventory diff --git a/src/backenddb/pg_insert_money_pot.c b/src/backenddb/pg_insert_money_pot.c @@ -32,17 +32,13 @@ TMH_PG_insert_money_pot ( const char *instance_id, const char *name, const char *description, - const char *pot_currency, uint64_t *money_pot_id) { struct PostgresClosure *pg = cls; - struct TALER_Amount zero_c; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (name), GNUNET_PQ_query_param_string (description), - TALER_PQ_query_param_amount_with_currency (pg->conn, - &zero_c), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -51,13 +47,6 @@ TMH_PG_insert_money_pot ( GNUNET_PQ_result_spec_end }; - if (GNUNET_OK != - TALER_amount_set_zero (pot_currency, - &zero_c)) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } check_connection (pg); PREPARE (pg, "insert_money_pot", diff --git a/src/backenddb/pg_insert_money_pot.h b/src/backenddb/pg_insert_money_pot.h @@ -32,7 +32,6 @@ * @param instance_id instance to insert pot for * @param name set to name of the pot * @param description set to description of the pot - * @param pod_currency the expected currency in the pot * @param[out] money_pot_id serial number of the new pot * @return database result code */ @@ -42,7 +41,6 @@ TMH_PG_insert_money_pot ( const char *instance_id, const char *name, const char *description, - const char *pod_currency, uint64_t *money_pot_id); #endif diff --git a/src/backenddb/pg_select_money_pot.c b/src/backenddb/pg_select_money_pot.c @@ -32,7 +32,8 @@ TMH_PG_select_money_pot (void *cls, uint64_t money_pot_id, char **name, char **description, - struct TALER_Amount *pot_total) + size_t *pot_total_len, + struct TALER_Amount **pot_totals) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -45,8 +46,10 @@ TMH_PG_select_money_pot (void *cls, name), GNUNET_PQ_result_spec_string ("money_pot_description", description), - TALER_PQ_result_spec_amount_with_currency ("pot_total", - pot_total), + TALER_PQ_result_spec_array_amount_with_currency (pg->conn, + "pot_total", + pot_total_len, + pot_totals), GNUNET_PQ_result_spec_end }; diff --git a/src/backenddb/pg_select_money_pot.h b/src/backenddb/pg_select_money_pot.h @@ -34,7 +34,8 @@ * @param money_pot_id serial number of the pot to lookup * @param [out] name set to name of the pot * @param[out] description set to description of the pot - * @param[out] pot_total set to amount currently in the pot + * @param[out] pot_total_len set to length of @a pot_totals + * @param[out] pot_totals set to amount currently in the pot * @return database result code */ enum GNUNET_DB_QueryStatus @@ -43,6 +44,7 @@ TMH_PG_select_money_pot (void *cls, uint64_t money_pot_id, char **name, char **description, - struct TALER_Amount *pot_total); + size_t *pot_total_len, + struct TALER_Amount **pot_totals); #endif diff --git a/src/backenddb/pg_select_money_pots.c b/src/backenddb/pg_select_money_pots.c @@ -32,6 +32,11 @@ struct LookupMoneyPotsContext { /** + * DB context. + */ + struct PostgresClosure *pg; + + /** * Function to call with the results. */ TALER_MERCHANTDB_MoneyPotsCallback cb; @@ -67,14 +72,17 @@ lookup_money_pots_cb (void *cls, { uint64_t money_pot_serial; char *money_pot_name; - struct TALER_Amount pot_total; + size_t pot_total_len; + struct TALER_Amount *pot_totals; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("money_pot_serial", &money_pot_serial), GNUNET_PQ_result_spec_string ("money_pot_name", &money_pot_name), - TALER_PQ_result_spec_amount_with_currency ("pot_total", - &pot_total), + TALER_PQ_result_spec_array_amount_with_currency (plc->pg->conn, + "pot_totals", + &pot_total_len, + &pot_totals), GNUNET_PQ_result_spec_end }; @@ -90,7 +98,8 @@ lookup_money_pots_cb (void *cls, plc->cb (plc->cb_cls, money_pot_serial, money_pot_name, - &pot_total); + pot_total_len, + pot_totals); GNUNET_PQ_cleanup_result (rs); } } @@ -107,6 +116,7 @@ TMH_PG_select_money_pots (void *cls, struct PostgresClosure *pg = cls; uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit); struct LookupMoneyPotsContext plc = { + .pg = pg, .cb = cb, .cb_cls = cb_cls, /* Can be overwritten by the lookup_money_pots_cb */ @@ -126,7 +136,7 @@ TMH_PG_select_money_pots (void *cls, "SELECT" " money_pot_serial" " ,money_pot_name" - " ,pot_total" + " ,pot_totals" " FROM merchant_money_pots" " JOIN merchant_instances" " USING (merchant_serial)" @@ -139,7 +149,7 @@ TMH_PG_select_money_pots (void *cls, "SELECT" " money_pot_serial" " ,money_pot_name" - " ,pot_total" + " ,pot_totals" " FROM merchant_money_pots" " JOIN merchant_instances" " USING (merchant_serial)" diff --git a/src/backenddb/pg_update_money_pot.c b/src/backenddb/pg_update_money_pot.c @@ -33,8 +33,10 @@ TMH_PG_update_money_pot ( uint64_t money_pot_id, const char *name, const char *description, - const struct TALER_Amount *old_pot_total, - const struct TALER_Amount *new_pot_total, + size_t opt_len, + const struct TALER_Amount *old_pot_totals, + size_t npt_len, + const struct TALER_Amount *new_pot_totals, bool *conflict_total, bool *conflict_name) { @@ -44,14 +46,16 @@ TMH_PG_update_money_pot ( GNUNET_PQ_query_param_uint64 (&money_pot_id), GNUNET_PQ_query_param_string (name), GNUNET_PQ_query_param_string (description), - (NULL == old_pot_total) + (NULL == old_pot_totals) ? GNUNET_PQ_query_param_null () - : TALER_PQ_query_param_amount_with_currency (pg->conn, - old_pot_total), - (NULL == new_pot_total) + : TALER_PQ_query_param_array_amount_with_currency (opt_len, + old_pot_totals, + pg->conn), + (NULL == new_pot_totals) ? GNUNET_PQ_query_param_null () - : TALER_PQ_query_param_amount_with_currency (pg->conn, - new_pot_total), + : TALER_PQ_query_param_array_amount_with_currency (npt_len, + new_pot_totals, + pg->conn), GNUNET_PQ_query_param_end }; bool not_found; diff --git a/src/backenddb/pg_update_money_pot.h b/src/backenddb/pg_update_money_pot.h @@ -33,10 +33,12 @@ * @param money_pot_id serial number of the pot to delete * @param name set to name of the pot * @param description set to description of the pot - * @param old_pot_total amount expected currently in the pot, - * NULL to not check - * @param new_pot_total new amount in the pot, - * NULL to not update + * @param opt_len length of @a old_pot_totals + * @param old_pot_totals amounts expected currently in the pot, + * NULL to not check (non-NULL but @a opt_len=0 checks for []) + * @param npt_len length of @a new_pot_totals + * @param new_pot_totals new amounts in the pot, + * NULL to not update (non-NULL but @a npt_len=0 resets to []) * @param[out] conflict_total set to true if @a old_pot_total does not match * @param[out] conflict_name set to true if @a name is used by another pot * @return database result code @@ -48,8 +50,10 @@ TMH_PG_update_money_pot ( uint64_t money_pot_id, const char *name, const char *description, - const struct TALER_Amount *old_pot_total, - const struct TALER_Amount *new_pot_total, + size_t opt_len, + const struct TALER_Amount *old_pot_totals, + size_t npt_len, + const struct TALER_Amount *new_pot_totals, bool *conflict_total, bool *conflict_name); diff --git a/src/backenddb/pg_update_money_pot.sql b/src/backenddb/pg_update_money_pot.sql @@ -21,8 +21,8 @@ CREATE FUNCTION merchant_do_update_money_pot ( IN in_money_pot_serial INT8, IN in_name TEXT, IN in_description TEXT, - IN in_old_total taler_amount_currency, -- can be NULL! - IN in_new_total taler_amount_currency, -- can be NULL! + IN in_old_totals taler_amount_currency[], -- can be NULL! + IN in_new_totals taler_amount_currency[], -- can be NULL! OUT out_conflict_total BOOL, OUT out_conflict_name BOOL, OUT out_not_found BOOL) @@ -50,10 +50,10 @@ BEGIN UPDATE merchant_money_pots SET money_pot_name=in_name ,money_pot_description=in_description - ,pot_total=COALESCE(in_new_total, pot_total) + ,pot_totals=COALESCE(in_new_totals, pot_total) WHERE merchant_serial=my_merchant_id AND money_pot_serial=in_money_pot_serial - AND ( (in_old_total IS NULL) OR (pot_total=in_old_total) ); + AND ( (in_old_totals IS NULL) OR (pot_totals=in_old_totals) ); IF NOT FOUND THEN -- Check if pot_total was the problem diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -1354,14 +1354,16 @@ typedef void * * @param cls closure * @param name set to name of the pot - * @param pot_total amount currently in the pot + * @param pot_total_len length of @a pot_totals array + * @param pot_totals amounts currently in the pot */ typedef void (*TALER_MERCHANTDB_MoneyPotsCallback)( void *cls, uint64_t money_pot_id, const char *name, - const struct TALER_Amount *pot_total); + size_t pot_total_len, + const struct TALER_Amount *pot_totals); /** @@ -4964,9 +4966,13 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure * @param instance_id instance to lookup token family for * @param money_pot_id serial number of the pot to lookup - * @param [out] name set to name of the pot - * @param[out] description set to description of the pot - * @param[out] pot_total set to amount currently in the pot + * @param [out] name set to name of the pot, + * must be freed by caller + * @param[out] description set to description of the pot, + * must be freed by caller + * @param[out] pot_total_len set to length of @a pot_totals array + * @param[out] pot_totals set to amounts currently in the pot; + * array must be freed by caller * @return database result code */ enum GNUNET_DB_QueryStatus @@ -4975,7 +4981,8 @@ struct TALER_MERCHANTDB_Plugin uint64_t money_pot_id, char **name, char **description, - struct TALER_Amount *pot_total); + size_t *pot_total_len, + struct TALER_Amount **pot_totals); /** * Delete information about a money pot. @@ -4998,10 +5005,12 @@ struct TALER_MERCHANTDB_Plugin * @param money_pot_id serial number of the pot to delete * @param name set to name of the pot * @param description set to description of the pot - * @param old_pot_total amount expected currently in the pot, - * NULL to not check - * @param new_pot_total new amount in the pot, - * NULL to not update + * @param opt_len length of @a old_pot_totals + * @param old_pot_totals amounts expected currently in the pot, + * NULL to not check (non-NULL but @a opt_len=0 checks for []) + * @param npt_len length of @a new_pot_totals + * @param new_pot_totals new amounts in the pot, + * NULL to not update (non-NULL but @a npt_len=0 resets to []) * @param[out] conflict_total set to true if @a old_pot_total does not match * @param[out] conflict_name set to true if @a name is used by another pot * @return database result code @@ -5013,8 +5022,10 @@ struct TALER_MERCHANTDB_Plugin uint64_t money_pot_id, const char *name, const char *description, - const struct TALER_Amount *old_pot_total, - const struct TALER_Amount *new_pot_total, + size_t opt_len, + const struct TALER_Amount *old_pot_totals, + size_t npt_len, + const struct TALER_Amount *new_pot_totals, bool *conflict_total, bool *conflict_name); @@ -5026,7 +5037,6 @@ struct TALER_MERCHANTDB_Plugin * @param instance_id instance to insert pot for * @param name set to name of the pot * @param description set to description of the pot - * @param pot_currency the expected currency in the pot * @param[out] money_pot_id serial number of the new pot * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS * on conflict (@a name already in use at @a instance_id). @@ -5037,7 +5047,6 @@ struct TALER_MERCHANTDB_Plugin const char *instance_id, const char *name, const char *description, - const char *pot_currency, uint64_t *money_pot_id); // Reports