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:
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",
- ¤cy),
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