commit 2d50f9bdd6a89f7a75acab039791acb3ede40f73
parent 9d7b48f002a81a015f02de7972fe1f4bbcbc75a6
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 2 Jun 2026 13:41:22 +0200
Merging 'insanity' branch.
This migrates all per-instance tables into their own schema.
Setting the search_path (via set_instance()) is now required
prior to accessing per-instance data. Global data remains
in the "merchant" schema. The "merchant-instances" schema
serves to hold the per-instance stored procedures.
Instead of using named prepared statements, we now use
anonymous prepared statements (one-shot) as prepared statements
do not work well with a changing search_path (the SQL is bound
to the current search_path on first preparation).
This simplifies many SQL statements by avoiding the
"WHERE instance-matches" clause entirely.
Future impact:
- #9453: this will enable per-instance data export by simply
exporting the respective schema (plus one entry from the
merchant_instances) table, giving a clear path towards
implementing data portability.
- In the future, this should also enable us to implement
tighter access control on the database by dropping rights
to a per-instance role instead of always acting as the
"taler-merchant-httpd" role.
Note that this change comes with a MAJOR database migration
transaction that is likely to blow through some default
PostgreSQL limits, so if taler-merchant-dbinit fails, the
solution is likely to increase limits. See README for
details.
Diffstat:
311 files changed, 11274 insertions(+), 8449 deletions(-)
diff --git a/README b/README
@@ -18,6 +18,24 @@ machine integration (taler-mdb) and various demonstrator front-ends at
https://git.taler.net/.
+Upgrading to v0.16
+==================
+
+Upgrading a taler-merchant-httpd < 0.15 to >= 0.16 requires stopping the
+existing service and running taler-merchant-dbinit to upgrade the database
+schema. The migration will be done in one LARGE transaction that can take
+several minutes. If the existing database has non-trivial amounts of data,
+that transaction is likely to run into a PostgreSQL error "out of shared
+memory" with the default PostgreSQL database settings. It is thus recommended
+to set
+
+max_locks_per_transaction = 512
+max_pred_locks_per_transaction = 512
+
+(and restart the database) if this problem is encountered.
+The values can afterwards be lowered again for production.
+
+
Joining GNU
===========
@@ -53,7 +71,7 @@ Direct dependencies
These are the direct dependencies for running a Taler exchange:
-- GNU Taler exchange >= 0.14.0
+- GNU Taler exchange >= 0.16.0
- PostgreSQL >= 15.0
- libqrencode
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c
@@ -34,6 +34,7 @@ struct ExchangeInteraction;
#include "merchant-database/select_exchange_keys.h"
#include "merchant-database/preflight.h"
#include "merchant-database/account_kyc_set_failed.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/insert_deposit_to_transfer.h"
#include "merchant-database/update_deposit_confirmation_status.h"
#include "merchant-database/start.h"
@@ -439,6 +440,15 @@ deposit_get_cb (
"Exchange returned wire transfer over %s for deposited coin %s\n",
TALER_amount2s (&dr->details.ok.coin_contribution),
TALER_B2S (&w->coin_pub));
+ qs = TALER_MERCHANTDB_set_instance (
+ pg,
+ w->instance_id);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
qs = TALER_MERCHANTDB_insert_deposit_to_transfer (
pg,
w->deposit_serial,
@@ -451,6 +461,10 @@ deposit_get_cb (
GNUNET_SCHEDULER_shutdown ();
return;
}
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
break;
}
case MHD_HTTP_ACCEPTED:
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -52,7 +52,8 @@
#include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h"
#include "taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h"
#include "merchant-database/lookup_instances.h"
-#include "merchant-database/select_accounts.h"
+#include "merchant-database/set_instance.h"
+#include "merchant-database/select_accounts_by_instance.h"
#include "merchant-database/event_listen.h"
#include "merchant-database/preflight.h"
#include "merchant-database/event_notify.h"
@@ -681,24 +682,17 @@ url_handler (void *cls,
(void) cls;
(void) version;
- if (NULL != hc->url)
- {
- /* MHD calls us again for a request, we already identified
- the handler, just continue processing with the handler */
- return process_upload_with_handler (hc,
- upload_data,
- upload_data_size);
- }
- hc->url = url;
- log_request (hc,
- method);
-
- /* Find out the merchant backend instance for the request.
- * If there is an instance, remove the instance specification
- * from the beginning of the request URL. */
+ if (NULL == hc->url)
{
+ /* First time.
+ * Find out the merchant backend instance for the request.
+ * If there is an instance, remove the instance specification
+ * from the beginning of the request URL. */
enum GNUNET_GenericReturnValue ret;
+ hc->url = url;
+ log_request (hc,
+ method);
ret = identify_instance (hc,
&url,
&use_admin);
@@ -706,6 +700,59 @@ url_handler (void *cls,
return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
}
+ if (NULL != hc->instance)
+ {
+ /* Narrow DB interaction to selected instance */
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TALER_MERCHANTDB_set_instance (TMH_db,
+ hc->instance->settings.id);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ "set_instance");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ "set_instance");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
+ hc->url);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ if (NULL != hc->rh)
+ {
+ enum MHD_Result res;
+
+ /* MHD calls us again for a request, we already identified
+ the handler, just continue processing with the handler */
+ res = process_upload_with_handler (hc,
+ upload_data,
+ upload_data_size);
+ if (NULL != hc->instance)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (TMH_db,
+ NULL));
+ }
+ return res;
+ }
+
+ /* First time, let's figure out the handler */
{
enum GNUNET_GenericReturnValue ret;
@@ -715,7 +762,15 @@ url_handler (void *cls,
use_admin,
&is_public);
if (GNUNET_OK != ret)
+ {
+ if (NULL != hc->instance)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (TMH_db,
+ NULL));
+ }
return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
}
/* At this point, we must have found a handler */
@@ -741,7 +796,21 @@ url_handler (void *cls,
ret = TMH_perform_access_control (hc);
if (GNUNET_OK != ret)
+ {
+ if (NULL != hc->instance)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (TMH_db,
+ NULL));
+ }
return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+ }
+ if (NULL != hc->instance)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (TMH_db,
+ NULL));
}
if ( (NULL != hc->instance) && /* make static analysis happy */
@@ -862,18 +931,34 @@ add_instance_cb (void *cls,
else
mi->deleted = true;
mi->merchant_pub = *merchant_pub;
- qs = TALER_MERCHANTDB_select_accounts (TMH_db,
- mi->settings.id,
- &add_account_cb,
- mi);
+ qs = TALER_MERCHANTDB_set_instance (TMH_db,
+ mi->settings.id);
+ if (0 > qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error setting instance `%s'\n",
+ mi->settings.id);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ qs = TALER_MERCHANTDB_select_accounts_by_instance (
+ TMH_db,
+ mi->settings.id,
+ &add_account_cb,
+ mi);
if (0 > qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Error loading accounts of `%s' from database\n",
mi->settings.id);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
}
GNUNET_assert (GNUNET_OK ==
TMH_add_instance (mi));
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (TMH_db,
+ NULL));
}
diff --git a/src/backend/taler-merchant-httpd_delete-private-units-UNIT.c b/src/backend/taler-merchant-httpd_delete-private-units-UNIT.c
@@ -29,7 +29,6 @@ TMH_private_delete_units_ID (const struct TMH_RequestHandler *rh,
struct TMH_HandlerContext *hc)
{
enum GNUNET_DB_QueryStatus qs;
- bool no_instance = false;
bool no_unit = false;
bool builtin_conflict = false;
@@ -37,7 +36,6 @@ TMH_private_delete_units_ID (const struct TMH_RequestHandler *rh,
qs = TALER_MERCHANTDB_delete_unit (TMH_db,
hc->instance->settings.id,
hc->infix,
- &no_instance,
&no_unit,
&builtin_conflict);
switch (qs)
@@ -58,11 +56,6 @@ TMH_private_delete_units_ID (const struct TMH_RequestHandler *rh,
TALER_EC_GENERIC_DB_STORE_FAILED,
"delete_unit");
}
- if (no_instance)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- hc->instance->settings.id);
if (no_unit)
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
@@ -25,6 +25,7 @@
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd.h"
#include "merchant-database/get_kyc_limits.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/select_exchange_keys.h"
#include "merchant-database/event_listen.h"
#include "merchant-database/event_notify.h"
@@ -723,6 +724,14 @@ TMH_exchange_check_debit (
json_t *jlimits = NULL;
enum GNUNET_DB_QueryStatus qs;
+ qs = TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ instance_id);
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ return TMH_ES_NO_KEYS | TMH_ES_RETRY_OK;
+ }
qs = TALER_MERCHANTDB_get_kyc_limits (TMH_db,
wm->payto_uri,
instance_id,
@@ -731,6 +740,10 @@ TMH_exchange_check_debit (
&no_access_token,
&jlimits);
GNUNET_break (qs >= 0);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ NULL));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"get_kyc_limits for %s at %s returned %s/%s\n",
wm->payto_uri.full_payto,
diff --git a/src/backend/taler-merchant-httpd_get-private-accounts.c b/src/backend/taler-merchant-httpd_get-private-accounts.c
@@ -21,7 +21,7 @@
#include "platform.h"
#include "taler-merchant-httpd_get-private-accounts.h"
#include <taler/taler_json_lib.h>
-#include "merchant-database/select_accounts.h"
+#include "merchant-database/select_accounts_by_instance.h"
/**
@@ -62,10 +62,11 @@ TMH_private_get_accounts (const struct TMH_RequestHandler *rh,
pa = json_array ();
GNUNET_assert (NULL != pa);
- qs = TALER_MERCHANTDB_select_accounts (TMH_db,
- hc->instance->settings.id,
- &add_account,
- pa);
+ qs = TALER_MERCHANTDB_select_accounts_by_instance (
+ TMH_db,
+ hc->instance->settings.id,
+ &add_account,
+ pa);
if (0 > qs)
{
GNUNET_break (0);
diff --git a/src/backend/taler-merchant-httpd_patch-private-products-PRODUCT_ID.c b/src/backend/taler-merchant-httpd_patch-private-products-PRODUCT_ID.c
@@ -140,7 +140,6 @@ TMH_private_patch_products_ID (
enum MHD_Result ret;
size_t num_cats = 0;
uint64_t *cats = NULL;
- bool no_instance;
ssize_t no_cat;
bool no_product;
bool lost_reduced;
@@ -356,7 +355,6 @@ TMH_private_patch_products_ID (
&pd,
num_cats,
cats,
- &no_instance,
&no_cat,
&no_product,
&lost_reduced,
@@ -391,14 +389,6 @@ TMH_private_patch_products_ID (
break;
}
- if (no_instance)
- {
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- mi->settings.id);
- goto cleanup;
- }
if (-1 != no_cat)
{
char cat_str[24];
diff --git a/src/backend/taler-merchant-httpd_patch-private-units-UNIT.c b/src/backend/taler-merchant-httpd_patch-private-units-UNIT.c
@@ -74,7 +74,6 @@ TMH_private_patch_units_ID (const struct TMH_RequestHandler *rh,
const uint32_t *unit_precision_ptr = NULL;
const bool *unit_active_ptr = NULL;
enum GNUNET_DB_QueryStatus qs;
- bool no_instance = false;
bool no_unit = false;
bool builtin_conflict = false;
enum MHD_Result ret = MHD_YES;
@@ -169,7 +168,6 @@ TMH_private_patch_units_ID (const struct TMH_RequestHandler *rh,
unit_allow_fraction_ptr,
unit_precision_ptr,
unit_active_ptr,
- &no_instance,
&no_unit,
&builtin_conflict);
switch (qs)
@@ -193,14 +191,6 @@ TMH_private_patch_units_ID (const struct TMH_RequestHandler *rh,
goto cleanup;
}
- if (no_instance)
- {
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- mi->settings.id);
- goto cleanup;
- }
if (no_unit)
{
ret = TALER_MHD_reply_with_error (connection,
diff --git a/src/backend/taler-merchant-httpd_post-management-instances.c b/src/backend/taler-merchant-httpd_post-management-instances.c
@@ -33,6 +33,7 @@
#include <taler/taler_json_lib.h>
#include <regex.h>
#include "merchant-database/insert_instance.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/insert_login_token.h"
#include "merchant-database/start.h"
@@ -299,10 +300,11 @@ post_instances (const struct TMH_RequestHandler *rh,
{
GNUNET_JSON_parse_free (spec);
GNUNET_free (is.phone);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
- is.id);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
+ is.id);
}
/* Check for idempotency */
if ( (0 == strcmp (mi->settings.id,
@@ -487,6 +489,7 @@ post_instances (const struct TMH_RequestHandler *rh,
TALER_MERCHANTDB_start (TMH_db,
"post /instances"))
{
+ GNUNET_break (0);
mi->rc = 1;
TMH_instance_decref (mi);
GNUNET_JSON_parse_free (spec);
@@ -548,6 +551,7 @@ retry:
} /* for .. MAX_RETRIES */
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
+ GNUNET_break (0);
mi->rc = 1;
TMH_instance_decref (mi);
GNUNET_JSON_parse_free (spec);
@@ -572,6 +576,39 @@ retry:
}
{
+ /* Narrow DB interaction to new instance */
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TALER_MERCHANTDB_set_instance (TMH_db,
+ is.id);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ "set_instance");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ "set_instance");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
+ hc->url);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ {
struct TALER_MERCHANTDB_LoginTokenP btoken;
enum TMH_AuthScope iscope = TMH_AS_REFRESHABLE | TMH_AS_SPA;
enum GNUNET_DB_QueryStatus qs;
@@ -596,9 +633,11 @@ retry:
case GNUNET_DB_STATUS_SOFT_ERROR:
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break (0);
- return TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_login_token");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_login_token");
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c
@@ -37,6 +37,7 @@ struct RefundDetails;
#include "merchant-database/refund_coin.h"
#include "merchant-database/preflight.h"
#include "merchant-database/start.h"
+#include "merchant-database/set_instance.h"
/**
@@ -740,6 +741,18 @@ begin_transaction (struct AbortContext *ac)
/* First, try to see if we have all we need already done */
TALER_MERCHANTDB_preflight (TMH_db);
+ qs = TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ ac->hc->instance->settings.id);
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "set_instance");
+ goto cleanup;
+ }
if (GNUNET_OK !=
TALER_MERCHANTDB_start (TMH_db,
"run abort"))
@@ -749,7 +762,7 @@ begin_transaction (struct AbortContext *ac)
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_START_FAILED,
NULL);
- return;
+ goto cleanup;
}
/* check payment was indeed incomplete
@@ -773,7 +786,7 @@ begin_transaction (struct AbortContext *ac)
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (ac);
- return;
+ goto cleanup;
}
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
@@ -781,14 +794,14 @@ begin_transaction (struct AbortContext *ac)
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"order status");
- return;
+ goto cleanup;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
TALER_MERCHANTDB_rollback (TMH_db);
resume_abort_with_error (ac,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND,
"Could not find contract");
- return;
+ goto cleanup;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
if (paid)
{
@@ -798,7 +811,7 @@ begin_transaction (struct AbortContext *ac)
MHD_HTTP_PRECONDITION_FAILED,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
"Payment was complete, refusing to abort");
- return;
+ goto cleanup;
}
}
if (0 !=
@@ -811,7 +824,7 @@ begin_transaction (struct AbortContext *ac)
MHD_HTTP_FORBIDDEN,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH,
"Provided hash does not match order on file");
- return;
+ goto cleanup;
}
}
@@ -827,7 +840,7 @@ begin_transaction (struct AbortContext *ac)
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (ac);
- return;
+ goto cleanup;
}
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
@@ -835,7 +848,7 @@ begin_transaction (struct AbortContext *ac)
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"deposits");
- return;
+ goto cleanup;
}
qs = TALER_MERCHANTDB_commit (TMH_db);
@@ -845,13 +858,13 @@ begin_transaction (struct AbortContext *ac)
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (ac);
- return;
+ goto cleanup;
}
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
- return;
+ goto cleanup;
}
/* At this point, the refund got correctly committed
@@ -859,9 +872,14 @@ begin_transaction (struct AbortContext *ac)
if (ac->pending > 0)
{
find_next_exchange (ac);
- return;
+ goto cleanup;
}
generate_success_response (ac);
+cleanup:
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ NULL));
}
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c
@@ -63,6 +63,7 @@ struct ExchangeGroup;
#include "merchant-database/lookup_deposits_by_order.h"
#include "merchant-database/lookup_order_charity.h"
#include "merchant-database/lookup_refunds.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/lookup_spent_tokens_by_order.h"
#include "merchant-database/lookup_token_family_key.h"
#include "merchant-database/mark_contract_paid.h"
@@ -1053,6 +1054,11 @@ batch_deposit_transaction (
uint64_t b_dep_serial;
uint32_t off = 0;
+ qs = TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ pc->hc->instance->settings.id);
+ if (qs <= 0)
+ return qs; /* failure, we're done */
qs = TALER_MERCHANTDB_insert_deposit_confirmation (
TMH_db,
pc->hc->instance->settings.id,
@@ -1067,7 +1073,7 @@ batch_deposit_transaction (
dr->details.ok.exchange_pub,
&b_dep_serial);
if (qs <= 0)
- return qs; /* Entire batch already known or failure, we're done */
+ goto cleanup; /* Entire batch already known or failure, we're done */
for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
{
@@ -1099,9 +1105,14 @@ batch_deposit_transaction (
pc->check_contract.contract_terms->pc->wire_deadline.abs_time,
GNUNET_TIME_randomize (GNUNET_TIME_UNIT_MINUTES)));
if (qs < 0)
- return qs;
+ goto cleanup;
GNUNET_break (qs > 0);
}
+cleanup:
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ NULL));
return qs;
}
@@ -4757,7 +4768,6 @@ phase_parse_wallet_data (struct PayContext *pc)
pc->hc->instance->settings.id,
donau_url_tmp,
&pc->parse_wallet_data.charity_id,
- &pc->parse_wallet_data.charity_priv,
&pc->parse_wallet_data.charity_max_per_year,
&pc->parse_wallet_data.charity_receipts_to_date,
&donau_keys_json,
@@ -4785,6 +4795,11 @@ phase_parse_wallet_data (struct PayContext *pc)
donau_url_tmp));
return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_static_assert (sizeof (pc->parse_wallet_data.charity_priv) ==
+ sizeof (pc->hc->instance->merchant_priv));
+ memcpy (&pc->parse_wallet_data.charity_priv,
+ &pc->hc->instance->merchant_priv,
+ sizeof (pc->hc->instance->merchant_priv));
pc->parse_wallet_data.donau.donau_url =
GNUNET_strdup (donau_url_tmp);
break;
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-refund.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-refund.c
@@ -35,6 +35,7 @@ struct CoinRefund;
#include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h"
#include "merchant-database/insert_refund_proof.h"
#include "merchant-database/lookup_contract_terms.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/lookup_refund_proof.h"
#include "merchant-database/lookup_refunds_detailed.h"
#include "merchant-database/event_notify.h"
@@ -414,12 +415,9 @@ refund_cb (struct CoinRefund *cr,
{
enum GNUNET_DB_QueryStatus qs;
- cr->exchange_pub = rr->details.ok.exchange_pub;
- cr->exchange_sig = rr->details.ok.exchange_sig;
- qs = TALER_MERCHANTDB_insert_refund_proof (TMH_db,
- cr->refund_serial,
- &rr->details.ok.exchange_sig,
- &rr->details.ok.exchange_pub);
+ qs = TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ cr->prd->hc->instance->settings.id);
if (0 >= qs)
{
/* generally, this is relatively harmless for the merchant, but let's at
@@ -430,7 +428,28 @@ refund_cb (struct CoinRefund *cr,
}
else
{
- notify_refund_obtained (cr->prd);
+ cr->exchange_pub = rr->details.ok.exchange_pub;
+ cr->exchange_sig = rr->details.ok.exchange_sig;
+ qs = TALER_MERCHANTDB_insert_refund_proof (TMH_db,
+ cr->refund_serial,
+ &rr->details.ok.exchange_sig,
+ &rr->details.ok.exchange_pub);
+ if (0 >= qs)
+ {
+ /* generally, this is relatively harmless for the merchant, but let's at
+ least log this. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to persist exchange response to /refund in database: %d\n",
+ qs);
+ }
+ else
+ {
+ notify_refund_obtained (cr->prd);
+ }
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ NULL));
}
}
break;
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-unclaim.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-unclaim.c
@@ -83,7 +83,6 @@ TMH_post_orders_ID_unclaim (const struct TMH_RequestHandler *rh,
TALER_MERCHANTDB_preflight (TMH_db);
qs = TALER_MERCHANTDB_insert_unclaim_signature (TMH_db,
hc->instance->settings.id,
- &hc->instance->merchant_pub,
order_id,
&nonce,
&h_contract,
diff --git a/src/backend/taler-merchant-httpd_post-private-accounts.c b/src/backend/taler-merchant-httpd_post-private-accounts.c
@@ -31,7 +31,7 @@
#include "taler-merchant-httpd_mfa.h"
#include <regex.h>
#include "merchant-database/activate_account.h"
-#include "merchant-database/select_accounts.h"
+#include "merchant-database/select_accounts_by_instance.h"
#include "merchant-database/preflight.h"
/**
@@ -239,10 +239,11 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh,
if (! ok)
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
- "The payment target type is forbidden by policy");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ "The payment target type is forbidden by policy");
}
}
@@ -255,20 +256,22 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh,
0)) )
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
- "The specific account is forbidden by policy");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ "The specific account is forbidden by policy");
}
if ( (NULL == pac.credit_facade_url) !=
(NULL == pac.credit_facade_credentials) )
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- (NULL == pac.credit_facade_url)
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ (NULL == pac.credit_facade_url)
? "credit_facade_url"
: "credit_facade_credentials");
}
@@ -283,10 +286,11 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh,
&auth))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "credit_facade_url or credit_facade_credentials");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "credit_facade_url or credit_facade_credentials");
}
TALER_MERCHANT_BANK_auth_free (&auth);
}
@@ -295,24 +299,27 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh,
enum GNUNET_DB_QueryStatus qs;
TALER_MERCHANTDB_preflight (TMH_db);
- qs = TALER_MERCHANTDB_select_accounts (TMH_db,
- mi->settings.id,
- &account_cb,
- &pac);
+ qs = TALER_MERCHANTDB_select_accounts_by_instance (
+ TMH_db,
+ mi->settings.id,
+ &account_cb,
+ &pac);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_accounts");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_accounts");
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_accounts");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_accounts");
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
@@ -333,24 +340,28 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh,
&pac.h_wire));
}
+#if 1
if (pac.have_conflicting_account)
{
/* Conflict, refuse request */
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_ACCOUNT_EXISTS,
- pac.uri.full_payto);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_ACCOUNT_EXISTS,
+ pac.uri.full_payto);
}
+#endif
if (pac.have_any_account)
{
/* MFA needed */
enum GNUNET_GenericReturnValue ret;
- ret = TMH_mfa_check_simple (hc,
- TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
- mi);
+ ret = TMH_mfa_check_simple (
+ hc,
+ TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
+ mi);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Account creation MFA check returned %d\n",
(int) ret);
diff --git a/src/backend/taler-merchant-httpd_post-private-donau.c b/src/backend/taler-merchant-httpd_post-private-donau.c
@@ -31,6 +31,8 @@
#include "merchant-database/insert_donau_instance.h"
#include "merchant-database/event_listen.h"
#include "merchant-database/event_notify.h"
+#include "merchant-database/set_instance.h"
+
/**
* Context for the POST /donau request handler.
@@ -134,6 +136,9 @@ donau_charity_get_cb (void *cls,
enum GNUNET_DB_QueryStatus qs;
pdc->get_handle = NULL;
+ pdc->suspended = GNUNET_NO;
+ MHD_resume_connection (pdc->connection);
+ TALER_MHD_daemon_trigger ();
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Processing DONAU charity get response");
/* Anything but 200 => propagate Donau’s response. */
@@ -144,9 +149,6 @@ donau_charity_get_cb (void *cls,
TALER_MHD_PACK_EC (gcr->hr.ec),
GNUNET_JSON_pack_uint64 ("donau_http_status",
gcr->hr.http_status));
- pdc->suspended = GNUNET_NO;
- MHD_resume_connection (pdc->connection);
- TALER_MHD_daemon_trigger ();
return;
}
@@ -160,8 +162,19 @@ donau_charity_get_cb (void *cls,
pdc->response = TALER_MHD_make_error (
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"charity_pub != merchant_pub");
- MHD_resume_connection (pdc->connection);
- TALER_MHD_daemon_trigger ();
+ return;
+ }
+
+ qs = TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ pdc->hc->instance->settings.id);
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ pdc->response = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "set_instance");
return;
}
@@ -169,6 +182,10 @@ donau_charity_get_cb (void *cls,
pdc->donau_url,
&gcr->details.ok.charity,
pdc->charity_id);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ TMH_db,
+ NULL));
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -212,9 +229,6 @@ donau_charity_get_cb (void *cls,
break;
}
}
- pdc->suspended = GNUNET_NO;
- MHD_resume_connection (pdc->connection);
- TALER_MHD_daemon_trigger ();
}
diff --git a/src/backend/taler-merchant-httpd_post-private-orders.c b/src/backend/taler-merchant-httpd_post-private-orders.c
@@ -1624,13 +1624,14 @@ add_input_token_family (struct OrderContext *oc,
enum TALER_ErrorCode ec = TALER_EC_INVALID; /* make compiler happy */
unsigned int http_status = 0; /* make compiler happy */
- qs = TALER_MERCHANTDB_lookup_token_family_keys (TMH_db,
- oc->hc->instance->settings.id,
- slug,
- now,
- end,
- &add_family_key,
- oc);
+ qs = TALER_MERCHANTDB_lookup_token_family_keys (
+ TMH_db,
+ oc->hc->instance->settings.id,
+ slug,
+ now,
+ end,
+ &add_family_key,
+ oc);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -3568,10 +3569,6 @@ phase_parse_choices (struct OrderContext *oc)
contract_input->details.token.token_family_slug))
{
GNUNET_break_op (0);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
- contract_input->details.token.token_family_slug);
return;
}
off++;
diff --git a/src/backend/taler-merchant-httpd_post-private-products.c b/src/backend/taler-merchant-httpd_post-private-products.c
@@ -130,7 +130,6 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
size_t num_cats = 0;
uint64_t *cats = NULL;
bool conflict;
- bool no_instance;
ssize_t no_cat;
bool no_group;
bool no_pot;
@@ -340,7 +339,6 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
&pd,
num_cats,
cats,
- &no_instance,
&conflict,
&no_cat,
&no_group,
@@ -367,15 +365,6 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
- if (no_instance)
- {
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- mi->settings.id);
- goto cleanup;
- }
if (no_group)
{
ret = TALER_MHD_reply_with_error (
diff --git a/src/backend/taler-merchant-httpd_post-private-transfers.c b/src/backend/taler-merchant-httpd_post-private-transfers.c
@@ -58,7 +58,6 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
};
enum GNUNET_GenericReturnValue res;
enum GNUNET_DB_QueryStatus qs;
- bool no_instance;
bool no_account;
bool conflict;
@@ -83,7 +82,6 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
&amount,
payto_uri,
0 /* no bank serial known! */,
- &no_instance,
&no_account,
&conflict);
switch (qs)
@@ -105,17 +103,6 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
- if (no_instance)
- {
- /* should be only possible if instance was concurrently deleted,
- that's so theoretical we rather log as error... */
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- hc->instance->settings.id);
- }
if (no_account)
{
GNUNET_break_op (0);
diff --git a/src/backend/taler-merchant-httpd_post-private-units.c b/src/backend/taler-merchant-httpd_post-private-units.c
@@ -139,14 +139,12 @@ TMH_private_post_units (const struct TMH_RequestHandler *rh,
nud.unit_builtin = false;
{
- bool no_instance = false;
bool conflict = false;
uint64_t unit_serial = 0;
qs = TALER_MERCHANTDB_insert_unit (TMH_db,
mi->settings.id,
&nud,
- &no_instance,
&conflict,
&unit_serial);
@@ -177,14 +175,6 @@ TMH_private_post_units (const struct TMH_RequestHandler *rh,
break;
}
- if (no_instance)
- {
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- mi->settings.id);
- goto cleanup;
- }
if (conflict)
{
ret = TALER_MHD_reply_with_error (connection,
diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c
@@ -36,6 +36,7 @@ struct Inquiry;
#include "merchant-database/account_kyc_get_outdated.h"
#include "merchant-database/account_kyc_set_status.h"
#include "merchant-database/get_kyc_status.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/select_accounts.h"
#include "merchant-database/select_exchange_keys.h"
#include "merchant-database/event_listen.h"
@@ -678,6 +679,15 @@ exchange_check_cb (
{
enum GNUNET_DB_QueryStatus qs;
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ i->a->instance_id);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
qs = TALER_MERCHANTDB_account_kyc_set_status (
pg,
i->a->instance_id,
@@ -695,6 +705,10 @@ exchange_check_cb (
i->jlimits,
i->aml_review,
i->kyc_ok);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
if (qs < 0)
{
GNUNET_break (0);
@@ -862,6 +876,15 @@ start_inquiry (struct Exchange *e,
GNUNET_CONTAINER_DLL_insert (a->i_head,
a->i_tail,
i);
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ a->instance_id);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
qs = TALER_MERCHANTDB_get_kyc_status (pg,
a->merchant_account_uri,
a->instance_id,
@@ -976,6 +999,15 @@ flag_ineligible (const char *instance_id,
"Account %s not eligible at exchange %s\n",
TALER_B2S (h_wire),
exchange_url);
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ instance_id);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
qs = TALER_MERCHANTDB_account_kyc_set_status (
pg,
instance_id,
@@ -991,6 +1023,10 @@ flag_ineligible (const char *instance_id,
NULL,
false,
false);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
if (qs < 0)
{
GNUNET_break (0);
@@ -1127,7 +1163,6 @@ find_accounts (void *cls)
account_task = NULL;
database_gen++;
qs = TALER_MERCHANTDB_select_accounts (pg,
- NULL, /* all instances */
&account_cb,
NULL);
if (qs < 0)
diff --git a/src/backend/taler-merchant-reconciliation.c b/src/backend/taler-merchant-reconciliation.c
@@ -36,6 +36,7 @@ struct Inquiry;
#include "merchant-database/lookup_wire_fee.h"
#include "merchant-database/select_exchange_keys.h"
#include "merchant-database/select_open_transfers.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/update_transfer_status.h"
#include "merchant-database/event_listen.h"
#include "merchant-database/preflight.h"
@@ -303,6 +304,15 @@ update_transaction_status (const struct Inquiry *w,
{
enum GNUNET_DB_QueryStatus qs;
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ w->instance_id);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
qs = TALER_MERCHANTDB_update_transfer_status (pg,
w->exchange->exchange_url,
&w->wtid,
@@ -311,6 +321,10 @@ update_transaction_status (const struct Inquiry *w,
ec,
last_hint,
needs_retry);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
if (qs < 0)
{
GNUNET_break (0);
@@ -781,6 +795,16 @@ wire_transfer_cb (struct Inquiry *w,
{
enum GNUNET_DB_QueryStatus qs;
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ w->instance_id);
+ if (0 > qs)
+ {
+ /* Always report on DB error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
qs = TALER_MERCHANTDB_insert_transfer_details (pg,
w->instance_id,
w->exchange->exchange_url,
@@ -804,6 +828,10 @@ wire_transfer_cb (struct Inquiry *w,
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Transfer already known. Ignoring duplicate.\n");
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
return;
}
}
@@ -860,6 +888,10 @@ wire_transfer_cb (struct Inquiry *w,
but still have no result? */
GNUNET_break (0);
ctc.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
return;
case GNUNET_SYSERR:
/* #check_transfer() failed, report conflict! */
@@ -882,6 +914,10 @@ wire_transfer_cb (struct Inquiry *w,
ctc.ec,
NULL /* no hint */,
! ctc.failure);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
end_inquiry (w);
return;
}
@@ -899,6 +935,10 @@ wire_transfer_cb (struct Inquiry *w,
TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE,
TALER_amount2s (&td->wire_fee),
false);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
end_inquiry (w);
return;
}
@@ -922,6 +962,10 @@ wire_transfer_cb (struct Inquiry *w,
return;
}
}
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (
+ pg,
+ NULL));
end_inquiry (w);
}
diff --git a/src/backend/taler-merchant-report-generator.c b/src/backend/taler-merchant-report-generator.c
@@ -35,6 +35,7 @@
#include "merchant-database/delete_report.h"
#include "merchant-database/lookup_reports_pending.h"
#include "merchant-database/update_report_status.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/event_listen.h"
@@ -268,6 +269,18 @@ finish_transmission (struct ReportActivity *ra,
struct GNUNET_TIME_Timestamp next_ts;
next_ts = GNUNET_TIME_absolute_to_timestamp (ra->next_transmission);
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ ra->instance_id);
+ if (qs <= 0)
+ {
+ free_ra (ra);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to set instance to update report status: %d\n",
+ qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
if ( (TALER_EC_NONE == ec) &&
(ra->one_shot) )
{
diff --git a/src/backend/taler-merchant-wirewatch.c b/src/backend/taler-merchant-wirewatch.c
@@ -29,6 +29,7 @@
#include "merchantdb_lib.h"
#include "merchantdb_lib.h"
#include "merchant-database/insert_transfer.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/select_wirewatch_accounts.h"
#include "merchant-database/update_wirewatch_progress.h"
#include "merchant-database/event_listen.h"
@@ -187,10 +188,26 @@ save (struct Watch *w)
{
enum GNUNET_DB_QueryStatus qs;
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ w->instance_id);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to set instance to %s (%d)\n",
+ w->instance_id,
+ qs);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+
qs = TALER_MERCHANTDB_update_wirewatch_progress (pg,
w->instance_id,
w->payto_uri,
w->start_row);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (pg,
+ NULL));
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -361,7 +378,6 @@ credit_cb (
enum GNUNET_DB_QueryStatus qs;
char *exchange_url;
struct TALER_WireTransferIdentifierRawP wtid;
- bool no_instance;
bool no_account;
bool conflict;
@@ -384,6 +400,16 @@ credit_cb (
}
/* FIXME-Performance-Optimization: consider grouping multiple inserts
into one bigger transaction with just one notify. */
+ qs = TALER_MERCHANTDB_set_instance (pg,
+ w->instance_id);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ w->hh = NULL;
+ GNUNET_free (exchange_url);
+ return GNUNET_SYSERR;
+ }
qs = TALER_MERCHANTDB_insert_transfer (pg,
w->instance_id,
exchange_url,
@@ -391,10 +417,12 @@ credit_cb (
&details->amount,
details->credit_account_uri,
serial_id,
- &no_instance,
&no_account,
&conflict);
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (pg,
+ NULL));
GNUNET_free (exchange_url);
if (qs < 0)
{
@@ -403,13 +431,6 @@ credit_cb (
w->hh = NULL;
return GNUNET_SYSERR;
}
- if (no_instance)
- {
- GNUNET_break (0);
- GNUNET_SCHEDULER_shutdown ();
- w->hh = NULL;
- return GNUNET_SYSERR;
- }
if (no_account)
{
GNUNET_break (0);
diff --git a/src/backenddb/account_kyc_get_outdated.c b/src/backenddb/account_kyc_get_outdated.c
@@ -76,11 +76,11 @@ kyc_status_cb (void *cls,
char *exchange_url;
char *instance_id;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ GNUNET_PQ_result_spec_auto_from_type ("out_h_wire",
&h_wire),
- GNUNET_PQ_result_spec_string ("exchange_url",
+ GNUNET_PQ_result_spec_string ("out_exchange_url",
&exchange_url),
- GNUNET_PQ_result_spec_string ("instance_id",
+ GNUNET_PQ_result_spec_string ("out_merchant_id",
&instance_id),
GNUNET_PQ_result_spec_end
};
@@ -104,9 +104,10 @@ kyc_status_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_account_kyc_get_outdated (struct TALER_MERCHANTDB_PostgresContext *pg,
- TALER_MERCHANTDB_KycOutdatedCallback kyc_cb,
- void *kyc_cb_cls)
+TALER_MERCHANTDB_account_kyc_get_outdated (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ TALER_MERCHANTDB_KycOutdatedCallback kyc_cb,
+ void *kyc_cb_cls)
{
struct KycStatusContext ksc = {
.kyc_cb = kyc_cb,
@@ -123,17 +124,11 @@ TALER_MERCHANTDB_account_kyc_get_outdated (struct TALER_MERCHANTDB_PostgresConte
check_connection (pg);
PREPARE (pg,
"account_kyc_get_outdated",
- "SELECT "
- " mi.merchant_id"
- " ,ma.h_wire"
- " ,kyc.exchange_url"
- " FROM merchant_kyc kyc"
- " JOIN merchant_accounts ma"
- " USING (account_serial)"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE kyc.next_kyc_poll < $1"
- " ORDER BY kyc.next_kyc_poll ASC;");
+ "SELECT"
+ " out_merchant_id"
+ " ,out_h_wire"
+ " ,out_exchange_url"
+ " FROM merchant.account_kyc_get_outdated($1)");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"account_kyc_get_outdated",
diff --git a/src/backenddb/account_kyc_get_outdated.sql b/src/backenddb/account_kyc_get_outdated.sql
@@ -0,0 +1,63 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.account_kyc_get_outdated;
+CREATE FUNCTION merchant.account_kyc_get_outdated(
+ IN in_now INT8
+)
+RETURNS TABLE(
+ out_merchant_id TEXT,
+ out_h_wire BYTEA,
+ out_exchange_url TEXT)
+LANGUAGE plpgsql
+AS $FN$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ inner_rec RECORD;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ ,merchant_id
+ FROM merchant.merchant_instances
+ LOOP
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ FOR inner_rec
+ IN
+ EXECUTE format('SELECT ma.h_wire AS h_wire, kyc.exchange_url AS exchange_url'
+ ' FROM %I.merchant_kyc kyc'
+ ' JOIN %I.merchant_accounts ma USING (account_serial)'
+ ' WHERE kyc.next_kyc_poll < $1'
+ ' ORDER BY kyc.next_kyc_poll ASC', s, s)
+ USING in_now
+ LOOP
+ out_merchant_id := rec.merchant_id;
+ out_h_wire := inner_rec.h_wire;
+ out_exchange_url := inner_rec.exchange_url;
+ RETURN NEXT;
+ END LOOP;
+ EXCEPTION
+ WHEN undefined_table
+ THEN
+ NULL;
+ END;
+ END LOOP;
+END
+$FN$;
+COMMENT ON FUNCTION merchant.account_kyc_get_outdated(INT8)
+ IS 'Returns one row per outdated KYC entry across all instance schemas.'
+ ' An entry is outdated if its next_kyc_poll value is less than in_now.';
diff --git a/src/backenddb/account_kyc_get_status.c b/src/backenddb/account_kyc_get_status.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/account_kyc_get_status.h"
#include "helper.h"
@@ -172,7 +170,6 @@ TALER_MERCHANTDB_account_kyc_get_status (
struct GNUNET_TIME_Absolute now
= GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_absolute_time (&now),
NULL == exchange_url
? GNUNET_PQ_query_param_null ()
@@ -184,24 +181,26 @@ TALER_MERCHANTDB_account_kyc_get_status (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (merchant_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "account_kyc_get_status",
- "SELECT "
- " out_h_wire AS h_wire"
- " ,out_payto_uri AS payto_uri"
- " ,out_exchange_url AS exchange_url"
- " ,out_kyc_timestamp AS kyc_timestamp"
- " ,out_kyc_ok AS kyc_ok"
- " ,out_access_token AS access_token"
- " ,out_exchange_http_status AS exchange_http_status"
- " ,out_exchange_ec_code AS exchange_ec_code"
- " ,out_aml_review AS aml_review"
- " ,out_jaccount_limits::TEXT AS jaccount_limits"
- " FROM merchant_do_account_kyc_get_status($1, $2, $3, $4);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT "
+ " out_h_wire AS h_wire"
+ " ,out_payto_uri AS payto_uri"
+ " ,out_exchange_url AS exchange_url"
+ " ,out_kyc_timestamp AS kyc_timestamp"
+ " ,out_kyc_ok AS kyc_ok"
+ " ,out_access_token AS access_token"
+ " ,out_exchange_http_status AS exchange_http_status"
+ " ,out_exchange_ec_code AS exchange_ec_code"
+ " ,out_aml_review AS aml_review"
+ " ,out_jaccount_limits::TEXT AS jaccount_limits"
+ " FROM merchant_do_account_kyc_get_status($1, $2, $3);");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "account_kyc_get_status",
+ "",
params,
&kyc_status_cb,
&ksc);
diff --git a/src/backenddb/account_kyc_get_status.sql b/src/backenddb/account_kyc_get_status.sql
@@ -0,0 +1,111 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_account_kyc_get_status;
+CREATE FUNCTION merchant_do_account_kyc_get_status (
+ IN in_now INT8,
+ IN in_exchange_url TEXT, -- can be NULL
+ IN in_h_wire BYTEA -- can be NULL
+) RETURNS TABLE (
+ out_h_wire BYTEA, -- never NULL
+ out_payto_uri TEXT, -- never NULL
+ out_exchange_url TEXT,
+ out_kyc_timestamp INT8,
+ out_kyc_ok BOOLEAN,
+ out_access_token BYTEA,
+ out_exchange_http_status INT4,
+ out_exchange_ec_code INT4,
+ out_aml_review BOOLEAN,
+ out_jaccount_limits TEXT
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_account_serial INT8;
+ my_h_wire BYTEA;
+ my_payto_uri TEXT;
+ my_kyc_record RECORD;
+
+BEGIN
+ -- Iterate over merchant_accounts
+ FOR my_account_serial, my_h_wire, my_payto_uri
+ IN SELECT account_serial, h_wire, payto_uri
+ FROM merchant_accounts
+ WHERE active
+ AND (in_h_wire IS NULL OR h_wire = in_h_wire)
+ ORDER BY account_serial ASC
+ LOOP
+
+ -- Fetch KYC info for this account (can have multiple results)
+ FOR my_kyc_record IN
+ SELECT
+ mk.kyc_serial_id
+ ,mk.exchange_url
+ ,mk.kyc_timestamp
+ ,mk.kyc_ok
+ ,mk.access_token
+ ,mk.exchange_http_status
+ ,mk.exchange_ec_code
+ ,mk.aml_review
+ ,mk.jaccount_limits::TEXT
+ FROM merchant_kyc mk
+ WHERE mk.account_serial = my_account_serial
+ AND (in_exchange_url IS NULL OR mk.exchange_url = in_exchange_url)
+ ORDER BY mk.kyc_serial_id ASC
+ LOOP
+ -- Ask taler-merchant-kyccheck to get us an update on the status ASAP
+ UPDATE merchant_kyc
+ SET next_kyc_poll=in_now
+ WHERE kyc_serial_id = my_kyc_record.kyc_serial_id;
+ NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG; -- MERCHANT_EXCHANGE_KYC_UPDATE_FORCED
+ RETURN QUERY
+ SELECT
+ my_h_wire,
+ my_payto_uri,
+ my_kyc_record.exchange_url,
+ my_kyc_record.kyc_timestamp,
+ my_kyc_record.kyc_ok,
+ my_kyc_record.access_token,
+ my_kyc_record.exchange_http_status,
+ my_kyc_record.exchange_ec_code,
+ my_kyc_record.aml_review,
+ my_kyc_record.jaccount_limits::TEXT;
+ END LOOP; -- loop over exchanges with KYC status for the given account
+
+ IF NOT FOUND
+ THEN
+ -- Still return to server that we do NOT know anything
+ -- for the given exchange yet (but that the bank account exists)
+ RETURN QUERY
+ SELECT
+ my_h_wire,
+ my_payto_uri,
+ NULL::TEXT,
+ NULL::INT8,
+ NULL::BOOLEAN,
+ NULL::BYTEA,
+ NULL::INT4,
+ NULL::INT4,
+ NULL::BOOLEAN,
+ NULL::TEXT;
+ END IF;
+
+ END LOOP; -- loop over merchant_accounts
+
+END $$;
+COMMENT ON FUNCTION merchant_do_account_kyc_get_status
+ IS 'Returns the KYC status of selected exchanges and accounts, but ALSO resets the next_kyc_check time for all returned data points to the current time (in_now argument)';
diff --git a/src/backenddb/account_kyc_set_failed.c b/src/backenddb/account_kyc_set_failed.c
@@ -19,7 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/account_kyc_set_failed.h"
@@ -27,13 +26,14 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_account_kyc_set_failed (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- struct GNUNET_TIME_Timestamp timestamp,
- unsigned int exchange_http_status,
- bool kyc_ok)
+TALER_MERCHANTDB_account_kyc_set_failed (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp timestamp,
+ unsigned int exchange_http_status,
+ bool kyc_ok)
{
struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = {
.header.size = htons (sizeof (ev)),
@@ -50,7 +50,6 @@ TALER_MERCHANTDB_account_kyc_set_failed (struct TALER_MERCHANTDB_PostgresContext
= GNUNET_PQ_get_event_notify_channel (&hdr);
uint32_t http_status32 = (uint32_t) exchange_http_status;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_timestamp (×tamp),
@@ -60,28 +59,26 @@ TALER_MERCHANTDB_account_kyc_set_failed (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_query_param_string (notify2_s),
GNUNET_PQ_query_param_end
};
- bool no_instance;
bool no_account;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("no_instance",
- &no_instance),
GNUNET_PQ_result_spec_bool ("no_account",
&no_account),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (merchant_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "account_kyc_set_failed",
- "SELECT "
- " out_no_instance AS no_instance"
- " ,out_no_account AS no_account"
- " FROM merchant_do_account_kyc_set_failed"
- "($1, $2, $3, $4, $5, $6, $7, $8);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT "
+ " out_no_account AS no_account"
+ " FROM merchant_do_account_kyc_set_failed"
+ "($1, $2, $3, $4, $5, $6, $7);");
qs = GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "account_kyc_set_failed",
+ "",
params,
rs);
GNUNET_free (notify_s);
@@ -93,7 +90,6 @@ TALER_MERCHANTDB_account_kyc_set_failed (struct TALER_MERCHANTDB_PostgresContext
GNUNET_break (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
return qs;
}
- GNUNET_break (! no_instance);
GNUNET_break (! no_account);
return qs;
}
diff --git a/src/backenddb/account_kyc_set_failed.sql b/src/backenddb/account_kyc_set_failed.sql
@@ -0,0 +1,82 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_account_kyc_set_failed;
+CREATE FUNCTION merchant_do_account_kyc_set_failed (
+ IN in_h_wire BYTEA,
+ IN in_exchange_url TEXT,
+ IN in_timestamp INT8,
+ IN in_exchange_http_status INT4,
+ IN in_kyc_ok BOOL,
+ IN in_notify_str TEXT,
+ IN in_notify2_str TEXT,
+ OUT out_no_account BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_account_serial INT8;
+BEGIN
+
+out_no_account=FALSE;
+
+SELECT account_serial
+ INTO my_account_serial
+ FROM merchant_accounts
+ WHERE h_wire=in_h_wire;
+
+IF NOT FOUND
+THEN
+ out_no_account=TRUE;
+ RETURN;
+END IF;
+
+UPDATE merchant_kyc
+ SET kyc_timestamp=in_timestamp
+ ,kyc_ok=in_kyc_ok
+ ,exchange_http_status=in_exchange_http_status
+ ,exchange_ec_code=0
+ WHERE account_serial=my_account_serial
+ AND exchange_url=in_exchange_url;
+
+IF NOT FOUND
+THEN
+
+ INSERT INTO merchant_kyc
+ (kyc_timestamp
+ ,kyc_ok
+ ,account_serial
+ ,exchange_url
+ ,exchange_http_status)
+ VALUES
+ (in_timestamp
+ ,in_kyc_ok
+ ,my_account_serial
+ ,in_exchange_url
+ ,in_exchange_http_status);
+END IF;
+
+EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify_str);
+
+EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify2_str);
+
+
+-- Success!
+END $$;
diff --git a/src/backenddb/account_kyc_set_status.c b/src/backenddb/account_kyc_set_status.c
@@ -20,7 +20,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/account_kyc_set_status.h"
@@ -28,20 +27,21 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_account_kyc_set_status (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- struct GNUNET_TIME_Timestamp timestamp,
- struct GNUNET_TIME_Absolute next_time,
- struct GNUNET_TIME_Relative kyc_backoff,
- unsigned int exchange_http_status,
- enum TALER_ErrorCode exchange_ec_code,
- uint64_t rule_gen,
- const struct TALER_AccountAccessTokenP *access_token,
- const json_t *jlimits,
- bool in_aml_review,
- bool kyc_ok)
+TALER_MERCHANTDB_account_kyc_set_status (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp timestamp,
+ struct GNUNET_TIME_Absolute next_time,
+ struct GNUNET_TIME_Relative kyc_backoff,
+ unsigned int exchange_http_status,
+ enum TALER_ErrorCode exchange_ec_code,
+ uint64_t rule_gen,
+ const struct TALER_AccountAccessTokenP *access_token,
+ const json_t *jlimits,
+ bool in_aml_review,
+ bool kyc_ok)
{
struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = {
.header.size = htons (sizeof (ev)),
@@ -59,7 +59,6 @@ TALER_MERCHANTDB_account_kyc_set_status (struct TALER_MERCHANTDB_PostgresContext
uint32_t http_status32 = (uint32_t) exchange_http_status;
uint32_t ec_code32 = (uint32_t) exchange_ec_code;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_timestamp (×tamp),
@@ -80,29 +79,27 @@ TALER_MERCHANTDB_account_kyc_set_status (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_query_param_relative_time (&kyc_backoff),
GNUNET_PQ_query_param_end
};
- bool no_instance;
bool no_account;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("no_instance",
- &no_instance),
GNUNET_PQ_result_spec_bool ("no_account",
&no_account),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (merchant_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "account_kyc_set_status",
- "SELECT "
- " out_no_instance AS no_instance"
- " ,out_no_account AS no_account"
- " FROM merchant_do_account_kyc_set_status"
- "($1, $2, $3, $4, $5, $6, $7, $8::TEXT::JSONB"
- ",$9, $10, $11, $12, $13, $14, $15);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT "
+ " out_no_account AS no_account"
+ " FROM merchant_do_account_kyc_set_status"
+ "($1, $2, $3, $4, $5, $6, $7::TEXT::JSONB"
+ ",$8, $9, $10, $11, $12, $13, $14);");
qs = GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "account_kyc_set_status",
+ "",
params,
rs);
GNUNET_free (notify_s);
@@ -114,7 +111,6 @@ TALER_MERCHANTDB_account_kyc_set_status (struct TALER_MERCHANTDB_PostgresContext
GNUNET_break (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
return qs;
}
- GNUNET_break (! no_instance);
GNUNET_break (! no_account);
return qs;
}
diff --git a/src/backenddb/account_kyc_set_status.sql b/src/backenddb/account_kyc_set_status.sql
@@ -0,0 +1,109 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024, 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_account_kyc_set_status;
+CREATE FUNCTION merchant_do_account_kyc_set_status (
+ IN in_h_wire BYTEA,
+ IN in_exchange_url TEXT,
+ IN in_timestamp INT8,
+ IN in_exchange_http_status INT4,
+ IN in_exchange_ec_code INT4,
+ IN in_access_token BYTEA, -- can be NULL
+ IN in_jlimits JSONB,
+ IN in_aml_active BOOL,
+ IN in_kyc_ok BOOL,
+ IN in_notify_str TEXT,
+ IN in_notify2_str TEXT,
+ IN in_rule_gen INT8,
+ IN in_next_time INT8,
+ IN in_kyc_backoff INT8,
+ OUT out_no_account BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_account_serial INT8;
+BEGIN
+
+out_no_account=FALSE;
+
+SELECT account_serial
+ INTO my_account_serial
+ FROM merchant_accounts
+ WHERE h_wire=in_h_wire;
+
+IF NOT FOUND
+THEN
+ out_no_account=TRUE;
+ RETURN;
+END IF;
+
+UPDATE merchant_kyc
+ SET kyc_timestamp=in_timestamp
+ ,kyc_ok=in_kyc_ok
+ ,jaccount_limits=in_jlimits
+ ,aml_review=in_aml_active
+ ,exchange_http_status=in_exchange_http_status
+ ,exchange_ec_code=in_exchange_ec_code
+ ,access_token=in_access_token
+ ,last_rule_gen=in_rule_gen
+ ,next_kyc_poll=in_next_time
+ ,kyc_backoff=in_kyc_backoff
+ WHERE account_serial=my_account_serial
+ AND exchange_url=in_exchange_url;
+
+IF NOT FOUND
+THEN
+
+ INSERT INTO merchant_kyc
+ (kyc_timestamp
+ ,kyc_ok
+ ,account_serial
+ ,exchange_url
+ ,jaccount_limits
+ ,aml_review
+ ,exchange_http_status
+ ,exchange_ec_code
+ ,access_token
+ ,last_rule_gen
+ ,next_kyc_poll
+ ,kyc_backoff)
+ VALUES
+ (in_timestamp
+ ,in_kyc_ok
+ ,my_account_serial
+ ,in_exchange_url
+ ,in_jlimits
+ ,in_aml_active
+ ,in_exchange_http_status
+ ,in_exchange_ec_code
+ ,in_access_token
+ ,in_rule_gen
+ ,in_next_time
+ ,in_kyc_backoff);
+END IF;
+
+EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify_str);
+
+EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify2_str);
+
+
+-- Success!
+END $$;
diff --git a/src/backenddb/activate_account.c b/src/backenddb/activate_account.c
@@ -19,23 +19,21 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/activate_account.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_activate_account (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MERCHANTDB_AccountDetails *account_details,
- struct TALER_MerchantWireHashP *h_wire,
- struct TALER_WireSaltP *salt,
- bool *not_found,
- bool *conflict)
+TALER_MERCHANTDB_activate_account (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MERCHANTDB_AccountDetails *account_details,
+ struct TALER_MerchantWireHashP *h_wire,
+ struct TALER_WireSaltP *salt,
+ bool *not_found,
+ bool *conflict)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (account_details->instance_id),
GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire),
GNUNET_PQ_query_param_auto_from_type (&account_details->salt),
GNUNET_PQ_query_param_string (account_details->payto_uri.full_payto),
@@ -64,18 +62,20 @@ TALER_MERCHANTDB_activate_account (struct TALER_MERCHANTDB_PostgresContext *pg,
};
GNUNET_assert (account_details->active);
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (account_details->instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "activate_account",
- "SELECT "
- " out_h_wire AS h_wire"
- " ,out_salt AS salt"
- " ,out_conflict AS conflict"
- " ,out_not_found AS not_found"
- " FROM merchant_do_activate_account"
- " ($1,$2,$3,$4,$5,$6,$7);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT "
+ " out_h_wire AS h_wire"
+ " ,out_salt AS salt"
+ " ,out_conflict AS conflict"
+ " ,out_not_found AS not_found"
+ " FROM merchant_do_activate_account"
+ " ($1,$2,$3,$4,$5,$6);");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "activate_account",
+ "",
params,
rs);
}
diff --git a/src/backenddb/activate_account.sql b/src/backenddb/activate_account.sql
@@ -0,0 +1,130 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant_do_activate_account;
+CREATE FUNCTION merchant_do_activate_account(
+ IN in_h_wire BYTEA
+ ,IN in_salt BYTEA
+ ,IN in_full_payto TEXT
+ ,IN in_credit_facade_url TEXT -- can be NULL
+ ,IN in_credit_facade_credentials TEXT -- can be NULL
+ ,IN in_extra_wire_subject_metadata TEXT -- can be NULL
+ ,OUT out_h_wire BYTEA
+ ,OUT out_salt BYTEA
+ ,OUT out_not_found BOOL
+ ,OUT out_conflict BOOL
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE my_active BOOL;
+DECLARE my_cfu TEXT;
+DECLARE my_cfc TEXT;
+DECLARE my_ewsm TEXT;
+DECLARE my_h_wire BYTEA;
+DECLARE my_salt BYTEA;
+BEGIN
+ out_not_found = FALSE;
+ out_conflict = FALSE;
+ out_h_wire = in_h_wire;
+ out_salt = in_salt;
+
+ INSERT INTO merchant_accounts
+ AS ma
+ (h_wire
+ ,salt
+ ,payto_uri
+ ,credit_facade_url
+ ,credit_facade_credentials
+ ,active
+ ,extra_wire_subject_metadata
+ ) VALUES (
+ in_h_wire
+ ,in_salt
+ ,in_full_payto
+ ,in_credit_facade_url
+ ,in_credit_facade_credentials::JSONB
+ ,TRUE
+ ,in_extra_wire_subject_metadata
+ ) ON CONFLICT DO NOTHING;
+ IF FOUND
+ THEN
+ -- Notify taler-merchant-kyccheck about the change in
+ -- accounts. (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
+ NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG;
+ RETURN;
+ END IF;
+
+ SELECT h_wire
+ ,salt
+ ,active
+ ,credit_facade_url
+ ,credit_facade_credentials::TEXT
+ ,extra_wire_subject_metadata
+ INTO my_h_wire
+ ,my_salt
+ ,my_active
+ ,my_cfu
+ ,my_cfc
+ ,my_ewsm
+ FROM merchant_accounts
+ WHERE payto_uri=in_full_payto;
+ IF NOT FOUND
+ THEN
+ -- This should never happen (we had a conflict!)
+ -- Still, safe way is to return not found.
+ out_not_found = TRUE;
+ RETURN;
+ END IF;
+
+ -- Check for conflict
+ IF (my_active AND
+ (ROW (my_cfu
+ ,my_cfc
+ ,my_ewsm)
+ IS DISTINCT FROM
+ ROW (in_credit_facade_url
+ ,in_credit_facade_credentials
+ ,in_extra_wire_subject_metadata)))
+ THEN
+ -- Active conflicting account, refuse!
+ out_conflict = TRUE;
+ RETURN;
+ END IF;
+
+ -- Equivalent account exists, use its salt instead of the new salt
+ -- and just set it to active!
+ out_salt = my_salt;
+ out_h_wire = my_h_wire;
+
+ -- Now check if existing account is already active
+ IF my_active
+ THEN
+ -- nothing to do
+ RETURN;
+ END IF;
+
+ UPDATE merchant_accounts
+ SET active=TRUE
+ ,credit_facade_url=in_credit_facade_url
+ ,credit_facade_credentials=in_credit_facade_credentials::JSONB
+ ,extra_wire_subject_metadata=in_extra_wire_subject_metadata
+ WHERE h_wire=out_h_wire;
+
+ -- Notify taler-merchant-kyccheck about the change in (active)
+ -- accounts. (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
+ NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG;
+
+END $$;
diff --git a/src/backenddb/check_donau_instance.c b/src/backenddb/check_donau_instance.c
@@ -27,36 +27,33 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_check_donau_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const char *donau_url,
- uint64_t charity_id
- )
+TALER_MERCHANTDB_check_donau_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *donau_url,
+ uint64_t charity_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (donau_url),
GNUNET_PQ_query_param_uint64 (&charity_id),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 ==
+ GNUNET_memcmp (merchant_pub,
+ &pg->current_merchant_pub));
check_connection (pg);
- PREPARE (pg,
- "check_donau_instance",
- "SELECT 1"
- " FROM merchant_donau_instances"
- " WHERE donau_url=$1"
- " AND charity_id=$2 "
- " AND merchant_instance_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances mi"
- " WHERE mi.merchant_pub = $3)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT 1"
+ " FROM merchant_donau_instances"
+ " WHERE donau_url=$1"
+ " AND charity_id=$2");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "check_donau_instance",
+ "",
params,
rs);
-
}
diff --git a/src/backenddb/check_money_pots.c b/src/backenddb/check_money_pots.c
@@ -27,14 +27,14 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_check_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- unsigned int pots_len,
- uint64_t pots[static pots_len],
- uint64_t *pot_missing)
+TALER_MERCHANTDB_check_money_pots (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ unsigned int pots_len,
+ uint64_t pots[static pots_len],
+ uint64_t *pot_missing)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_array_uint64 (pots_len,
pots,
pg->conn),
@@ -47,23 +47,22 @@ TALER_MERCHANTDB_check_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "check_money_pots",
- "SELECT n AS out_missing"
- " FROM UNNEST($2::INT8[]) AS n"
- " WHERE NOT EXISTS ("
- " SELECT 1"
- " FROM merchant_money_pots mmp"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mmp.money_pot_serial=n"
- " AND mi.merchant_id=$1"
- " )"
- " LIMIT 1;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT n AS out_missing"
+ " FROM UNNEST($1::INT8[]) AS n"
+ " WHERE NOT EXISTS ("
+ " SELECT 1"
+ " FROM merchant_money_pots mmp"
+ " WHERE mmp.money_pot_serial=n"
+ " )"
+ " LIMIT 1;");
qs = GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "check_money_pots",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
diff --git a/src/backenddb/check_report.c b/src/backenddb/check_report.c
@@ -19,19 +19,19 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/check_report.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_check_report (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t report_id,
- const struct TALER_MERCHANT_ReportToken *report_token,
- const char *mime_type,
- char **instance_id,
- char **data_source)
+TALER_MERCHANTDB_check_report (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t report_id,
+ const struct TALER_MERCHANT_ReportToken *report_token,
+ const char *mime_type,
+ char **instance_id,
+ char **data_source)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&report_id),
@@ -40,9 +40,9 @@ TALER_MERCHANTDB_check_report (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("merchant_id",
+ GNUNET_PQ_result_spec_string ("out_merchant_id",
instance_id),
- GNUNET_PQ_result_spec_string ("data_source",
+ GNUNET_PQ_result_spec_string ("out_data_source",
data_source),
GNUNET_PQ_result_spec_end
};
@@ -51,14 +51,9 @@ TALER_MERCHANTDB_check_report (struct TALER_MERCHANTDB_PostgresContext *pg,
PREPARE (pg,
"check_report",
"SELECT"
- " mi.merchant_id"
- " ,mr.data_source"
- " FROM merchant_reports mr"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mr.report_serial=$1"
- " AND mr.report_token=$2"
- " AND mr.mime_type=$3;");
+ " out_merchant_id"
+ " ,out_data_source"
+ " FROM merchant.check_report($1, $2, $3)");
return GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
"check_report",
diff --git a/src/backenddb/check_report.sql b/src/backenddb/check_report.sql
@@ -0,0 +1,68 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant.check_report;
+CREATE FUNCTION merchant.check_report(
+ IN in_report_id INT8,
+ IN in_report_token BYTEA,
+ IN in_mime_type TEXT)
+RETURNS TABLE(
+ out_merchant_id TEXT,
+ out_data_source TEXT)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ inner_rec RECORD;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ ,merchant_id
+ FROM merchant.merchant_instances
+ LOOP
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ EXECUTE format(
+ 'SELECT data_source AS ds'
+ ' FROM %I.merchant_reports'
+ ' WHERE report_serial=$1'
+ ' AND report_token=$2'
+ ' AND mime_type=$3', s)
+ USING in_report_id
+ ,in_report_token
+ ,in_mime_type
+ INTO inner_rec;
+ IF inner_rec IS NOT NULL
+ THEN
+ out_merchant_id := rec.merchant_id;
+ out_data_source := inner_rec.ds;
+ RETURN NEXT;
+ RETURN;
+ END IF;
+ EXCEPTION
+ WHEN undefined_table
+ THEN
+ NULL;
+ END;
+ END LOOP;
+END
+$$;
+COMMENT ON FUNCTION merchant.check_report(INT8, BYTEA, TEXT)
+ IS 'Searches all instance schemas for a report matching the given'
+ ' (report_serial, report_token, mime_type). Returns at most one row'
+ ' (the first matching instance encountered).';
diff --git a/src/backenddb/check_transfer_exists.c b/src/backenddb/check_transfer_exists.c
@@ -19,19 +19,18 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/check_transfer_exists.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_check_transfer_exists (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t transfer_serial_id)
+TALER_MERCHANTDB_check_transfer_exists (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t transfer_serial_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
GNUNET_PQ_query_param_end
};
@@ -39,24 +38,17 @@ TALER_MERCHANTDB_check_transfer_exists (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "check_transfer_exists",
- "SELECT"
- " 1"
- " FROM merchant_transfers"
- " JOIN merchant_accounts"
- " USING (account_serial)"
- " WHERE"
- " credit_serial=$2"
- " AND"
- " merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
-
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " 1"
+ " FROM merchant_transfers"
+ " WHERE credit_serial=$1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "check_transfer_exists",
+ "",
params,
rs);
}
diff --git a/src/backenddb/create_mfa_challenge.c b/src/backenddb/create_mfa_challenge.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "taler/taler_merchant_util.h"
#include "merchantdb_lib.h"
@@ -55,7 +53,7 @@ TALER_MERCHANTDB_create_mfa_challenge (
GNUNET_PQ_query_param_absolute_time (&retransmission_date),
GNUNET_PQ_query_param_string (channel_str),
GNUNET_PQ_query_param_string (required_address), /* $9 */
- GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&pg->current_merchant_serial), /* $10 */
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
@@ -64,9 +62,13 @@ TALER_MERCHANTDB_create_mfa_challenge (
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ GNUNET_assert (0 != pg->current_merchant_serial);
PREPARE (pg,
"create_mfa_challenge",
- "INSERT INTO tan_challenges"
+ "INSERT INTO merchant.tan_challenges"
" (h_body"
" ,salt"
" ,op"
@@ -78,10 +80,8 @@ TALER_MERCHANTDB_create_mfa_challenge (
" ,tan_channel"
" ,required_address"
" ,merchant_serial)"
- " SELECT"
- " $1, $2, $3, $4, $5, $6, $7, 3, $8, $9, merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$10"
+ " VALUES"
+ " ($1, $2, $3, $4, $5, $6, $7, 3, $8, $9, $10)"
" RETURNING challenge_id;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"create_mfa_challenge",
diff --git a/src/backenddb/create_tables.c b/src/backenddb/create_tables.c
@@ -21,6 +21,7 @@
#include <gnunet/gnunet_util_lib.h>
#include "merchantdb_lib.h"
#include "merchant-database/create_tables.h"
+#include "helper.h"
enum GNUNET_GenericReturnValue
@@ -33,6 +34,10 @@ TALER_MERCHANTDB_create_tables (
GNUNET_PQ_EXECUTE_STATEMENT_END
};
enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
conn = GNUNET_PQ_connect_with_cfg (cfg,
"merchantdb-postgres",
@@ -42,7 +47,24 @@ TALER_MERCHANTDB_create_tables (
if (NULL == conn)
return GNUNET_SYSERR;
ret = GNUNET_PQ_exec_sql (conn,
- "procedures");
+ "global_procedures");
+ if (GNUNET_OK == ret)
+ ret = GNUNET_PQ_exec_sql (conn,
+ "instance_procedures");
+ if (GNUNET_OK !=
+ GNUNET_PQ_prepare_anon (
+ conn,
+ "CALL merchant.sync_all_instance_procedures()"))
+ {
+ GNUNET_break (0);
+ GNUNET_PQ_disconnect (conn);
+ return GNUNET_SYSERR;
+ }
+ qs = GNUNET_PQ_eval_prepared_non_select (conn,
+ "",
+ params);
+ if (qs < 0)
+ return GNUNET_SYSERR;
GNUNET_PQ_disconnect (conn);
return ret;
}
diff --git a/src/backenddb/create_tables.sql b/src/backenddb/create_tables.sql
@@ -0,0 +1,82 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP PROCEDURE IF EXISTS merchant.sync_instance_procedures(BIGINT);
+CREATE PROCEDURE merchant.sync_instance_procedures(
+ in_merchant_serial BIGINT
+)
+ LANGUAGE plpgsql
+AS $$
+DECLARE
+ rec RECORD;
+ r RECORD;
+ my_schema_name TEXT;
+ v_new_def TEXT;
+BEGIN
+ my_schema_name = format('merchant_instance_%s.', in_merchant_serial);
+ FOR r IN
+ SELECT pg_get_functiondef(p.oid) AS definition
+ FROM pg_proc p
+ JOIN pg_namespace n
+ ON n.oid = p.pronamespace
+ WHERE n.nspname = 'merchant_instances'
+ LOOP
+ v_new_def := replace(
+ r.definition,
+ 'merchant_instances.',
+ my_schema_name
+ );
+ EXECUTE v_new_def;
+ END LOOP;
+
+ FOR r IN
+ SELECT pg_get_triggerdef(t.oid, true) AS trigger_def
+ FROM pg_trigger t
+ JOIN pg_class c
+ ON c.oid = t.tgrelid
+ JOIN pg_namespace n
+ ON n.oid = c.relnamespace
+ WHERE n.nspname = 'merchant_instances'
+ AND NOT t.tgisinternal
+ LOOP
+ v_new_def := replace(
+ r.trigger_def,
+ 'merchant_instances.',
+ my_schema_name
+ );
+ EXECUTE v_new_def;
+ END LOOP;
+END $$;
+
+COMMENT ON PROCEDURE merchant.sync_instance_procedures(BIGINT)
+ IS 'Synchronizes procedures and triggers for the given instance by copying the current version from merchant_instances into the per-instance SCHEMA';
+
+
+DROP PROCEDURE IF EXISTS merchant.sync_all_instance_procedures();
+CREATE PROCEDURE merchant.sync_all_instance_procedures()
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ merchant_id BIGINT;
+BEGIN
+ FOR merchant_id IN
+ SELECT merchant_serial
+ FROM merchant.merchant_instances
+ LOOP
+ CALL merchant.sync_instance_procedures
+ (merchant_id);
+ END LOOP;
+END $$;
diff --git a/src/backenddb/delete_category.c b/src/backenddb/delete_category.c
@@ -19,35 +19,31 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_category.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_category (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t category_id)
+TALER_MERCHANTDB_delete_category (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t category_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&category_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_category",
- "DELETE"
- " FROM merchant_categories"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND category_serial=$2");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_categories"
+ " WHERE category_serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_category",
+ "",
params);
}
diff --git a/src/backenddb/delete_contract_terms.c b/src/backenddb/delete_contract_terms.c
@@ -19,40 +19,37 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_contract_terms.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_contract_terms (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- struct GNUNET_TIME_Relative legal_expiration)
+TALER_MERCHANTDB_delete_contract_terms (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Relative legal_expiration)
{
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_relative_time (&legal_expiration),
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_contract_terms",
- "DELETE FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND ( ( (pay_deadline < $4) AND"
- " (NOT paid) ) OR"
- " (creation_time + $3 < $4) )");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE FROM merchant_contract_terms"
+ " WHERE order_id=$1"
+ " AND ( ( (pay_deadline < $3) AND"
+ " (NOT paid) ) OR"
+ " (creation_time + $2 < $3) )");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_contract_terms",
+ "",
params);
}
diff --git a/src/backenddb/delete_donau_instance.c b/src/backenddb/delete_donau_instance.c
@@ -20,32 +20,30 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_donau_instance.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_donau_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- const uint64_t donau_serial_id)
+TALER_MERCHANTDB_delete_donau_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ const uint64_t donau_serial_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&donau_serial_id),
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_donau_instance",
- "DELETE FROM merchant_donau_instances di"
- " USING merchant_instances mi"
- " WHERE di.merchant_instance_serial = mi.merchant_serial"
- " AND di.donau_instances_serial = $1"
- " AND mi.merchant_id = $2;");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE FROM merchant_donau_instances"
+ " WHERE donau_instances_serial = $1;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_donau_instance",
+ "",
params);
-}
-\ No newline at end of file
+}
diff --git a/src/backenddb/delete_exchange_accounts.c b/src/backenddb/delete_exchange_accounts.c
@@ -19,16 +19,15 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_exchange_accounts.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_exchange_accounts (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MasterPublicKeyP *master_pub)
+TALER_MERCHANTDB_delete_exchange_accounts (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MasterPublicKeyP *master_pub)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_pub),
diff --git a/src/backenddb/delete_instance_private_key.c b/src/backenddb/delete_instance_private_key.c
@@ -19,15 +19,15 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_instance_private_key.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_instance_private_key (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *merchant_id)
+TALER_MERCHANTDB_delete_instance_private_key (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *merchant_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
@@ -36,13 +36,11 @@ TALER_MERCHANTDB_delete_instance_private_key (struct TALER_MERCHANTDB_PostgresCo
check_connection (pg);
PREPARE (pg,
- "delete_key",
- "DELETE FROM merchant_keys"
- " USING merchant_instances"
- " WHERE merchant_keys.merchant_serial"
- " = merchant_instances.merchant_serial"
- " AND merchant_instances.merchant_id = $1");
+ "delete_instance_private_key",
+ "UPDATE merchant.merchant_instances"
+ " SET merchant_priv = NULL"
+ " WHERE merchant_id = $1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_key",
+ "delete_instance_private_key",
params);
}
diff --git a/src/backenddb/delete_login_token.c b/src/backenddb/delete_login_token.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2023, 2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,59 +19,54 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_login_token.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_login_token_serial (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- uint64_t serial)
+TALER_MERCHANTDB_delete_login_token_serial (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ uint64_t serial)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_uint64 (&serial),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_login_token_serial",
- "DELETE FROM merchant_login_tokens"
- " WHERE serial=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE FROM merchant_login_tokens"
+ " WHERE serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_login_token_serial",
+ "",
params);
}
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_login_token (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- const struct TALER_MERCHANTDB_LoginTokenP *token)
+TALER_MERCHANTDB_delete_login_token (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_auto_from_type (token),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_login_token",
- "DELETE FROM merchant_login_tokens"
- " WHERE token=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE FROM merchant_login_tokens"
+ " WHERE token=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_login_token",
+ "",
params);
}
diff --git a/src/backenddb/delete_money_pot.c b/src/backenddb/delete_money_pot.c
@@ -19,35 +19,31 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_money_pot.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t money_pot_id)
+TALER_MERCHANTDB_delete_money_pot (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t money_pot_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&money_pot_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_money_pot",
- "DELETE"
- " FROM merchant_money_pots"
- " WHERE merchant_money_pots.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_money_pots.money_pot_serial=$2");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_money_pots"
+ " WHERE money_pot_serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_money_pot",
+ "",
params);
}
diff --git a/src/backenddb/delete_order.c b/src/backenddb/delete_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022, 2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,70 +19,60 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_order.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_order (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- bool force)
+TALER_MERCHANTDB_delete_order (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ bool force)
{
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_bool (force),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_QueryParam params2[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
enum GNUNET_DB_QueryStatus qs2;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_order",
- "WITH ms AS"
- "(SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- ", mc AS"
- "(SELECT paid"
- " FROM merchant_contract_terms"
- " JOIN ms USING (merchant_serial)"
- " WHERE order_id=$2) "
- "DELETE"
- " FROM merchant_orders mo"
- " WHERE order_id=$2"
- " AND merchant_serial=(SELECT merchant_serial FROM ms)"
- " AND ( (pay_deadline < $3)"
- " OR (NOT EXISTS (SELECT paid FROM mc))"
- " OR ($4 AND (FALSE=(SELECT paid FROM mc))) );");
+ // FIXME: replace two statements by stored procedure!
+ TMH_PQ_prepare_anon (pg,
+ "WITH mc AS"
+ "(SELECT paid"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1) "
+ "DELETE"
+ " FROM merchant_orders mo"
+ " WHERE order_id=$1"
+ " AND ( (pay_deadline < $2)"
+ " OR (NOT EXISTS (SELECT paid FROM mc))"
+ " OR ($3 AND (FALSE=(SELECT paid FROM mc))) );");
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_order",
+ "",
params);
if ( (qs < 0) || (! force) )
return qs;
- PREPARE (pg,
- "delete_contract",
- "DELETE"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2 AND"
- " merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND NOT paid;");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1"
+ " AND NOT paid;");
qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_contract",
+ "",
params2);
if (qs2 < 0)
return qs2;
diff --git a/src/backenddb/delete_otp.c b/src/backenddb/delete_otp.c
@@ -19,35 +19,31 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_otp.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_otp (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *otp_id)
+TALER_MERCHANTDB_delete_otp (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *otp_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (otp_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_otp",
- "DELETE"
- " FROM merchant_otp_devices"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND otp_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_otp_devices"
+ " WHERE otp_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_otp",
+ "",
params);
}
diff --git a/src/backenddb/delete_pending_webhook.c b/src/backenddb/delete_pending_webhook.c
@@ -25,9 +25,11 @@
#include "merchant-database/delete_pending_webhook.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_pending_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t webhook_pending_serial)
+TALER_MERCHANTDB_delete_pending_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t webhook_pending_serial)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&webhook_pending_serial),
@@ -38,9 +40,8 @@ TALER_MERCHANTDB_delete_pending_webhook (struct TALER_MERCHANTDB_PostgresContext
PREPARE (pg,
"delete_pending_webhook",
"DELETE"
- " FROM merchant_pending_webhooks"
+ " FROM merchant.merchant_pending_webhooks"
" WHERE webhook_pending_serial=$1");
-
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_pending_webhook",
params);
diff --git a/src/backenddb/delete_product.c b/src/backenddb/delete_product.c
@@ -19,38 +19,35 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_product.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_product (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *product_id)
+TALER_MERCHANTDB_delete_product (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *product_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_product",
- "DELETE"
- " FROM merchant_inventory"
- " WHERE merchant_inventory.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_inventory.product_id=$2"
- " AND product_serial NOT IN "
- " (SELECT product_serial FROM merchant_order_locks)"
- " AND product_serial NOT IN "
- " (SELECT product_serial FROM merchant_inventory_locks)");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_inventory"
+ " WHERE product_id=$1"
+ " AND product_serial NOT IN "
+ " (SELECT product_serial FROM merchant_order_locks)"
+ " AND product_serial NOT IN "
+ " (SELECT product_serial FROM merchant_inventory_locks)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_product",
+ "",
params);
}
diff --git a/src/backenddb/delete_product_group.c b/src/backenddb/delete_product_group.c
@@ -19,34 +19,31 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_product_group.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_product_group (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t product_group_id)
+TALER_MERCHANTDB_delete_product_group (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t product_group_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&product_group_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_product_group",
- "DELETE"
- " FROM merchant_product_groups"
- " WHERE merchant_product_groups.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_product_groups.product_group_serial=$2");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_product_groups"
+ " WHERE product_group_serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_product_group",
+ "",
params);
}
diff --git a/src/backenddb/delete_report.c b/src/backenddb/delete_report.c
@@ -19,35 +19,31 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_report.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_report (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t report_id)
+TALER_MERCHANTDB_delete_report (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t report_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&report_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_report",
- "DELETE"
- " FROM merchant_reports"
- " WHERE merchant_reports.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_reports.report_serial=$2");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_reports"
+ " WHERE report_serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_report",
+ "",
params);
}
diff --git a/src/backenddb/delete_template.c b/src/backenddb/delete_template.c
@@ -19,35 +19,31 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_template.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_template (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *template_id)
+TALER_MERCHANTDB_delete_template (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *template_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_template",
- "DELETE"
- " FROM merchant_template"
- " WHERE merchant_template.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_template.template_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_template"
+ " WHERE template_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_template",
+ "",
params);
}
diff --git a/src/backenddb/delete_token_family.c b/src/backenddb/delete_token_family.c
@@ -19,34 +19,31 @@
* @author Christian Blättler
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_token_family.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_token_family (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *token_family_slug)
+TALER_MERCHANTDB_delete_token_family (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *token_family_slug)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (token_family_slug),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_token_family",
- "DELETE"
- " FROM merchant_token_families"
- " WHERE merchant_token_families.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND slug=$2");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_token_families"
+ " WHERE slug=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_token_family",
+ "",
params);
-}
-\ No newline at end of file
+}
diff --git a/src/backenddb/delete_transfer.c b/src/backenddb/delete_transfer.c
@@ -19,39 +19,30 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_transfer.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_transfer (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t transfer_serial_id)
+TALER_MERCHANTDB_delete_transfer (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t transfer_serial_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_transfer",
- "DELETE FROM merchant_transfers"
- " WHERE"
- " credit_serial=$2"
- " AND account_serial IN "
- " (SELECT account_serial "
- " FROM merchant_accounts"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))");
-
+ TMH_PQ_prepare_anon (pg,
+ "DELETE FROM merchant_transfers"
+ " WHERE credit_serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_transfer",
+ "",
params);
}
diff --git a/src/backenddb/delete_unit.c b/src/backenddb/delete_unit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2025 Taler Systems SA
+ Copyright (C) 2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,49 +19,42 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_unit.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *unit_id,
- bool *no_instance,
- bool *no_unit,
- bool *builtin_conflict)
+TALER_MERCHANTDB_delete_unit (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *unit_id,
+ bool *no_unit,
+ bool *builtin_conflict)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (unit_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("out_no_instance",
- no_instance),
GNUNET_PQ_result_spec_bool ("out_no_unit",
no_unit),
GNUNET_PQ_result_spec_bool ("out_builtin_conflict",
builtin_conflict),
GNUNET_PQ_result_spec_end
};
- enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_unit",
- "SELECT"
- " out_no_instance"
- " ,out_no_unit"
- " ,out_builtin_conflict"
- " FROM merchant_do_delete_unit($1,$2);");
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "delete_unit",
- params,
- rs);
- GNUNET_PQ_cleanup_query_params_closures (params);
- return qs;
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_no_unit"
+ " ,out_builtin_conflict"
+ " FROM merchant_do_delete_unit($1);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
}
diff --git a/src/backenddb/delete_unit.sql b/src/backenddb/delete_unit.sql
@@ -0,0 +1,58 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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 delete_unit.sql
+-- @brief SQL for deleting units
+-- @author Bohdan Potuzhnyi
+
+
+DROP FUNCTION IF EXISTS merchant_do_delete_unit;
+CREATE FUNCTION merchant_do_delete_unit (
+ IN in_unit_id TEXT,
+ OUT out_no_instance BOOL,
+ OUT out_no_unit BOOL,
+ OUT out_builtin_conflict BOOL)
+ LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_unit RECORD;
+BEGIN
+ out_no_unit := FALSE;
+ out_builtin_conflict := FALSE;
+
+ SELECT *
+ INTO my_unit
+ FROM merchant_custom_units
+ WHERE unit = in_unit_id
+ FOR UPDATE;
+
+ IF NOT FOUND THEN
+ IF EXISTS (SELECT 1
+ FROM merchant.merchant_builtin_units bu
+ WHERE bu.unit = in_unit_id)
+ THEN
+ out_builtin_conflict := TRUE;
+ ELSE
+ out_no_unit := TRUE;
+ END IF;
+ RETURN;
+ END IF;
+
+ DELETE
+ FROM merchant_custom_units
+ WHERE unit_serial = my_unit.unit_serial;
+
+ RETURN;
+END $$;
diff --git a/src/backenddb/delete_webhook.c b/src/backenddb/delete_webhook.c
@@ -19,35 +19,31 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/delete_webhook.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_delete_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *webhook_id)
+TALER_MERCHANTDB_delete_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *webhook_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "delete_webhook",
- "DELETE"
- " FROM merchant_webhook"
- " WHERE merchant_webhook.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_webhook.webhook_id=$2");
-
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_webhook"
+ " WHERE webhook_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_webhook",
+ "",
params);
}
diff --git a/src/backenddb/drop_tables.c b/src/backenddb/drop_tables.c
@@ -22,6 +22,7 @@
#include "merchantdb_lib.h"
#include "merchant-database/drop_tables.h"
+
enum GNUNET_GenericReturnValue
TALER_MERCHANTDB_drop_tables (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
diff --git a/src/backenddb/example-statistics-0001.sql b/src/backenddb/example-statistics-0001.sql
@@ -43,7 +43,7 @@ VALUES
('deposits'
,'sales (before refunds)'
,'amount'
- ,ARRAY['second'::statistic_range, 'minute', 'hour', 'day', 'month', 'quarter', 'year']
+ ,ARRAY['second'::merchant.statistic_range, 'minute', 'hour', 'day', 'month', 'quarter', 'year']
,ARRAY[120, 120, 48, 95, 36, 40, 100] -- track last 120 s, 120 minutes, 48 hours, 95 days, 36 months, 40 quarters & 10 years
);
@@ -76,7 +76,7 @@ VALUES
('products-sold'
,'products sold (only those tracked in inventory)'
,'number'
- ,ARRAY['second'::statistic_range, 'minute' 'day', 'week', 'month', 'quarter', 'year']
+ ,ARRAY['second'::merchant.statistic_range, 'minute' 'day', 'week', 'month', 'quarter', 'year']
,ARRAY[120, 120, 60, 12, 24, 8, 10] -- track last 120s, 120 minutes, 60 days, 12 weeks, 24 months, 8 quarters and 10 years
);
@@ -92,7 +92,6 @@ BEGIN
THEN
CALL merchant_do_bump_number_stat
('products-sold'
- ,NEW.merchant_serial
,CURRENT_TIMESTAMP(0)::TIMESTAMP
,my_sold);
END IF;
diff --git a/src/backenddb/expire_locks.c b/src/backenddb/expire_locks.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/expire_locks.h"
#include "helper.h"
@@ -34,52 +32,30 @@ TALER_MERCHANTDB_expire_locks (struct TALER_MERCHANTDB_PostgresContext *pg)
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
- enum GNUNET_DB_QueryStatus qs1;
- enum GNUNET_DB_QueryStatus qs2;
- enum GNUNET_DB_QueryStatus qs3;
+ uint64_t total;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("expire_locks",
+ &total),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
- "unlock_products",
- "DELETE FROM merchant_inventory_locks"
- " WHERE expiration < $1");
- qs1 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_products",
- params);
- if (qs1 < 0)
- {
- GNUNET_break (0);
- return qs1;
- }
- PREPARE (pg,
- "unlock_orders",
- "DELETE FROM merchant_orders"
- " WHERE pay_deadline < $1");
- qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_orders",
- params);
- if (qs2 < 0)
- {
- GNUNET_break (0);
- return qs2;
- }
- PREPARE (pg,
- "unlock_contracts",
- "DELETE FROM merchant_contract_terms"
- " WHERE NOT paid"
- " AND pay_deadline < $1");
- qs3 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_contracts",
- params);
- if (qs3 < 0)
+ "expire_locks",
+ "SELECT expire_locks($1) "
+ " AS expire_locks");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "expire_locks",
+ params,
+ rs);
+ if (qs < 0)
{
GNUNET_break (0);
- return qs3;
+ return qs;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Released %d+%d+%d locks\n",
- qs1,
- qs2,
- qs3);
- return qs1 + qs2 + qs3;
+ "Released %llu locks across all instances\n",
+ (unsigned long long) total);
+ return (enum GNUNET_DB_QueryStatus) total;
}
diff --git a/src/backenddb/expire_locks.sql b/src/backenddb/expire_locks.sql
@@ -0,0 +1,53 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS expire_locks;
+CREATE FUNCTION expire_locks(
+ IN in_now INT8
+)
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ total INT8 := 0;
+ affected INT8;
+BEGIN
+ DELETE FROM merchant_inventory_locks
+ WHERE expiration < in_now;
+ GET DIAGNOSTICS affected = ROW_COUNT;
+ total := total + affected;
+
+ DELETE FROM merchant_orders
+ WHERE pay_deadline < in_now;
+ GET DIAGNOSTICS affected = ROW_COUNT;
+ total := total + affected;
+
+ DELETE FROM merchant_contract_terms
+ WHERE NOT paid
+ AND pay_deadline < in_now;
+ GET DIAGNOSTICS affected = ROW_COUNT;
+ total := total + affected;
+ RETURN total;
+END
+$$;
+
+COMMENT ON FUNCTION expire_locks(INT8)
+ IS 'DELETEs expired inventory locks, unpaid orders past their'
+ ' pay_deadline, and unpaid contracts past their'
+ ' pay_deadline. Returns the total number of rows deleted.';
diff --git a/src/backenddb/finalize_transfer_status.c b/src/backenddb/finalize_transfer_status.c
@@ -19,22 +19,21 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/finalize_transfer_status.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_finalize_transfer_status (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct GNUNET_HashCode *h_details,
- const struct TALER_Amount *total_amount,
- const struct TALER_Amount *wire_fee,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig)
+TALER_MERCHANTDB_finalize_transfer_status (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct GNUNET_HashCode *h_details,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (wtid),
@@ -49,30 +48,30 @@ TALER_MERCHANTDB_finalize_transfer_status (struct TALER_MERCHANTDB_PostgresConte
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "finalize_transfer_status",
- "WITH subquery AS ("
- " SELECT signkey_serial"
- " FROM merchant_exchange_signing_keys"
- " WHERE exchange_pub=$6"
- ")"
- "UPDATE merchant_expected_transfers SET"
- " last_http_status=200"
- ",last_ec=0"
- ",last_detail=NULL"
- ",retry_needed=FALSE"
- ",retry_time=0"
- ",expected_credit_amount=$3"
- ",wire_fee=$4"
- ",h_details=$5"
- ",signkey_serial=subquery.signkey_serial"
- ",exchange_sig=$7"
- " FROM subquery"
- " WHERE wtid=$1"
- " AND exchange_url=$2");
+ TMH_PQ_prepare_anon (pg,
+ "WITH subquery AS ("
+ " SELECT signkey_serial"
+ " FROM merchant.merchant_exchange_signing_keys"
+ " WHERE exchange_pub=$6"
+ ")"
+ "UPDATE merchant_expected_transfers SET"
+ " last_http_status=200"
+ ",last_ec=0"
+ ",last_detail=NULL"
+ ",retry_needed=FALSE"
+ ",retry_time=0"
+ ",expected_credit_amount=$3"
+ ",wire_fee=$4"
+ ",h_details=$5"
+ ",signkey_serial=subquery.signkey_serial"
+ ",exchange_sig=$7"
+ " FROM subquery"
+ " WHERE wtid=$1"
+ " AND exchange_url=$2");
return GNUNET_PQ_eval_prepared_non_select (
pg->conn,
- "finalize_transfer_status",
+ "",
params);
}
diff --git a/src/backenddb/future.sql b/src/backenddb/future.sql
@@ -19,7 +19,7 @@ CREATE TABLE tan_challenges
,confirmation_date INT8 DEFAULT NULL
,retry_counter INT4 NOT NULL
,merchant_serial INT8 NOT NULL
- REFERENCES merchant_instances(merchant_serial)
+ REFERENCES merchant.merchant_instances(merchant_serial)
ON DELETE CASCADE
,tan_channel tan_enum NULL DEFAULT NULL
,tan_info TEXT NULL DEFAULT NULL -- FIXME: when is this NULL?
diff --git a/src/backenddb/gc.c b/src/backenddb/gc.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/gc.h"
#include "helper.h"
@@ -44,7 +42,7 @@ TALER_MERCHANTDB_gc (struct TALER_MERCHANTDB_PostgresContext *pg)
};
struct GNUNET_PQ_PreparedStatement ps[] = {
GNUNET_PQ_make_prepare ("run_gc",
- "CALL merchant_do_gc ($1);"),
+ "CALL merchant.merchant_do_gc ($1);"),
GNUNET_PQ_PREPARED_STATEMENT_END
};
diff --git a/src/backenddb/gc.sql b/src/backenddb/gc.sql
@@ -0,0 +1,64 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP PROCEDURE IF EXISTS merchant_do_gc;
+CREATE PROCEDURE merchant_do_gc(in_now INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ rec RECORD;
+ s TEXT;
+BEGIN
+ -- Drop validation-pending instances that never confirmed in time. The
+ -- AFTER DELETE trigger on merchant.merchant_instances will DROP the
+ -- per-instance schema for each removed row.
+ DELETE FROM merchant.merchant_instances
+ WHERE validation_needed
+ AND validation_expiration < in_now;
+
+ -- Per-instance GC: loop over all surviving instances and run the
+ -- per-instance GC helpers + targeted DELETEs in each schema.
+ FOR rec IN SELECT merchant_serial FROM merchant.merchant_instances
+ LOOP
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ EXECUTE format('SET LOCAL search_path TO %I, merchant', s);
+ CALL merchant_statistic_amount_gc ();
+ CALL merchant_statistic_bucket_gc ();
+ CALL merchant_statistic_counter_gc ();
+ EXECUTE format('DELETE FROM %I.merchant_unclaim_signatures'
+ ' WHERE expiration_time < $1', s) USING in_now;
+ EXCEPTION
+ WHEN undefined_table THEN
+ NULL;
+ WHEN undefined_function THEN
+ NULL;
+ END;
+ END LOOP;
+ DELETE FROM merchant.tan_challenges
+ WHERE expiration_date < $1;
+ -- Restore the global search_path so subsequent statements on this
+ -- session don't leak into the last visited instance schema.
+ SET LOCAL search_path TO merchant;
+END $$;
+COMMENT ON PROCEDURE merchant_do_gc
+ IS 'Calls per-instance garbage collection subroutines across every instance.'
+ ' Removes expired pending-validation instances first (whose ON DELETE'
+ ' trigger drops the entire per-instance schema), then for each surviving'
+ ' instance runs merchant_statistic_*_gc and DELETEs expired tan_challenges'
+ ' / merchant_unclaim_signatures.';
+
+COMMIT;
diff --git a/src/backenddb/get_kyc_limits.c b/src/backenddb/get_kyc_limits.c
@@ -19,24 +19,23 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/get_kyc_limits.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_get_kyc_limits (struct TALER_MERCHANTDB_PostgresContext *pg,
- struct TALER_FullPayto merchant_account_uri,
- const char *instance_id,
- const char *exchange_url,
- bool *kyc_ok,
- bool *no_access_token,
- json_t **jlimits)
+TALER_MERCHANTDB_get_kyc_limits (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ struct TALER_FullPayto merchant_account_uri,
+ const char *instance_id,
+ const char *exchange_url,
+ bool *kyc_ok,
+ bool *no_access_token,
+ json_t **jlimits)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_account_uri.full_payto),
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_end
};
@@ -52,26 +51,24 @@ TALER_MERCHANTDB_get_kyc_limits (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "get_kyc_limits",
- "SELECT"
- " mk.kyc_ok"
- ",mk.jaccount_limits::TEXT"
- ",mk.access_token IS NULL AS no_access_token"
- " FROM merchant_kyc mk"
- " WHERE mk.exchange_url=$3"
- " AND mk.account_serial="
- " (SELECT account_serial"
- " FROM merchant_accounts"
- " WHERE payto_uri=$1"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$2));");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mk.kyc_ok"
+ ",mk.jaccount_limits::TEXT"
+ ",mk.access_token IS NULL AS no_access_token"
+ " FROM merchant_kyc mk"
+ " WHERE mk.exchange_url=$2"
+ " AND mk.account_serial="
+ " (SELECT account_serial"
+ " FROM merchant_accounts"
+ " WHERE payto_uri=$1);");
*jlimits = NULL;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_kyc_limits",
+ "",
params,
rs);
}
diff --git a/src/backenddb/get_kyc_status.c b/src/backenddb/get_kyc_status.c
@@ -19,33 +19,31 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/get_kyc_status.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_get_kyc_status (struct TALER_MERCHANTDB_PostgresContext *pg,
- struct TALER_FullPayto merchant_account_uri,
- const char *instance_id,
- const char *exchange_url,
- bool *auth_ok,
- struct TALER_AccountAccessTokenP *access_token,
- bool *kyc_ok,
- unsigned int *last_http_status,
- enum TALER_ErrorCode *last_ec,
- uint64_t *rule_gen,
- struct GNUNET_TIME_Timestamp *last_kyc_check,
- struct GNUNET_TIME_Absolute *next_kyc_poll,
- struct GNUNET_TIME_Relative *kyc_backoff,
- bool *aml_review,
- json_t **jlimits)
+TALER_MERCHANTDB_get_kyc_status (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ struct TALER_FullPayto merchant_account_uri,
+ const char *instance_id,
+ const char *exchange_url,
+ bool *auth_ok,
+ struct TALER_AccountAccessTokenP *access_token,
+ bool *kyc_ok,
+ unsigned int *last_http_status,
+ enum TALER_ErrorCode *last_ec,
+ uint64_t *rule_gen,
+ struct GNUNET_TIME_Timestamp *last_kyc_check,
+ struct GNUNET_TIME_Absolute *next_kyc_poll,
+ struct GNUNET_TIME_Relative *kyc_backoff,
+ bool *aml_review,
+ json_t **jlimits)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_account_uri.full_payto),
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_end
};
@@ -81,33 +79,31 @@ TALER_MERCHANTDB_get_kyc_status (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "get_kyc_status",
- "SELECT"
- " mk.access_token"
- ",mk.exchange_http_status"
- ",mk.exchange_ec_code"
- ",mk.kyc_ok"
- ",mk.last_rule_gen"
- ",mk.kyc_timestamp"
- ",mk.next_kyc_poll"
- ",mk.kyc_backoff"
- ",mk.aml_review"
- ",mk.jaccount_limits::TEXT"
- " FROM merchant_kyc mk"
- " WHERE mk.exchange_url=$3"
- " AND mk.account_serial="
- " (SELECT account_serial"
- " FROM merchant_accounts"
- " WHERE payto_uri=$1"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$2));");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mk.access_token"
+ ",mk.exchange_http_status"
+ ",mk.exchange_ec_code"
+ ",mk.kyc_ok"
+ ",mk.last_rule_gen"
+ ",mk.kyc_timestamp"
+ ",mk.next_kyc_poll"
+ ",mk.kyc_backoff"
+ ",mk.aml_review"
+ ",mk.jaccount_limits::TEXT"
+ " FROM merchant_kyc mk"
+ " WHERE mk.exchange_url=$2"
+ " AND mk.account_serial="
+ " (SELECT account_serial"
+ " FROM merchant_accounts"
+ " WHERE payto_uri=$1);");
*jlimits = NULL;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_kyc_status",
+ "",
params,
rs);
*last_ec = (enum TALER_ErrorCode) (int) e32;
diff --git a/src/backenddb/helper.h b/src/backenddb/helper.h
@@ -24,6 +24,7 @@
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_time_lib.h>
+#include <taler/taler_util.h>
/**
* Type of the "cls" argument given to each of the functions in
@@ -53,6 +54,29 @@ struct TALER_MERCHANTDB_PostgresContext
const char *transaction_name;
/**
+ * Instance id ("merchant_id") that the search_path is currently
+ * pointing at, or NULL if no per-instance schema is selected.
+ * Owned by this struct; set by TALER_MERCHANTDB_set_instance().
+ */
+ char *current_merchant_id;
+
+ /**
+ * merchant_serial corresponding to @e current_merchant_id, or 0
+ * if no per-instance schema is selected. Used as the suffix in
+ * per-instance prepared-statement names.
+ */
+ uint64_t current_merchant_serial;
+
+ /**
+ * Public key of the currently selected instance. Populated by
+ * TALER_MERCHANTDB_set_instance() together with @e current_merchant_id
+ * and @e current_merchant_serial. Used by call sites that emit
+ * cross-process events (e.g. order-pay notifications) which carry the
+ * instance public key in their payload.
+ */
+ struct TALER_MerchantPublicKeyP current_merchant_pub;
+
+ /**
* How many times have we connected to the DB.
*/
uint64_t prep_gen;
@@ -93,13 +117,33 @@ struct TALER_MERCHANTDB_PostgresContext
/**
+ * Prepares SQL statement @a sql under no name ("") for
+ * connection @a pg. Useful for prepared statements that
+ * should not be cached.
+ * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
+ *
+ * @param pg a `struct TALER_MERCHANTDB_PostgresContext`
+ * @param sql actual SQL text
+ */
+#define TMH_PQ_prepare_anon(pg,sql) \
+ do { \
+ if (GNUNET_OK != \
+ GNUNET_PQ_prepare_anon (pg->conn, \
+ sql)) { \
+ GNUNET_break (0); \
+ return GNUNET_DB_STATUS_HARD_ERROR; \
+ } } while (0)
+
+
+/**
* Check that the database connection is still up and automatically reconnects
* unless we are already inside of a transaction.
*
* @param pg connection to check
*/
void
-TALER_MERCHANTDB_check_connection (struct TALER_MERCHANTDB_PostgresContext *pg);
+TALER_MERCHANTDB_check_connection (
+ struct TALER_MERCHANTDB_PostgresContext *pg);
#endif
diff --git a/src/backenddb/inactivate_account.c b/src/backenddb/inactivate_account.c
@@ -19,19 +19,18 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/inactivate_account.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_inactivate_account (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire)
+TALER_MERCHANTDB_inactivate_account (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_end
};
@@ -43,14 +42,16 @@ TALER_MERCHANTDB_inactivate_account (struct TALER_MERCHANTDB_PostgresContext *pg
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (merchant_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "inactivate_account",
- "SELECT out_found AS found"
- " FROM merchant_do_inactivate_account"
- " ($1,$2);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT out_found AS found"
+ " FROM merchant_do_inactivate_account"
+ " ($1);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "inactivate_account",
+ "",
params,
rs);
if (qs < 0)
diff --git a/src/backenddb/inactivate_account.sql b/src/backenddb/inactivate_account.sql
@@ -0,0 +1,36 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant_do_inactivate_account;
+CREATE FUNCTION merchant_do_inactivate_account (
+ IN in_h_wire BYTEA
+ ,OUT out_found BOOL
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ UPDATE merchant_accounts
+ SET active=FALSE
+ WHERE h_wire=in_h_wire;
+ out_found = FOUND;
+ IF out_found
+ THEN
+ -- Notify taler-merchant-kyccheck about the change in
+ -- accounts. (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
+ NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG;
+ END IF;
+
+END $$;
diff --git a/src/backenddb/increase_refund.c b/src/backenddb/increase_refund.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/increase_refund.h"
#include "helper.h"
@@ -133,6 +131,7 @@ struct InsertRefundContext
* due to legal limits?
*/
bool legal_capped;
+
};
@@ -316,6 +315,50 @@ process_refund_cb (void *cls,
/**
+ * Helper function to prepare statement to select refunds
+ *
+ * @param pg context to prepare statement in
+ * @return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS on success
+ */
+static enum GNUNET_DB_QueryStatus
+prep_select_refund (struct TALER_MERCHANTDB_PostgresContext *pg)
+{
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " refund_amount"
+ ",rtransaction_id"
+ " FROM merchant_refunds"
+ " WHERE coin_pub=$1"
+ " AND order_serial=$2");
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+}
+
+
+/**
+ * Helper function to prepare statement to insert refund
+ *
+ * @param pg context to prepare statement in
+ * @return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS on success
+ */
+static enum GNUNET_DB_QueryStatus
+prep_insert_refund (struct TALER_MERCHANTDB_PostgresContext *pg)
+{
+ // FIXME: return 'refund_serial' from this INSERT statement for #10577
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_refunds"
+ "(order_serial"
+ ",rtransaction_id"
+ ",refund_timestamp"
+ ",coin_pub"
+ ",reason"
+ ",refund_amount"
+ ") VALUES"
+ "($1, $2, $3, $4, $5, $6)");
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+}
+
+
+/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
@@ -358,7 +401,7 @@ process_deposits_for_refund_cb (void *cls,
GNUNET_PQ_result_spec_end
};
struct FindRefundContext ictx = {
- .pg = pg
+ .pg = pg,
};
struct ExchangeLimit *el;
@@ -393,9 +436,16 @@ process_deposits_for_refund_cb (void *cls,
TALER_amount_set_zero (
ctx->refund->currency,
&ictx.refunded_amount));
+ ires = prep_select_refund (pg);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != ires)
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ goto cleanup;
+ }
ires = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "find_refunds_by_coin",
+ "",
params,
&process_refund_cb,
&ictx);
@@ -559,8 +609,15 @@ process_deposits_for_refund_cb (void *cls,
};
check_connection (pg);
+ qs = prep_insert_refund (pg);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ {
+ GNUNET_break (0);
+ ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+ goto cleanup;
+ }
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_refund",
+ "",
params);
switch (qs)
{
@@ -619,7 +676,6 @@ TALER_MERCHANTDB_increase_refund (
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
@@ -628,54 +684,32 @@ TALER_MERCHANTDB_increase_refund (
.refund = refund,
.olc = olc,
.olc_cls = olc_cls,
- .reason = reason
+ .reason = reason,
};
- // FIXME: return 'refund_serial' from this INSERT statement for #10577
- PREPARE (pg,
- "insert_refund",
- "INSERT INTO merchant_refunds"
- "(order_serial"
- ",rtransaction_id"
- ",refund_timestamp"
- ",coin_pub"
- ",reason"
- ",refund_amount"
- ") VALUES"
- "($1, $2, $3, $4, $5, $6)");
- PREPARE (pg,
- "find_refunds_by_coin",
- "SELECT"
- " refund_amount"
- ",rtransaction_id"
- " FROM merchant_refunds"
- " WHERE coin_pub=$1"
- " AND order_serial=$2");
- PREPARE (pg,
- "find_deposits_for_refund",
- "SELECT"
- " dep.coin_pub"
- ",dco.order_serial"
- ",dep.amount_with_fee"
- ",dco.exchange_url"
- " FROM merchant_deposits dep"
- " JOIN merchant_deposit_confirmations dco"
- " USING (deposit_confirmation_serial)"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND paid"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))");
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " dep.coin_pub"
+ ",dco.order_serial"
+ ",dep.amount_with_fee"
+ ",dco.exchange_url"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations dco"
+ " USING (deposit_confirmation_serial)"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1"
+ " AND paid)");
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Asked to refund %s on order %s\n",
TALER_amount2s (refund),
order_id);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "find_deposits_for_refund",
+ "",
params,
&process_deposits_for_refund_cb,
&ctx);
diff --git a/src/backenddb/increment_money_pots.c b/src/backenddb/increment_money_pots.c
@@ -27,14 +27,14 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_increment_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- size_t money_pots_len,
- const uint64_t *money_pot_ids,
- const struct TALER_Amount *pot_increments)
+TALER_MERCHANTDB_increment_money_pots (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ size_t money_pots_len,
+ const uint64_t *money_pot_ids,
+ const struct TALER_Amount *pot_increments)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_array_uint64 (money_pots_len,
money_pot_ids,
pg->conn),
@@ -51,15 +51,17 @@ TALER_MERCHANTDB_increment_money_pots (struct TALER_MERCHANTDB_PostgresContext *
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "increment_money_pots",
- "SELECT"
- " out_not_found AS not_found"
- " FROM merchant_do_increment_money_pots"
- "($1,$2,$3);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_not_found AS not_found"
+ " FROM merchant_do_increment_money_pots"
+ "($1,$2);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "increment_money_pots",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
diff --git a/src/backenddb/increment_money_pots.sql b/src/backenddb/increment_money_pots.sql
@@ -0,0 +1,93 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_increment_money_pots;
+CREATE FUNCTION merchant_do_increment_money_pots (
+ IN ina_money_pots_ids INT8[],
+ IN ina_increments merchant.taler_amount_currency[],
+ OUT out_not_found BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ i INT;
+ ini_current_pot_id INT8;
+ ini_current_increment merchant.taler_amount_currency;
+ my_totals merchant.taler_amount_currency[];
+ currency_found BOOL;
+ j INT;
+BEGIN
+
+out_not_found = FALSE;
+
+IF ( COALESCE(array_length(ina_money_pots_ids, 1), 0) !=
+ COALESCE(array_length(ina_increments, 1), 0) )
+THEN
+ RAISE EXCEPTION 'Array lengths must match';
+END IF;
+
+FOR i IN 1..COALESCE(array_length(ina_money_pots_ids, 1),0)
+ LOOP
+ ini_current_pot_id = ina_money_pots_ids[i];
+ ini_current_increment = ina_increments[i];
+
+ SELECT pot_totals
+ INTO my_totals
+ FROM merchant_money_pots
+ WHERE money_pot_serial = ini_current_pot_id;
+
+ IF NOT FOUND
+ THEN
+ -- If pot does not exist, we just ignore the entire
+ -- requested increment, but update the return value.
+ -- (We may have other pots to update, so we continue
+ -- to iterate!).
+ out_not_found = TRUE;
+ ELSE
+ -- Check if currency exists in pot_totals and update
+ currency_found = FALSE;
+
+ FOR j IN 1..COALESCE(array_length(my_totals, 1), 0)
+ LOOP
+ IF (my_totals[j]).curr = (ini_current_increment).curr
+ THEN
+ my_totals[j].frac
+ = my_totals[j].frac + ini_current_increment.frac;
+ my_totals[j].val
+ = my_totals[j].val + ini_current_increment.val;
+ IF my_totals[j].frac >= 100000000
+ THEN
+ my_totals[j].frac = my_totals[j].frac - 100000000;
+ my_totals[j].val = my_totals[j].val + 1;
+ END IF;
+ currency_found = TRUE;
+ EXIT; -- break out of loop
+ END IF;
+ END LOOP;
+
+ IF NOT currency_found
+ THEN
+ my_totals = array_append(my_totals, ini_current_increment);
+ END IF;
+
+ UPDATE merchant_money_pots
+ SET pot_totals = my_totals
+ WHERE money_pot_serial = ini_current_pot_id;
+
+ END IF;
+ END LOOP;
+
+END $$;
diff --git a/src/backenddb/insert_account.c b/src/backenddb/insert_account.c
@@ -27,11 +27,11 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_account (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MERCHANTDB_AccountDetails *account_details)
+TALER_MERCHANTDB_insert_account (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MERCHANTDB_AccountDetails *account_details)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (account_details->instance_id),
GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire),
GNUNET_PQ_query_param_auto_from_type (&account_details->salt),
GNUNET_PQ_query_param_string (account_details->payto_uri.full_payto),
@@ -49,29 +49,28 @@ TALER_MERCHANTDB_insert_account (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (account_details->instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_account",
- "INSERT INTO merchant_accounts AS ma"
- "(merchant_serial"
- ",h_wire"
- ",salt"
- ",payto_uri"
- ",credit_facade_url"
- ",credit_facade_credentials"
- ",active"
- ",extra_wire_subject_metadata)"
- " SELECT merchant_serial, $2, $3, $4, $5, $6::TEXT::JSONB, $7, $8"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"
- " ON CONFLICT(merchant_serial,payto_uri)"
- " DO UPDATE SET"
- " active = true"
- ",credit_facade_url = EXCLUDED.credit_facade_url"
- ",credit_facade_credentials = EXCLUDED.credit_facade_credentials"
- ",extra_wire_subject_metadata = EXCLUDED.extra_wire_subject_metadata"
- " WHERE NOT ma.active");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_accounts AS ma"
+ "(h_wire"
+ ",salt"
+ ",payto_uri"
+ ",credit_facade_url"
+ ",credit_facade_credentials"
+ ",active"
+ ",extra_wire_subject_metadata)"
+ " VALUES ($1, $2, $3, $4, $5::TEXT::JSONB, $6, $7)"
+ " ON CONFLICT(payto_uri)"
+ " DO UPDATE SET"
+ " active = TRUE"
+ ",credit_facade_url = EXCLUDED.credit_facade_url"
+ ",credit_facade_credentials = EXCLUDED.credit_facade_credentials"
+ ",extra_wire_subject_metadata = EXCLUDED.extra_wire_subject_metadata"
+ " WHERE NOT ma.active");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_account",
+ "",
params);
}
diff --git a/src/backenddb/insert_category.c b/src/backenddb/insert_category.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2024 Taler Systems SA
+ Copyright (C) 2024, 2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,22 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_category.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_category (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *category_name,
- const json_t *category_name_i18n,
- uint64_t *category_id)
+TALER_MERCHANTDB_insert_category (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t *category_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (category_name),
TALER_PQ_query_param_json (category_name_i18n),
GNUNET_PQ_query_param_end
@@ -45,21 +43,19 @@ TALER_MERCHANTDB_insert_category (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_category",
- "INSERT INTO merchant_categories"
- "(merchant_serial"
- ",category_name"
- ",category_name_i18n"
- ")"
- " SELECT merchant_serial,"
- " $2, $3::TEXT::JSONB"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"
- " RETURNING category_serial");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_categories"
+ "(category_name"
+ ",category_name_i18n"
+ ")"
+ " VALUES ($1, $2::TEXT::JSONB)"
+ " RETURNING category_serial");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_category",
+ "",
params,
rs);
}
diff --git a/src/backenddb/insert_contract_terms.c b/src/backenddb/insert_contract_terms.c
@@ -19,18 +19,17 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_contract_terms.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_contract_terms (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- json_t *contract_terms,
- uint64_t *order_serial)
+TALER_MERCHANTDB_insert_contract_terms (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms,
+ uint64_t *order_serial)
{
struct GNUNET_TIME_Timestamp pay_deadline;
struct GNUNET_TIME_Timestamp refund_deadline;
@@ -71,10 +70,12 @@ TALER_MERCHANTDB_insert_contract_terms (struct TALER_MERCHANTDB_PostgresContext
fulfillment_url =
json_string_value (json_object_get (contract_terms,
"fulfillment_url"));
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
TALER_PQ_query_param_json (contract_terms),
GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
@@ -90,43 +91,37 @@ TALER_MERCHANTDB_insert_contract_terms (struct TALER_MERCHANTDB_PostgresContext
order_serial),
GNUNET_PQ_result_spec_end
};
- PREPARE (pg,
- "insert_contract_terms",
- "INSERT INTO merchant_contract_terms"
- "(order_serial"
- ",merchant_serial"
- ",order_id"
- ",contract_terms"
- ",h_contract_terms"
- ",creation_time"
- ",pay_deadline"
- ",refund_deadline"
- ",fulfillment_url"
- ",claim_token"
- ",pos_key"
- ",pos_algorithm)"
- "SELECT"
- " mo.order_serial"
- ",mo.merchant_serial"
- ",mo.order_id"
- ",$3::TEXT::JSONB" /* contract_terms */
- ",$4" /* h_contract_terms */
- ",mo.creation_time"
- ",$5" /* pay_deadline */
- ",$6" /* refund_deadline */
- ",$7" /* fulfillment_url */
- ",mo.claim_token"
- ",mo.pos_key"
- ",mo.pos_algorithm"
- " FROM merchant_orders mo"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " RETURNING order_serial");
+
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_contract_terms"
+ "(order_serial"
+ ",order_id"
+ ",contract_terms"
+ ",h_contract_terms"
+ ",creation_time"
+ ",pay_deadline"
+ ",refund_deadline"
+ ",fulfillment_url"
+ ",claim_token"
+ ",pos_key"
+ ",pos_algorithm)"
+ "SELECT"
+ " mo.order_serial"
+ ",mo.order_id"
+ ",$2::TEXT::JSONB" /* contract_terms */
+ ",$3" /* h_contract_terms */
+ ",mo.creation_time"
+ ",$4" /* pay_deadline */
+ ",$5" /* refund_deadline */
+ ",$6" /* fulfillment_url */
+ ",mo.claim_token"
+ ",mo.pos_key"
+ ",mo.pos_algorithm"
+ " FROM merchant_orders mo"
+ " WHERE order_id=$1"
+ " RETURNING order_serial");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_contract_terms",
+ "",
params,
rs);
}
diff --git a/src/backenddb/insert_deposit.c b/src/backenddb/insert_deposit.c
@@ -20,23 +20,22 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_deposit.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_deposit (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint32_t offset,
- uint64_t deposit_confirmation_serial,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- struct GNUNET_TIME_Absolute check_time)
+TALER_MERCHANTDB_insert_deposit (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint32_t offset,
+ uint64_t deposit_confirmation_serial,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee,
+ struct GNUNET_TIME_Absolute check_time)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&deposit_confirmation_serial),
@@ -58,21 +57,20 @@ TALER_MERCHANTDB_insert_deposit (struct TALER_MERCHANTDB_PostgresContext *pg,
"Storing deposit for coin_pub: `%s', amount_with_fee: %s\n",
TALER_B2S (coin_pub),
TALER_amount2s (amount_with_fee));
- check_connection (pg);
- PREPARE (pg,
- "insert_deposit",
- "INSERT INTO merchant_deposits"
- "(deposit_confirmation_serial"
- ",coin_offset"
- ",coin_pub"
- ",coin_sig"
- ",amount_with_fee"
- ",deposit_fee"
- ",refund_fee"
- ",settlement_retry_time"
- ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8)"
- " ON CONFLICT DO NOTHING;");
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_deposits"
+ "(deposit_confirmation_serial"
+ ",coin_offset"
+ ",coin_pub"
+ ",coin_sig"
+ ",amount_with_fee"
+ ",deposit_fee"
+ ",refund_fee"
+ ",settlement_retry_time"
+ ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8)"
+ " ON CONFLICT DO NOTHING;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_deposit",
+ "",
params);
}
diff --git a/src/backenddb/insert_deposit_confirmation.c b/src/backenddb/insert_deposit_confirmation.c
@@ -19,27 +19,25 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_deposit_confirmation.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- struct GNUNET_TIME_Timestamp deposit_timestamp,
- const struct TALER_PrivateContractHashP *
- h_contract_terms,
- const char *exchange_url,
- struct GNUNET_TIME_Timestamp wire_transfer_deadline,
- const struct TALER_Amount *total_without_fees,
- const struct TALER_Amount *wire_fee,
- const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- uint64_t *deposit_confirmation_serial_id)
+TALER_MERCHANTDB_insert_deposit_confirmation (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp wire_transfer_deadline,
+ const struct TALER_Amount *total_without_fees,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ uint64_t *deposit_confirmation_serial_id)
{
struct GNUNET_TIME_AbsoluteNBO nbo
= GNUNET_TIME_absolute_hton (wire_transfer_deadline.abs_time);
@@ -47,7 +45,6 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo
= GNUNET_STRINGS_data_to_string_alloc (&nbo,
sizeof (nbo));
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_timestamp (&deposit_timestamp),
GNUNET_PQ_query_param_string (exchange_url),
@@ -55,21 +52,18 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo
total_without_fees),
TALER_PQ_query_param_amount_with_currency (pg->conn,
wire_fee),
- GNUNET_PQ_query_param_auto_from_type (h_wire), /* 7 */
+ GNUNET_PQ_query_param_auto_from_type (h_wire), /* 6 */
GNUNET_PQ_query_param_auto_from_type (exchange_sig),
GNUNET_PQ_query_param_auto_from_type (exchange_pub),
GNUNET_PQ_query_param_timestamp (&wire_transfer_deadline),
GNUNET_PQ_query_param_string (nbo_str),
GNUNET_PQ_query_param_end
};
- bool no_instance;
bool no_order;
bool no_account;
bool no_signkey;
bool conflict;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("no_instance",
- &no_instance),
GNUNET_PQ_result_spec_bool ("no_order",
&no_order),
GNUNET_PQ_result_spec_bool ("no_account",
@@ -84,6 +78,9 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
/* no preflight check here, run in transaction by caller! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Storing deposit confirmation for instance `%s' h_contract_terms `%s', total_without_fees: %s and wire transfer deadline in %s\n",
@@ -95,19 +92,17 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo
wire_transfer_deadline.abs_time),
true));
check_connection (pg);
- PREPARE (pg,
- "insert_deposit_confirmation",
- "SELECT "
- " out_no_instance AS no_instance"
- " ,out_no_account AS no_account"
- " ,out_no_order AS no_order"
- " ,out_no_signkey AS no_signkey"
- " ,out_conflict AS conflict"
- " ,out_deposit_confirmation_serial AS deposit_confirmation_serial"
- " FROM merchant_do_insert_deposit_confirmation"
- " ($1, $2 ,$3, $4, $5, $6, $7, $8, $9, $10, $11);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT "
+ " out_no_account AS no_account"
+ " ,out_no_order AS no_order"
+ " ,out_no_signkey AS no_signkey"
+ " ,out_conflict AS conflict"
+ " ,out_deposit_confirmation_serial AS deposit_confirmation_serial"
+ " FROM merchant_do_insert_deposit_confirmation"
+ " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_deposit_confirmation",
+ "",
params,
rs);
GNUNET_free (nbo_str);
@@ -116,11 +111,6 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo
return qs;
// FIXME: in the future, return these codes to the client and
// return more specific error codes to the client from the API!
- if (no_instance)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
if (no_order)
{
GNUNET_break (0);
diff --git a/src/backenddb/insert_deposit_confirmation.sql b/src/backenddb/insert_deposit_confirmation.sql
@@ -0,0 +1,154 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant_do_insert_deposit_confirmation;
+CREATE FUNCTION merchant_do_insert_deposit_confirmation (
+ IN in_h_contract_terms BYTEA,
+ IN in_deposit_timestamp INT8,
+ IN in_exchange_url TEXT,
+ IN in_total_without_fee merchant.taler_amount_currency,
+ IN in_wire_fee merchant.taler_amount_currency,
+ IN in_h_wire BYTEA,
+ IN in_exchange_sig BYTEA,
+ IN in_exchange_pub BYTEA,
+ IN in_wire_transfer_deadline INT8,
+ IN in_notify_arg_str TEXT,
+ OUT out_no_order BOOL,
+ OUT out_no_account BOOL,
+ OUT out_no_signkey BOOL,
+ OUT out_conflict BOOL,
+ OUT out_deposit_confirmation_serial INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_order_serial INT8;
+ my_account_serial INT8;
+ my_signkey_serial INT8;
+ my_record RECORD;
+ my_bank_serial_id INT8;
+ my_credit_amount merchant.taler_amount_currency;
+BEGIN
+
+out_no_order=TRUE;
+out_no_account=TRUE;
+out_no_signkey=TRUE;
+out_conflict=FALSE;
+out_deposit_confirmation_serial=0;
+
+SELECT account_serial
+ INTO my_account_serial
+ FROM merchant_accounts
+ WHERE h_wire=in_h_wire;
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+out_no_account=FALSE;
+
+SELECT signkey_serial
+ INTO my_signkey_serial
+ FROM merchant.merchant_exchange_signing_keys
+ WHERE exchange_pub=in_exchange_pub
+ ORDER BY start_date DESC
+ LIMIT 1;
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+out_no_signkey=FALSE;
+
+SELECT order_serial
+ INTO my_order_serial
+ FROM merchant_contract_terms
+ WHERE h_contract_terms=in_h_contract_terms;
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+out_no_order=FALSE;
+
+SELECT deposit_confirmation_serial
+ ,deposit_timestamp
+ ,exchange_url
+ ,total_without_fee
+ ,wire_fee
+ ,wire_transfer_deadline
+ ,account_serial
+ INTO my_record
+ FROM merchant_deposit_confirmations
+ WHERE order_serial=my_order_serial
+ AND exchange_url=in_exchange_url;
+IF NOT FOUND
+THEN
+ INSERT INTO merchant_deposit_confirmations
+ (order_serial
+ ,deposit_timestamp
+ ,exchange_url
+ ,total_without_fee
+ ,wire_fee
+ ,exchange_sig
+ ,wire_transfer_deadline
+ ,signkey_serial
+ ,account_serial
+ ) VALUES (
+ my_order_serial
+ ,in_deposit_timestamp
+ ,in_exchange_url
+ ,in_total_without_fee
+ ,in_wire_fee
+ ,in_exchange_sig
+ ,in_wire_transfer_deadline
+ ,my_signkey_serial
+ ,my_account_serial
+ ) RETURNING deposit_confirmation_serial
+ INTO out_deposit_confirmation_serial;
+ELSE
+ out_deposit_confirmation_serial = my_record.deposit_confirmation_serial;
+ IF (in_deposit_timestamp,
+ in_wire_transfer_deadline,
+ in_wire_fee,
+ my_account_serial)
+ IS DISTINCT FROM
+ (my_record.deposit_timestamp,
+ my_record.wire_transfer_deadline,
+ my_record.wire_fee,
+ my_record.account_serial)
+ THEN
+ out_conflict = TRUE;
+ RETURN;
+ END IF;
+ IF ( ((in_total_without_fee).val < (my_record.total_without_fee).val) OR
+ ( ((in_total_without_fee).val = (my_record.total_without_fee).val) AND
+ ((in_total_without_fee).frac <= (my_record.total_without_fee).frac) ) )
+ THEN
+ -- new amount smaller or did not change, do NOT update.
+ RETURN;
+ END IF;
+
+ -- Same deposit, but total amount increased, store this!
+ UPDATE merchant_deposit_confirmations
+ SET total_without_fee = in_total_without_fee
+ ,exchange_sig = in_exchange_sig
+ ,signkey_serial = my_signkey_serial
+ WHERE deposit_confirmation_serial = my_record.deposit_confirmation_serial;
+
+END IF;
+
+-- Do notify on TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE
+PERFORM pg_notify ('XBZ19D98AK2REYNX93F736A56MT14SCY2EEX7XNXQMNCQ01B121R0',
+ in_notify_arg_str);
+
+END $$;
diff --git a/src/backenddb/insert_deposit_to_transfer.c b/src/backenddb/insert_deposit_to_transfer.c
@@ -19,19 +19,18 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_deposit_to_transfer.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_deposit_to_transfer (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t deposit_serial,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- const struct TALER_EXCHANGE_DepositData *dd)
+TALER_MERCHANTDB_insert_deposit_to_transfer (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t deposit_serial,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DepositData *dd)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&deposit_serial),
@@ -52,15 +51,15 @@ TALER_MERCHANTDB_insert_deposit_to_transfer (struct TALER_MERCHANTDB_PostgresCon
GNUNET_PQ_result_spec_end
};
- PREPARE (pg,
- "insert_deposit_to_transfer",
- "SELECT"
- " out_dummy"
- " FROM merchant_insert_deposit_to_transfer"
- " ($1,$2,$3,$4,$5,$6,$7,$8);");
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_dummy"
+ " FROM merchant_insert_deposit_to_transfer"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
return GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "insert_deposit_to_transfer",
+ "",
params,
rs);
}
diff --git a/src/backenddb/insert_deposit_to_transfer.sql b/src/backenddb/insert_deposit_to_transfer.sql
@@ -0,0 +1,136 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024, 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_insert_deposit_to_transfer;
+CREATE FUNCTION merchant_insert_deposit_to_transfer (
+ IN in_deposit_serial INT8,
+ IN in_coin_contribution merchant.taler_amount_currency,
+ IN in_execution_time INT8,
+ IN in_exchange_url TEXT,
+ IN in_h_wire BYTEA,
+ IN in_exchange_sig BYTEA,
+ IN in_exchange_pub BYTEA,
+ IN in_wtid BYTEA,
+ OUT out_dummy BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_signkey_serial INT8;
+ my_account_serial INT8;
+ my_decose INT8;
+ my_expected_credit_serial INT8;
+ my_wire_pending_cleared BOOL;
+BEGIN
+ -- Just to return something (for now).
+ out_dummy=FALSE;
+
+-- Find exchange sign key
+SELECT signkey_serial
+ INTO my_signkey_serial
+ FROM merchant.merchant_exchange_signing_keys
+ WHERE exchange_pub=in_exchange_pub
+ ORDER BY start_date DESC
+ LIMIT 1;
+
+IF NOT FOUND
+THEN
+ -- Maybe 'keys' is outdated, try again in 8 hours.
+ UPDATE merchant_deposits
+ SET settlement_last_ec=2029 -- MERCHANT_EXCHANGE_SIGN_PUB_UNKNOWN
+ ,settlement_last_http_status=200
+ ,settlement_last_detail=ENCODE(in_exchange_pub, 'hex')
+ ,settlement_wtid=in_wtid
+ ,settlement_retry_needed=TRUE
+ ,settlement_retry_time=(EXTRACT(epoch FROM (CURRENT_TIME + interval '8 hours')) * 1000000)::INT8
+ WHERE deposit_serial=in_deposit_serial;
+ RETURN;
+END IF;
+
+-- Find deposit confirmation
+SELECT deposit_confirmation_serial
+ INTO my_decose
+ FROM merchant_deposits
+ WHERE deposit_serial=in_deposit_serial;
+
+-- Find merchant account
+SELECT account_serial
+ INTO my_account_serial
+ FROM merchant_deposit_confirmations mdc
+ JOIN merchant_accounts ma
+ USING (account_serial)
+ WHERE mdc.deposit_confirmation_serial=my_decose
+ AND ma.h_wire=in_h_wire;
+
+IF NOT FOUND
+THEN
+ -- Merchant account referenced in exchange response is unknown to us.
+ -- Remember fatal error and do not try again.
+ UPDATE merchant_deposits
+ SET settlement_last_ec=2558 -- MERCHANT_EXCHANGE_TRANSFERS_TARGET_ACCOUNT_UNKNOWN
+ ,settlement_last_http_status=200
+ ,settlement_last_detail=ENCODE(in_h_wire, 'hex')
+ ,settlement_wtid=in_wtid
+ ,settlement_retry_needed=FALSE
+ ,settlement_coin_contribution=in_coin_contribution
+ ,signkey_serial=my_signkey_serial
+ ,settlement_exchange_sig=in_exchange_sig
+ WHERE deposit_serial=in_deposit_serial;
+ RETURN;
+END IF;
+
+
+-- Make sure wire transfer is expected.
+SELECT expected_credit_serial
+ INTO my_expected_credit_serial
+ FROM merchant_expected_transfers
+ WHERE wtid=in_wtid
+ AND exchange_url=in_exchange_url
+ AND account_serial=my_account_serial;
+
+IF NOT FOUND
+THEN
+ INSERT INTO merchant_expected_transfers
+ (exchange_url
+ ,wtid
+ ,account_serial
+ ,expected_time)
+ VALUES
+ (in_exchange_url
+ ,in_wtid
+ ,my_account_serial
+ ,in_execution_time)
+ RETURNING expected_credit_serial
+ INTO my_expected_credit_serial;
+END IF;
+
+-- Finally, update merchant_deposits so we do not try again.
+UPDATE merchant_deposits
+ SET settlement_last_ec=0
+ ,settlement_last_http_status=200
+ ,settlement_last_detail=NULL
+ ,settlement_wtid=in_wtid
+ ,settlement_retry_needed=FALSE
+ ,settlement_coin_contribution=in_coin_contribution
+ ,settlement_expected_credit_serial=my_expected_credit_serial
+ ,signkey_serial=my_signkey_serial
+ ,settlement_exchange_sig=in_exchange_sig
+ WHERE deposit_serial=in_deposit_serial;
+
+-- MERCHANT_WIRE_TRANSFER_EXPECTED
+NOTIFY XR6849FMRD2AJFY1E2YY0GWA8GN0YT407Z66WHJB0SAKJWF8G2Q60;
+
+END $$;
diff --git a/src/backenddb/insert_donau_instance.c b/src/backenddb/insert_donau_instance.c
@@ -20,8 +20,6 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "donau/donau_service.h"
#include "merchant-database/insert_donau_instance.h"
@@ -29,15 +27,15 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_donau_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *donau_url,
- const struct DONAU_Charity *charity,
- uint64_t charity_id)
+TALER_MERCHANTDB_insert_donau_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ uint64_t charity_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (donau_url),
GNUNET_PQ_query_param_string (charity->name),
- GNUNET_PQ_query_param_auto_from_type (&charity->charity_pub),
GNUNET_PQ_query_param_uint64 (&charity_id),
TALER_PQ_query_param_amount_with_currency (pg->conn,
&charity->max_per_year),
@@ -47,25 +45,26 @@ TALER_MERCHANTDB_insert_donau_instance (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_query_param_end
};
+ GNUNET_static_assert (sizeof (pg->current_merchant_pub) ==
+ sizeof (charity->charity_pub) );
+ GNUNET_assert (0 ==
+ memcmp (&pg->current_merchant_pub,
+ &charity->charity_pub,
+ sizeof (pg->current_merchant_pub)));
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "insert_donau_instance",
- "INSERT INTO merchant_donau_instances"
- " (donau_url"
- " ,charity_name"
- " ,merchant_instance_serial"
- " ,charity_id"
- " ,charity_max_per_year"
- " ,charity_receipts_to_date"
- " ,current_year)"
- "VALUES"
- " ($1, $2,"
- " (SELECT merchant_serial"
- " FROM merchant_instances mi"
- " WHERE mi.merchant_pub = $3),"
- " $4, $5, $6, $7)"
- " ON CONFLICT DO NOTHING;");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_donau_instances"
+ " (donau_url"
+ " ,charity_name"
+ " ,charity_id"
+ " ,charity_max_per_year"
+ " ,charity_receipts_to_date"
+ " ,current_year)"
+ "VALUES"
+ " ($1, $2, $3, $4, $5, $6)"
+ " ON CONFLICT DO NOTHING;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_donau_instance",
+ "",
params);
}
diff --git a/src/backenddb/insert_exchange_account.c b/src/backenddb/insert_exchange_account.c
@@ -19,21 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_exchange_account.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_exchange_account (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_FullPayto payto_uri,
- const char *conversion_url,
- const json_t *debit_restrictions,
- const json_t *credit_restrictions,
- const struct TALER_MasterSignatureP *master_sig)
+TALER_MERCHANTDB_insert_exchange_account (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_FullPayto payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_pub),
diff --git a/src/backenddb/insert_exchange_keys.c b/src/backenddb/insert_exchange_keys.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_exchange_keys.h"
#include "helper.h"
@@ -56,9 +54,10 @@ TALER_MERCHANTDB_insert_exchange_keys (
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
+ // FIXME: use a stored procedure instead!
PREPARE (pg,
"insert_exchange_keys",
- "INSERT INTO merchant_exchange_keys"
+ "INSERT INTO merchant.merchant_exchange_keys"
"(keys_json"
",first_retry"
",expiration_time"
@@ -70,7 +69,7 @@ TALER_MERCHANTDB_insert_exchange_keys (
");");
PREPARE (pg,
"update_exchange_keys",
- "UPDATE merchant_exchange_keys SET"
+ "UPDATE merchant.merchant_exchange_keys SET"
/* preserve old keys if new ones failed to download */
" keys_json=COALESCE($1::TEXT::JSONB,keys_json)"
",first_retry=$2"
diff --git a/src/backenddb/insert_exchange_signkey.c b/src/backenddb/insert_exchange_signkey.c
@@ -19,20 +19,20 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_exchange_signkey.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_exchange_signkey (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp expire_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *master_sig)
+TALER_MERCHANTDB_insert_exchange_signkey (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp expire_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_pub),
@@ -47,7 +47,7 @@ TALER_MERCHANTDB_insert_exchange_signkey (struct TALER_MERCHANTDB_PostgresContex
check_connection (pg);
PREPARE (pg,
"insert_exchange_signkey",
- "INSERT INTO merchant_exchange_signing_keys"
+ "INSERT INTO merchant.merchant_exchange_signing_keys"
"(master_pub"
",exchange_pub"
",start_date"
diff --git a/src/backenddb/insert_instance.c b/src/backenddb/insert_instance.c
@@ -20,12 +20,11 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_instance.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
TALER_MERCHANTDB_insert_instance (
struct TALER_MERCHANTDB_PostgresContext *pg,
@@ -37,6 +36,7 @@ TALER_MERCHANTDB_insert_instance (
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (merchant_priv),
GNUNET_PQ_query_param_auto_from_type (&ias->auth_hash),
GNUNET_PQ_query_param_auto_from_type (&ias->auth_salt),
GNUNET_PQ_query_param_string (is->id),
@@ -68,18 +68,13 @@ TALER_MERCHANTDB_insert_instance (
is->default_wire_transfer_rounding_interval)),
GNUNET_PQ_query_param_end
};
- struct GNUNET_PQ_QueryParam params_priv[] = {
- GNUNET_PQ_query_param_auto_from_type (merchant_priv),
- GNUNET_PQ_query_param_string (is->id),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
"insert_instance",
- "INSERT INTO merchant_instances"
+ "INSERT INTO merchant.merchant_instances"
"(merchant_pub"
+ ",merchant_priv"
",auth_hash"
",auth_salt"
",merchant_id"
@@ -99,22 +94,10 @@ TALER_MERCHANTDB_insert_instance (
",validation_needed"
",default_wire_transfer_rounding_interval)"
"VALUES"
- "($1,$2,$3,LOWER($4),$5,$6::TEXT::JSONB,$7::TEXT::JSONB,$8,$9,$10,$11,"
- "$12,$13,$14,$15,$16,$17,$18,$19::time_rounder_interval)");
- PREPARE (pg,
- "insert_keys",
- "INSERT INTO merchant_keys"
- "(merchant_priv"
- ",merchant_serial)"
- " SELECT $1, merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$2");
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_instance",
- params);
- if (qs <= 0)
- return qs;
+ "($1,$2,$3,$4,LOWER($5),$6,$7::TEXT::JSONB,$8::TEXT::JSONB,"
+ "$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,"
+ "$20::merchant.time_rounder_interval)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_keys",
- params_priv);
+ "insert_instance",
+ params);
}
diff --git a/src/backenddb/insert_issued_token.c b/src/backenddb/insert_issued_token.c
@@ -19,20 +19,17 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_issued_token.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_issued_token (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_PrivateContractHashP *
- h_contract_terms,
- const struct TALER_TokenIssuePublicKeyHashP *
- h_issue_pub,
- const struct TALER_BlindedTokenIssueSignature *
- blind_sig)
+TALER_MERCHANTDB_insert_issued_token (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
+ const struct TALER_BlindedTokenIssueSignature *blind_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_issue_pub),
@@ -51,17 +48,17 @@ TALER_MERCHANTDB_insert_issued_token (struct TALER_MERCHANTDB_PostgresContext *p
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "issued_token_insert",
- "SELECT"
- " out_no_family"
- " ,out_existed"
- " FROM merchant_do_insert_issued_token"
- " ($1, $2, $3);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_no_family"
+ " ,out_existed"
+ " FROM merchant_do_insert_issued_token"
+ " ($1, $2, $3);");
qs = GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "issued_token_insert",
+ "",
params,
rs);
if (qs < 0)
diff --git a/src/backenddb/pg_insert_issued_token.sql b/src/backenddb/insert_issued_token.sql
diff --git a/src/backenddb/insert_login_token.c b/src/backenddb/insert_login_token.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2023, 2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,24 +19,22 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_login_token.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_login_token (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- const struct TALER_MERCHANTDB_LoginTokenP *token,
- struct GNUNET_TIME_Timestamp creation_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- uint32_t validity_scope,
- const char *description)
+TALER_MERCHANTDB_insert_login_token (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp creation_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ uint32_t validity_scope,
+ const char *description)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_auto_from_type (token),
GNUNET_PQ_query_param_timestamp (&creation_time),
GNUNET_PQ_query_param_timestamp (&expiration_time),
@@ -45,21 +43,20 @@ TALER_MERCHANTDB_insert_login_token (struct TALER_MERCHANTDB_PostgresContext *pg
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_login_token",
- "INSERT INTO merchant_login_tokens"
- "(token"
- ",creation_time"
- ",expiration_time"
- ",validity_scope"
- ",description"
- ",merchant_serial"
- ")"
- "SELECT $2, $3, $4, $5, $6, merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_login_tokens"
+ "(token"
+ ",creation_time"
+ ",expiration_time"
+ ",validity_scope"
+ ",description"
+ ")"
+ " VALUES ($1, $2, $3, $4, $5)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_login_token",
+ "",
params);
}
diff --git a/src/backenddb/insert_money_pot.c b/src/backenddb/insert_money_pot.c
@@ -19,22 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_money_pot.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *name,
- const char *description,
- uint64_t *money_pot_id)
+TALER_MERCHANTDB_insert_money_pot (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *name,
+ const char *description,
+ uint64_t *money_pot_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (name),
GNUNET_PQ_query_param_string (description),
GNUNET_PQ_query_param_end
@@ -45,20 +43,19 @@ TALER_MERCHANTDB_insert_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_money_pot",
- "INSERT INTO merchant_money_pots"
- "(merchant_serial"
- ",money_pot_name"
- ",money_pot_description)"
- " SELECT merchant_serial, $2, $3"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"
- " ON CONFLICT DO NOTHING"
- " RETURNING money_pot_serial;");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_money_pots"
+ "(money_pot_name"
+ ",money_pot_description)"
+ " VALUES ($1, $2)"
+ " ON CONFLICT DO NOTHING"
+ " RETURNING money_pot_serial;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_money_pot",
+ "",
params,
rs);
}
diff --git a/src/backenddb/insert_order.c b/src/backenddb/insert_order.c
@@ -20,23 +20,23 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_order.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_order (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- const char *session_id,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- struct GNUNET_TIME_Timestamp pay_deadline,
- const struct TALER_ClaimTokenP *claim_token,
- const json_t *contract_terms,
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+TALER_MERCHANTDB_insert_order (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ const struct TALER_MerchantPostDataHashP *h_post_data,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const struct TALER_ClaimTokenP *claim_token,
+ const json_t *contract_terms,
+ const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
{
struct GNUNET_TIME_Timestamp now;
uint32_t pos32 = (uint32_t) pos_algorithm;
@@ -44,7 +44,6 @@ TALER_MERCHANTDB_insert_order (struct TALER_MERCHANTDB_PostgresContext *pg,
= json_string_value (json_object_get (contract_terms,
"fulfillment_url"));
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_timestamp (&pay_deadline),
GNUNET_PQ_query_param_auto_from_type (claim_token),
@@ -69,26 +68,25 @@ TALER_MERCHANTDB_insert_order (struct TALER_MERCHANTDB_PostgresContext *pg,
"inserting order: order_id: %s, instance_id: %s.\n",
order_id,
instance_id);
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_order",
- "INSERT INTO merchant_orders"
- "(merchant_serial"
- ",order_id"
- ",pay_deadline"
- ",claim_token"
- ",h_post_data"
- ",creation_time"
- ",contract_terms"
- ",pos_key"
- ",pos_algorithm"
- ",session_id"
- ",fulfillment_url)"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5, $6, $7::TEXT::JSONB, $8, $9, $10, $11"
- " FROM merchant_instances"
- " WHERE merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_orders"
+ "(order_id"
+ ",pay_deadline"
+ ",claim_token"
+ ",h_post_data"
+ ",creation_time"
+ ",contract_terms"
+ ",pos_key"
+ ",pos_algorithm"
+ ",session_id"
+ ",fulfillment_url)"
+ " VALUES"
+ " ($1, $2, $3, $4, $5, $6::TEXT::JSONB, $7, $8, $9, $10)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_order",
+ "",
params);
}
diff --git a/src/backenddb/insert_order_blinded_sigs.c b/src/backenddb/insert_order_blinded_sigs.c
@@ -19,18 +19,18 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_order_blinded_sigs.h"
#include "helper.h"
+/* FIXME-Optimize: this smells like it should support the use of arrays... */
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_order_blinded_sigs (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *order_id,
- uint32_t i,
- const struct GNUNET_HashCode *hash,
- const struct GNUNET_CRYPTO_BlindedSignature *blind_sig)
+TALER_MERCHANTDB_insert_order_blinded_sigs (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *order_id,
+ uint32_t i,
+ const struct GNUNET_HashCode *hash,
+ const struct GNUNET_CRYPTO_BlindedSignature *blind_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (order_id),
@@ -40,20 +40,19 @@ TALER_MERCHANTDB_insert_order_blinded_sigs (struct TALER_MERCHANTDB_PostgresCont
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "insert_blinded_sigs",
- "INSERT INTO merchant_order_token_blinded_sigs"
- " (order_serial"
- " ,token_index"
- " ,token_hash"
- " ,token_blinded_signature"
- ")"
- " SELECT order_serial, $2, $3, $4"
- " FROM merchant_contract_terms"
- " WHERE order_id = $1");
-
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_order_token_blinded_sigs"
+ " (order_serial"
+ " ,token_index"
+ " ,token_hash"
+ " ,token_blinded_signature"
+ ")"
+ " SELECT order_serial, $2, $3, $4"
+ " FROM merchant_contract_terms"
+ " WHERE order_id = $1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_blinded_sigs",
+ "",
params);
}
diff --git a/src/backenddb/insert_order_lock.c b/src/backenddb/insert_order_lock.c
@@ -19,22 +19,21 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_order_lock.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_order_lock (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- const char *product_id,
- uint64_t quantity,
- uint32_t quantity_frac)
+TALER_MERCHANTDB_insert_order_lock (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ const char *product_id,
+ uint64_t quantity,
+ uint32_t quantity_frac)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_uint64 (&quantity),
@@ -42,56 +41,53 @@ TALER_MERCHANTDB_insert_order_lock (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_order_lock",
- "WITH tmp AS"
- " (SELECT "
- " product_serial"
- " ,merchant_serial"
- " ,total_stock"
- " ,total_stock_frac"
- " ,total_sold"
- " ,total_sold_frac"
- " ,total_lost"
- " ,total_lost_frac"
- " ,allow_fractional_quantity"
- " FROM merchant_inventory"
- " WHERE product_id=$3"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"
- " INSERT INTO merchant_order_locks"
- " (product_serial"
- " ,total_locked"
- " ,total_locked_frac"
- " ,order_serial)"
- " SELECT tmp.product_serial, $4::INT8, $5::INT4, order_serial"
- " FROM merchant_orders"
- " JOIN tmp USING(merchant_serial)"
- " WHERE order_id=$2"
- " AND (tmp.allow_fractional_quantity OR $5 = 0)"
- " AND (tmp.total_stock = 9223372036854775807"
- " OR ("
- " (tmp.total_stock::NUMERIC * 1000000"
- " + tmp.total_stock_frac::NUMERIC)"
- " - (tmp.total_sold::NUMERIC * 1000000"
- " + tmp.total_sold_frac::NUMERIC)"
- " - (tmp.total_lost::NUMERIC * 1000000"
- " + tmp.total_lost_frac::NUMERIC)"
- " >= "
- " (($4::NUMERIC * 1000000) + $5::NUMERIC)"
- " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " FROM merchant_inventory_locks mil"
- " WHERE mil.product_serial = tmp.product_serial)"
- " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " FROM merchant_order_locks mol"
- " WHERE mol.product_serial = tmp.product_serial)"
- " ))");
+ TMH_PQ_prepare_anon (pg,
+ "WITH tmp AS"
+ " (SELECT "
+ " product_serial"
+ " ,total_stock"
+ " ,total_stock_frac"
+ " ,total_sold"
+ " ,total_sold_frac"
+ " ,total_lost"
+ " ,total_lost_frac"
+ " ,allow_fractional_quantity"
+ " FROM merchant_inventory"
+ " WHERE product_id=$2)"
+ " INSERT INTO merchant_order_locks"
+ " (product_serial"
+ " ,total_locked"
+ " ,total_locked_frac"
+ " ,order_serial)"
+ " SELECT tmp.product_serial, $3::INT8, $4::INT4, order_serial"
+ " FROM merchant_orders"
+ " CROSS JOIN tmp"
+ " WHERE order_id=$1"
+ " AND (tmp.allow_fractional_quantity OR $4 = 0)"
+ " AND (tmp.total_stock = 9223372036854775807"
+ " OR ("
+ " (tmp.total_stock::NUMERIC * 1000000"
+ " + tmp.total_stock_frac::NUMERIC)"
+ " - (tmp.total_sold::NUMERIC * 1000000"
+ " + tmp.total_sold_frac::NUMERIC)"
+ " - (tmp.total_lost::NUMERIC * 1000000"
+ " + tmp.total_lost_frac::NUMERIC)"
+ " >= "
+ " (($3::NUMERIC * 1000000) + $4::NUMERIC)"
+ " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " FROM merchant_inventory_locks mil"
+ " WHERE mil.product_serial = tmp.product_serial)"
+ " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " FROM merchant_order_locks mol"
+ " WHERE mol.product_serial = tmp.product_serial)"
+ " ))");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_order_lock",
+ "",
params);
}
diff --git a/src/backenddb/insert_otp.c b/src/backenddb/insert_otp.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_otp.h"
#include "helper.h"
@@ -36,14 +34,14 @@
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_otp (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *otp_id,
- const struct TALER_MERCHANTDB_OtpDeviceDetails *td)
+TALER_MERCHANTDB_insert_otp (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td)
{
uint32_t pos32 = (uint32_t) td->otp_algorithm;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (otp_id),
GNUNET_PQ_query_param_string (td->otp_description),
GNUNET_PQ_query_param_string (td->otp_key),
@@ -52,22 +50,20 @@ TALER_MERCHANTDB_insert_otp (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_otp",
- "INSERT INTO merchant_otp_devices"
- "(merchant_serial"
- ",otp_id"
- ",otp_description"
- ",otp_key"
- ",otp_algorithm"
- ",otp_ctr"
- ")"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5, $6"
- " FROM merchant_instances"
- " WHERE merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_otp_devices"
+ "(otp_id"
+ ",otp_description"
+ ",otp_key"
+ ",otp_algorithm"
+ ",otp_ctr"
+ ")"
+ " VALUES ($1, $2, $3, $4, $5)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_otp",
+ "",
params);
}
diff --git a/src/backenddb/insert_pending_webhook.c b/src/backenddb/insert_pending_webhook.c
@@ -19,23 +19,23 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_pending_webhook.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_pending_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t webhook_serial,
- const char *url,
- const char *http_method,
- const char *header,
- const char *body)
+TALER_MERCHANTDB_insert_pending_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t webhook_serial,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&pg->current_merchant_serial),
GNUNET_PQ_query_param_uint64 (&webhook_serial),
GNUNET_PQ_query_param_string (url),
GNUNET_PQ_query_param_string (http_method),
@@ -47,10 +47,14 @@ TALER_MERCHANTDB_insert_pending_webhook (struct TALER_MERCHANTDB_PostgresContext
: GNUNET_PQ_query_param_string (body),
GNUNET_PQ_query_param_end
};
+
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
PREPARE (pg,
"insert_pending_webhook",
- "INSERT INTO merchant_pending_webhooks"
+ "INSERT INTO merchant.merchant_pending_webhooks"
"(merchant_serial"
",webhook_serial"
",url"
@@ -58,11 +62,7 @@ TALER_MERCHANTDB_insert_pending_webhook (struct TALER_MERCHANTDB_PostgresContext
",header"
",body"
")"
- " SELECT mi.merchant_serial,"
- " $2, $3, $4, $5, $6"
- " FROM merchant_instances mi"
- " WHERE mi.merchant_id=$1");
-
+ " VALUES ($1, $2, $3, $4, $5, $6)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_pending_webhook",
params);
diff --git a/src/backenddb/insert_product.c b/src/backenddb/insert_product.c
@@ -20,56 +20,53 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_product.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_product (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t num_cats,
- const uint64_t *cats,
- bool *no_instance,
- bool *conflict,
- ssize_t *no_cat,
- bool *no_group,
- bool *no_pot)
+TALER_MERCHANTDB_insert_product (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *conflict,
+ ssize_t *no_cat,
+ bool *no_group,
+ bool *no_pot)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_string (pd->description),
- TALER_PQ_query_param_json (pd->description_i18n), /* $4 */
+ TALER_PQ_query_param_json (pd->description_i18n), /* $3 */
GNUNET_PQ_query_param_string (pd->unit),
GNUNET_PQ_query_param_string (pd->image),
- TALER_PQ_query_param_json (pd->taxes), /* $7 */
+ TALER_PQ_query_param_json (pd->taxes), /* $6 */
TALER_PQ_query_param_array_amount_with_currency (
pd->price_array_length,
pd->price_array,
- pg->conn), /* $8 */
- GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $9 */
- GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $10 */
+ pg->conn), /* $7 */
+ GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $8 */
+ GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $9 */
GNUNET_PQ_query_param_bool (pd->allow_fractional_quantity),
GNUNET_PQ_query_param_uint32 (&pd->fractional_precision_level),
- TALER_PQ_query_param_json (pd->address), /* $13 */
+ TALER_PQ_query_param_json (pd->address), /* $12 */
GNUNET_PQ_query_param_timestamp (&pd->next_restock),
GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
GNUNET_PQ_query_param_array_uint64 (num_cats,
cats,
- pg->conn), /* $16 */
+ pg->conn), /* $15 */
GNUNET_PQ_query_param_string (pd->product_name),
(0 == pd->product_group_id)
? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_uint64 (&pd->product_group_id), /* $18 */
+ : GNUNET_PQ_query_param_uint64 (&pd->product_group_id), /* $17 */
(0 == pd->money_pot_id)
? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $19 */
- GNUNET_PQ_query_param_bool (pd->price_is_net), /* $20 */
+ : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $18 */
+ GNUNET_PQ_query_param_bool (pd->price_is_net), /* $19 */
GNUNET_PQ_query_param_end
};
uint64_t ncat;
@@ -77,8 +74,6 @@ TALER_MERCHANTDB_insert_product (struct TALER_MERCHANTDB_PostgresContext *pg,
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_bool ("conflict",
conflict),
- GNUNET_PQ_result_spec_bool ("no_instance",
- no_instance),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_uint64 ("no_cat",
&ncat),
@@ -91,21 +86,22 @@ TALER_MERCHANTDB_insert_product (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_product",
- "SELECT"
- " out_conflict AS conflict"
- ",out_no_instance AS no_instance"
- ",out_no_cat AS no_cat"
- ",out_no_group AS no_group"
- ",out_no_pot AS no_pot"
- " FROM merchant_do_insert_product"
- "($1, $2, $3, $4::TEXT::JSONB, $5, $6, $7::TEXT::JSONB, $8"
- ",$9, $10, $11, $12, $13::TEXT::JSONB, $14, $15, $16"
- ",$17, $18, $19, $20);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_conflict AS conflict"
+ ",out_no_cat AS no_cat"
+ ",out_no_group AS no_group"
+ ",out_no_pot AS no_pot"
+ " FROM merchant_do_insert_product"
+ "($1, $2, $3::TEXT::JSONB, $4, $5, $6::TEXT::JSONB, $7"
+ ",$8, $9, $10, $11, $12::TEXT::JSONB, $13, $14, $15"
+ ",$16, $17, $18, $19);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_product",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
diff --git a/src/backenddb/insert_product.sql b/src/backenddb/insert_product.sql
@@ -0,0 +1,223 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024, 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant_do_insert_product;
+CREATE FUNCTION merchant_do_insert_product (
+ IN in_product_id TEXT,
+ IN in_description TEXT,
+ IN in_description_i18n JSONB, -- $3
+ IN in_unit TEXT,
+ IN in_image TEXT,
+ IN in_taxes JSONB, -- $6
+ IN ina_price_list merchant.taler_amount_currency[],
+ IN in_total_stock INT8, -- $8
+ IN in_total_stock_frac INT4, --$9
+ IN in_allow_fractional_quantity BOOL,
+ IN in_fractional_precision_level INT4,
+ IN in_address JSONB, -- $12
+ IN in_next_restock INT8,
+ IN in_minimum_age INT4,
+ IN ina_categories INT8[], -- $15
+ IN in_product_name TEXT,
+ IN in_product_group_id INT8, -- NULL for default
+ IN in_money_pot_id INT8, -- NULL for none
+ IN in_price_is_net BOOL, -- $19
+ OUT out_conflict BOOL,
+ OUT out_no_cat INT8,
+ OUT out_no_group BOOL,
+ OUT out_no_pot BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_product_serial INT8;
+ i INT8;
+ ini_cat INT8;
+BEGIN
+
+out_no_group = FALSE;
+out_no_pot = FALSE;
+
+IF in_product_group_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_product_groups
+ WHERE product_group_serial=in_product_group_id;
+ IF NOT FOUND
+ THEN
+ out_no_group=TRUE;
+ out_conflict=FALSE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+END IF;
+
+IF in_money_pot_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_money_pots
+ WHERE money_pot_serial=in_money_pot_id;
+ IF NOT FOUND
+ THEN
+ out_no_pot=TRUE;
+ out_conflict=FALSE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+END IF;
+
+INSERT INTO merchant_inventory
+ (product_id
+ ,product_name
+ ,description
+ ,description_i18n
+ ,unit
+ ,image
+ ,image_hash
+ ,taxes
+ ,price_array
+ ,total_stock
+ ,total_stock_frac
+ ,allow_fractional_quantity
+ ,fractional_precision_level
+ ,address
+ ,next_restock
+ ,minimum_age
+ ,product_group_serial
+ ,money_pot_serial
+ ,price_is_net
+) VALUES (
+ in_product_id
+ ,in_product_name
+ ,in_description
+ ,in_description_i18n
+ ,in_unit
+ ,in_image
+ ,CASE
+ WHEN (in_image IS NULL) OR (in_image = '')
+ THEN NULL
+ ELSE encode(public.digest(convert_to(in_image, 'UTF8'),
+ 'sha256'),
+ 'hex')
+ END
+ ,in_taxes
+ ,ina_price_list
+ ,in_total_stock
+ ,in_total_stock_frac
+ ,in_allow_fractional_quantity
+ ,in_fractional_precision_level
+ ,in_address
+ ,in_next_restock
+ ,in_minimum_age
+ ,in_product_group_id
+ ,in_money_pot_id
+ ,in_price_is_net
+ )
+ON CONFLICT (product_id) DO NOTHING
+ RETURNING product_serial
+ INTO my_product_serial;
+
+
+IF NOT FOUND
+THEN
+ -- Check for idempotency
+ SELECT product_serial
+ INTO my_product_serial
+ FROM merchant_inventory
+ WHERE product_id=in_product_id
+ AND product_name=in_product_name
+ AND description=in_description
+ AND description_i18n=in_description_i18n
+ AND unit=in_unit
+ AND image=in_image
+ AND taxes=in_taxes
+ AND to_jsonb(COALESCE(price_array, ARRAY[]::merchant.taler_amount_currency[]))
+ = to_jsonb(COALESCE(ina_price_list, ARRAY[]::merchant.taler_amount_currency[])) -- FIXME: wild. Why so complicated?
+ AND total_stock=in_total_stock
+ AND total_stock_frac=in_total_stock_frac
+ AND allow_fractional_quantity=in_allow_fractional_quantity
+ AND fractional_precision_level=in_fractional_precision_level
+ AND address=in_address
+ AND next_restock=in_next_restock
+ AND minimum_age=in_minimum_age
+ AND product_group_serial IS NOT DISTINCT FROM in_product_group_id
+ AND money_pot_serial IS NOT DISTINCT FROM in_money_pot_id
+ AND price_is_net=in_price_is_net;
+ IF NOT FOUND
+ THEN
+ out_conflict=TRUE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+
+ -- Check categories match as well
+ FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
+ LOOP
+ ini_cat=ina_categories[i];
+
+ PERFORM
+ FROM merchant_product_categories
+ WHERE product_serial=my_product_serial
+ AND category_serial=ini_cat;
+ IF NOT FOUND
+ THEN
+ out_conflict=TRUE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+ END LOOP;
+
+ -- Also check there are no additional categories
+ -- in either set.
+ SELECT COUNT(*)
+ INTO i
+ FROM merchant_product_categories
+ WHERE product_serial=my_product_serial;
+ IF i != COALESCE(array_length(ina_categories,1),0)
+ THEN
+ out_conflict=TRUE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+
+ -- Is idempotent!
+ out_conflict=FALSE;
+ out_no_cat=NULL;
+ RETURN;
+END IF;
+out_conflict=FALSE;
+
+
+-- Add categories
+FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
+LOOP
+ ini_cat=ina_categories[i];
+
+ INSERT INTO merchant_product_categories
+ (product_serial
+ ,category_serial)
+ VALUES
+ (my_product_serial
+ ,ini_cat)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ out_no_cat=i;
+ RETURN;
+ END IF;
+END LOOP;
+
+-- Success!
+out_no_cat=NULL;
+END $$;
diff --git a/src/backenddb/insert_product_group.c b/src/backenddb/insert_product_group.c
@@ -19,22 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_product_group.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_product_group (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *name,
- const char *description,
- uint64_t *product_group_id)
+TALER_MERCHANTDB_insert_product_group (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *name,
+ const char *description,
+ uint64_t *product_group_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (name),
GNUNET_PQ_query_param_string (description),
GNUNET_PQ_query_param_end
@@ -45,21 +43,20 @@ TALER_MERCHANTDB_insert_product_group (struct TALER_MERCHANTDB_PostgresContext *
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_product_group",
- "INSERT INTO merchant_product_groups"
- "(merchant_serial"
- ",product_group_name"
- ",product_group_description)"
- " SELECT merchant_serial, $2, $3"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"
- " ON CONFLICT DO NOTHING"
- " RETURNING product_group_serial");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_product_groups"
+ "(product_group_name"
+ ",product_group_description)"
+ " VALUES ($1, $2)"
+ " ON CONFLICT DO NOTHING"
+ " RETURNING product_group_serial");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_product_group",
+ "",
params,
rs);
}
diff --git a/src/backenddb/insert_refund_proof.c b/src/backenddb/insert_refund_proof.c
@@ -19,17 +19,17 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_refund_proof.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_refund_proof (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t refund_serial,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
+TALER_MERCHANTDB_insert_refund_proof (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t refund_serial,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&refund_serial),
@@ -38,20 +38,19 @@ TALER_MERCHANTDB_insert_refund_proof (struct TALER_MERCHANTDB_PostgresContext *p
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "insert_refund_proof",
- "INSERT INTO merchant_refund_proofs"
- "(refund_serial"
- ",exchange_sig"
- ",signkey_serial)"
- "SELECT $1, $2, signkey_serial"
- " FROM merchant_exchange_signing_keys"
- " WHERE exchange_pub=$3"
- " ORDER BY start_date DESC"
- " LIMIT 1");
-
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_refund_proofs"
+ "(refund_serial"
+ ",exchange_sig"
+ ",signkey_serial)"
+ "SELECT $1, $2, signkey_serial"
+ " FROM merchant.merchant_exchange_signing_keys"
+ " WHERE exchange_pub=$3"
+ " ORDER BY start_date DESC"
+ " LIMIT 1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_refund_proof",
+ "",
params);
}
diff --git a/src/backenddb/insert_report.c b/src/backenddb/insert_report.c
@@ -43,7 +43,6 @@ TALER_MERCHANTDB_insert_report (
struct TALER_MERCHANT_ReportToken report_token;
struct GNUNET_TIME_Timestamp start;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (report_program_section),
GNUNET_PQ_query_param_string (report_description),
GNUNET_PQ_query_param_string (mime_type),
@@ -61,6 +60,9 @@ TALER_MERCHANTDB_insert_report (
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&report_token,
sizeof (report_token));
@@ -73,27 +75,22 @@ TALER_MERCHANTDB_insert_report (
frequency_shift)));
check_connection (pg);
- PREPARE (pg,
- "insert_report",
- "INSERT INTO merchant_reports"
- "(merchant_serial"
- ",report_program_section"
- ",report_description"
- ",mime_type"
- ",report_token"
- ",data_source"
- ",target_address"
- ",frequency"
- ",frequency_shift"
- ",next_transmission)"
- " SELECT merchant_serial, $2, $3, $4, $5,"
- " $6, $7, $8, $9, $10"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"
- " ON CONFLICT DO NOTHING;");
-
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_reports"
+ "(report_program_section"
+ ",report_description"
+ ",mime_type"
+ ",report_token"
+ ",data_source"
+ ",target_address"
+ ",frequency"
+ ",frequency_shift"
+ ",next_transmission)"
+ " VALUES ($1, $2, $3, $4,"
+ " $5, $6, $7, $8, $9)"
+ " ON CONFLICT DO NOTHING;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_report",
+ "",
params,
rs);
}
diff --git a/src/backenddb/insert_spent_token.c b/src/backenddb/insert_spent_token.c
@@ -19,20 +19,19 @@
* @author Christian Blättler
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_spent_token.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_spent_token (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
- const struct TALER_TokenUsePublicKeyP *use_pub,
- const struct TALER_TokenUseSignatureP *use_sig,
- const struct TALER_TokenIssueSignature *issue_sig)
+TALER_MERCHANTDB_insert_spent_token (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignature *issue_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
@@ -53,24 +52,25 @@ TALER_MERCHANTDB_insert_spent_token (struct TALER_MERCHANTDB_PostgresContext *pg
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Storing token spent with key %s\n",
GNUNET_h2s (&h_issue_pub->hash));
- PREPARE (pg,
- "spent_token_insert",
- "SELECT"
- " out_no_family"
- " ,out_conflict"
- " FROM merchant_do_insert_spent_token"
- "($1, $2, $3, $4, $5);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_no_family"
+ " ,out_conflict"
+ " FROM merchant_do_insert_spent_token"
+ "($1, $2, $3, $4, $5);");
qs = GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "spent_token_insert",
+ "",
params,
rs);
if (qs < 0)
return qs;
+ // FIXME: return specific errors to caller
if (no_fam)
{
GNUNET_break (0);
diff --git a/src/backenddb/pg_insert_spent_token.sql b/src/backenddb/insert_spent_token.sql
diff --git a/src/backenddb/insert_template.c b/src/backenddb/insert_template.c
@@ -19,22 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_template.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_template (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *template_id,
- uint64_t otp_serial_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td)
+TALER_MERCHANTDB_insert_template (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *template_id,
+ uint64_t otp_serial_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_string (td->template_description),
(0 == otp_serial_id)
@@ -48,23 +46,22 @@ TALER_MERCHANTDB_insert_template (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_template",
- "INSERT INTO merchant_template"
- "(merchant_serial"
- ",template_id"
- ",template_description"
- ",otp_device_id"
- ",template_contract"
- ",editable_defaults"
- ")"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5::TEXT::JSONB, $6::TEXT::JSONB"
- " FROM merchant_instances"
- " WHERE merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_template"
+ "(template_id"
+ ",template_description"
+ ",otp_device_id"
+ ",template_contract"
+ ",editable_defaults"
+ ")"
+ " VALUES"
+ " ($1, $2, $3, $4::TEXT::JSONB, $5::TEXT::JSONB)");
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_template",
+ "",
params);
GNUNET_PQ_cleanup_query_params_closures (params);
return qs;
diff --git a/src/backenddb/insert_token_family.c b/src/backenddb/insert_token_family.c
@@ -19,18 +19,17 @@
* @author Christian Blättler
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_token_family.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_token_family (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *token_family_slug,
- const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
+TALER_MERCHANTDB_insert_token_family (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
{
const char *kind;
@@ -46,37 +45,35 @@ TALER_MERCHANTDB_insert_token_family (struct TALER_MERCHANTDB_PostgresContext *p
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_token_family",
- "INSERT INTO merchant_token_families"
- "(merchant_serial"
- ",slug"
- ",name"
- ",description"
- ",description_i18n"
- ",extra_data"
- ",valid_after"
- ",valid_before"
- ",duration"
- ",validity_granularity"
- ",start_offset"
- ",kind)"
- " SELECT merchant_serial, $2, $3, $4, $5::TEXT::JSONB,"
- " $6::TEXT::JSONB, $7, $8, $9, $10, $11, $12"
- " FROM merchant_instances"
- " WHERE merchant_id=$1"
- " ON CONFLICT DO NOTHING;");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_token_families"
+ "(slug"
+ ",name"
+ ",description"
+ ",description_i18n"
+ ",extra_data"
+ ",valid_after"
+ ",valid_before"
+ ",duration"
+ ",validity_granularity"
+ ",start_offset"
+ ",kind)"
+ " VALUES ($1, $2, $3, $4::TEXT::JSONB,"
+ " $5::TEXT::JSONB, $6, $7, $8, $9, $10, $11)"
+ " ON CONFLICT DO NOTHING;");
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (token_family_slug),
GNUNET_PQ_query_param_string (details->name),
GNUNET_PQ_query_param_string (details->description),
TALER_PQ_query_param_json (details->description_i18n),
NULL == details->extra_data
- ? GNUNET_PQ_query_param_null ()
- : TALER_PQ_query_param_json (details->extra_data),
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_json (details->extra_data),
GNUNET_PQ_query_param_timestamp (&details->valid_after),
GNUNET_PQ_query_param_timestamp (&details->valid_before),
GNUNET_PQ_query_param_relative_time (&details->duration),
@@ -87,7 +84,7 @@ TALER_MERCHANTDB_insert_token_family (struct TALER_MERCHANTDB_PostgresContext *p
};
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_token_family",
+ "",
params);
}
}
diff --git a/src/backenddb/insert_token_family_key.c b/src/backenddb/insert_token_family_key.c
@@ -21,22 +21,21 @@
#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_pq_lib.h>
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_token_family_key.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_token_family_key (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *merchant_id,
- const char *token_family_slug,
- const struct TALER_TokenIssuePublicKey *pub,
- const struct TALER_TokenIssuePrivateKey *priv,
- struct GNUNET_TIME_Timestamp key_expires,
- struct GNUNET_TIME_Timestamp valid_after,
- struct GNUNET_TIME_Timestamp valid_before)
+TALER_MERCHANTDB_insert_token_family_key (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *merchant_id,
+ const char *token_family_slug,
+ const struct TALER_TokenIssuePublicKey *pub,
+ const struct TALER_TokenIssuePrivateKey *priv,
+ struct GNUNET_TIME_Timestamp key_expires,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before)
{
struct GNUNET_TIME_Timestamp now
= GNUNET_TIME_timestamp_get ();
@@ -89,25 +88,23 @@ TALER_MERCHANTDB_insert_token_family_key (struct TALER_MERCHANTDB_PostgresContex
valid_after.abs_time));
GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
valid_before.abs_time));
- PREPARE (pg,
- "token_family_key_insert",
- "INSERT INTO merchant_token_family_keys "
- "(token_family_serial"
- ",pub"
- ",h_pub"
- ",priv"
- ",private_key_created_at"
- ",private_key_deleted_at"
- ",signature_validity_start"
- ",signature_validity_end"
- ",cipher)"
- " SELECT token_family_serial, $2, $3, $4, $5, $6, $7, $8, $9"
- " FROM merchant_token_families"
- " WHERE (slug = $1)"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$10)");
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (merchant_id,
+ pg->current_merchant_id));
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_token_family_keys "
+ "(token_family_serial"
+ ",pub"
+ ",h_pub"
+ ",priv"
+ ",private_key_created_at"
+ ",private_key_deleted_at"
+ ",signature_validity_start"
+ ",signature_validity_end"
+ ",cipher)"
+ " SELECT token_family_serial, $2, $3, $4, $5, $6, $7, $8, $9"
+ " FROM merchant_token_families"
+ " WHERE (slug = $1)");
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (token_family_slug),
@@ -119,13 +116,12 @@ TALER_MERCHANTDB_insert_token_family_key (struct TALER_MERCHANTDB_PostgresContex
GNUNET_PQ_query_param_timestamp (&valid_after),
GNUNET_PQ_query_param_timestamp (&valid_before),
GNUNET_PQ_query_param_string (cipher),
- GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "token_family_key_insert",
+ "",
params);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Insert into MTFK %s with valid [%llu,%llu] got %d\n",
diff --git a/src/backenddb/insert_transfer.c b/src/backenddb/insert_transfer.c
@@ -19,28 +19,25 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_transfer.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_transfer (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *credit_amount,
- struct TALER_FullPayto payto_uri,
- uint64_t bank_serial_id,
- bool *no_instance,
- bool *no_account,
- bool *conflict)
+TALER_MERCHANTDB_insert_transfer (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *credit_amount,
+ struct TALER_FullPayto payto_uri,
+ uint64_t bank_serial_id,
+ bool *no_account,
+ bool *conflict)
{
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_auto_from_type (wtid),
TALER_PQ_query_param_amount_with_currency (pg->conn,
@@ -53,8 +50,6 @@ TALER_MERCHANTDB_insert_transfer (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("no_instance",
- no_instance),
GNUNET_PQ_result_spec_bool ("no_account",
no_account),
GNUNET_PQ_result_spec_bool ("conflict",
@@ -62,17 +57,18 @@ TALER_MERCHANTDB_insert_transfer (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_transfer",
- "SELECT "
- " out_no_instance AS no_instance"
- " ,out_no_account AS no_account"
- " ,out_conflict AS conflict"
- " FROM merchant_do_insert_transfer"
- " ($1, $2, $3, $4, $5, $6, $7);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT "
+ " out_no_account AS no_account"
+ " ,out_conflict AS conflict"
+ " FROM merchant_do_insert_transfer"
+ " ($1, $2, $3, $4, $5, $6);");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_transfer",
+ "",
params,
rs);
}
diff --git a/src/backenddb/insert_transfer.sql b/src/backenddb/insert_transfer.sql
@@ -0,0 +1,105 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant_do_insert_transfer;
+CREATE FUNCTION merchant_do_insert_transfer (
+ IN in_exchange_url TEXT,
+ IN in_wtid BYTEA,
+ IN in_credit_amount merchant.taler_amount_currency,
+ IN in_credited_account_payto TEXT,
+ IN in_bank_serial_id INT8, -- can be NULL if unknown
+ IN in_execution_time INT8,
+ OUT out_no_account BOOL,
+ OUT out_conflict BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_account_serial INT8;
+ my_record RECORD;
+ my_bank_serial_id INT8;
+ my_credit_amount merchant.taler_amount_currency;
+BEGIN
+
+out_conflict=FALSE;
+
+SELECT account_serial
+ INTO my_account_serial
+ FROM merchant_accounts
+ WHERE REGEXP_REPLACE(payto_uri,
+ '\\?.*','')
+ =REGEXP_REPLACE(in_credited_account_payto,
+ '\\?.*','');
+IF NOT FOUND
+THEN
+ out_no_account=TRUE;
+ RETURN;
+END IF;
+out_no_account=FALSE;
+
+SELECT bank_serial_id
+ ,credit_amount
+ INTO my_record
+ FROM merchant_transfers
+ WHERE wtid=in_wtid
+ AND account_serial=my_account_serial
+ AND exchange_url=in_exchange_url;
+IF NOT FOUND
+THEN
+ INSERT INTO merchant_transfers
+ (exchange_url
+ ,wtid
+ ,credit_amount
+ ,account_serial
+ ,bank_serial_id
+ ,execution_time
+ ) VALUES
+ (in_exchange_url
+ ,in_wtid
+ ,in_credit_amount
+ ,my_account_serial
+ ,in_bank_serial_id
+ ,in_execution_time);
+ -- Do notify on TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED
+ NOTIFY XJ5N652FA4TBS2WXGY3S1FMPMQYTD8KAZA9B7HW9JWJ4PZ2DB852G;
+ RETURN;
+END IF;
+
+my_bank_serial_id = my_record.bank_serial_id;
+my_credit_amount = my_record.credit_amount;
+
+IF ( (in_credit_amount.val != my_credit_amount.val) OR
+ (in_credit_amount.frac != my_credit_amount.frac) OR
+ (in_credit_amount.curr != my_credit_amount.curr) )
+THEN
+ out_conflict = TRUE; -- amounts differ, not OK!
+ RETURN;
+END IF;
+
+IF ( (my_bank_serial_id IS NULL) AND
+ (in_bank_serial_id IS NOT NULL) )
+THEN
+ -- We learned the bank_bank_serial_id, update that
+ UPDATE merchant_transfers
+ SET bank_serial_id=in_bank_serial_id
+ WHERE wtid=in_wtid
+ AND account_serial=my_account_serial
+ AND exchange_url=in_exchange_url;
+ RETURN;
+END IF;
+
+-- idempotent request, success.
+
+END $$;
diff --git a/src/backenddb/insert_transfer_details.c b/src/backenddb/insert_transfer_details.c
@@ -19,9 +19,8 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
+#include <taler/taler_dbevents.h>
#include "merchant-database/start.h"
#include "merchant-database/insert_transfer_details.h"
#include "helper.h"
@@ -34,12 +33,13 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *exchange_url,
- struct TALER_FullPayto payto_uri,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_EXCHANGE_TransferData *td)
+TALER_MERCHANTDB_insert_transfer_details (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *exchange_url,
+ struct TALER_FullPayto payto_uri,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_EXCHANGE_TransferData *td)
{
unsigned int len = td->details_length;
struct TALER_Amount coin_values[GNUNET_NZL (len)];
@@ -49,6 +49,10 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex
enum GNUNET_DB_QueryStatus qs;
bool duplicate;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+
for (unsigned int i = 0; i<len; i++)
{
const struct TALER_TrackTransferDetails *tdd = &td->details[i];
@@ -60,19 +64,8 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex
}
check_connection (pg);
- PREPARE (pg,
- "insert_transfer_details",
- "SELECT"
- " out_no_instance"
- ",out_no_account"
- ",out_no_exchange"
- ",out_duplicate"
- ",out_conflict"
- ",out_order_id"
- ",out_merchant_pub"
- " FROM merchant_do_insert_transfer_details"
- " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);");
-
+ // FIXME: why do we retry here, but not most other SQL statements?
+ // We need a slightly more consistent strategy...
for (unsigned int retries = 0;
retries < MAX_RETRIES;
retries++)
@@ -87,7 +80,7 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&pg->current_merchant_serial),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_string (payto_uri.full_payto),
GNUNET_PQ_query_param_auto_from_type (wtid),
@@ -116,15 +109,11 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex
pg->conn),
GNUNET_PQ_query_param_end
};
- bool no_instance;
bool no_account;
bool no_exchange;
bool conflict;
char *order_id = NULL;
- struct TALER_MerchantPublicKeyP merchant_pub;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("out_no_instance",
- &no_instance),
GNUNET_PQ_result_spec_bool ("out_no_account",
&no_account),
GNUNET_PQ_result_spec_bool ("out_no_exchange",
@@ -137,15 +126,20 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex
GNUNET_PQ_result_spec_string ("out_order_id",
&order_id),
NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_auto_from_type ("out_merchant_pub",
- &merchant_pub),
- NULL),
GNUNET_PQ_result_spec_end
};
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_no_account"
+ ",out_no_exchange"
+ ",out_duplicate"
+ ",out_conflict"
+ ",out_order_id"
+ " FROM merchant_do_insert_transfer_details"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_transfer_details",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
@@ -165,7 +159,7 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex
struct TMH_OrderPayEventP pay_eh = {
.header.size = htons (sizeof (pay_eh)),
.header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
- .merchant_pub = merchant_pub
+ .merchant_pub = pg->current_merchant_pub
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -181,8 +175,7 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex
GNUNET_free (order_id);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Transfer details inserted: %s%s%s%s%s\n",
- no_instance ? "no instance " : "",
+ "Transfer details inserted: %s%s%s%s\n",
no_account ? "no account " : "",
no_exchange ? "no exchange ": "",
duplicate ? "duplicate ": "",
diff --git a/src/backenddb/insert_transfer_details.sql b/src/backenddb/insert_transfer_details.sql
@@ -0,0 +1,252 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024, 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_insert_transfer_details;
+CREATE FUNCTION merchant_do_insert_transfer_details (
+ IN in_merchant_serial INT8,
+ IN in_exchange_url TEXT,
+ IN in_payto_uri TEXT,
+ IN in_wtid BYTEA,
+ IN in_execution_time INT8,
+ IN in_exchange_pub BYTEA,
+ IN in_exchange_sig BYTEA,
+ IN in_total_amount merchant.taler_amount_currency,
+ IN in_wire_fee merchant.taler_amount_currency,
+ IN ina_coin_values merchant.taler_amount_currency[],
+ IN ina_deposit_fees merchant.taler_amount_currency[],
+ IN ina_coin_pubs BYTEA[],
+ IN ina_contract_terms BYTEA[],
+ OUT out_no_account BOOL,
+ OUT out_no_exchange BOOL,
+ OUT out_duplicate BOOL,
+ OUT out_conflict BOOL,
+ OUT out_order_id TEXT)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_signkey_serial INT8;
+ my_expected_credit_serial INT8;
+ my_affected_orders RECORD;
+ my_decose INT8;
+ my_order_id TEXT;
+ i INT8;
+ curs CURSOR (arg_coin_pub BYTEA) FOR
+ SELECT mcon.deposit_confirmation_serial,
+ mcon.order_serial
+ FROM merchant_deposits dep
+ JOIN merchant_deposit_confirmations mcon
+ USING (deposit_confirmation_serial)
+ WHERE dep.coin_pub=arg_coin_pub;
+ ini_coin_pub BYTEA;
+ ini_contract_term BYTEA;
+ ini_coin_value merchant.taler_amount_currency;
+ ini_deposit_fee merchant.taler_amount_currency;
+BEGIN
+
+-- Determine account that was credited.
+SELECT expected_credit_serial
+ INTO my_expected_credit_serial
+ FROM merchant_expected_transfers
+ WHERE exchange_url=in_exchange_url
+ AND wtid=in_wtid
+ AND account_serial=
+ (SELECT account_serial
+ FROM merchant_accounts
+ WHERE payto_uri=in_payto_uri);
+
+IF NOT FOUND
+THEN
+ out_no_account=TRUE;
+ out_no_exchange=FALSE;
+ out_duplicate=FALSE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+out_no_account=FALSE;
+
+-- Find exchange sign key
+SELECT signkey_serial
+ INTO my_signkey_serial
+ FROM merchant.merchant_exchange_signing_keys
+ WHERE exchange_pub=in_exchange_pub
+ ORDER BY start_date DESC
+ LIMIT 1;
+
+IF NOT FOUND
+THEN
+ out_no_exchange=TRUE;
+ out_conflict=FALSE;
+ out_duplicate=FALSE;
+ RETURN;
+END IF;
+out_no_exchange=FALSE;
+
+-- Add signature first, check for idempotent request
+INSERT INTO merchant_transfer_signatures
+ (expected_credit_serial
+ ,signkey_serial
+ ,credit_amount
+ ,wire_fee
+ ,execution_time
+ ,exchange_sig)
+ VALUES
+ (my_expected_credit_serial
+ ,my_signkey_serial
+ ,in_total_amount
+ ,in_wire_fee
+ ,in_execution_time
+ ,in_exchange_sig)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ PERFORM 1
+ FROM merchant_transfer_signatures
+ WHERE expected_credit_serial=my_expected_credit_serial
+ AND signkey_serial=my_signkey_serial
+ AND credit_amount=in_total_amount
+ AND wire_fee=in_wire_fee
+ AND execution_time=in_execution_time
+ AND exchange_sig=in_exchange_sig;
+ IF FOUND
+ THEN
+ -- duplicate case
+ out_duplicate=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+ END IF;
+ -- conflict case
+ out_duplicate=FALSE;
+ out_conflict=TRUE;
+ RETURN;
+END IF;
+
+out_duplicate=FALSE;
+out_conflict=FALSE;
+
+
+FOR i IN 1..array_length(ina_coin_pubs,1)
+LOOP
+ ini_coin_value=ina_coin_values[i];
+ ini_deposit_fee=ina_deposit_fees[i];
+ ini_coin_pub=ina_coin_pubs[i];
+ ini_contract_term=ina_contract_terms[i];
+
+ INSERT INTO merchant_expected_transfer_to_coin
+ (deposit_serial
+ ,expected_credit_serial
+ ,offset_in_exchange_list
+ ,exchange_deposit_value
+ ,exchange_deposit_fee)
+ SELECT
+ dep.deposit_serial
+ ,my_expected_credit_serial
+ ,i
+ ,ini_coin_value
+ ,ini_deposit_fee
+ FROM merchant_deposits dep
+ JOIN merchant_deposit_confirmations dcon
+ USING (deposit_confirmation_serial)
+ JOIN merchant_contract_terms cterm
+ USING (order_serial)
+ WHERE dep.coin_pub=ini_coin_pub
+ AND cterm.h_contract_terms=ini_contract_term;
+
+ RAISE NOTICE 'iterating over affected orders';
+ OPEN curs (arg_coin_pub:=ini_coin_pub);
+ LOOP
+ FETCH NEXT FROM curs INTO my_affected_orders;
+ EXIT WHEN NOT FOUND;
+
+ RAISE NOTICE 'checking affected order for completion';
+
+ my_decose=my_affected_orders.deposit_confirmation_serial;
+
+ PERFORM FROM merchant_deposits md
+ WHERE md.deposit_confirmation_serial=my_decose
+ AND (settlement_retry_needed
+ OR (settlement_wtid IS NULL) );
+ IF NOT FOUND
+ THEN
+ -- must be all done, clear flag
+ UPDATE merchant_deposit_confirmations
+ SET wire_pending=FALSE
+ WHERE (deposit_confirmation_serial=my_decose);
+
+ IF FOUND
+ THEN
+ -- Also update contract terms, if all (other) associated
+ -- deposit_confirmations are also done.
+
+ RAISE NOTICE 'checking affected contract for completion';
+ PERFORM FROM merchant_deposit_confirmations mdc
+ WHERE mdc.wire_pending
+ AND mdc.order_serial=my_affected_orders.order_serial;
+ IF NOT FOUND
+ THEN
+
+ UPDATE merchant_contract_terms
+ SET wired=TRUE
+ WHERE (order_serial=my_affected_orders.order_serial);
+
+ -- Select merchant_serial and order_id for webhook
+ SELECT order_id
+ INTO my_order_id
+ FROM merchant_contract_terms
+ WHERE order_serial=my_affected_orders.order_serial;
+ out_order_id = my_order_id;
+ -- Insert pending webhook if it exists
+ INSERT INTO merchant.merchant_pending_webhooks
+ (merchant_serial
+ ,webhook_serial
+ ,url
+ ,http_method
+ ,header
+ ,body)
+ SELECT in_merchant_serial
+ ,mw.webhook_serial
+ ,mw.url
+ ,mw.http_method
+ ,merchant.replace_placeholder(
+ merchant.replace_placeholder(mw.header_template,
+ 'order_id',
+ my_order_id),
+ 'wtid',
+ encode(in_wtid, 'hex')
+ )::TEXT
+ ,merchant.replace_placeholder(
+ merchant.replace_placeholder(mw.body_template,
+ 'order_id',
+ my_order_id),
+ 'wtid',
+ encode(in_wtid, 'hex')
+ )::TEXT
+ FROM merchant_webhook mw
+ WHERE mw.event_type = 'order_settled';
+ IF FOUND
+ THEN
+ NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
+ END IF; -- found pending order_settled webhooks to deliver
+ END IF; -- no more merchant_deposits waiting for wire_pending
+ END IF; -- did clear wire_pending flag for deposit confirmation
+ END IF; -- no more merchant_deposits wait for settlement
+
+ END LOOP; -- END curs LOOP
+ CLOSE curs;
+END LOOP; -- END FOR loop
+
+END $$;
diff --git a/src/backenddb/insert_unclaim_signature.c b/src/backenddb/insert_unclaim_signature.c
@@ -19,13 +19,19 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_unclaim_signature.h"
#include "helper.h"
-
+/**
+ * Compute the notification code for the given
+ * @a order_id and @a merchant_pub.
+ *
+ * @param order_id order to notify for
+ * @param merchant_pub merchant to notify for
+ * @return notification code, to be freed by caller
+ */
static char *
get_notify_str (const char *order_id,
const struct TALER_MerchantPublicKeyP *merchant_pub)
@@ -50,18 +56,16 @@ enum GNUNET_DB_QueryStatus
TALER_MERCHANTDB_insert_unclaim_signature (
struct TALER_MERCHANTDB_PostgresContext *pg,
const char *instance_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *order_id,
const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
const struct GNUNET_HashCode *h_contract,
const struct GNUNET_CRYPTO_EddsaSignature *nsig)
{
- char *nonce_str = GNUNET_STRINGS_data_to_string_alloc (nonce,
- sizeof (*nonce));
+ char nonce_str[sizeof (*nonce) * 2];
+ char *end;
char *notify_str = get_notify_str (order_id,
- merchant_pub);
+ &pg->current_merchant_pub);
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_string (nonce_str),
GNUNET_PQ_query_param_string (notify_str),
@@ -77,22 +81,30 @@ TALER_MERCHANTDB_insert_unclaim_signature (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_unclaim_signature",
- "SELECT"
- " out_found"
- " FROM merchant_do_insert_unclaim_signature"
- "($1, $2, $3, $4, $5, $6);");
+ end = GNUNET_STRINGS_data_to_string (nonce,
+ sizeof (*nonce),
+ nonce_str,
+ sizeof (nonce_str));
+ GNUNET_assert (NULL != end);
+ *end = '\0';
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_found"
+ " FROM merchant_do_insert_unclaim_signature"
+ "($1, $2, $3, $4, $5);");
qs = GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "insert_unclaim_signature",
+ "",
params,
rs);
- GNUNET_free (nonce_str);
GNUNET_free (notify_str);
if (qs <= 0)
return qs;
+ /* FIXME: not a precise set of possible errors... (see stored proc!) */
return found
? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
: GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
diff --git a/src/backenddb/insert_unclaim_signature.sql b/src/backenddb/insert_unclaim_signature.sql
@@ -0,0 +1,73 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_insert_unclaim_signature;
+CREATE FUNCTION merchant_do_insert_unclaim_signature (
+ IN in_order_id TEXT,
+ IN in_nonce_str TEXT,
+ IN in_notify_str TEXT,
+ IN in_h_contract_terms BYTEA,
+ IN in_nonce_sig BYTEA,
+ OUT out_found BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_expiration_time INT8;
+BEGIN
+
+ SELECT pay_deadline
+ INTO my_expiration_time
+ FROM merchant_contract_terms
+ WHERE order_id=in_order_id
+ AND contract_terms->>'nonce' = in_nonce_str;
+
+ IF NOT FOUND
+ THEN
+ -- FIXME: distinguish better between
+ -- different "not found" cases (contract, unclaim sig exists)
+ out_found = FALSE;
+ RETURN;
+ END IF;
+
+ INSERT INTO merchant_unclaim_signatures
+ (h_contract_terms
+ ,unclaim_sig
+ ,expiration_time)
+ VALUES
+ (in_h_contract_terms
+ ,in_nonce_sig
+ ,my_expiration_time)
+ ON CONFLICT DO NOTHING;
+
+ IF FOUND
+ THEN
+ out_found = TRUE;
+
+ -- order status change notification
+ EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify_str);
+
+ RETURN;
+ END IF;
+
+ PERFORM FROM merchant_unclaim_signatures
+ WHERE h_contract_terms = in_h_contract_terms
+ AND unclaim_sig = in_nonce_sig;
+ out_found = FOUND;
+
+END $$;
diff --git a/src/backenddb/insert_unit.c b/src/backenddb/insert_unit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2025 Taler Systems SA
+ Copyright (C) 2025, 2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,23 +19,20 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_unit.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const struct TALER_MERCHANTDB_UnitDetails *ud,
- bool *no_instance,
- bool *conflict,
- uint64_t *unit_serial)
+TALER_MERCHANTDB_insert_unit (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const struct TALER_MERCHANTDB_UnitDetails *ud,
+ bool *conflict,
+ uint64_t *unit_serial)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (ud->unit),
GNUNET_PQ_query_param_string (ud->unit_name_long),
GNUNET_PQ_query_param_string (ud->unit_name_short),
@@ -48,8 +45,6 @@ TALER_MERCHANTDB_insert_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
};
bool unit_serial_present = true;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("no_instance",
- no_instance),
GNUNET_PQ_result_spec_bool ("conflict",
conflict),
GNUNET_PQ_result_spec_allow_null (
@@ -60,20 +55,19 @@ TALER_MERCHANTDB_insert_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
- *no_instance = false;
*conflict = false;
-
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_unit",
- "SELECT"
- " out_no_instance AS no_instance"
- " ,out_conflict AS conflict"
- " ,out_unit_serial AS unit_serial"
- " FROM merchant_do_insert_unit"
- " ($1,$2,$3,$4,$5,$6,$7,$8,$9);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_conflict AS conflict"
+ " ,out_unit_serial AS unit_serial"
+ " FROM merchant_do_insert_unit"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_unit",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
diff --git a/src/backenddb/insert_unit.sql b/src/backenddb/insert_unit.sql
@@ -0,0 +1,81 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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 insert_unit.sql
+-- @brief SQL for inserting units
+-- @author Bohdan Potuzhnyi
+
+DROP FUNCTION IF EXISTS merchant_do_insert_unit;
+CREATE FUNCTION merchant_do_insert_unit (
+ IN in_unit TEXT,
+ IN in_unit_name_long TEXT,
+ IN in_unit_name_short TEXT,
+ IN in_unit_name_long_i18n BYTEA,
+ IN in_unit_name_short_i18n BYTEA,
+ IN in_unit_allow_fraction BOOL,
+ IN in_unit_precision_level INT4,
+ IN in_unit_active BOOL,
+ OUT out_conflict BOOL,
+ OUT out_unit_serial INT8)
+ LANGUAGE plpgsql
+AS $$
+BEGIN
+ -- Reject attempts to shadow builtin identifiers.
+ IF EXISTS (
+ SELECT 1
+ FROM merchant.merchant_builtin_units bu
+ WHERE bu.unit = in_unit
+ ) THEN
+ out_conflict := TRUE;
+ out_unit_serial := NULL;
+ RETURN;
+ END IF;
+
+ INSERT INTO merchant_custom_units (
+ unit,
+ unit_name_long,
+ unit_name_short,
+ unit_name_long_i18n,
+ unit_name_short_i18n,
+ unit_allow_fraction,
+ unit_precision_level,
+ unit_active)
+ VALUES (
+ in_unit,
+ in_unit_name_long,
+ in_unit_name_short,
+ in_unit_name_long_i18n,
+ in_unit_name_short_i18n,
+ in_unit_allow_fraction,
+ in_unit_precision_level,
+ in_unit_active)
+ ON CONFLICT (merchant_serial, unit)
+ DO NOTHING
+ RETURNING unit_serial
+ INTO out_unit_serial;
+
+ IF FOUND THEN
+ out_conflict := FALSE;
+ RETURN;
+ END IF;
+
+ -- Conflict: custom unit already exists.
+ SELECT unit_serial
+ INTO out_unit_serial
+ FROM merchant_custom_units
+ WHERE unit = in_unit;
+
+ out_conflict := TRUE;
+END $$;
diff --git a/src/backenddb/insert_webhook.c b/src/backenddb/insert_webhook.c
@@ -19,20 +19,19 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/insert_webhook.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *webhook_id,
- const struct TALER_MERCHANTDB_WebhookDetails *wb)
+TALER_MERCHANTDB_insert_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_string (wb->event_type),
GNUNET_PQ_query_param_string (wb->url),
@@ -46,24 +45,22 @@ TALER_MERCHANTDB_insert_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "insert_webhook",
- "INSERT INTO merchant_webhook"
- "(merchant_serial"
- ",webhook_id"
- ",event_type"
- ",url"
- ",http_method"
- ",header_template"
- ",body_template"
- ")"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5, $6, $7"
- " FROM merchant_instances"
- " WHERE merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_webhook"
+ "(webhook_id"
+ ",event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ ")"
+ " VALUES ($1, $2, $3, $4, $5, $6)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_webhook",
+ "",
params);
}
diff --git a/src/backenddb/lock_product.c b/src/backenddb/lock_product.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lock_product.h"
#include "helper.h"
@@ -37,7 +35,6 @@ TALER_MERCHANTDB_lock_product (
struct GNUNET_TIME_Timestamp expiration_time)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_auto_from_type (uuid),
GNUNET_PQ_query_param_uint64 (&quantity),
@@ -46,58 +43,52 @@ TALER_MERCHANTDB_lock_product (
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lock_product",
- "WITH ps AS"
- " (SELECT product_serial"
- " FROM merchant_inventory"
- " WHERE product_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"
- ",tmp AS"
- " (SELECT"
- " mi.product_serial"
- " ,mi.total_stock"
- " ,mi.total_stock_frac"
- " ,mi.total_sold"
- " ,mi.total_sold_frac"
- " ,mi.total_lost"
- " ,mi.total_lost_frac"
- " ,mi.allow_fractional_quantity"
- " FROM merchant_inventory mi"
- " JOIN ps USING (product_serial))"
- "INSERT INTO merchant_inventory_locks"
- "(product_serial"
- ",lock_uuid"
- ",total_locked"
- ",total_locked_frac"
- ",expiration)"
- " SELECT tmp.product_serial, $3, $4::INT8, $5::INT4, $6"
- " FROM tmp"
- " WHERE (tmp.allow_fractional_quantity OR $5 = 0)"
- " AND (tmp.total_stock = 9223372036854775807"
- " OR ("
- " (tmp.total_stock::NUMERIC * 1000000"
- " + tmp.total_stock_frac::NUMERIC)"
- " - (tmp.total_sold::NUMERIC * 1000000"
- " + tmp.total_sold_frac::NUMERIC)"
- " - (tmp.total_lost::NUMERIC * 1000000"
- " + tmp.total_lost_frac::NUMERIC)"
- " >= "
- " (($4::NUMERIC * 1000000) + $5::NUMERIC)"
- " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " FROM merchant_inventory_locks mil"
- " WHERE mil.product_serial = tmp.product_serial)"
- " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " FROM merchant_order_locks mol"
- " WHERE mol.product_serial = tmp.product_serial)"
- " ))");
+ TMH_PQ_prepare_anon (pg,
+ "WITH tmp AS"
+ " (SELECT"
+ " mi.product_serial"
+ " ,mi.total_stock"
+ " ,mi.total_stock_frac"
+ " ,mi.total_sold"
+ " ,mi.total_sold_frac"
+ " ,mi.total_lost"
+ " ,mi.total_lost_frac"
+ " ,mi.allow_fractional_quantity"
+ " FROM merchant_inventory mi"
+ " WHERE mi.product_id=$1)"
+ "INSERT INTO merchant_inventory_locks"
+ "(product_serial"
+ ",lock_uuid"
+ ",total_locked"
+ ",total_locked_frac"
+ ",expiration)"
+ " SELECT tmp.product_serial, $2, $3::INT8, $4::INT4, $5"
+ " FROM tmp"
+ " WHERE (tmp.allow_fractional_quantity OR $4 = 0)"
+ " AND (tmp.total_stock = 9223372036854775807"
+ " OR ("
+ " (tmp.total_stock::NUMERIC * 1000000"
+ " + tmp.total_stock_frac::NUMERIC)"
+ " - (tmp.total_sold::NUMERIC * 1000000"
+ " + tmp.total_sold_frac::NUMERIC)"
+ " - (tmp.total_lost::NUMERIC * 1000000"
+ " + tmp.total_lost_frac::NUMERIC)"
+ " >= "
+ " (($3::NUMERIC * 1000000) + $4::NUMERIC)"
+ " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " FROM merchant_inventory_locks mil"
+ " WHERE mil.product_serial = tmp.product_serial)"
+ " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " FROM merchant_order_locks mol"
+ " WHERE mol.product_serial = tmp.product_serial)"
+ " ))");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "lock_product",
+ "",
params);
}
diff --git a/src/backenddb/lookup_account.c b/src/backenddb/lookup_account.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2023, 2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,20 +19,19 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_account.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_account (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- struct TALER_FullPayto payto_uri,
- uint64_t *account_serial)
+TALER_MERCHANTDB_lookup_account (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ struct TALER_FullPayto payto_uri,
+ uint64_t *account_serial)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (payto_uri.full_payto),
GNUNET_PQ_query_param_end
};
@@ -42,20 +41,18 @@ TALER_MERCHANTDB_lookup_account (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_account",
- "SELECT"
- " account_serial"
- " FROM merchant_accounts"
- " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
- " =REGEXP_REPLACE($2,'\\?.*','')"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " account_serial"
+ " FROM merchant_accounts"
+ " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($1,'\\?.*','')");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_account",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_all_products.c b/src/backenddb/lookup_all_products.c
@@ -19,12 +19,11 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_all_products.h"
#include "helper.h"
+
/**
* Context used for TALER_MERCHANTDB_lookup_all_products().
*/
@@ -174,53 +173,51 @@ TALER_MERCHANTDB_lookup_all_products (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_all_products",
- "SELECT"
- " description"
- ",description_i18n::TEXT"
- ",product_name"
- ",unit"
- ",price_array"
- ",taxes::TEXT"
- ",total_stock"
- ",total_stock_frac"
- ",allow_fractional_quantity"
- ",fractional_precision_level"
- ",total_sold"
- ",total_sold_frac"
- ",total_lost"
- ",total_lost_frac"
- ",image"
- ",minv.address::TEXT"
- ",next_restock"
- ",minimum_age"
- ",product_id"
- ",product_serial"
- ",t.category_array AS categories"
- ",product_group_serial"
- ",money_pot_serial"
- ",price_is_net"
- " FROM merchant_inventory minv"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- ",LATERAL ("
- " SELECT ARRAY ("
- " SELECT mpc.category_serial"
- " FROM merchant_product_categories mpc"
- " WHERE mpc.product_serial = minv.product_serial"
- " ) AS category_array"
- " ) t"
- " WHERE inst.merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " description"
+ ",description_i18n::TEXT"
+ ",product_name"
+ ",unit"
+ ",price_array"
+ ",taxes::TEXT"
+ ",total_stock"
+ ",total_stock_frac"
+ ",allow_fractional_quantity"
+ ",fractional_precision_level"
+ ",total_sold"
+ ",total_sold_frac"
+ ",total_lost"
+ ",total_lost_frac"
+ ",image"
+ ",minv.address::TEXT"
+ ",next_restock"
+ ",minimum_age"
+ ",product_id"
+ ",product_serial"
+ ",t.category_array AS categories"
+ ",product_group_serial"
+ ",money_pot_serial"
+ ",price_is_net"
+ " FROM merchant_inventory minv"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = minv.product_serial"
+ " ) AS category_array"
+ " ) t");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_all_products",
+ "",
params,
&lookup_products_cb,
&plc);
diff --git a/src/backenddb/lookup_all_webhooks.c b/src/backenddb/lookup_all_webhooks.c
@@ -0,0 +1,166 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 src/backenddb/lookup_all_webhooks.c
+ * @brief Implementation of the lookup_all_webhooks function for Postgres
+ * @author Iván Ávalos
+ */
+#include "platform.h"
+#include <taler/taler_pq_lib.h>
+#include "merchant-database/lookup_all_webhooks.h"
+#include "helper.h"
+
+/**
+ * Context used for lookup_all_webhooks_cb().
+ */
+struct LookupAllWebhookContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_AllWebhooksCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about webhook.
+ *
+ * @param[in,out] cls of type `struct LookupAllWebhookContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_all_webhooks_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupAllWebhookContext *pwlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t webhook_pending_serial;
+ struct GNUNET_TIME_Absolute next_attempt;
+ uint32_t retries;
+ char *url;
+ char *http_method;
+ char *header = NULL;
+ char *body = NULL;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("out_webhook_pending_serial",
+ &webhook_pending_serial),
+ GNUNET_PQ_result_spec_absolute_time ("out_next_attempt",
+ &next_attempt),
+ GNUNET_PQ_result_spec_uint32 ("out_retries",
+ &retries),
+ GNUNET_PQ_result_spec_string ("out_url",
+ &url),
+ GNUNET_PQ_result_spec_string ("out_http_method",
+ &http_method),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("out_header",
+ &header),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("out_body",
+ &body),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ pwlc->extract_failed = true;
+ return;
+ }
+ pwlc->cb (pwlc->cb_cls,
+ webhook_pending_serial,
+ next_attempt,
+ retries,
+ url,
+ http_method,
+ header,
+ body);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TALER_MERCHANTDB_lookup_all_webhooks (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_AllWebhooksCallback cb,
+ void *cb_cls)
+{
+ struct LookupAllWebhookContext pwlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+ uint64_t max_results64 = max_results;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&pg->current_merchant_serial),
+ GNUNET_PQ_query_param_uint64 (&min_row),
+ GNUNET_PQ_query_param_uint64 (&max_results64),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_all_webhooks",
+ " SELECT"
+ " webhook_pending_serial AS out_webhook_pending_serial"
+ " ,next_attempt AS out_next_attempt"
+ " ,retries AS out_retries"
+ " ,url AS out_url"
+ " ,http_method AS out_http_method"
+ " ,header AS out_header"
+ " ,body AS out_body"
+ " FROM merchant.merchant_pending_webhooks"
+ " WHERE merchant_serial = $1"
+ " AND webhook_pending_serial > $2"
+ " ORDER BY webhook_pending_serial ASC"
+ " LIMIT $3");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_all_webhooks",
+ params,
+ &lookup_all_webhooks_cb,
+ &pwlc);
+ if (pwlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/lookup_categories.c b/src/backenddb/lookup_categories.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_categories.h"
#include "helper.h"
@@ -101,10 +99,11 @@ lookup_categories_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_categories (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- TALER_MERCHANTDB_CategoriesCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_categories (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls)
{
struct LookupCategoryContext tlc = {
.cb = cb,
@@ -113,30 +112,28 @@ TALER_MERCHANTDB_lookup_categories (struct TALER_MERCHANTDB_PostgresContext *pg,
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_categories",
- "SELECT"
- " mc.category_serial"
- ",mc.category_name"
- ",mc.category_name_i18n::TEXT"
- ",COALESCE(COUNT(mpc.product_serial),0)"
- " AS product_count"
- " FROM merchant_categories mc"
- " LEFT JOIN merchant_product_categories mpc"
- " USING (category_serial)"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mi.merchant_id=$1"
- " GROUP BY mc.category_serial"
- " ORDER BY mc.category_serial;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mc.category_serial"
+ ",mc.category_name"
+ ",mc.category_name_i18n::TEXT"
+ ",COALESCE(COUNT(mpc.product_serial),0)"
+ " AS product_count"
+ " FROM merchant_categories mc"
+ " LEFT JOIN merchant_product_categories mpc"
+ " USING (category_serial)"
+ " GROUP BY mc.category_serial"
+ " ORDER BY mc.category_serial;");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_categories",
+ "",
params,
&lookup_categories_cb,
&tlc);
diff --git a/src/backenddb/lookup_categories_by_ids.c b/src/backenddb/lookup_categories_by_ids.c
@@ -19,8 +19,6 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_categories_by_ids.h"
#include "helper.h"
@@ -107,7 +105,6 @@ TALER_MERCHANTDB_lookup_categories_by_ids (struct TALER_MERCHANTDB_PostgresConte
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_array_uint64 (num_category_ids,
category_ids,
pg->conn),
@@ -115,26 +112,25 @@ TALER_MERCHANTDB_lookup_categories_by_ids (struct TALER_MERCHANTDB_PostgresConte
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_categories_by_ids",
- "SELECT"
- " mc.category_serial"
- ",mc.category_name"
- ",mc.category_name_i18n::TEXT"
- ",COALESCE(COUNT(mpc.product_serial),0)"
- " AS product_count"
- " FROM merchant_categories mc"
- " LEFT JOIN merchant_product_categories mpc"
- " USING (category_serial)"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mi.merchant_id=$1"
- " AND mc.category_serial = ANY ($2)"
- " GROUP BY mc.category_serial"
- " ORDER BY mc.category_serial;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mc.category_serial"
+ ",mc.category_name"
+ ",mc.category_name_i18n::TEXT"
+ ",COALESCE(COUNT(mpc.product_serial),0)"
+ " AS product_count"
+ " FROM merchant_categories mc"
+ " LEFT JOIN merchant_product_categories mpc"
+ " USING (category_serial)"
+ " WHERE mc.category_serial = ANY ($1)"
+ " GROUP BY mc.category_serial"
+ " ORDER BY mc.category_serial;");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_categories_by_ids",
+ "",
params,
&lookup_categories_cb,
&tlc);
diff --git a/src/backenddb/lookup_contract_terms.c b/src/backenddb/lookup_contract_terms.c
@@ -20,25 +20,23 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_contract_terms.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_contract_terms (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- json_t **contract_terms,
- uint64_t *order_serial,
- struct TALER_ClaimTokenP *claim_token)
+TALER_MERCHANTDB_lookup_contract_terms (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ struct TALER_ClaimTokenP *claim_token)
{
enum GNUNET_DB_QueryStatus qs;
struct TALER_ClaimTokenP ct;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
@@ -53,25 +51,23 @@ TALER_MERCHANTDB_lookup_contract_terms (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_contract_terms",
- "SELECT"
- " contract_terms::TEXT"
- ",order_serial"
- ",claim_token"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " contract_terms::TEXT"
+ ",order_serial"
+ ",claim_token"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_contract_terms",
+ "",
params,
(NULL != contract_terms)
- ? rs
- : &rs[1]);
+ ? rs
+ : &rs[1]);
if (NULL != claim_token)
*claim_token = ct;
return qs;
diff --git a/src/backenddb/lookup_contract_terms2.c b/src/backenddb/lookup_contract_terms2.c
@@ -19,27 +19,26 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_contract_terms2.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_contract_terms2 (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- json_t **contract_terms,
- uint64_t *order_serial,
- bool *paid,
- struct TALER_ClaimTokenP *claim_token,
- char **pos_key,
- enum TALER_MerchantConfirmationAlgorithm *pos_algorithm)
+TALER_MERCHANTDB_lookup_contract_terms2 (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ struct TALER_ClaimTokenP *claim_token,
+ char **pos_key,
+ enum TALER_MerchantConfirmationAlgorithm *pos_algorithm)
{
enum GNUNET_DB_QueryStatus qs;
struct TALER_ClaimTokenP ct;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
@@ -65,24 +64,22 @@ TALER_MERCHANTDB_lookup_contract_terms2 (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_contract_terms2",
- "SELECT"
- " contract_terms::TEXT"
- ",order_serial"
- ",claim_token"
- ",paid"
- ",pos_key"
- ",pos_algorithm"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " contract_terms::TEXT"
+ ",order_serial"
+ ",claim_token"
+ ",paid"
+ ",pos_key"
+ ",pos_algorithm"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_contract_terms2",
+ "",
params,
(NULL != contract_terms)
? rs
diff --git a/src/backenddb/lookup_contract_terms3.c b/src/backenddb/lookup_contract_terms3.c
@@ -21,32 +21,30 @@
*/
#include "platform.h"
#include <sys/types.h>
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_contract_terms3.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_contract_terms3 (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- const char *session_id,
- json_t **contract_terms,
- uint64_t *order_serial,
- bool *paid,
- bool *wired,
- bool *session_matches,
- struct TALER_ClaimTokenP *claim_token,
- int16_t *choice_index)
+TALER_MERCHANTDB_lookup_contract_terms3 (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ json_t **contract_terms,
+ uint64_t *order_serial,
+ bool *paid,
+ bool *wired,
+ bool *session_matches,
+ struct TALER_ClaimTokenP *claim_token,
+ int16_t *choice_index)
{
enum GNUNET_DB_QueryStatus qs;
struct TALER_ClaimTokenP ct;
uint16_t ci = 0;
bool choice_index_null = false;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
NULL == session_id
? GNUNET_PQ_query_param_null ()
@@ -76,35 +74,30 @@ TALER_MERCHANTDB_lookup_contract_terms3 (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
*session_matches = false;
check_connection (pg);
- PREPARE (pg,
- "lookup_contract_terms3",
- "SELECT"
- " contract_terms::TEXT"
- ",order_serial"
- ",claim_token"
- ",paid"
- ",wired"
- ",(session_id=$3) AS session_matches"
- ",choice_index"
- " FROM merchant_contract_terms"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " contract_terms::TEXT"
+ ",order_serial"
+ ",claim_token"
+ ",paid"
+ ",wired"
+ ",(session_id=$2) AS session_matches"
+ ",choice_index"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_contract_terms3",
+ "",
params,
(NULL != contract_terms)
- ? rs
- : &rs[1]);
+ ? rs
+ : &rs[1]);
if (NULL != claim_token)
*claim_token = ct;
- if (! choice_index_null)
- *choice_index = (int16_t) ci;
- else
- *choice_index = -1;
+ *choice_index = (choice_index_null) ? -1 : (int16_t) ci;
return qs;
}
diff --git a/src/backenddb/lookup_custom_units_by_names.c b/src/backenddb/lookup_custom_units_by_names.c
@@ -89,12 +89,13 @@ lookup_units_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_custom_units_by_names (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *const *units,
- size_t num_units,
- TALER_MERCHANTDB_UnitsCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_custom_units_by_names (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *const *units,
+ size_t num_units,
+ TALER_MERCHANTDB_UnitsCallback cb,
+ void *cb_cls)
{
struct LookupUnitsContext luc = {
.cb = cb,
@@ -102,7 +103,6 @@ TALER_MERCHANTDB_lookup_custom_units_by_names (struct TALER_MERCHANTDB_PostgresC
.extract_failed = false
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_array_ptrs_string (num_units,
(const char **) units,
pg->conn),
@@ -110,28 +110,26 @@ TALER_MERCHANTDB_lookup_custom_units_by_names (struct TALER_MERCHANTDB_PostgresC
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_custom_units_by_names",
- "WITH mi AS ("
- " SELECT merchant_serial FROM merchant_instances WHERE merchant_id=$1"
- ")"
- "SELECT cu.unit_serial"
- " ,cu.unit"
- " ,cu.unit_name_long"
- " ,cu.unit_name_short"
- " ,cu.unit_name_long_i18n"
- " ,cu.unit_name_short_i18n"
- " ,cu.unit_allow_fraction"
- " ,cu.unit_precision_level"
- " ,cu.unit_active"
- " ,FALSE AS unit_builtin"
- " FROM merchant_custom_units cu"
- " JOIN mi ON cu.merchant_serial = mi.merchant_serial"
- " WHERE cu.unit = ANY ($2)"
- " ORDER BY cu.unit");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT cu.unit_serial"
+ " ,cu.unit"
+ " ,cu.unit_name_long"
+ " ,cu.unit_name_short"
+ " ,cu.unit_name_long_i18n"
+ " ,cu.unit_name_short_i18n"
+ " ,cu.unit_allow_fraction"
+ " ,cu.unit_precision_level"
+ " ,cu.unit_active"
+ " ,FALSE AS unit_builtin"
+ " FROM merchant_custom_units cu"
+ " WHERE cu.unit = ANY ($1)"
+ " ORDER BY cu.unit");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_custom_units_by_names",
+ "",
params,
&lookup_units_cb,
&luc);
diff --git a/src/backenddb/lookup_deposits.c b/src/backenddb/lookup_deposits.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_deposits.h"
#include "helper.h"
@@ -118,7 +116,6 @@ TALER_MERCHANTDB_lookup_deposits (
void *cb_cls)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
@@ -129,32 +126,30 @@ TALER_MERCHANTDB_lookup_deposits (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
/* no preflight check here, run in its own transaction by the caller! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finding deposits for h_contract_terms '%s'\n",
GNUNET_h2s (&h_contract_terms->hash));
check_connection (pg);
- PREPARE (pg,
- "lookup_deposits",
- "SELECT"
- " dcom.exchange_url"
- ",dep.coin_pub"
- ",dep.amount_with_fee"
- ",dep.deposit_fee"
- ",dep.refund_fee"
- " FROM merchant_deposits dep"
- " JOIN merchant_deposit_confirmations dcom"
- " USING (deposit_confirmation_serial)"
- " WHERE dcom.order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " dcom.exchange_url"
+ ",dep.coin_pub"
+ ",dep.amount_with_fee"
+ ",dep.deposit_fee"
+ ",dep.refund_fee"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations dcom"
+ " USING (deposit_confirmation_serial)"
+ " WHERE dcom.order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$1)");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_deposits",
+ "",
params,
&lookup_deposits_cb,
&ldc);
diff --git a/src/backenddb/lookup_deposits_by_contract_and_coin.c b/src/backenddb/lookup_deposits_by_contract_and_coin.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_deposits_by_contract_and_coin.h"
#include "helper.h"
@@ -245,7 +243,6 @@ TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin (
void *cb_cls)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_auto_from_type (coin_pub),
GNUNET_PQ_query_param_end
@@ -257,70 +254,63 @@ TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
/* no preflight check here, run in transaction by caller! */
TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n",
GNUNET_h2s (&h_contract_terms->hash),
instance_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_refunds_by_coin_and_contract",
- "SELECT"
- " refund_amount"
- " FROM merchant_refunds"
- /* Join to filter by refunds that actually
- did work, not only those we approved */
- " JOIN merchant_refund_proofs"
- " USING (refund_serial)"
- " WHERE coin_pub=$3"
- " AND order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " refund_amount"
+ " FROM merchant_refunds"
+ /* Join to filter by refunds that actually
+ did work, not only those we approved */
+ " JOIN merchant_refund_proofs"
+ " USING (refund_serial)"
+ " WHERE coin_pub=$2"
+ " AND order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$1)");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_refunds_by_coin_and_contract",
+ "",
params,
&lookup_refunds_cb,
&ldcc);
if (0 > qs)
return qs;
- PREPARE (pg,
- "lookup_deposits_by_contract_and_coin",
- "SELECT"
- " mcon.exchange_url"
- ",dep.amount_with_fee"
- ",dep.deposit_fee"
- ",dep.refund_fee"
- ",mcon.wire_fee"
- ",acc.h_wire"
- ",mcon.deposit_timestamp"
- ",mct.refund_deadline"
- ",mcon.exchange_sig"
- ",msig.exchange_pub"
- " FROM merchant_contract_terms mct"
- " JOIN merchant_deposit_confirmations mcon"
- " USING (order_serial)"
- " JOIN merchant_deposits dep"
- " USING (deposit_confirmation_serial)"
- " JOIN merchant_exchange_signing_keys msig"
- " ON (mcon.signkey_serial=msig.signkey_serial)"
- " JOIN merchant_accounts acc"
- " USING (account_serial)"
- " WHERE h_contract_terms=$2"
- " AND dep.coin_pub=$3"
- " AND mct.merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mcon.exchange_url"
+ ",dep.amount_with_fee"
+ ",dep.deposit_fee"
+ ",dep.refund_fee"
+ ",mcon.wire_fee"
+ ",acc.h_wire"
+ ",mcon.deposit_timestamp"
+ ",mct.refund_deadline"
+ ",mcon.exchange_sig"
+ ",msig.exchange_pub"
+ " FROM merchant_contract_terms mct"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (order_serial)"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant.merchant_exchange_signing_keys msig"
+ " ON (mcon.signkey_serial=msig.signkey_serial)"
+ " JOIN merchant_accounts acc"
+ " USING (account_serial)"
+ " WHERE h_contract_terms=$1"
+ " AND dep.coin_pub=$2");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_deposits_by_contract_and_coin",
+ "",
params,
&lookup_deposits_by_contract_and_coin_cb,
&ldcc);
diff --git a/src/backenddb/lookup_deposits_by_order.c b/src/backenddb/lookup_deposits_by_order.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_deposits_by_order.h"
#include "helper.h"
@@ -119,10 +117,11 @@ lookup_deposits_by_order_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_deposits_by_order (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t order_serial,
- TALER_MERCHANTDB_DepositedCoinsCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_deposits_by_order (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_DepositedCoinsCallback cb,
+ void *cb_cls)
{
struct LookupDepositsByOrderContext ldoc = {
.pg = pg,
@@ -135,30 +134,28 @@ TALER_MERCHANTDB_lookup_deposits_by_order (struct TALER_MERCHANTDB_PostgresConte
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_deposits_by_order",
- "SELECT"
- " dep.deposit_serial"
- ",mcon.exchange_url"
- ",acc.h_wire"
- ",mcon.deposit_timestamp"
- ",dep.amount_with_fee"
- ",dep.deposit_fee"
- ",dep.coin_pub"
- " FROM merchant_deposits dep"
- " JOIN merchant_deposit_confirmations mcon"
- " USING(deposit_confirmation_serial)"
- " JOIN merchant_accounts acc"
- " USING (account_serial)"
- " WHERE mcon.order_serial=$1");
-
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " dep.deposit_serial"
+ ",mcon.exchange_url"
+ ",acc.h_wire"
+ ",mcon.deposit_timestamp"
+ ",dep.amount_with_fee"
+ ",dep.deposit_fee"
+ ",dep.coin_pub"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING(deposit_confirmation_serial)"
+ " JOIN merchant_accounts acc"
+ " USING (account_serial)"
+ " WHERE mcon.order_serial=$1");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_deposits_by_order",
+ "",
params,
&lookup_deposits_by_order_cb,
&ldoc);
-
if (qs < 0)
return qs;
return ldoc.qs;
diff --git a/src/backenddb/lookup_donau_keys.c b/src/backenddb/lookup_donau_keys.c
@@ -20,18 +20,18 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "donau/donau_service.h"
#include "merchant-database/lookup_donau_keys.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_donau_keys (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *donau_url,
- struct GNUNET_TIME_Absolute *first_retry,
- struct DONAU_Keys **keys)
+TALER_MERCHANTDB_lookup_donau_keys (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *donau_url,
+ struct GNUNET_TIME_Absolute *first_retry,
+ struct DONAU_Keys **keys)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (donau_url),
diff --git a/src/backenddb/lookup_expected_transfer.c b/src/backenddb/lookup_expected_transfer.c
@@ -19,28 +19,26 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_expected_transfer.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_expected_transfer (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t expected_incoming_serial,
- struct GNUNET_TIME_Timestamp *expected_time,
- struct TALER_Amount *expected_credit_amount,
- struct TALER_WireTransferIdentifierRawP *wtid,
- struct TALER_FullPayto *payto_uri,
- char **exchange_url,
- struct GNUNET_TIME_Timestamp *execution_time,
- bool *confirmed,
- struct TALER_MasterPublicKeyP *master_pub)
+TALER_MERCHANTDB_lookup_expected_transfer (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t expected_incoming_serial,
+ struct GNUNET_TIME_Timestamp *expected_time,
+ struct TALER_Amount *expected_credit_amount,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_FullPayto *payto_uri,
+ char **exchange_url,
+ struct GNUNET_TIME_Timestamp *execution_time,
+ bool *confirmed,
+ struct TALER_MasterPublicKeyP *master_pub)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&expected_incoming_serial),
GNUNET_PQ_query_param_end
};
@@ -68,35 +66,34 @@ TALER_MERCHANTDB_lookup_expected_transfer (struct TALER_MERCHANTDB_PostgresConte
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
*execution_time = GNUNET_TIME_UNIT_ZERO_TS;
memset (expected_credit_amount,
0,
sizeof (*expected_credit_amount));
check_connection (pg);
- PREPARE (pg,
- "lookup_expected_transfer",
- "SELECT"
- " met.expected_time"
- " ,met.confirmed"
- " ,met.expected_credit_amount"
- " ,met.exchange_url"
- " ,met.wtid"
- " ,ma.payto_uri"
- " ,mts.execution_time"
- " ,esk.master_pub"
- " FROM merchant_expected_transfers met"
- " JOIN merchant_exchange_signing_keys esk"
- " USING (signkey_serial)"
- " JOIN merchant_accounts ma"
- " USING (account_serial)"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- " LEFT JOIN merchant_transfer_signatures mts"
- " USING (expected_credit_serial)"
- " WHERE inst.merchant_id=$1"
- " AND met.expected_credit_serial=$2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " met.expected_time"
+ " ,met.confirmed"
+ " ,met.expected_credit_amount"
+ " ,met.exchange_url"
+ " ,met.wtid"
+ " ,ma.payto_uri"
+ " ,mts.execution_time"
+ " ,esk.master_pub"
+ " FROM merchant_expected_transfers met"
+ " JOIN merchant.merchant_exchange_signing_keys esk"
+ " USING (signkey_serial)"
+ " JOIN merchant_accounts ma"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_transfer_signatures mts"
+ " USING (expected_credit_serial)"
+ " WHERE met.expected_credit_serial=$1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_expected_transfer",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_expected_transfers.c b/src/backenddb/lookup_expected_transfers.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_expected_transfers.h"
#include "helper.h"
@@ -150,17 +148,18 @@ lookup_expected_transfers_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_expected_transfers (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- struct TALER_FullPayto payto_uri,
- struct GNUNET_TIME_Timestamp before,
- struct GNUNET_TIME_Timestamp after,
- int64_t limit,
- uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll confirmed,
- enum TALER_EXCHANGE_YesNoAll verified,
- TALER_MERCHANTDB_IncomingCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_expected_transfers (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ struct TALER_FullPayto payto_uri,
+ struct GNUNET_TIME_Timestamp before,
+ struct GNUNET_TIME_Timestamp after,
+ int64_t limit,
+ uint64_t offset,
+ enum TALER_EXCHANGE_YesNoAll confirmed,
+ enum TALER_EXCHANGE_YesNoAll verified,
+ TALER_MERCHANTDB_IncomingCallback cb,
+ void *cb_cls)
{
uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
bool by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
@@ -171,7 +170,6 @@ TALER_MERCHANTDB_lookup_expected_transfers (struct TALER_MERCHANTDB_PostgresCont
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_timestamp (&before),
GNUNET_PQ_query_param_timestamp (&after),
GNUNET_PQ_query_param_uint64 (&offset),
@@ -179,7 +177,7 @@ TALER_MERCHANTDB_lookup_expected_transfers (struct TALER_MERCHANTDB_PostgresCont
NULL == payto_uri.full_payto
? GNUNET_PQ_query_param_null () /* NULL: do not filter by payto URI */
: GNUNET_PQ_query_param_string (payto_uri.full_payto),
- GNUNET_PQ_query_param_bool (! by_time), /* $7: filter by time? */
+ GNUNET_PQ_query_param_bool (! by_time), /* $6: filter by time? */
GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == confirmed), /* filter by confirmed? */
GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == confirmed),
GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == verified), /* filter by verified? */
@@ -189,86 +187,83 @@ TALER_MERCHANTDB_lookup_expected_transfers (struct TALER_MERCHANTDB_PostgresCont
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_expected_transfers_asc",
- "SELECT"
- " met.expected_credit_amount"
- ",met.wtid"
- ",mac.payto_uri"
- ",met.exchange_url"
- ",met.expected_credit_serial"
- ",mts.execution_time"
- ",met.confirmed"
- ",met.last_http_status"
- ",met.last_ec"
- ",met.last_detail"
- " FROM merchant_expected_transfers met"
- " JOIN merchant_accounts mac"
- " USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures mts"
- " USING (expected_credit_serial)"
- " WHERE ( $7 OR "
- " (mts.execution_time IS NOT NULL AND"
- " mts.execution_time < $2 AND"
- " mts.execution_time >= $3) )"
- " AND ( (CAST($6 AS TEXT) IS NULL) OR "
- " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
- " =REGEXP_REPLACE($6,'\\?.*','')) )"
- " AND ( $8 OR "
- " (met.confirmed = $9) )"
- " AND ( $10 OR "
- " ($11 = (200=met.last_http_status) AND"
- " (0=met.last_ec) ) )"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND (met.expected_credit_serial > $4)"
- " ORDER BY met.expected_credit_serial ASC"
- " LIMIT $5");
- PREPARE (pg,
- "lookup_expected_transfers_desc",
- "SELECT"
- " met.expected_credit_amount"
- ",met.wtid"
- ",mac.payto_uri"
- ",met.exchange_url"
- ",met.expected_credit_serial"
- ",mts.execution_time"
- ",met.confirmed"
- ",met.last_http_status"
- ",met.last_ec"
- ",met.last_detail"
- " FROM merchant_expected_transfers met"
- " JOIN merchant_accounts mac"
- " USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures mts"
- " USING (expected_credit_serial)"
- " WHERE ( $7 OR "
- " (mts.execution_time IS NOT NULL AND"
- " mts.execution_time < $2 AND"
- " mts.execution_time >= $3) )"
- " AND ( (CAST($6 AS TEXT) IS NULL) OR "
- " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
- " =REGEXP_REPLACE($6,'\\?.*','')) )"
- " AND ( $8 OR "
- " (met.confirmed = $9) )"
- " AND ( $10 OR "
- " ($11 = (200=met.last_http_status) AND"
- " (0=met.last_ec) ) )"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND (met.expected_credit_serial < $4)"
- " ORDER BY met.expected_credit_serial DESC"
- " LIMIT $5");
+ if (limit > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " met.expected_credit_amount"
+ ",met.wtid"
+ ",mac.payto_uri"
+ ",met.exchange_url"
+ ",met.expected_credit_serial"
+ ",mts.execution_time"
+ ",met.confirmed"
+ ",met.last_http_status"
+ ",met.last_ec"
+ ",met.last_detail"
+ " FROM merchant_expected_transfers met"
+ " JOIN merchant_accounts mac"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_transfer_signatures mts"
+ " USING (expected_credit_serial)"
+ " WHERE ( $6 OR "
+ " (mts.execution_time IS NOT NULL AND"
+ " mts.execution_time < $1 AND"
+ " mts.execution_time >= $2) )"
+ " AND ( (CAST($5 AS TEXT) IS NULL) OR "
+ " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($5,'\\?.*','')) )"
+ " AND ( $7 OR "
+ " (met.confirmed = $8) )"
+ " AND ( $9 OR "
+ " ($10 = (200=met.last_http_status) AND"
+ " (0=met.last_ec) ) )"
+ " AND (met.expected_credit_serial > $3)"
+ " ORDER BY met.expected_credit_serial ASC"
+ " LIMIT $4");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " met.expected_credit_amount"
+ ",met.wtid"
+ ",mac.payto_uri"
+ ",met.exchange_url"
+ ",met.expected_credit_serial"
+ ",mts.execution_time"
+ ",met.confirmed"
+ ",met.last_http_status"
+ ",met.last_ec"
+ ",met.last_detail"
+ " FROM merchant_expected_transfers met"
+ " JOIN merchant_accounts mac"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_transfer_signatures mts"
+ " USING (expected_credit_serial)"
+ " WHERE ( $6 OR "
+ " (mts.execution_time IS NOT NULL AND"
+ " mts.execution_time < $1 AND"
+ " mts.execution_time >= $2) )"
+ " AND ( (CAST($5 AS TEXT) IS NULL) OR "
+ " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($5,'\\?.*','')) )"
+ " AND ( $7 OR "
+ " (met.confirmed = $8) )"
+ " AND ( $9 OR "
+ " ($10 = (200=met.last_http_status) AND"
+ " (0=met.last_ec) ) )"
+ " AND (met.expected_credit_serial < $3)"
+ " ORDER BY met.expected_credit_serial DESC"
+ " LIMIT $4");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- (limit > 0)
- ? "lookup_expected_transfers_asc"
- : "lookup_expected_transfers_desc",
+ "",
params,
&lookup_expected_transfers_cb,
<c);
diff --git a/src/backenddb/lookup_instance_auth.c b/src/backenddb/lookup_instance_auth.c
@@ -19,16 +19,16 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_instance_auth.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_instance_auth (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
+TALER_MERCHANTDB_lookup_instance_auth (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
@@ -48,7 +48,7 @@ TALER_MERCHANTDB_lookup_instance_auth (struct TALER_MERCHANTDB_PostgresContext *
"SELECT"
" auth_hash"
",auth_salt"
- " FROM merchant_instances"
+ " FROM merchant.merchant_instances"
" WHERE merchant_id=$1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_instance_auth",
diff --git a/src/backenddb/lookup_instances.c b/src/backenddb/lookup_instances.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022--2025 Taler Systems SA
+ Copyright (C) 2022--2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,7 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_instances.h"
#include "helper.h"
@@ -57,7 +56,7 @@ struct LookupInstancesContext
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about instances.
*
- * @param cls of type `struct FindInstancesContext *`
+ * @param cls of type `struct LookupInstancesContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
@@ -186,6 +185,7 @@ TALER_MERCHANTDB_lookup_instances (
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_bool (active_only),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
@@ -194,63 +194,33 @@ TALER_MERCHANTDB_lookup_instances (
PREPARE (pg,
"lookup_instances",
"SELECT"
- " mi.merchant_serial"
- ",mi.merchant_pub"
- ",mi.auth_hash"
- ",mi.auth_salt"
- ",mi.merchant_id"
- ",mi.merchant_name"
- ",mi.address::TEXT"
- ",mi.jurisdiction::TEXT"
- ",mi.use_stefan"
- ",mi.default_wire_transfer_delay"
- ",mi.default_pay_delay"
- ",mi.default_refund_delay"
- ",mi.website"
- ",mi.email"
- ",mi.phone_number"
- ",mi.phone_validated"
- ",mi.email_validated"
- ",mi.logo"
- ",mi.default_wire_transfer_rounding_interval::TEXT"
- ",mk.merchant_priv"
- " FROM merchant_instances mi"
- " LEFT JOIN merchant_keys mk"
- " USING (merchant_serial)");
- PREPARE (pg,
- "lookup_active_instances",
- "SELECT "
- " mi.merchant_serial"
- ",mi.merchant_pub"
- ",mi.auth_hash"
- ",mi.auth_salt"
- ",mi.merchant_id"
- ",mi.merchant_name"
- ",mi.address::TEXT"
- ",mi.jurisdiction::TEXT"
- ",mi.use_stefan"
- ",mi.default_wire_transfer_delay"
- ",mi.default_pay_delay"
- ",mi.default_refund_delay"
- ",mi.website"
- ",mi.email"
- ",mi.phone_number"
- ",mi.phone_validated"
- ",mi.email_validated"
- ",mi.logo"
- ",mi.default_wire_transfer_rounding_interval::TEXT"
- ",mk.merchant_priv"
- " FROM merchant_instances mi"
- " JOIN merchant_keys mk"
- " USING (merchant_serial)");
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- active_only
- ? "lookup_active_instances"
- : "lookup_instances",
- params,
- &lookup_instances_cb,
- &lic);
+ " merchant_serial"
+ " ,merchant_pub"
+ " ,auth_hash"
+ " ,auth_salt"
+ " ,merchant_priv"
+ " ,merchant_id"
+ " ,merchant_name"
+ " ,address::TEXT"
+ " ,jurisdiction::TEXT"
+ " ,use_stefan"
+ " ,phone_validated"
+ " ,email_validated"
+ " ,default_wire_transfer_delay"
+ " ,default_pay_delay"
+ " ,default_refund_delay"
+ " ,website"
+ " ,email"
+ " ,phone_number"
+ " ,logo"
+ " ,default_wire_transfer_rounding_interval::TEXT"
+ " FROM merchant.merchant_instances"
+ " WHERE (NOT $1 OR merchant_priv IS NOT NULL)");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_instances",
+ params,
+ &lookup_instances_cb,
+ &lic);
if (0 > lic.qs)
return lic.qs;
return qs;
@@ -258,11 +228,12 @@ TALER_MERCHANTDB_lookup_instances (
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- bool active_only,
- TALER_MERCHANTDB_InstanceCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ bool active_only,
+ TALER_MERCHANTDB_InstanceCallback cb,
+ void *cb_cls)
{
struct LookupInstancesContext lic = {
.cb = cb,
@@ -270,6 +241,7 @@ TALER_MERCHANTDB_lookup_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_bool (active_only),
GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_end
};
@@ -279,65 +251,34 @@ TALER_MERCHANTDB_lookup_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
PREPARE (pg,
"lookup_instance",
"SELECT"
- " mi.merchant_serial"
- ",mi.merchant_pub"
- ",mi.auth_hash"
- ",mi.auth_salt"
- ",mi.merchant_id"
- ",mi.merchant_name"
- ",mi.address::TEXT"
- ",mi.jurisdiction::TEXT"
- ",mi.use_stefan"
- ",mi.default_wire_transfer_delay"
- ",mi.default_pay_delay"
- ",mi.default_refund_delay"
- ",mi.website"
- ",mi.email"
- ",mi.phone_number"
- ",mi.phone_validated"
- ",mi.email_validated"
- ",mi.logo"
- ",mi.default_wire_transfer_rounding_interval::TEXT"
- ",mk.merchant_priv"
- " FROM merchant_instances mi"
- " LEFT JOIN merchant_keys mk"
- " USING (merchant_serial)"
- " WHERE merchant_id=$1");
- PREPARE (pg,
- "lookup_active_instance",
- "SELECT"
- " mi.merchant_serial"
- ",mi.merchant_pub"
- ",mi.auth_hash"
- ",mi.auth_salt"
- ",mi.merchant_id"
- ",mi.merchant_name"
- ",mi.address::TEXT"
- ",mi.jurisdiction::TEXT"
- ",mi.use_stefan"
- ",mi.default_wire_transfer_delay"
- ",mi.default_pay_delay"
- ",mi.default_refund_delay"
- ",mi.website"
- ",mi.email"
- ",mi.phone_number"
- ",mi.phone_validated"
- ",mi.email_validated"
- ",mi.logo"
- ",mi.default_wire_transfer_rounding_interval::TEXT"
- ",mk.merchant_priv"
- " FROM merchant_instances mi"
- " JOIN merchant_keys mk"
- " USING (merchant_serial)"
- " WHERE merchant_id=$1");
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- active_only
- ? "lookup_active_instance"
- : "lookup_instance",
- params,
- &lookup_instances_cb,
- &lic);
+ " merchant_serial"
+ " ,merchant_pub"
+ " ,auth_hash"
+ " ,auth_salt"
+ " ,merchant_priv"
+ " ,merchant_id"
+ " ,merchant_name"
+ " ,address::TEXT"
+ " ,jurisdiction::TEXT"
+ " ,use_stefan"
+ " ,phone_validated"
+ " ,email_validated"
+ " ,default_wire_transfer_delay"
+ " ,default_pay_delay"
+ " ,default_refund_delay"
+ " ,website"
+ " ,email"
+ " ,phone_number"
+ " ,logo"
+ " ,default_wire_transfer_rounding_interval::TEXT"
+ " FROM merchant.merchant_instances"
+ " WHERE (merchant_id = $2)"
+ " AND (NOT $1 OR merchant_priv IS NOT NULL)");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_instance",
+ params,
+ &lookup_instances_cb,
+ &lic);
if (0 > lic.qs)
return lic.qs;
return qs;
diff --git a/src/backenddb/lookup_inventory_products.c b/src/backenddb/lookup_inventory_products.c
@@ -19,8 +19,6 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_inventory_products.h"
#include "helper.h"
@@ -146,74 +144,72 @@ TALER_MERCHANTDB_lookup_inventory_products (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_inventory_products",
- "SELECT"
- " description"
- ",description_i18n::TEXT"
- ",product_name"
- ",unit"
- ",price_array"
- ",CASE WHEN minv.total_stock = 9223372036854775807"
- " THEN minv.total_stock"
- " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8"
- " END AS remaining_stock"
- ",CASE WHEN minv.total_stock = 9223372036854775807"
- " THEN 2147483647"
- " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4"
- " END AS remaining_stock_frac"
- ",taxes::TEXT"
- ",image_hash"
- ",allow_fractional_quantity"
- ",fractional_precision_level"
- ",product_id"
- ",t.category_array AS categories"
- " FROM merchant_inventory minv"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- ",LATERAL ("
- " SELECT ARRAY ("
- " SELECT mpc.category_serial"
- " FROM merchant_product_categories mpc"
- " WHERE mpc.product_serial = minv.product_serial"
- " ) AS category_array"
- " ) t"
- ",LATERAL ("
- " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " AS total_locked"
- " FROM merchant_inventory_locks mil"
- " WHERE mil.product_serial = minv.product_serial"
- " ) il"
- ",LATERAL ("
- " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " AS total_locked"
- " FROM merchant_order_locks mol"
- " WHERE mol.product_serial = minv.product_serial"
- " ) ol"
- ",LATERAL ("
- " SELECT"
- " (minv.total_stock::NUMERIC * 1000000"
- " + minv.total_stock_frac::NUMERIC)"
- " - (minv.total_sold::NUMERIC * 1000000"
- " + minv.total_sold_frac::NUMERIC)"
- " - (minv.total_lost::NUMERIC * 1000000"
- " + minv.total_lost_frac::NUMERIC)"
- " - il.total_locked"
- " - ol.total_locked"
- " AS remaining_total"
- " ) rt"
- " WHERE inst.merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " description"
+ ",description_i18n::TEXT"
+ ",product_name"
+ ",unit"
+ ",price_array"
+ ",CASE WHEN minv.total_stock = 9223372036854775807"
+ " THEN minv.total_stock"
+ " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8"
+ " END AS remaining_stock"
+ ",CASE WHEN minv.total_stock = 9223372036854775807"
+ " THEN 2147483647"
+ " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4"
+ " END AS remaining_stock_frac"
+ ",taxes::TEXT"
+ ",image_hash"
+ ",allow_fractional_quantity"
+ ",fractional_precision_level"
+ ",product_id"
+ ",t.category_array AS categories"
+ " FROM merchant_inventory minv"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = minv.product_serial"
+ " ) AS category_array"
+ " ) t"
+ ",LATERAL ("
+ " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " AS total_locked"
+ " FROM merchant_inventory_locks mil"
+ " WHERE mil.product_serial = minv.product_serial"
+ " ) il"
+ ",LATERAL ("
+ " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " AS total_locked"
+ " FROM merchant_order_locks mol"
+ " WHERE mol.product_serial = minv.product_serial"
+ " ) ol"
+ ",LATERAL ("
+ " SELECT"
+ " (minv.total_stock::NUMERIC * 1000000"
+ " + minv.total_stock_frac::NUMERIC)"
+ " - (minv.total_sold::NUMERIC * 1000000"
+ " + minv.total_sold_frac::NUMERIC)"
+ " - (minv.total_lost::NUMERIC * 1000000"
+ " + minv.total_lost_frac::NUMERIC)"
+ " - il.total_locked"
+ " - ol.total_locked"
+ " AS remaining_total"
+ " ) rt");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_inventory_products",
+ "",
params,
&lookup_inventory_products_cb,
&plc);
diff --git a/src/backenddb/lookup_inventory_products_filtered.c b/src/backenddb/lookup_inventory_products_filtered.c
@@ -19,8 +19,6 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_inventory_products_filtered.h"
#include "helper.h"
@@ -143,7 +141,6 @@ TALER_MERCHANTDB_lookup_inventory_products_filtered (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
(0 == num_product_ids)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_array_ptrs_string (
@@ -159,81 +156,80 @@ TALER_MERCHANTDB_lookup_inventory_products_filtered (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_inventory_products_filtered",
- "SELECT"
- " description"
- ",description_i18n::TEXT"
- ",product_name"
- ",unit"
- ",price_array"
- ",CASE WHEN minv.total_stock = 9223372036854775807"
- " THEN minv.total_stock"
- " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8"
- " END AS remaining_stock"
- ",CASE WHEN minv.total_stock = 9223372036854775807"
- " THEN 2147483647"
- " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4"
- " END AS remaining_stock_frac"
- ",taxes::TEXT"
- ",image_hash"
- ",allow_fractional_quantity"
- ",fractional_precision_level"
- ",product_id"
- ",t.category_array AS categories"
- " FROM merchant_inventory minv"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- ",LATERAL ("
- " SELECT ARRAY ("
- " SELECT mpc.category_serial"
- " FROM merchant_product_categories mpc"
- " WHERE mpc.product_serial = minv.product_serial"
- " ) AS category_array"
- " ) t"
- ",LATERAL ("
- " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " AS total_locked"
- " FROM merchant_inventory_locks mil"
- " WHERE mil.product_serial = minv.product_serial"
- " ) il"
- ",LATERAL ("
- " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
- " + total_locked_frac::NUMERIC), 0)"
- " AS total_locked"
- " FROM merchant_order_locks mol"
- " WHERE mol.product_serial = minv.product_serial"
- " ) ol"
- ",LATERAL ("
- " SELECT"
- " (minv.total_stock::NUMERIC * 1000000"
- " + minv.total_stock_frac::NUMERIC)"
- " - (minv.total_sold::NUMERIC * 1000000"
- " + minv.total_sold_frac::NUMERIC)"
- " - (minv.total_lost::NUMERIC * 1000000"
- " + minv.total_lost_frac::NUMERIC)"
- " - il.total_locked"
- " - ol.total_locked"
- " AS remaining_total"
- " ) rt"
- " WHERE inst.merchant_id=$1"
- " AND ("
- " (COALESCE (array_length ($2::TEXT[], 1), 0) > 0"
- " AND minv.product_id = ANY ($2::TEXT[]))"
- " OR"
- " (COALESCE (array_length ($3::BIGINT[], 1), 0) > 0"
- " AND EXISTS ("
- " SELECT 1"
- " FROM merchant_product_categories mpc"
- " WHERE mpc.product_serial = minv.product_serial"
- " AND mpc.category_serial = ANY ($3::BIGINT[])"
- " ))"
- " )");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " description"
+ ",description_i18n::TEXT"
+ ",product_name"
+ ",unit"
+ ",price_array"
+ ",CASE WHEN minv.total_stock = 9223372036854775807"
+ " THEN minv.total_stock"
+ " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8"
+ " END AS remaining_stock"
+ ",CASE WHEN minv.total_stock = 9223372036854775807"
+ " THEN 2147483647"
+ " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4"
+ " END AS remaining_stock_frac"
+ ",taxes::TEXT"
+ ",image_hash"
+ ",allow_fractional_quantity"
+ ",fractional_precision_level"
+ ",product_id"
+ ",t.category_array AS categories"
+ " FROM merchant_inventory minv"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = minv.product_serial"
+ " ) AS category_array"
+ " ) t"
+ ",LATERAL ("
+ " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " AS total_locked"
+ " FROM merchant_inventory_locks mil"
+ " WHERE mil.product_serial = minv.product_serial"
+ " ) il"
+ ",LATERAL ("
+ " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000"
+ " + total_locked_frac::NUMERIC), 0)"
+ " AS total_locked"
+ " FROM merchant_order_locks mol"
+ " WHERE mol.product_serial = minv.product_serial"
+ " ) ol"
+ ",LATERAL ("
+ " SELECT"
+ " (minv.total_stock::NUMERIC * 1000000"
+ " + minv.total_stock_frac::NUMERIC)"
+ " - (minv.total_sold::NUMERIC * 1000000"
+ " + minv.total_sold_frac::NUMERIC)"
+ " - (minv.total_lost::NUMERIC * 1000000"
+ " + minv.total_lost_frac::NUMERIC)"
+ " - il.total_locked"
+ " - ol.total_locked"
+ " AS remaining_total"
+ " ) rt"
+ " WHERE ("
+ " (COALESCE (array_length ($1::TEXT[], 1), 0) > 0"
+ " AND minv.product_id = ANY ($1::TEXT[]))"
+ " OR"
+ " (COALESCE (array_length ($2::BIGINT[], 1), 0) > 0"
+ " AND EXISTS ("
+ " SELECT 1"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = minv.product_serial"
+ " AND mpc.category_serial = ANY ($2::BIGINT[])"
+ " ))"
+ " )");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_inventory_products_filtered",
+ "",
params,
&lookup_inventory_products_cb,
&plc);
diff --git a/src/backenddb/lookup_login_tokens.c b/src/backenddb/lookup_login_tokens.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_login_tokens.h"
#include "helper.h"
@@ -107,12 +105,13 @@ lookup_login_tokens_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_login_tokens (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t offset,
- int64_t limit,
- TALER_MERCHANTDB_LoginTokensCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_login_tokens (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t offset,
+ int64_t limit,
+ TALER_MERCHANTDB_LoginTokensCallback cb,
+ void *cb_cls)
{
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
@@ -123,7 +122,6 @@ TALER_MERCHANTDB_lookup_login_tokens (struct TALER_MERCHANTDB_PostgresContext *p
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_timestamp (&now),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
@@ -131,46 +129,45 @@ TALER_MERCHANTDB_lookup_login_tokens (struct TALER_MERCHANTDB_PostgresContext *p
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_login_tokens_asc",
- "SELECT"
- " token"
- ",serial"
- ",creation_time"
- ",expiration_time"
- ",validity_scope"
- ",description"
- " FROM merchant_login_tokens"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND expiration_time > $2"
- " AND serial > $3"
- " ORDER BY serial ASC"
- " LIMIT $4");
- PREPARE (pg,
- "lookup_login_tokens_desc",
- "SELECT"
- " token"
- ",serial"
- ",creation_time"
- ",expiration_time"
- ",validity_scope"
- ",description"
- " FROM merchant_login_tokens"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND expiration_time > $2"
- " AND serial < $3"
- " ORDER BY serial DESC"
- " LIMIT $4");
+ if (limit > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " token"
+ ",serial"
+ ",creation_time"
+ ",expiration_time"
+ ",validity_scope"
+ ",description"
+ " FROM merchant_login_tokens"
+ " WHERE expiration_time > $1"
+ " AND serial > $2"
+ " ORDER BY serial ASC"
+ " LIMIT $3");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " token"
+ ",serial"
+ ",creation_time"
+ ",expiration_time"
+ ",validity_scope"
+ ",description"
+ " FROM merchant_login_tokens"
+ " WHERE expiration_time > $1"
+ " AND serial < $2"
+ " ORDER BY serial DESC"
+ " LIMIT $3");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- (limit > 0)
- ? "lookup_login_tokens_asc"
- : "lookup_login_tokens_desc",
+ "",
params,
&lookup_login_tokens_cb,
&plc);
diff --git a/src/backenddb/lookup_mfa_challenge.c b/src/backenddb/lookup_mfa_challenge.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "taler/taler_merchant_util.h"
#include "merchantdb_lib.h"
@@ -86,8 +84,8 @@ TALER_MERCHANTDB_lookup_mfa_challenge (
" ,tc.required_address"
" ,tc.tan_channel::TEXT"
" ,mi.merchant_id"
- " FROM tan_challenges tc"
- " JOIN merchant_instances mi"
+ " FROM merchant.tan_challenges tc"
+ " JOIN merchant.merchant_instances mi"
" USING (merchant_serial)"
" WHERE (challenge_id = $1)"
" AND (h_body = $2)"
@@ -95,6 +93,8 @@ TALER_MERCHANTDB_lookup_mfa_challenge (
/* Initialize to conservative values in case qs ends up <= 0 */
*tan_channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
*op = TALER_MERCHANT_MFA_CO_NONE;
+ *instance_name = NULL;
+ *required_address = NULL;
*retry_counter = 0;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_mfa_challenge",
diff --git a/src/backenddb/lookup_order.c b/src/backenddb/lookup_order.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_order.h"
#include "helper.h"
@@ -39,7 +37,6 @@ TALER_MERCHANTDB_lookup_order (
struct TALER_ClaimTokenP ct;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
@@ -53,25 +50,23 @@ TALER_MERCHANTDB_lookup_order (
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finding contract term, order_id: '%s', instance_id: '%s'.\n",
order_id,
instance_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_order",
- "SELECT"
- " contract_terms::TEXT"
- ",claim_token"
- ",h_post_data"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_orders.order_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " contract_terms::TEXT"
+ ",claim_token"
+ ",h_post_data"
+ " FROM merchant_orders"
+ " WHERE order_id=$1");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order",
+ "",
params,
rs);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
diff --git a/src/backenddb/lookup_order_by_fulfillment.c b/src/backenddb/lookup_order_by_fulfillment.c
@@ -19,23 +19,21 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_order_by_fulfillment.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_order_by_fulfillment (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *fulfillment_url,
- const char *session_id,
- bool allow_refunded_for_repurchase,
- char **order_id)
+TALER_MERCHANTDB_lookup_order_by_fulfillment (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *fulfillment_url,
+ const char *session_id,
+ bool allow_refunded_for_repurchase,
+ char **order_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (fulfillment_url),
GNUNET_PQ_query_param_string (session_id),
GNUNET_PQ_query_param_bool (allow_refunded_for_repurchase),
@@ -47,32 +45,30 @@ TALER_MERCHANTDB_lookup_order_by_fulfillment (struct TALER_MERCHANTDB_PostgresCo
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_order_by_fulfillment",
- "SELECT"
- " mct.order_id"
- " FROM merchant_contract_terms mct"
- " LEFT JOIN merchant_refunds mref"
- " USING (order_serial)"
- " WHERE fulfillment_url=$2"
- " AND session_id=$3"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND ((CAST($4 AS BOOL)) OR"
- " mref.refund_serial IS NULL)"
- /* Theoretically, multiple paid orders
- for the same fulfillment URL could
- exist for this session_id -- if a
- wallet was broken and did multiple
- payments without repurchase detection.
- So we need to limit to 1 when returning! */
- " ORDER BY order_id DESC"
- " LIMIT 1;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mct.order_id"
+ " FROM merchant_contract_terms mct"
+ " LEFT JOIN merchant_refunds mref"
+ " USING (order_serial)"
+ " WHERE fulfillment_url=$1"
+ " AND session_id=$2"
+ " AND ((CAST($3 AS BOOL)) OR"
+ " mref.refund_serial IS NULL)"
+ /* Theoretically, multiple paid orders
+ for the same fulfillment URL could
+ exist for this session_id -- if a
+ wallet was broken and did multiple
+ payments without repurchase detection.
+ So we need to limit to 1 when returning! */
+ " ORDER BY order_id DESC"
+ " LIMIT 1;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_by_fulfillment",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_order_charity.c b/src/backenddb/lookup_order_charity.c
@@ -23,7 +23,6 @@
* @author Bohdan Potuzhnyi
* @author Vlada Svirsh
*/
-
#include "platform.h"
#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
@@ -40,23 +39,18 @@ TALER_MERCHANTDB_lookup_order_charity (
const char *instance_id,
const char *donau_url,
uint64_t *charity_id,
- struct DONAU_CharityPrivateKeyP *charity_priv,
struct TALER_Amount *charity_max_per_year,
struct TALER_Amount *charity_receipts_to_date,
json_t **donau_keys_json,
uint64_t *donau_instance_serial)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (donau_url),
GNUNET_PQ_query_param_end
};
-
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("charity_id",
charity_id),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
- charity_priv),
TALER_PQ_result_spec_json ("keys_json",
donau_keys_json),
TALER_PQ_result_spec_amount_with_currency ("charity_max_per_year",
@@ -68,28 +62,23 @@ TALER_MERCHANTDB_lookup_order_charity (
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_donau_charity",
- "SELECT"
- " di.donau_instances_serial"
- " ,di.charity_id"
- " ,k.merchant_priv"
- " ,dk.keys_json::TEXT"
- " ,di.charity_max_per_year"
- " ,di.charity_receipts_to_date"
- " FROM merchant_donau_instances di"
- " JOIN merchant_donau_keys dk"
- " ON dk.donau_url = di.donau_url"
- " JOIN merchant_instances mi"
- " ON mi.merchant_serial = di.merchant_instance_serial"
- " JOIN merchant_keys k"
- " ON k.merchant_serial = mi.merchant_serial"
- " WHERE mi.merchant_id = $1"
- " AND di.donau_url = $2;");
-
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " di.donau_instances_serial"
+ " ,di.charity_id"
+ " ,dk.keys_json::TEXT"
+ " ,di.charity_max_per_year"
+ " ,di.charity_receipts_to_date"
+ " FROM merchant_donau_instances di"
+ " JOIN merchant.merchant_donau_keys dk"
+ " ON dk.donau_url = di.donau_url"
+ " WHERE di.donau_url = $1;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_donau_charity",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_order_status.c b/src/backenddb/lookup_order_status.c
@@ -25,47 +25,40 @@
#include "merchant-database/lookup_order_status.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_order_status (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- struct TALER_PrivateContractHashP *h_contract_terms,
- bool *paid)
+TALER_MERCHANTDB_lookup_order_status (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ bool *paid)
{
- uint8_t paid8;
- enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("paid",
- &paid8),
+ GNUNET_PQ_result_spec_bool ("paid",
+ paid),
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_order_status",
- "SELECT"
- " h_contract_terms"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND order_id=$2");
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_status",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- *paid = (0 != paid8);
- else
- *paid = false; /* just to be safe(r) */
- return qs;
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " h_contract_terms"
+ ",paid"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1");
+ *paid = false;
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
}
diff --git a/src/backenddb/lookup_order_status_by_serial.c b/src/backenddb/lookup_order_status_by_serial.c
@@ -19,22 +19,21 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_order_status_by_serial.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_order_status_by_serial (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t order_serial,
- char **order_id,
- struct TALER_PrivateContractHashP *h_contract_terms,
- bool *paid)
+TALER_MERCHANTDB_lookup_order_status_by_serial (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t order_serial,
+ char **order_id,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ bool *paid)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_end
};
@@ -48,22 +47,20 @@ TALER_MERCHANTDB_lookup_order_status_by_serial (struct TALER_MERCHANTDB_Postgres
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
*paid = false; /* just to be safe(r) */
- PREPARE (pg,
- "lookup_order_status_by_serial",
- "SELECT"
- " h_contract_terms"
- ",order_id"
- ",paid"
- " FROM merchant_contract_terms"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND order_serial=$2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " h_contract_terms"
+ ",order_id"
+ ",paid"
+ " FROM merchant_contract_terms"
+ " WHERE order_serial=$1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_status_by_serial",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_order_summary.c b/src/backenddb/lookup_order_summary.c
@@ -19,21 +19,20 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_order_summary.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_order_summary (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- struct GNUNET_TIME_Timestamp *timestamp,
- uint64_t *order_serial)
+TALER_MERCHANTDB_lookup_order_summary (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp *timestamp,
+ uint64_t *order_serial)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
@@ -45,30 +44,24 @@ TALER_MERCHANTDB_lookup_order_summary (struct TALER_MERCHANTDB_PostgresContext *
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_order_summary",
- "(SELECT"
- " creation_time"
- ",order_serial"
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_contract_terms.order_id=$2)"
- "UNION"
- "(SELECT"
- " creation_time"
- ",order_serial"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND merchant_orders.order_id=$2)");
+ TMH_PQ_prepare_anon (pg,
+ "(SELECT"
+ " creation_time"
+ ",order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE order_id=$1)"
+ "UNION"
+ "(SELECT"
+ " creation_time"
+ ",order_serial"
+ " FROM merchant_orders"
+ " WHERE order_id=$1)");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_order_summary",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_orders.c b/src/backenddb/lookup_orders.c
@@ -20,8 +20,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_orders.h"
#include "helper.h"
@@ -111,18 +109,17 @@ TALER_MERCHANTDB_lookup_orders (
uint64_t limit = (of->delta > 0) ? of->delta : -of->delta;
struct GNUNET_PQ_QueryParam params[] = {
/* $1 */
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&limit),
GNUNET_PQ_query_param_uint64 (&of->start_row),
GNUNET_PQ_query_param_timestamp (&of->date),
GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->paid)),
- /* $6 */
+ /* $5 */
GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->paid)),
GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->refunded)),
GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->refunded)),
GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->wired)),
GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->wired)),
- /* $11 */
+ /* $10 */
GNUNET_PQ_query_param_bool (NULL == of->session_id),
NULL == of->session_id
? GNUNET_PQ_query_param_null ()
@@ -137,151 +134,132 @@ TALER_MERCHANTDB_lookup_orders (
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
- char stmt[128];
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Looking up orders, using filter paid: %d, refunded: %d, wired: %d\n",
of->paid,
of->refunded,
of->wired);
- GNUNET_snprintf (stmt,
- sizeof (stmt),
- "lookup_orders_%s",
- (of->delta > 0) ? "inc" : "dec");
- PREPARE (pg,
- "lookup_orders_dec",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND ($5 OR "
- " NOT CAST($6 AS BOOL))" /* unclaimed orders are never paid */
- " AND ($7 OR "
- " NOT CAST($8 AS BOOL))"/* unclaimed orders are never refunded */
- " AND ($9 OR "
- " NOT CAST($10 AS BOOL))" /* unclaimed orders are never wired */
- " AND"
- " order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms)" /* only select unclaimed orders */
- " AND ($11 OR "
- " ($12 = session_id))"
- " AND ($13 OR "
- " ($14 = fulfillment_url))"
- " AND ( ($15::TEXT IS NULL) OR "
- " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial < $3"
- " AND"
- " creation_time < $4"
- " AND ($5 OR "
- " (CAST($6 AS BOOL) = paid))"
- " AND ($7 OR "
- " (CAST($8 AS BOOL) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))))"
- " AND ($9 OR"
- " (CAST($10 AS BOOL) = wired))"
- " AND ($11 OR "
- " ($12 = session_id))"
- " AND ($13 OR "
- " ($14 = fulfillment_url))"
- " AND ( ($15::TEXT IS NULL) OR "
- " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )"
- " ORDER BY order_serial DESC"
- " LIMIT $2)"
- " ORDER BY order_serial DESC"
- " LIMIT $2");
-
- PREPARE (pg,
- "lookup_orders_inc",
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_orders"
- " WHERE merchant_orders.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND ($5 OR "
- " NOT CAST($6 AS BOOL))" /* unclaimed orders are never paid */
- " AND ($7 OR "
- " NOT CAST($8 AS BOOL))"/* unclaimed orders are never refunded */
- " AND ($9 OR "
- " NOT CAST($10 AS BOOL))" /* unclaimed orders are never wired */
- " AND"
- " (order_serial NOT IN"
- " (SELECT order_serial"
- " FROM merchant_contract_terms))" /* only select unclaimed orders */
- " AND ($11 OR "
- " ($12 = session_id))"
- " AND ($13 OR "
- " ($14 = fulfillment_url))"
- " AND ( ($15::TEXT IS NULL) OR "
- " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- "UNION " /* union ensures elements are distinct! */
- "(SELECT"
- " order_id"
- ",order_serial"
- ",creation_time"
- " FROM merchant_contract_terms"
- " WHERE merchant_contract_terms.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND"
- " order_serial > $3"
- " AND"
- " creation_time > $4"
- " AND ($5 OR "
- " (CAST($6 AS BOOL) = paid))"
- " AND ($7 OR "
- " (CAST($8 AS BOOL) = (order_serial IN"
- " (SELECT order_serial "
- " FROM merchant_refunds))))"
- " AND ($9 OR"
- " (CAST($10 AS BOOL) = wired))"
- " AND ($11 OR "
- " ($12 = session_id))"
- " AND ($13 OR "
- " ($14 = fulfillment_url))"
- " AND ( ($15::TEXT IS NULL) OR "
- " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )"
- " ORDER BY order_serial ASC"
- " LIMIT $2)"
- " ORDER BY order_serial ASC"
- " LIMIT $2");
+ if (of->delta > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_orders"
+ " WHERE order_serial > $2"
+ " AND"
+ " creation_time > $3"
+ " AND ($4 OR "
+ " NOT CAST($5 AS BOOL))" /* unclaimed orders are never paid */
+ " AND ($6 OR "
+ " NOT CAST($7 AS BOOL))"/* unclaimed orders are never refunded */
+ " AND ($8 OR "
+ " NOT CAST($9 AS BOOL))" /* unclaimed orders are never wired */
+ " AND"
+ " (order_serial NOT IN"
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms))" /* only select unclaimed orders */
+ " AND ($10 OR "
+ " ($11 = session_id))"
+ " AND ($12 OR "
+ " ($13 = fulfillment_url))"
+ " AND ( ($14::TEXT IS NULL) OR "
+ " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
+ " ORDER BY order_serial ASC"
+ " LIMIT $1)"
+ "UNION " /* union ensures elements are distinct! */
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_contract_terms"
+ " WHERE order_serial > $2"
+ " AND"
+ " creation_time > $3"
+ " AND ($4 OR "
+ " (CAST($5 AS BOOL) = paid))"
+ " AND ($6 OR "
+ " (CAST($7 AS BOOL) = (order_serial IN"
+ " (SELECT order_serial "
+ " FROM merchant_refunds))))"
+ " AND ($8 OR"
+ " (CAST($9 AS BOOL) = wired))"
+ " AND ($10 OR "
+ " ($11 = session_id))"
+ " AND ($12 OR "
+ " ($13 = fulfillment_url))"
+ " AND ( ($14::TEXT IS NULL) OR "
+ " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
+ " ORDER BY order_serial ASC"
+ " LIMIT $1)"
+ " ORDER BY order_serial ASC"
+ " LIMIT $1");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_orders"
+ " WHERE order_serial < $2"
+ " AND"
+ " creation_time < $3"
+ " AND ($4 OR "
+ " NOT CAST($5 AS BOOL))" /* unclaimed orders are never paid */
+ " AND ($6 OR "
+ " NOT CAST($7 AS BOOL))"/* unclaimed orders are never refunded */
+ " AND ($8 OR "
+ " NOT CAST($9 AS BOOL))" /* unclaimed orders are never wired */
+ " AND"
+ " order_serial NOT IN"
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms)" /* only select unclaimed orders */
+ " AND ($10 OR "
+ " ($11 = session_id))"
+ " AND ($12 OR "
+ " ($13 = fulfillment_url))"
+ " AND ( ($14::TEXT IS NULL) OR "
+ " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
+ " ORDER BY order_serial DESC"
+ " LIMIT $1)"
+ "UNION " /* union ensures elements are distinct! */
+ "(SELECT"
+ " order_id"
+ ",order_serial"
+ ",creation_time"
+ " FROM merchant_contract_terms"
+ " WHERE order_serial < $2"
+ " AND"
+ " creation_time < $3"
+ " AND ($4 OR "
+ " (CAST($5 AS BOOL) = paid))"
+ " AND ($6 OR "
+ " (CAST($7 AS BOOL) = (order_serial IN"
+ " (SELECT order_serial "
+ " FROM merchant_refunds))))"
+ " AND ($8 OR"
+ " (CAST($9 AS BOOL) = wired))"
+ " AND ($10 OR "
+ " ($11 = session_id))"
+ " AND ($12 OR "
+ " ($13 = fulfillment_url))"
+ " AND ( ($14::TEXT IS NULL) OR "
+ " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
+ " ORDER BY order_serial DESC"
+ " LIMIT $1)"
+ " ORDER BY order_serial DESC"
+ " LIMIT $1");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- stmt,
+ "",
params,
&lookup_orders_cb,
&plc);
diff --git a/src/backenddb/lookup_otp_devices.c b/src/backenddb/lookup_otp_devices.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_otp_devices.h"
#include "helper.h"
@@ -106,23 +104,21 @@ TALER_MERCHANTDB_lookup_otp_devices (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_otp_devices",
- "SELECT"
- " otp_id"
- ",otp_description"
- " FROM merchant_otp_devices"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " otp_id"
+ ",otp_description"
+ " FROM merchant_otp_devices");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_otp_devices",
+ "",
params,
&lookup_otp_devices_cb,
&tlc);
diff --git a/src/backenddb/lookup_pending_deposits.c b/src/backenddb/lookup_pending_deposits.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_pending_deposits.h"
#include "helper.h"
@@ -82,25 +80,25 @@ lookup_deposits_cb (void *cls,
struct TALER_Amount deposit_fee;
struct TALER_CoinSpendPublicKeyP coin_pub;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial",
+ GNUNET_PQ_result_spec_uint64 ("out_deposit_serial",
&deposit_serial),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ GNUNET_PQ_result_spec_auto_from_type ("out_h_contract_terms",
&h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
+ GNUNET_PQ_result_spec_auto_from_type ("out_merchant_priv",
&merchant_priv),
- GNUNET_PQ_result_spec_string ("merchant_id",
+ GNUNET_PQ_result_spec_string ("out_merchant_id",
&instance_id),
- GNUNET_PQ_result_spec_absolute_time ("wire_transfer_deadline",
+ GNUNET_PQ_result_spec_absolute_time ("out_wire_transfer_deadline",
&wire_deadline),
- GNUNET_PQ_result_spec_absolute_time ("retry_time",
+ GNUNET_PQ_result_spec_absolute_time ("out_retry_time",
&retry_time),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ GNUNET_PQ_result_spec_auto_from_type ("out_h_wire",
&h_wire),
- TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
+ TALER_PQ_result_spec_amount_with_currency ("out_amount_with_fee",
&amount_with_fee),
- TALER_PQ_result_spec_amount_with_currency ("deposit_fee",
+ TALER_PQ_result_spec_amount_with_currency ("out_deposit_fee",
&deposit_fee),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ GNUNET_PQ_result_spec_auto_from_type ("out_coin_pub",
&coin_pub),
GNUNET_PQ_result_spec_end
};
@@ -159,35 +157,17 @@ TALER_MERCHANTDB_lookup_pending_deposits (
PREPARE (pg,
"lookup_pending_deposits",
"SELECT"
- " md.deposit_serial"
- ",mct.h_contract_terms"
- ",mk.merchant_priv"
- ",mi.merchant_id"
- ",mdc.wire_transfer_deadline"
- ",md.settlement_retry_time AS retry_time"
- ",ma.h_wire"
- ",md.amount_with_fee"
- ",md.deposit_fee"
- ",md.coin_pub"
- " FROM merchant_deposits md"
- " JOIN merchant_deposit_confirmations mdc"
- " USING (deposit_confirmation_serial)"
- " JOIN merchant_contract_terms mct"
- " ON (mct.order_serial=mdc.order_serial)"
- " JOIN merchant_accounts ma"
- " ON (mdc.account_serial=ma.account_serial)"
- " LEFT JOIN merchant_kyc kyc"
- " ON (mdc.account_serial=kyc.account_serial)"
- " JOIN merchant_instances mi"
- " ON (mct.merchant_serial=mi.merchant_serial)"
- " JOIN merchant_keys mk"
- " ON (mi.merchant_serial=mk.merchant_serial)"
- " WHERE (mdc.exchange_url=$1)"
- " AND md.settlement_retry_needed"
- " AND ($4 OR (md.settlement_retry_time < $2))"
- " AND ( (kyc.kyc_ok IS NULL) OR kyc.kyc_ok)"
- " ORDER BY md.settlement_retry_time ASC"
- " LIMIT $3");
+ " out_deposit_serial"
+ " ,out_h_contract_terms"
+ " ,out_merchant_priv"
+ " ,out_merchant_id"
+ " ,out_wire_transfer_deadline"
+ " ,out_retry_time"
+ " ,out_h_wire"
+ " ,out_amount_with_fee"
+ " ,out_deposit_fee"
+ " ,out_coin_pub"
+ " FROM merchant.lookup_pending_deposits($1, $2, $3, $4)");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_pending_deposits",
params,
diff --git a/src/backenddb/lookup_pending_deposits.sql b/src/backenddb/lookup_pending_deposits.sql
@@ -0,0 +1,108 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.lookup_pending_deposits(TEXT, INT8, INT8, BOOLEAN);
+CREATE FUNCTION merchant.lookup_pending_deposits(
+ IN p_exchange_url TEXT,
+ IN p_now INT8,
+ IN p_limit INT8,
+ IN p_allow_future BOOLEAN)
+RETURNS TABLE(
+ out_deposit_serial INT8,
+ out_h_contract_terms BYTEA,
+ out_merchant_priv BYTEA,
+ out_merchant_id TEXT,
+ out_wire_transfer_deadline INT8,
+ out_retry_time INT8,
+ out_h_wire BYTEA,
+ out_amount_with_fee merchant.taler_amount_currency,
+ out_deposit_fee merchant.taler_amount_currency,
+ out_coin_pub BYTEA)
+LANGUAGE plpgsql
+AS $FN$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ inner_rec RECORD;
+ remaining INT8 := p_limit;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ ,merchant_id
+ ,merchant_priv
+ FROM merchant.merchant_instances
+ LOOP
+ EXIT WHEN remaining <= 0;
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ FOR inner_rec IN
+ EXECUTE format(
+ 'SELECT'
+ ' md.deposit_serial AS deposit_serial'
+ ' ,mct.h_contract_terms AS h_contract_terms'
+ ' ,mdc.wire_transfer_deadline AS wire_transfer_deadline'
+ ' ,md.settlement_retry_time AS retry_time'
+ ' ,ma.h_wire AS h_wire'
+ ' ,md.amount_with_fee AS amount_with_fee'
+ ' ,md.deposit_fee AS deposit_fee'
+ ' ,md.coin_pub AS coin_pub'
+ ' FROM %I.merchant_deposits md'
+ ' JOIN %I.merchant_deposit_confirmations mdc'
+ ' USING (deposit_confirmation_serial)'
+ ' JOIN %I.merchant_contract_terms mct'
+ ' ON (mct.order_serial=mdc.order_serial)'
+ ' JOIN %I.merchant_accounts ma'
+ ' ON (mdc.account_serial=ma.account_serial)'
+ ' LEFT JOIN %I.merchant_kyc kyc'
+ ' ON (mdc.account_serial=kyc.account_serial)'
+ ' WHERE (mdc.exchange_url=$1)'
+ ' AND md.settlement_retry_needed'
+ ' AND ($4 OR (md.settlement_retry_time < $2))'
+ ' AND ( (kyc.kyc_ok IS NULL) OR kyc.kyc_ok)'
+ ' ORDER BY md.settlement_retry_time ASC'
+ ' LIMIT $3',
+ s, s, s, s, s)
+ USING p_exchange_url
+ ,p_now
+ ,remaining
+ ,p_allow_future
+ LOOP
+ out_deposit_serial := inner_rec.deposit_serial;
+ out_h_contract_terms := inner_rec.h_contract_terms;
+ out_merchant_priv := rec.merchant_priv;
+ out_merchant_id := rec.merchant_id;
+ out_wire_transfer_deadline := inner_rec.wire_transfer_deadline;
+ out_retry_time := inner_rec.retry_time;
+ out_h_wire := inner_rec.h_wire;
+ out_amount_with_fee := inner_rec.amount_with_fee;
+ out_deposit_fee := inner_rec.deposit_fee;
+ out_coin_pub := inner_rec.coin_pub;
+ RETURN NEXT;
+ remaining := remaining - 1;
+ EXIT WHEN remaining <= 0;
+ END LOOP;
+ EXCEPTION
+ WHEN undefined_table
+ THEN
+ NULL;
+ END;
+ END LOOP;
+END
+$FN$;
+COMMENT ON FUNCTION merchant.lookup_pending_deposits(TEXT, INT8, INT8, BOOLEAN)
+ IS 'Returns up to p_limit pending-settlement deposit rows for the given exchange'
+ ' across all instance schemas, with merchant_priv read from'
+ ' merchant.merchant_instances (NULL if absent).';
diff --git a/src/backenddb/lookup_pending_webhooks.c b/src/backenddb/lookup_pending_webhooks.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_pending_webhooks.h"
#include "helper.h"
@@ -127,35 +125,31 @@ TALER_MERCHANTDB_lookup_pending_webhooks (
.extract_failed = false,
};
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params_null[] = {
+ struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
-
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
"lookup_pending_webhooks",
"SELECT"
- " webhook_pending_serial"
- ",next_attempt"
- ",retries"
- ",url"
- ",http_method"
- ",header"
- ",body"
- " FROM merchant_pending_webhooks"
+ " webhook_pending_serial"
+ " ,next_attempt"
+ " ,retries"
+ " ,url"
+ " ,http_method"
+ " ,header"
+ " ,body"
+ " FROM merchant.merchant_pending_webhooks"
" WHERE next_attempt <= $1"
- " ORDER BY next_attempt ASC"
- );
-
+ " ORDER BY next_attempt ASC");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_pending_webhooks",
- params_null,
+ params,
&lookup_pending_webhooks_cb,
&pwlc);
-
if (pwlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
@@ -163,9 +157,10 @@ TALER_MERCHANTDB_lookup_pending_webhooks (
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_future_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_future_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls)
{
struct LookupPendingWebhookContext pwlc = {
.cb = cb,
@@ -175,84 +170,27 @@ TALER_MERCHANTDB_lookup_future_webhook (struct TALER_MERCHANTDB_PostgresContext
struct GNUNET_PQ_QueryParam params_null[] = {
GNUNET_PQ_query_param_end
};
-
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
"lookup_future_webhook",
"SELECT"
- " webhook_pending_serial"
- ",next_attempt"
- ",retries"
- ",url"
- ",http_method"
- ",header"
- ",body"
- " FROM merchant_pending_webhooks"
- " ORDER BY next_attempt ASC LIMIT 1"
- );
-
+ " webhook_pending_serial"
+ " ,next_attempt"
+ " ,retries"
+ " ,url"
+ " ,http_method"
+ " ,header"
+ " ,body"
+ " FROM merchant.merchant_pending_webhooks"
+ " ORDER BY next_attempt ASC"
+ " LIMIT 1");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_future_webhook",
params_null,
&lookup_pending_webhooks_cb,
&pwlc);
-
- if (pwlc.extract_failed)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_all_webhooks (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t min_row,
- uint32_t max_results,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls)
-{
- struct LookupPendingWebhookContext pwlc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .extract_failed = false,
- };
- uint64_t max_results64 = max_results;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&min_row),
- GNUNET_PQ_query_param_uint64 (&max_results64),
- GNUNET_PQ_query_param_end
- };
-
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- PREPARE (pg,
- "lookup_all_webhooks",
- " SELECT"
- " webhook_pending_serial"
- ",next_attempt"
- ",retries"
- ",url"
- ",http_method"
- ",header"
- ",body"
- " FROM merchant_pending_webhooks"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND webhook_pending_serial > $2"
- " ORDER BY webhook_pending_serial"
- " ASC LIMIT $3");
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_all_webhooks",
- params,
- &lookup_pending_webhooks_cb,
- &pwlc);
-
if (pwlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
diff --git a/src/backenddb/lookup_product.c b/src/backenddb/lookup_product.c
@@ -19,66 +19,63 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_product.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_product (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *product_id,
- struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t *num_categories,
- uint64_t **categories)
+TALER_MERCHANTDB_lookup_product (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *product_id,
+ struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t *num_categories,
+ uint64_t **categories)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_end
};
- PREPARE (pg,
- "lookup_product",
- "SELECT"
- " mi.description"
- ",mi.description_i18n::TEXT"
- ",mi.product_name"
- ",mi.unit"
- ",mi.price_array"
- ",mi.taxes::TEXT"
- ",mi.total_stock"
- ",mi.total_stock_frac"
- ",mi.allow_fractional_quantity"
- ",mi.fractional_precision_level"
- ",mi.total_sold"
- ",mi.total_sold_frac"
- ",mi.total_lost"
- ",mi.total_lost_frac"
- ",mi.image"
- ",mi.address::TEXT"
- ",mi.next_restock"
- ",mi.minimum_age"
- ",mi.product_group_serial"
- ",mi.money_pot_serial"
- ",mi.price_is_net"
- ",t.category_array AS categories"
- " FROM merchant_inventory mi"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- ",LATERAL ("
- " SELECT ARRAY ("
- " SELECT mpc.category_serial"
- " FROM merchant_product_categories mpc"
- " WHERE mpc.product_serial = mi.product_serial"
- " ) AS category_array"
- " ) t"
- " WHERE inst.merchant_id=$1"
- " AND mi.product_id=$2"
- );
- if (NULL == pd)
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mi.description"
+ ",mi.description_i18n::TEXT"
+ ",mi.product_name"
+ ",mi.unit"
+ ",mi.price_array"
+ ",mi.taxes::TEXT"
+ ",mi.total_stock"
+ ",mi.total_stock_frac"
+ ",mi.allow_fractional_quantity"
+ ",mi.fractional_precision_level"
+ ",mi.total_sold"
+ ",mi.total_sold_frac"
+ ",mi.total_lost"
+ ",mi.total_lost_frac"
+ ",mi.image"
+ ",mi.address::TEXT"
+ ",mi.next_restock"
+ ",mi.minimum_age"
+ ",mi.product_group_serial"
+ ",mi.money_pot_serial"
+ ",mi.price_is_net"
+ ",t.category_array AS categories"
+ " FROM merchant_inventory mi"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = mi.product_serial"
+ " ) AS category_array"
+ " ) t"
+ " WHERE mi.product_id=$1"
+ );
+ if (NULL == pd) // FIXME: is this case needed? For now yes: delete-private-products-PRODUCT_ID uses it!
{
struct GNUNET_PQ_ResultSpec rs_null[] = {
GNUNET_PQ_result_spec_end
@@ -86,7 +83,7 @@ TALER_MERCHANTDB_lookup_product (struct TALER_MERCHANTDB_PostgresContext *pg,
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_product",
+ "",
params,
rs_null);
}
@@ -164,7 +161,7 @@ TALER_MERCHANTDB_lookup_product (struct TALER_MERCHANTDB_PostgresContext *pg,
pd->product_group_id = 0;
pd->money_pot_id = 0;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_product",
+ "",
params,
rs);
pd->product_name = my_name;
diff --git a/src/backenddb/lookup_product_image.c b/src/backenddb/lookup_product_image.c
@@ -18,8 +18,6 @@
* @brief Implementation of the lookup_product_image_by_hash function for Postgres
* @author Bohdan Potuzhnyi
*/
-#include <taler/taler_error_codes.h>
-#include <taler/taler_pq_lib.h>
#include "merchantdb_lib.h"
#include "merchant-database/lookup_product_image.h"
#include "helper.h"
@@ -33,35 +31,29 @@ TALER_MERCHANTDB_lookup_product_image_by_hash (
char **image)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (image_hash),
GNUNET_PQ_query_param_end
};
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("image",
+ image),
+ GNUNET_PQ_result_spec_end
+ };
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (
+ TMH_PQ_prepare_anon (
pg,
- "lookup_product_image_by_hash",
"SELECT"
" mi.image"
" FROM merchant_inventory mi"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- " WHERE inst.merchant_id=$1"
- " AND mi.image_hash=$2");
-
+ " WHERE mi.image_hash=$1");
*image = NULL;
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("image",
- image),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (
- pg->conn,
- "lookup_product_image_by_hash",
- params,
- rs);
- }
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "",
+ params,
+ rs);
}
diff --git a/src/backenddb/lookup_products.c b/src/backenddb/lookup_products.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_products.h"
#include "helper.h"
@@ -112,7 +110,6 @@ TALER_MERCHANTDB_lookup_products (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&offset),
NULL == category_filter
? GNUNET_PQ_query_param_null ()
@@ -129,64 +126,63 @@ TALER_MERCHANTDB_lookup_products (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_products_asc",
- "SELECT"
- " product_id"
- " ,product_serial"
- " FROM merchant_inventory"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND product_serial > $2"
- " AND ( ($3::TEXT IS NULL) OR"
- " (product_serial IN"
- " (SELECT product_serial"
- " FROM merchant_product_categories"
- " WHERE category_serial IN"
- " (SELECT category_serial"
- " FROM merchant_categories"
- " WHERE category_name LIKE LOWER($3)))) )"
- " AND ( (0 = $7::INT8) OR"
- " (product_group_serial = $7) )"
- " AND ( ($4::TEXT IS NULL) OR"
- " (product_name LIKE LOWER($4)) )"
- " AND ( ($5::TEXT IS NULL) OR"
- " (description LIKE LOWER($5)) )"
- " ORDER BY product_serial ASC"
- " LIMIT $6");
- PREPARE (pg,
- "lookup_products_desc",
- "SELECT"
- " product_id"
- " ,product_serial"
- " FROM merchant_inventory"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND product_serial < $2"
- " AND ( ($3::TEXT IS NULL) OR"
- " (product_serial IN"
- " (SELECT product_serial"
- " FROM merchant_product_categories"
- " WHERE category_serial IN"
- " (SELECT category_serial"
- " FROM merchant_categories"
- " WHERE category_name LIKE LOWER($3)))) )"
- " AND ( (0 = $7::INT8) OR"
- " (product_group_serial = $7) )"
- " AND ( ($4::TEXT IS NULL) OR"
- " (product_name LIKE LOWER($4)) )"
- " AND ( ($5::TEXT IS NULL) OR"
- " (description LIKE LOWER($5)) )"
- " ORDER BY product_serial DESC"
- " LIMIT $6");
+ if (limit > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " product_id"
+ " ,product_serial"
+ " FROM merchant_inventory"
+ " WHERE product_serial > $1"
+ " AND ( ($2::TEXT IS NULL) OR"
+ " (product_serial IN"
+ " (SELECT product_serial"
+ " FROM merchant_product_categories"
+ " WHERE category_serial IN"
+ " (SELECT category_serial"
+ " FROM merchant_categories"
+ " WHERE LOWER(category_name) LIKE LOWER($2)))) )"
+ " AND ( (0 = $6::INT8) OR"
+ " (product_group_serial = $6) )"
+ " AND ( ($3::TEXT IS NULL) OR"
+ " (product_name LIKE LOWER($3)) )"
+ " AND ( ($4::TEXT IS NULL) OR"
+ " (description LIKE LOWER($4)) )"
+ " ORDER BY product_serial ASC"
+ " LIMIT $5");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " product_id"
+ " ,product_serial"
+ " FROM merchant_inventory"
+ " WHERE product_serial < $1"
+ " AND ( ($2::TEXT IS NULL) OR"
+ " (product_serial IN"
+ " (SELECT product_serial"
+ " FROM merchant_product_categories"
+ " WHERE category_serial IN"
+ " (SELECT category_serial"
+ " FROM merchant_categories"
+ " WHERE LOWER(category_name) LIKE LOWER($2)))) )"
+ " AND ( (0 = $6::INT8) OR"
+ " (product_group_serial = $6) )"
+ " AND ( ($3::TEXT IS NULL) OR"
+ " (LOWER(product_name) LIKE LOWER($3)) )"
+ " AND ( ($4::TEXT IS NULL) OR"
+ " (description LIKE LOWER($4)) )"
+ " ORDER BY product_serial DESC"
+ " LIMIT $5");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- (limit > 0)
- ? "lookup_products_asc"
- : "lookup_products_desc",
+ "",
params,
&lookup_products_cb,
&plc);
diff --git a/src/backenddb/lookup_reconciliation_details.c b/src/backenddb/lookup_reconciliation_details.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_reconciliation_details.h"
#include "helper.h"
@@ -139,36 +137,34 @@ TALER_MERCHANTDB_lookup_reconciliation_details (
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&expected_incoming_serial),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_reconciliation_details",
- "SELECT"
- " mct.order_id"
- ",SUM((etc.exchange_deposit_value).val)::INT8 AS deposit_value_sum"
- ",SUM((etc.exchange_deposit_value).frac)::INT8 AS deposit_frac_sum"
- ",SUM((etc.exchange_deposit_fee).val)::INT8 AS fee_value_sum"
- ",SUM((etc.exchange_deposit_fee).frac)::INT8 AS fee_frac_sum"
- ",(etc.exchange_deposit_value).curr AS currency"
- " FROM merchant_expected_transfer_to_coin etc"
- " JOIN merchant_deposits md"
- " USING (deposit_serial)"
- " JOIN merchant_deposit_confirmations mdc"
- " USING (deposit_confirmation_serial)"
- " JOIN merchant_contract_terms mct"
- " USING (order_serial)"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE expected_credit_serial=$2"
- " AND mi.merchant_id=$1"
- " GROUP BY mct.order_id, (etc.exchange_deposit_value).curr;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mct.order_id"
+ ",SUM((etc.exchange_deposit_value).val)::INT8 AS deposit_value_sum"
+ ",SUM((etc.exchange_deposit_value).frac)::INT8 AS deposit_frac_sum"
+ ",SUM((etc.exchange_deposit_fee).val)::INT8 AS fee_value_sum"
+ ",SUM((etc.exchange_deposit_fee).frac)::INT8 AS fee_frac_sum"
+ ",(etc.exchange_deposit_value).curr AS currency"
+ " FROM merchant_expected_transfer_to_coin etc"
+ " JOIN merchant_deposits md"
+ " USING (deposit_serial)"
+ " JOIN merchant_deposit_confirmations mdc"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_contract_terms mct"
+ " USING (order_serial)"
+ " WHERE expected_credit_serial=$1"
+ " GROUP BY mct.order_id, (etc.exchange_deposit_value).curr;");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_reconciliation_details",
+ "",
params,
&reconciliation_cb,
&lic);
diff --git a/src/backenddb/lookup_refund_proof.c b/src/backenddb/lookup_refund_proof.c
@@ -19,17 +19,17 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_refund_proof.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_refund_proof (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t refund_serial,
- struct TALER_ExchangeSignatureP *exchange_sig,
- struct TALER_ExchangePublicKeyP *exchange_pub)
+TALER_MERCHANTDB_lookup_refund_proof (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t refund_serial,
+ struct TALER_ExchangeSignatureP *exchange_sig,
+ struct TALER_ExchangePublicKeyP *exchange_pub)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&refund_serial),
@@ -43,20 +43,19 @@ TALER_MERCHANTDB_lookup_refund_proof (struct TALER_MERCHANTDB_PostgresContext *p
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_refund_proof",
- "SELECT"
- " merchant_exchange_signing_keys.exchange_pub"
- ",exchange_sig"
- " FROM merchant_refund_proofs"
- " JOIN merchant_exchange_signing_keys"
- " USING (signkey_serial)"
- " WHERE"
- " refund_serial=$1");
-
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " merchant.merchant_exchange_signing_keys.exchange_pub"
+ ",exchange_sig"
+ " FROM merchant_refund_proofs"
+ " JOIN merchant.merchant_exchange_signing_keys"
+ " USING (signkey_serial)"
+ " WHERE"
+ " refund_serial=$1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_refund_proof",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_refunds.c b/src/backenddb/lookup_refunds.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_refunds.h"
#include "helper.h"
@@ -98,14 +96,14 @@ lookup_refunds_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_refunds (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- TALER_MERCHANTDB_RefundCallback rc,
- void *rc_cls)
+TALER_MERCHANTDB_lookup_refunds (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_RefundCallback rc,
+ void *rc_cls)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
@@ -116,27 +114,25 @@ TALER_MERCHANTDB_lookup_refunds (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
/* no preflight check here, run in transaction by caller! */
TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n",
GNUNET_h2s (&h_contract_terms->hash),
instance_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_refunds",
- "SELECT"
- " coin_pub"
- ",refund_amount"
- " FROM merchant_refunds"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " coin_pub"
+ ",refund_amount"
+ " FROM merchant_refunds"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$1)");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_refunds",
+ "",
params,
&lookup_refunds_cb,
&lrc);
diff --git a/src/backenddb/lookup_refunds_detailed.c b/src/backenddb/lookup_refunds_detailed.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_refunds_detailed.h"
#include "helper.h"
@@ -131,7 +129,6 @@ TALER_MERCHANTDB_lookup_refunds_detailed (
void *rc_cls)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
@@ -142,41 +139,39 @@ TALER_MERCHANTDB_lookup_refunds_detailed (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
/* no preflight check here, run in transaction by caller! */
TALER_LOG_DEBUG ("Looking for refund %s + %s\n",
GNUNET_h2s (&h_contract_terms->hash),
instance_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_refunds_detailed",
- "SELECT"
- " ref.refund_serial"
- ",ref.refund_timestamp"
- ",dep.coin_pub"
- ",mcon.exchange_url"
- ",ref.rtransaction_id"
- ",ref.reason"
- ",ref.refund_amount"
- ",merchant_refund_proofs.exchange_sig IS NULL AS pending"
- " FROM merchant_deposit_confirmations mcon"
- " JOIN merchant_deposits dep"
- " USING (deposit_confirmation_serial)"
- " JOIN merchant_refunds ref"
- " USING (order_serial, coin_pub)"
- " LEFT JOIN merchant_refund_proofs"
- " USING (refund_serial)"
- " WHERE mcon.order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " ref.refund_serial"
+ ",ref.refund_timestamp"
+ ",dep.coin_pub"
+ ",mcon.exchange_url"
+ ",ref.rtransaction_id"
+ ",ref.reason"
+ ",ref.refund_amount"
+ ",merchant_refund_proofs.exchange_sig IS NULL AS pending"
+ " FROM merchant_deposit_confirmations mcon"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_refunds ref"
+ " USING (order_serial, coin_pub)"
+ " LEFT JOIN merchant_refund_proofs"
+ " USING (refund_serial)"
+ " WHERE mcon.order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$1)");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_refunds_detailed",
+ "",
params,
&lookup_refunds_detailed_cb,
&lrdc);
diff --git a/src/backenddb/lookup_reports_pending.c b/src/backenddb/lookup_reports_pending.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_reports_pending.h"
#include "helper.h"
@@ -77,27 +75,27 @@ select_pending_reports_cb (void *cls,
struct GNUNET_TIME_Absolute next_transmission;
bool one_shot;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("merchant_id",
+ GNUNET_PQ_result_spec_string ("out_merchant_id",
&instance_id),
- GNUNET_PQ_result_spec_uint64 ("report_serial",
+ GNUNET_PQ_result_spec_uint64 ("out_report_serial",
&report_serial),
- GNUNET_PQ_result_spec_string ("report_program_section",
+ GNUNET_PQ_result_spec_string ("out_report_program_section",
&report_program_section),
- GNUNET_PQ_result_spec_string ("report_description",
+ GNUNET_PQ_result_spec_string ("out_report_description",
&report_description),
- GNUNET_PQ_result_spec_string ("mime_type",
+ GNUNET_PQ_result_spec_string ("out_mime_type",
&mime_type),
- GNUNET_PQ_result_spec_auto_from_type ("report_token",
+ GNUNET_PQ_result_spec_auto_from_type ("out_report_token",
&report_token),
- GNUNET_PQ_result_spec_string ("target_address",
+ GNUNET_PQ_result_spec_string ("out_target_address",
&target_address),
- GNUNET_PQ_result_spec_relative_time ("frequency",
+ GNUNET_PQ_result_spec_relative_time ("out_frequency",
&frequency),
- GNUNET_PQ_result_spec_relative_time ("frequency_shift",
+ GNUNET_PQ_result_spec_relative_time ("out_frequency_shift",
&frequency_shift),
- GNUNET_PQ_result_spec_absolute_time ("next_transmission",
+ GNUNET_PQ_result_spec_absolute_time ("out_next_transmission",
&next_transmission),
- GNUNET_PQ_result_spec_bool ("one_shot_hidden",
+ GNUNET_PQ_result_spec_bool ("out_one_shot_hidden",
&one_shot),
GNUNET_PQ_result_spec_end
};
@@ -149,22 +147,18 @@ TALER_MERCHANTDB_lookup_reports_pending (
PREPARE (pg,
"lookup_reports_pending",
"SELECT"
- " mi.merchant_id"
- " ,mr.report_serial"
- " ,mr.report_program_section"
- " ,mr.report_description"
- " ,mr.mime_type"
- " ,mr.report_token"
- " ,mr.target_address"
- " ,mr.frequency"
- " ,mr.frequency_shift"
- " ,mr.next_transmission"
- " ,mr.one_shot_hidden"
- " FROM merchant_reports mr"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " ORDER BY next_transmission ASC"
- " LIMIT 1;");
+ " out_merchant_id"
+ " ,out_report_serial"
+ " ,out_report_program_section"
+ " ,out_report_description"
+ " ,out_mime_type"
+ " ,out_report_token"
+ " ,out_target_address"
+ " ,out_frequency"
+ " ,out_frequency_shift"
+ " ,out_next_transmission"
+ " ,out_one_shot_hidden"
+ " FROM merchant.lookup_reports_pending()");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"lookup_reports_pending",
diff --git a/src/backenddb/lookup_reports_pending.sql b/src/backenddb/lookup_reports_pending.sql
@@ -0,0 +1,93 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.lookup_reports_pending();
+CREATE FUNCTION merchant.lookup_reports_pending()
+RETURNS TABLE(
+ out_merchant_id TEXT,
+ out_report_serial INT8,
+ out_report_program_section TEXT,
+ out_report_description TEXT,
+ out_mime_type TEXT,
+ out_report_token BYTEA,
+ out_target_address TEXT,
+ out_frequency INT8,
+ out_frequency_shift INT8,
+ out_next_transmission INT8,
+ out_one_shot_hidden BOOLEAN)
+LANGUAGE plpgsql
+AS $FN$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ inner_rec RECORD;
+ xfound BOOLEAN := FALSE;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ ,merchant_id
+ FROM merchant.merchant_instances
+ LOOP
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ EXECUTE format(
+ 'SELECT'
+ ' report_serial AS rs'
+ ' ,report_program_section AS rps'
+ ' ,report_description AS rd'
+ ' ,mime_type AS mt'
+ ' ,report_token AS rt'
+ ' ,target_address AS ta'
+ ' ,frequency AS f'
+ ' ,frequency_shift AS fs'
+ ' ,next_transmission AS nt'
+ ' ,one_shot_hidden AS osh'
+ ' FROM %I.merchant_reports'
+ ' ORDER BY next_transmission ASC LIMIT 1', s)
+ INTO inner_rec;
+ IF inner_rec IS NULL
+ THEN
+ CONTINUE;
+ END IF;
+ IF (NOT found) OR (inner_rec.nt < out_next_transmission)
+ THEN
+ out_merchant_id := rec.merchant_id;
+ out_report_serial := inner_rec.rs;
+ out_report_program_section := inner_rec.rps;
+ out_report_description := inner_rec.rd;
+ out_mime_type := inner_rec.mt;
+ out_report_token := inner_rec.rt;
+ out_target_address := inner_rec.ta;
+ out_frequency := inner_rec.f;
+ out_frequency_shift := inner_rec.fs;
+ out_next_transmission := inner_rec.nt;
+ out_one_shot_hidden := inner_rec.osh;
+ xfound := TRUE;
+ END IF;
+ EXCEPTION
+ WHEN undefined_table
+ THEN
+ NULL;
+ END;
+ END LOOP;
+ IF xfound THEN
+ RETURN NEXT;
+ END IF;
+END
+$FN$;
+COMMENT ON FUNCTION merchant.lookup_reports_pending()
+ IS 'Returns the single next-due report (smallest next_transmission)'
+ ' across all instance schemas, or no row if no reports exist.';
diff --git a/src/backenddb/lookup_spent_tokens_by_order.c b/src/backenddb/lookup_spent_tokens_by_order.c
@@ -19,8 +19,6 @@
* @author Christian Blättler
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_spent_tokens_by_order.h"
#include "helper.h"
@@ -133,25 +131,24 @@ TALER_MERCHANTDB_lookup_spent_tokens_by_order (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_spent_tokens_by_order",
- "SELECT"
- " spent_token_serial"
- ",h_contract_terms"
- ",h_pub"
- ",token_pub"
- ",token_sig"
- ",blind_sig"
- " FROM merchant_used_tokens"
- " JOIN merchant_contract_terms"
- " USING (h_contract_terms)"
- " JOIN merchant_token_family_keys"
- " USING (token_family_key_serial)"
- " WHERE order_serial=$1");
-
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " spent_token_serial"
+ ",h_contract_terms"
+ ",h_pub"
+ ",token_pub"
+ ",token_sig"
+ ",blind_sig"
+ " FROM merchant_used_tokens"
+ " JOIN merchant_contract_terms"
+ " USING (h_contract_terms)"
+ " JOIN merchant_token_family_keys"
+ " USING (token_family_key_serial)"
+ " WHERE order_serial=$1");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_spent_tokens_by_order",
+ "",
params,
&lookup_spent_tokens_by_order_cb,
&ctx);
diff --git a/src/backenddb/lookup_statistics_amount_by_bucket.c b/src/backenddb/lookup_statistics_amount_by_bucket.c
@@ -19,8 +19,6 @@
* @author Martin Schanzenbach
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_statistics_amount_by_bucket.h"
#include "helper.h"
@@ -186,33 +184,31 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_bucket (
.pg = pg,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (slug),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_statistics_amount_by_bucket",
- "SELECT"
- " bmeta_serial_id"
- ",description"
- ",bucket_start"
- ",bucket_range::TEXT"
- ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end"
- ",(cumulative_value,cumulative_frac,curr)::taler_amount_currency AS cumulative_amount"
- " FROM merchant_statistic_bucket_amount"
- " JOIN merchant_statistic_bucket_meta"
- " USING (bmeta_serial_id)"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_statistic_bucket_meta.slug=$2"
- " AND merchant_statistic_bucket_meta.stype='amount'");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " bmeta_serial_id"
+ ",description"
+ ",bucket_start"
+ ",bucket_range::TEXT"
+ ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end"
+ ",(cumulative_value,cumulative_frac,curr)::merchant.taler_amount_currency AS cumulative_amount"
+ " FROM merchant_statistic_bucket_amount"
+ " JOIN merchant_statistic_bucket_meta"
+ " USING (bmeta_serial_id)"
+ " WHERE merchant_statistic_bucket_meta.slug=$1"
+ " AND merchant_statistic_bucket_meta.stype='amount'");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_amount_by_bucket",
+ "",
params,
&lookup_statistics_amount_by_bucket_cb,
&context);
diff --git a/src/backenddb/lookup_statistics_amount_by_bucket2.c b/src/backenddb/lookup_statistics_amount_by_bucket2.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_statistics_amount_by_bucket2.h"
#include "helper.h"
@@ -124,7 +122,6 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_bucket2 (
.pg = pg,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (slug),
GNUNET_PQ_query_param_string (granularity),
GNUNET_PQ_query_param_uint64 (&counter),
@@ -132,29 +129,28 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_bucket2 (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_statistics_amount_by_bucket2",
- "SELECT"
- " msba.bucket_start"
- ",ARRAY_AGG ("
- " ROW(msba.cumulative_value::INT8, msba.cumulative_frac::INT4, msba.curr)::taler_amount_currency"
- " ) AS cumulative_amounts"
- " FROM merchant_statistic_bucket_meta msbm"
- " JOIN merchant_statistic_bucket_amount msba"
- " USING (bmeta_serial_id)"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mi.merchant_id=$1"
- " AND msbm.slug=$2"
- " AND msba.bucket_range=$3::TEXT::statistic_range"
- " AND msbm.stype='amount'"
- " GROUP BY msba.bucket_start"
- " ORDER BY msba.bucket_start DESC"
- " LIMIT $4;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " msba.bucket_start"
+ ",ARRAY_AGG ("
+ " ROW(msba.cumulative_value::INT8, msba.cumulative_frac::INT4, msba.curr)::merchant.taler_amount_currency"
+ " ) AS cumulative_amounts"
+ " FROM merchant_statistic_bucket_meta msbm"
+ " JOIN merchant_statistic_bucket_amount msba"
+ " USING (bmeta_serial_id)"
+ " WHERE msbm.slug=$1"
+ " AND msba.bucket_range=$2::TEXT::merchant.statistic_range"
+ " AND msbm.stype='amount'"
+ " GROUP BY msba.bucket_start"
+ " ORDER BY msba.bucket_start DESC"
+ " LIMIT $3;");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_amount_by_bucket2",
+ "",
params,
&lookup_statistics_amount_by_bucket_cb2,
&context);
diff --git a/src/backenddb/lookup_statistics_amount_by_interval.c b/src/backenddb/lookup_statistics_amount_by_interval.c
@@ -19,8 +19,6 @@
* @author Martin Schanzenbach
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_statistics_amount_by_interval.h"
#include "helper.h"
@@ -198,21 +196,22 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_interval (
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (slug),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_statistics_amount_by_interval_description",
- "SELECT description"
- " FROM merchant_statistic_interval_meta"
- " WHERE slug=$1 LIMIT 1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT description"
+ " FROM merchant_statistic_interval_meta"
+ " WHERE slug=$1 LIMIT 1");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_amount_by_interval_description",
+ "",
descParams,
&lookup_statistics_amount_by_interval_desc_cb,
&context);
@@ -222,13 +221,12 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_interval (
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- PREPARE (pg,
- "lookup_statistics_amount_by_interval",
- "SELECT *"
- " FROM merchant_statistic_interval_amount_get($2,$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT *"
+ " FROM merchant_statistic_interval_amount_get($1)");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_amount_by_interval",
+ "",
params,
&lookup_statistics_amount_by_interval_cb,
&context);
diff --git a/src/backenddb/lookup_statistics_amount_by_interval.sql b/src/backenddb/lookup_statistics_amount_by_interval.sql
@@ -0,0 +1,202 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_statistic_interval_amount_get;
+CREATE FUNCTION merchant_statistic_interval_amount_get (
+ IN in_slug TEXT
+)
+RETURNS SETOF merchant.merchant_statistic_interval_amount_get_return_value
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
+ my_ranges INT8[];
+ my_range INT8;
+ my_delta_value INT8;
+ my_delta_frac INT8;
+ my_meta INT8;
+ my_next_max_serial INT8;
+ my_currency TEXT;
+ my_rec RECORD;
+ my_irec RECORD;
+ my_jrec RECORD;
+ my_i INT;
+ my_min_serial INT8 DEFAULT NULL;
+ my_rval merchant.merchant_statistic_interval_amount_get_return_value;
+BEGIN
+
+ SELECT imeta_serial_id
+ ,ranges
+ ,precisions
+ INTO my_rec
+ FROM merchant_statistic_interval_meta
+ WHERE slug=in_slug;
+ IF NOT FOUND
+ THEN
+ RETURN;
+ END IF;
+
+ my_meta = my_rec.imeta_serial_id;
+ my_ranges = my_rec.ranges;
+
+ FOR my_currency IN
+ SELECT DISTINCT delta_curr
+ FROM merchant_statistic_amount_event
+ WHERE imeta_serial_id = my_meta
+ LOOP
+
+ my_rval.rvalue.val = 0;
+ my_rval.rvalue.frac = 0;
+ my_rval.rvalue.curr = my_currency;
+
+ FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
+ LOOP
+ my_range = my_ranges[my_i];
+ SELECT event_delimiter
+ ,cumulative_value
+ ,cumulative_frac
+ INTO my_irec
+ FROM merchant_statistic_interval_amount
+ WHERE imeta_serial_id = my_meta
+ AND curr = my_currency
+ AND range = my_range;
+
+ IF FOUND
+ THEN
+ my_min_serial = my_irec.event_delimiter;
+ my_rval.rvalue.val = (my_rval.rvalue).val + my_irec.cumulative_value + my_irec.cumulative_frac / 100000000;
+ my_rval.rvalue.frac = (my_rval.rvalue).frac + my_irec.cumulative_frac % 100000000;
+ IF (my_rval.rvalue).frac > 100000000
+ THEN
+ my_rval.rvalue.frac = (my_rval.rvalue).frac - 100000000;
+ my_rval.rvalue.val = (my_rval.rvalue).val + 1;
+ END IF;
+
+ -- Check if we have events that left the applicable range
+ SELECT SUM(delta_value) AS value_sum
+ ,SUM(delta_frac) AS frac_sum
+ INTO my_jrec
+ FROM merchant_statistic_amount_event
+ WHERE imeta_serial_id = my_meta
+ AND delta_curr = my_currency
+ AND slot < my_time - my_range
+ AND aevent_serial_id >= my_min_serial;
+
+ IF FOUND AND my_jrec.value_sum IS NOT NULL
+ THEN
+ -- Normalize sum
+ my_delta_value = my_jrec.value_sum + my_jrec.frac_sum / 100000000;
+ my_delta_frac = my_jrec.frac_sum % 100000000;
+ my_rval.rvalue.val = (my_rval.rvalue).val - my_delta_value;
+ IF ((my_rval.rvalue).frac >= my_delta_frac)
+ THEN
+ my_rval.rvalue.frac = (my_rval.rvalue).frac - my_delta_frac;
+ ELSE
+ my_rval.rvalue.frac = 100000000 + (my_rval.rvalue).frac - my_delta_frac;
+ my_rval.rvalue.val = (my_rval.rvalue).val - 1;
+ END IF;
+
+ -- First find out the next event delimiter value
+ SELECT aevent_serial_id
+ INTO my_next_max_serial
+ FROM merchant_statistic_amount_event
+ WHERE imeta_serial_id = my_meta
+ AND delta_curr = my_currency
+ AND slot >= my_time - my_range
+ AND aevent_serial_id >= my_min_serial
+ ORDER BY slot ASC
+ LIMIT 1;
+ IF FOUND
+ THEN
+ -- remove expired events from the sum of the current slot
+ UPDATE merchant_statistic_interval_amount SET
+ cumulative_value = cumulative_value - my_delta_value
+ - CASE
+ WHEN cumulative_frac < my_delta_frac
+ THEN 1
+ ELSE 0
+ END,
+ cumulative_frac = cumulative_frac - my_delta_frac
+ + CASE
+ WHEN cumulative_frac < my_delta_frac
+ THEN 100000000
+ ELSE 0
+ END,
+ event_delimiter = my_next_max_serial
+ WHERE imeta_serial_id = my_meta
+ AND curr = my_currency
+ AND range = my_range;
+ ELSE
+ -- actually, slot is now empty, remove it entirely
+ DELETE FROM merchant_statistic_interval_amount
+ WHERE imeta_serial_id = my_meta
+ AND curr = my_currency
+ AND range = my_range;
+ END IF;
+ IF (my_i < COALESCE(array_length(my_ranges,1),0))
+ THEN
+ -- carry over all events into the next (larger) slot
+ UPDATE merchant_statistic_interval_amount AS msia SET
+ cumulative_value = cumulative_value + my_delta_value
+ + CASE
+ WHEN cumulative_frac + my_delta_frac > 100000000
+ THEN 1
+ ELSE 0
+ END,
+ cumulative_frac = cumulative_frac + my_delta_frac
+ - CASE
+ WHEN cumulative_frac + my_delta_frac > 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ event_delimiter = LEAST (msia.event_delimiter,my_min_serial)
+ WHERE imeta_serial_id = my_meta
+ AND range=my_ranges[my_i+1];
+ IF NOT FOUND
+ THEN
+ INSERT INTO merchant_statistic_interval_amount
+ (imeta_serial_id
+ ,event_delimiter
+ ,range
+ ,curr
+ ,cumulative_value
+ ,cumulative_frac
+ ) VALUES (
+ my_meta
+ ,my_min_serial
+ ,my_ranges[my_i+1]
+ ,my_currency
+ ,my_delta_value
+ ,my_delta_frac);
+ END IF;
+ ELSE
+ -- events are obsolete, delete them
+ DELETE FROM merchant_statistic_amount_event
+ WHERE imeta_serial_id = my_meta
+ AND slot < my_time - my_range;
+ END IF;
+ END IF;
+
+ my_rval.range = my_range;
+ RETURN NEXT my_rval;
+ END IF;
+ END LOOP; -- over my_ranges
+ END LOOP; -- over my_currency
+END $$;
+
+COMMENT ON FUNCTION merchant_statistic_interval_amount_get
+ IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value; multiple values are returned, one per currency and range';
diff --git a/src/backenddb/lookup_statistics_counter_by_bucket.c b/src/backenddb/lookup_statistics_counter_by_bucket.c
@@ -19,8 +19,6 @@
* @author Martin Schanzenbach
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_statistics_counter_by_bucket.h"
#include "helper.h"
@@ -125,32 +123,30 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_bucket (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (slug),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_statistics_counter_by_bucket",
- "SELECT"
- " description"
- ",bucket_start"
- ",bucket_range::TEXT"
- ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end"
- ",cumulative_number"
- " FROM merchant_statistic_bucket_counter"
- " JOIN merchant_statistic_bucket_meta"
- " USING (bmeta_serial_id)"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_statistic_bucket_meta.slug=$2"
- " AND merchant_statistic_bucket_meta.stype = 'number'");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " description"
+ ",bucket_start"
+ ",bucket_range::TEXT"
+ ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end"
+ ",cumulative_number"
+ " FROM merchant_statistic_bucket_counter"
+ " JOIN merchant_statistic_bucket_meta"
+ " USING (bmeta_serial_id)"
+ " WHERE merchant_statistic_bucket_meta.slug=$1"
+ " AND merchant_statistic_bucket_meta.stype = 'number'");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_counter_by_bucket",
+ "",
params,
&lookup_statistics_counter_by_bucket_cb,
&context);
diff --git a/src/backenddb/lookup_statistics_counter_by_bucket2.c b/src/backenddb/lookup_statistics_counter_by_bucket2.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_statistics_counter_by_bucket2.h"
#include "helper.h"
@@ -147,7 +145,6 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_bucket2 (
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (prefix),
GNUNET_PQ_query_param_string (granularity),
GNUNET_PQ_query_param_uint64 (&counter),
@@ -155,28 +152,27 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_bucket2 (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_statistics_counter_by_bucket2",
- "SELECT"
- " msbc.bucket_start"
- ",ARRAY_AGG(msbm.slug) AS slugs"
- ",ARRAY_AGG(msbc.cumulative_number) AS counters"
- " FROM merchant_statistic_bucket_counter msbc"
- " JOIN merchant_statistic_bucket_meta msbm"
- " USING (bmeta_serial_id)"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mi.merchant_id=$1"
- " AND msbm.slug LIKE $2"
- " AND msbc.bucket_range=$3::TEXT::statistic_range"
- " AND msbm.stype = 'number'"
- " GROUP BY msbc.bucket_start"
- " ORDER BY msbc.bucket_start DESC"
- " LIMIT $4");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " msbc.bucket_start"
+ ",ARRAY_AGG(msbm.slug) AS slugs"
+ ",ARRAY_AGG(msbc.cumulative_number) AS counters"
+ " FROM merchant_statistic_bucket_counter msbc"
+ " JOIN merchant_statistic_bucket_meta msbm"
+ " USING (bmeta_serial_id)"
+ " WHERE msbm.slug LIKE $1"
+ " AND msbc.bucket_range=$2::TEXT::merchant.statistic_range"
+ " AND msbm.stype = 'number'"
+ " GROUP BY msbc.bucket_start"
+ " ORDER BY msbc.bucket_start DESC"
+ " LIMIT $3");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_counter_by_bucket2",
+ "",
params,
&lookup_statistics_counter_by_bucket_cb2,
&context);
diff --git a/src/backenddb/lookup_statistics_counter_by_interval.c b/src/backenddb/lookup_statistics_counter_by_interval.c
@@ -19,8 +19,6 @@
* @author Martin Schanzenbach
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_statistics_counter_by_interval.h"
#include "helper.h"
@@ -164,21 +162,23 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_interval (
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (slug),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_statistics_counter_by_interval_description",
- "SELECT description"
- " FROM merchant_statistic_interval_meta"
- " WHERE slug=$1 LIMIT 1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT description"
+ " FROM merchant_statistic_interval_meta"
+ " WHERE slug=$1"
+ " LIMIT 1");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_counter_by_interval_description",
+ "",
descParams,
&lookup_statistics_counter_by_interval_desc_cb,
&context);
@@ -188,13 +188,14 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_interval (
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- PREPARE (pg,
- "lookup_statistics_counter_by_interval",
- "SELECT *"
- " FROM merchant_statistic_interval_number_get($2,$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " range"
+ " ,rvalue"
+ " FROM merchant_statistic_interval_number_get($1)");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_statistics_counter_by_interval",
+ "",
params,
&lookup_statistics_counter_by_interval_cb,
&context);
diff --git a/src/backenddb/lookup_statistics_counter_by_interval.sql b/src/backenddb/lookup_statistics_counter_by_interval.sql
@@ -0,0 +1,140 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_statistic_interval_number_get;
+CREATE FUNCTION merchant_statistic_interval_number_get (
+ IN in_slug TEXT
+)
+RETURNS SETOF merchant.merchant_statistic_interval_number_get_return_value
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
+ my_ranges INT8[];
+ my_range INT8;
+ my_delta INT8;
+ my_meta INT8;
+ my_next_max_serial INT8;
+ my_rec RECORD;
+ my_irec RECORD;
+ my_i INT;
+ my_min_serial INT8 DEFAULT NULL;
+ my_rval merchant.merchant_statistic_interval_number_get_return_value;
+BEGIN
+ SELECT imeta_serial_id
+ ,ranges
+ ,precisions
+ INTO my_rec
+ FROM merchant_statistic_interval_meta
+ WHERE slug=in_slug;
+ IF NOT FOUND
+ THEN
+ RETURN;
+ END IF;
+ my_rval.rvalue = 0;
+ my_ranges = my_rec.ranges;
+ my_meta = my_rec.imeta_serial_id;
+
+ FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
+ LOOP
+ my_range = my_ranges[my_i];
+ SELECT event_delimiter
+ ,cumulative_number
+ INTO my_irec
+ FROM merchant_statistic_interval_counter
+ WHERE imeta_serial_id = my_meta
+ AND range = my_range;
+ IF FOUND
+ THEN
+ my_min_serial = my_irec.event_delimiter;
+ my_rval.rvalue = my_rval.rvalue + my_irec.cumulative_number;
+
+ -- Check if we have events that left the applicable range
+ SELECT SUM(delta) AS delta_sum
+ INTO my_irec
+ FROM merchant_statistic_counter_event
+ WHERE imeta_serial_id = my_meta
+ AND slot < my_time - my_range
+ AND nevent_serial_id >= my_min_serial;
+
+ IF FOUND AND my_irec.delta_sum IS NOT NULL
+ THEN
+ my_delta = my_irec.delta_sum;
+ my_rval.rvalue = my_rval.rvalue - my_delta;
+
+ -- First find out the next event delimiter value
+ SELECT nevent_serial_id
+ INTO my_next_max_serial
+ FROM merchant_statistic_counter_event
+ WHERE imeta_serial_id = my_meta
+ AND slot >= my_time - my_range
+ AND nevent_serial_id >= my_min_serial
+ ORDER BY slot ASC
+ LIMIT 1;
+
+ IF FOUND
+ THEN
+ -- remove expired events from the sum of the current slot
+
+ UPDATE merchant_statistic_interval_counter
+ SET cumulative_number = cumulative_number - my_delta,
+ event_delimiter = my_next_max_serial
+ WHERE imeta_serial_id = my_meta
+ AND range = my_range;
+ ELSE
+ -- actually, slot is now empty, remove it entirely
+ DELETE FROM merchant_statistic_interval_counter
+ WHERE imeta_serial_id = my_meta
+ AND range = my_range;
+ END IF;
+ IF (my_i < COALESCE(array_length(my_ranges,1),0))
+ THEN
+ -- carry over all events into the next slot
+ UPDATE merchant_statistic_interval_counter AS usic SET
+ cumulative_number = cumulative_number + my_delta,
+ event_delimiter = LEAST(usic.event_delimiter,my_min_serial)
+ WHERE imeta_serial_id = my_meta
+ AND range=my_ranges[my_i+1];
+ IF NOT FOUND
+ THEN
+ INSERT INTO merchant_statistic_interval_counter
+ (imeta_serial_id
+ ,range
+ ,event_delimiter
+ ,cumulative_number
+ ) VALUES (
+ my_meta
+ ,my_ranges[my_i+1]
+ ,my_min_serial
+ ,my_delta);
+ END IF;
+ ELSE
+ -- events are obsolete, delete them
+ DELETE FROM merchant_statistic_counter_event
+ WHERE imeta_serial_id = my_meta
+ AND slot < my_time - my_range;
+ END IF;
+ END IF;
+
+ my_rval.range = my_range;
+ RETURN NEXT my_rval;
+ END IF;
+ END LOOP;
+END $$;
+
+COMMENT ON FUNCTION merchant_statistic_interval_number_get
+ IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value for each range';
diff --git a/src/backenddb/lookup_template.c b/src/backenddb/lookup_template.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_template.h"
#include "helper.h"
@@ -37,72 +35,54 @@
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_template (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *template_id,
- struct TALER_MERCHANTDB_TemplateDetails *td)
+TALER_MERCHANTDB_lookup_template (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *template_id,
+ struct TALER_MERCHANTDB_TemplateDetails *td)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_end
};
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("template_description",
+ &td->template_description),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("otp_id",
+ &td->otp_id),
+ NULL),
+ TALER_PQ_result_spec_json ("template_contract",
+ &td->template_contract),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("editable_defaults",
+ &td->editable_defaults),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_template",
- "SELECT"
- " mt.template_description"
- ",mod.otp_id"
- ",mt.template_contract::TEXT"
- ",mt.editable_defaults::TEXT"
- " FROM merchant_template mt"
- " JOIN merchant_instances mi"
- " ON (mi.merchant_serial = mt.merchant_serial)"
- " LEFT JOIN merchant_otp_devices mod"
- " ON (mod.otp_serial = mt.otp_device_id)"
- " WHERE mi.merchant_id=$1"
- " AND mt.template_id=$2");
- if (NULL == td)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_template",
- params,
- rs_null);
- GNUNET_PQ_cleanup_query_params_closures (params);
- return qs;
- }
- else
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("template_description",
- &td->template_description),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("otp_id",
- &td->otp_id),
- NULL),
- TALER_PQ_result_spec_json ("template_contract",
- &td->template_contract),
- GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_result_spec_json ("editable_defaults",
- &td->editable_defaults),
- NULL),
- GNUNET_PQ_result_spec_end
- };
-
- memset (td,
- 0,
- sizeof (*td));
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_template",
- params,
- rs);
- GNUNET_PQ_cleanup_query_params_closures (params);
- return qs;
- }
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mt.template_description"
+ ",mod.otp_id"
+ ",mt.template_contract::TEXT"
+ ",mt.editable_defaults::TEXT"
+ " FROM merchant_template mt"
+ " LEFT JOIN merchant_otp_devices mod"
+ " ON (mod.otp_serial = mt.otp_device_id)"
+ " WHERE mt.template_id=$1");
+ memset (td,
+ 0,
+ sizeof (*td));
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
}
diff --git a/src/backenddb/lookup_templates.c b/src/backenddb/lookup_templates.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_templates.h"
#include "helper.h"
@@ -93,10 +91,11 @@ lookup_templates_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_templates (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- TALER_MERCHANTDB_TemplatesCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_templates (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ TALER_MERCHANTDB_TemplatesCallback cb,
+ void *cb_cls)
{
struct LookupTemplateContext tlc = {
.cb = cb,
@@ -105,23 +104,21 @@ TALER_MERCHANTDB_lookup_templates (struct TALER_MERCHANTDB_PostgresContext *pg,
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_templates",
- "SELECT"
- " template_id"
- ",template_description"
- " FROM merchant_template"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " template_id"
+ ",template_description"
+ " FROM merchant_template");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_templates",
+ "",
params,
&lookup_templates_cb,
&tlc);
diff --git a/src/backenddb/lookup_token_families.c b/src/backenddb/lookup_token_families.c
@@ -19,8 +19,6 @@
* @author Christian Blättler
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_token_families.h"
#include "helper.h"
@@ -130,28 +128,26 @@ TALER_MERCHANTDB_lookup_token_families (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_token_families",
- "SELECT"
- " slug"
- ",name"
- ",description"
- ",description_i18n::TEXT"
- ",valid_after"
- ",valid_before"
- ",kind"
- " FROM merchant_token_families"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " slug"
+ ",name"
+ ",description"
+ ",description_i18n::TEXT"
+ ",valid_after"
+ ",valid_before"
+ ",kind"
+ " FROM merchant_token_families");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_token_families",
+ "",
params,
&lookup_token_families_cb,
&context);
diff --git a/src/backenddb/lookup_token_family.c b/src/backenddb/lookup_token_family.c
@@ -19,117 +19,100 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_token_family.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_token_family (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *token_family_slug,
- struct TALER_MERCHANTDB_TokenFamilyDetails *details)
+TALER_MERCHANTDB_lookup_token_family (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct TALER_MERCHANTDB_TokenFamilyDetails *details)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (token_family_slug),
GNUNET_PQ_query_param_end
};
+ char *kind;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("slug",
+ &details->slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &details->name),
+ GNUNET_PQ_result_spec_string ("cipher_choice",
+ &details->cipher_spec),
+ GNUNET_PQ_result_spec_string ("description",
+ &details->description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &details->description_i18n),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("extra_data",
+ &details->extra_data),
+ NULL),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &details->valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &details->valid_before),
+ GNUNET_PQ_result_spec_relative_time ("duration",
+ &details->duration),
+ GNUNET_PQ_result_spec_relative_time ("validity_granularity",
+ &details->validity_granularity),
+ GNUNET_PQ_result_spec_relative_time ("start_offset",
+ &details->start_offset),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_uint64 ("issued",
+ &details->issued),
+ GNUNET_PQ_result_spec_uint64 ("used",
+ &details->used),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
- if (NULL == details)
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ check_connection (pg);
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " slug"
+ ",name"
+ ",cipher_choice"
+ ",description"
+ ",description_i18n::TEXT"
+ ",extra_data::TEXT"
+ ",valid_after"
+ ",valid_before"
+ ",duration"
+ ",validity_granularity"
+ ",start_offset"
+ ",kind"
+ ",issued"
+ ",used"
+ " FROM merchant_token_families"
+ " WHERE slug=$1");
+ memset (details,
+ 0,
+ sizeof (*details));
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_token_family",
- params,
- rs_null);
- }
- else
- {
- char *kind;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("slug",
- &details->slug),
- GNUNET_PQ_result_spec_string ("name",
- &details->name),
- GNUNET_PQ_result_spec_string ("cipher_choice",
- &details->cipher_spec),
- GNUNET_PQ_result_spec_string ("description",
- &details->description),
- TALER_PQ_result_spec_json ("description_i18n",
- &details->description_i18n),
- GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_result_spec_json ("extra_data",
- &details->extra_data),
- NULL),
- GNUNET_PQ_result_spec_timestamp ("valid_after",
- &details->valid_after),
- GNUNET_PQ_result_spec_timestamp ("valid_before",
- &details->valid_before),
- GNUNET_PQ_result_spec_relative_time ("duration",
- &details->duration),
- GNUNET_PQ_result_spec_relative_time ("validity_granularity",
- &details->validity_granularity),
- GNUNET_PQ_result_spec_relative_time ("start_offset",
- &details->start_offset),
- GNUNET_PQ_result_spec_string ("kind",
- &kind),
- GNUNET_PQ_result_spec_uint64 ("issued",
- &details->issued),
- GNUNET_PQ_result_spec_uint64 ("used",
- &details->used),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- PREPARE (pg,
- "lookup_token_family",
- "SELECT"
- " slug"
- ",name"
- ",cipher_choice"
- ",description"
- ",description_i18n::TEXT"
- ",extra_data::TEXT"
- ",valid_after"
- ",valid_before"
- ",duration"
- ",validity_granularity"
- ",start_offset"
- ",kind"
- ",issued"
- ",used"
- " FROM merchant_token_families"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_token_families.slug=$2");
- memset (details,
- 0,
- sizeof (*details));
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_token_family",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ if (0 == strcmp (kind,
+ "discount"))
+ details->kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (0 == strcmp (kind,
+ "subscription"))
+ details->kind = TALER_MERCHANTDB_TFK_Subscription;
+ else
{
- if (0 == strcmp (kind, "discount"))
- details->kind = TALER_MERCHANTDB_TFK_Discount;
- else if (0 == strcmp (kind, "subscription"))
- details->kind = TALER_MERCHANTDB_TFK_Subscription;
- else
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
- return qs;
}
+ return qs;
}
diff --git a/src/backenddb/lookup_token_family_key.c b/src/backenddb/lookup_token_family_key.c
@@ -39,151 +39,133 @@ TALER_MERCHANTDB_lookup_token_family_key (
struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (token_family_slug),
GNUNET_PQ_query_param_timestamp (&valid_at),
GNUNET_PQ_query_param_timestamp (&sign_until),
GNUNET_PQ_query_param_end
};
+ char *kind;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_blind_sign_pub ("pub",
+ &details->pub.public_key),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_blind_sign_priv ("priv",
+ &details->priv.private_key),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("signature_validity_start",
+ &details->signature_validity_start),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("signature_validity_end",
+ &details->signature_validity_end),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("private_key_deleted_at",
+ &details->private_key_deleted_at),
+ NULL),
+ GNUNET_PQ_result_spec_string ("slug",
+ &details->token_family.slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &details->token_family.name),
+ GNUNET_PQ_result_spec_string ("cipher_choice",
+ &details->token_family.cipher_spec),
+ GNUNET_PQ_result_spec_string ("description",
+ &details->token_family.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &details->token_family.description_i18n),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &details->token_family.valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &details->token_family.valid_before),
+ GNUNET_PQ_result_spec_relative_time ("duration",
+ &details->token_family.duration),
+ GNUNET_PQ_result_spec_relative_time ("validity_granularity",
+ &details->token_family.
+ validity_granularity),
+ GNUNET_PQ_result_spec_relative_time ("start_offset",
+ &details->token_family.start_offset),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_uint64 ("issued",
+ &details->token_family.issued),
+ GNUNET_PQ_result_spec_uint64 ("used",
+ &details->token_family.used),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_token_family_key",
- "SELECT"
- " mtfk.h_pub"
- ",mtfk.pub"
- ",mtfk.priv"
- ",cipher_choice"
- ",mtfk.signature_validity_start"
- ",mtfk.signature_validity_end"
- ",mtfk.private_key_deleted_at"
- ",slug"
- ",name"
- ",description"
- ",description_i18n::TEXT"
- ",mtf.valid_after"
- ",mtf.valid_before"
- ",duration"
- ",validity_granularity"
- ",start_offset"
- ",kind"
- ",issued"
- ",used"
- " FROM merchant_token_families mtf"
- " LEFT JOIN merchant_token_family_keys mtfk"
- " ON ( (mtf.token_family_serial = mtfk.token_family_serial)"
- " AND ($3 >= mtfk.signature_validity_start)"
- " AND ($3 <= mtfk.signature_validity_end)"
- " AND ($4 <= mtfk.private_key_deleted_at) )"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mi.merchant_id=$1"
- " AND slug=$2"
- " AND ($3 >= valid_after)"
- " AND ($3 <= valid_before)"
- " ORDER BY mtfk.signature_validity_start ASC"
- " LIMIT 1");
-
- if (NULL == details)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_token_family_key",
- params,
- rs_null);
- }
-
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " h_pub"
+ ",pub"
+ ",priv"
+ ",cipher_choice"
+ ",mtfk.signature_validity_start"
+ ",mtfk.signature_validity_end"
+ ",mtfk.private_key_deleted_at"
+ ",slug"
+ ",name"
+ ",description"
+ ",description_i18n::TEXT"
+ ",mtf.valid_after"
+ ",mtf.valid_before"
+ ",duration"
+ ",validity_granularity"
+ ",start_offset"
+ ",kind"
+ ",issued"
+ ",used"
+ " FROM merchant_token_families mtf"
+ " LEFT JOIN merchant_token_family_keys mtfk"
+ " ON ( (mtf.token_family_serial = mtfk.token_family_serial)"
+ " AND ($2 >= mtfk.signature_validity_start)"
+ " AND ($2 <= mtfk.signature_validity_end)"
+ " AND ($3 <= mtfk.private_key_deleted_at) )"
+ " WHERE slug=$1"
+ " AND ($2 >= valid_after)"
+ " AND ($2 <= valid_before)"
+ " ORDER BY mtfk.signature_validity_start ASC"
+ " LIMIT 1");
+ memset (details,
+ 0,
+ sizeof (*details));
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
- char *kind;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_blind_sign_pub ("pub",
- &details->pub.public_key),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_blind_sign_priv ("priv",
- &details->priv.private_key),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("signature_validity_start",
- &details->signature_validity_start),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("signature_validity_end",
- &details->signature_validity_end),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("private_key_deleted_at",
- &details->private_key_deleted_at),
- NULL),
- GNUNET_PQ_result_spec_string ("slug",
- &details->token_family.slug),
- GNUNET_PQ_result_spec_string ("name",
- &details->token_family.name),
- GNUNET_PQ_result_spec_string ("cipher_choice",
- &details->token_family.cipher_spec),
- GNUNET_PQ_result_spec_string ("description",
- &details->token_family.description),
- TALER_PQ_result_spec_json ("description_i18n",
- &details->token_family.description_i18n),
- GNUNET_PQ_result_spec_timestamp ("valid_after",
- &details->token_family.valid_after),
- GNUNET_PQ_result_spec_timestamp ("valid_before",
- &details->token_family.valid_before),
- GNUNET_PQ_result_spec_relative_time ("duration",
- &details->token_family.duration),
- GNUNET_PQ_result_spec_relative_time ("validity_granularity",
- &details->token_family.
- validity_granularity),
- GNUNET_PQ_result_spec_relative_time ("start_offset",
- &details->token_family.start_offset),
- GNUNET_PQ_result_spec_string ("kind",
- &kind),
- GNUNET_PQ_result_spec_uint64 ("issued",
- &details->token_family.issued),
- GNUNET_PQ_result_spec_uint64 ("used",
- &details->token_family.used),
- GNUNET_PQ_result_spec_end
- };
-
- memset (details,
- 0,
- sizeof (*details));
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_token_family_key",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ if (0 == strcmp (kind,
+ "discount"))
+ {
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Discount;
+ }
+ else if (0 == strcmp (kind,
+ "subscription"))
+ {
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
+ }
+ else
{
- if (0 == strcmp (kind,
- "discount"))
- {
- details->token_family.kind = TALER_MERCHANTDB_TFK_Discount;
- }
- else if (0 == strcmp (kind,
- "subscription"))
- {
- details->token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
- }
- else
- {
- GNUNET_free (kind);
- GNUNET_free (details->token_family.slug);
- GNUNET_free (details->token_family.name);
- GNUNET_free (details->token_family.description);
- json_decref (details->token_family.description_i18n);
- GNUNET_CRYPTO_blind_sign_pub_decref (details->pub.public_key);
- GNUNET_CRYPTO_blind_sign_priv_decref (details->priv.private_key);
- GNUNET_free (details->token_family.cipher_spec);
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
GNUNET_free (kind);
+ GNUNET_free (details->token_family.slug);
+ GNUNET_free (details->token_family.name);
+ GNUNET_free (details->token_family.description);
+ json_decref (details->token_family.description_i18n);
+ GNUNET_CRYPTO_blind_sign_pub_decref (details->pub.public_key);
+ GNUNET_CRYPTO_blind_sign_priv_decref (details->priv.private_key);
+ GNUNET_free (details->token_family.cipher_spec);
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
- return qs;
+ GNUNET_free (kind);
}
+ return qs;
}
diff --git a/src/backenddb/lookup_token_family_keys.c b/src/backenddb/lookup_token_family_keys.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_token_family_keys.h"
#include "helper.h"
@@ -166,7 +164,6 @@ TALER_MERCHANTDB_lookup_token_family_keys (
void *cb_cls)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (token_family_slug),
GNUNET_PQ_query_param_timestamp (&start_time),
GNUNET_PQ_query_param_timestamp (&end_time),
@@ -179,41 +176,40 @@ TALER_MERCHANTDB_lookup_token_family_keys (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_token_family_keys",
- "SELECT"
- " h_pub"
- ",mtfk.pub"
- ",mtfk.priv"
- ",cipher_choice"
- ",mtfk.signature_validity_start"
- ",mtfk.signature_validity_end"
- ",mtfk.private_key_deleted_at"
- ",slug"
- ",name"
- ",description"
- ",description_i18n::TEXT"
- ",mtf.valid_after"
- ",mtf.valid_before"
- ",duration"
- ",validity_granularity"
- ",start_offset"
- ",kind"
- ",issued"
- ",used"
- " FROM merchant_token_families mtf"
- " LEFT JOIN merchant_token_family_keys mtfk"
- " USING (token_family_serial)"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mi.merchant_id=$1"
- " AND slug=$2"
- " AND COALESCE ($3 <= mtfk.signature_validity_end, TRUE)"
- " AND COALESCE ($4 >= mtfk.signature_validity_start, TRUE);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " h_pub"
+ ",mtfk.pub"
+ ",mtfk.priv"
+ ",cipher_choice"
+ ",mtfk.signature_validity_start"
+ ",mtfk.signature_validity_end"
+ ",mtfk.private_key_deleted_at"
+ ",slug"
+ ",name"
+ ",description"
+ ",description_i18n::TEXT"
+ ",mtf.valid_after"
+ ",mtf.valid_before"
+ ",duration"
+ ",validity_granularity"
+ ",start_offset"
+ ",kind"
+ ",issued"
+ ",used"
+ " FROM merchant_token_families mtf"
+ " LEFT JOIN merchant_token_family_keys mtfk"
+ " USING (token_family_serial)"
+ " WHERE slug=$1"
+ " AND COALESCE ($2 <= mtfk.signature_validity_end, TRUE)"
+ " AND COALESCE ($3 >= mtfk.signature_validity_start, TRUE);");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_token_family_keys",
+ "",
params,
&lookup_token_keys_cb,
&ctx);
diff --git a/src/backenddb/lookup_transfer_details.c b/src/backenddb/lookup_transfer_details.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_transfer_details.h"
#include "helper.h"
@@ -123,30 +121,30 @@ TALER_MERCHANTDB_lookup_transfer_details (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_transfer_details",
- "SELECT"
- " mterm.h_contract_terms"
- ",mtcoin.offset_in_exchange_list"
- ",dep.coin_pub"
- ",mtcoin.exchange_deposit_value"
- ",mtcoin.exchange_deposit_fee"
- " FROM merchant_expected_transfer_to_coin mtcoin"
- " JOIN merchant_deposits dep"
- " USING (deposit_serial)"
- " JOIN merchant_deposit_confirmations mcon"
- " USING (deposit_confirmation_serial)"
- " JOIN merchant_contract_terms mterm"
- " USING (order_serial)"
- " JOIN merchant_expected_transfers met"
- " USING (expected_credit_serial)"
- " WHERE met.wtid=$2"
- " AND met.exchange_url=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mterm.h_contract_terms"
+ ",mtcoin.offset_in_exchange_list"
+ ",dep.coin_pub"
+ ",mtcoin.exchange_deposit_value"
+ ",mtcoin.exchange_deposit_fee"
+ " FROM merchant_expected_transfer_to_coin mtcoin"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_serial)"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_contract_terms mterm"
+ " USING (order_serial)"
+ " JOIN merchant_expected_transfers met"
+ " USING (expected_credit_serial)"
+ " WHERE met.wtid=$2"
+ " AND met.exchange_url=$1");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_transfer_details",
+ "",
params,
&lookup_transfer_details_cb,
<dc);
diff --git a/src/backenddb/lookup_transfer_details_by_order.c b/src/backenddb/lookup_transfer_details_by_order.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_transfer_details_by_order.h"
#include "helper.h"
@@ -201,36 +199,35 @@ TALER_MERCHANTDB_lookup_transfer_details_by_order (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_transfer_details_by_order",
- "SELECT"
- " md.deposit_serial"
- ",mcon.exchange_url"
- ",met.wtid"
- ",mtc.exchange_deposit_value"
- ",mtc.exchange_deposit_fee"
- ",mcon.deposit_timestamp"
- ",met.confirmed"
- ",met.expected_credit_serial"
- " FROM merchant_expected_transfer_to_coin mtc"
- " JOIN merchant_deposits md"
- " USING (deposit_serial)"
- " JOIN merchant_deposit_confirmations mcon"
- " USING (deposit_confirmation_serial)"
- " JOIN merchant_expected_transfers met"
- " USING (expected_credit_serial)"
- " JOIN merchant_accounts acc"
- " ON (acc.account_serial = met.account_serial)"
- /* Check that all this is for the same instance */
- " JOIN merchant_contract_terms contracts"
- " USING (merchant_serial, order_serial)"
- " WHERE mcon.order_serial=$1"
- " ORDER BY met.wtid");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " md.deposit_serial"
+ ",mcon.exchange_url"
+ ",met.wtid"
+ ",mtc.exchange_deposit_value"
+ ",mtc.exchange_deposit_fee"
+ ",mcon.deposit_timestamp"
+ ",met.confirmed"
+ ",met.expected_credit_serial"
+ " FROM merchant_expected_transfer_to_coin mtc"
+ " JOIN merchant_deposits md"
+ " USING (deposit_serial)"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_expected_transfers met"
+ " USING (expected_credit_serial)"
+ " JOIN merchant_accounts acc"
+ " ON (acc.account_serial = met.account_serial)"
+ " JOIN merchant_contract_terms contracts"
+ " USING (order_serial)"
+ " WHERE mcon.order_serial=$1"
+ " ORDER BY met.wtid");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_transfer_details_by_order",
+ "",
params,
&lookup_transfer_details_by_order_cb,
<do);
diff --git a/src/backenddb/lookup_transfer_summary.c b/src/backenddb/lookup_transfer_summary.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_transfer_summary.h"
#include "helper.h"
@@ -121,28 +119,28 @@ TALER_MERCHANTDB_lookup_transfer_summary (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "lookup_transfer_summary",
- "SELECT"
- " mct.order_id"
- ",mtc.exchange_deposit_value"
- ",mtc.exchange_deposit_fee"
- " FROM merchant_expected_transfers met"
- " JOIN merchant_expected_transfer_to_coin mtc"
- " USING (expected_credit_serial)"
- " JOIN merchant_deposits dep"
- " USING (deposit_serial)"
- " JOIN merchant_deposit_confirmations mcon"
- " USING (deposit_confirmation_serial)"
- " JOIN merchant_contract_terms mct"
- " USING (order_serial)"
- " WHERE met.wtid=$2"
- " AND met.exchange_url=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mct.order_id"
+ ",mtc.exchange_deposit_value"
+ ",mtc.exchange_deposit_fee"
+ " FROM merchant_expected_transfers met"
+ " JOIN merchant_expected_transfer_to_coin mtc"
+ " USING (expected_credit_serial)"
+ " JOIN merchant_deposits dep"
+ " USING (deposit_serial)"
+ " JOIN merchant_deposit_confirmations mcon"
+ " USING (deposit_confirmation_serial)"
+ " JOIN merchant_contract_terms mct"
+ " USING (order_serial)"
+ " WHERE met.wtid=$2"
+ " AND met.exchange_url=$1");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "lookup_transfer_summary",
+ "",
params,
&lookup_transfer_summary_cb,
<dc);
diff --git a/src/backenddb/lookup_transfers.c b/src/backenddb/lookup_transfers.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_transfers.h"
#include "helper.h"
@@ -147,7 +145,6 @@ TALER_MERCHANTDB_lookup_transfers (
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_timestamp (&before),
GNUNET_PQ_query_param_timestamp (&after),
GNUNET_PQ_query_param_uint64 (&offset),
@@ -155,7 +152,7 @@ TALER_MERCHANTDB_lookup_transfers (
NULL == payto_uri.full_payto
? GNUNET_PQ_query_param_null () /* NULL: do not filter by payto URI */
: GNUNET_PQ_query_param_string (payto_uri.full_payto),
- GNUNET_PQ_query_param_bool (! by_time), /* $7: filter by time? */
+ GNUNET_PQ_query_param_bool (! by_time), /* $6: filter by time? */
GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == expected), /* filter by expected? */
GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == expected),
@@ -163,80 +160,77 @@ TALER_MERCHANTDB_lookup_transfers (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_transfers_asc",
- "SELECT"
- " mt.credit_amount"
- ",mt.wtid"
- ",mac.payto_uri"
- ",mt.exchange_url"
- ",mt.credit_serial"
- ",mt.execution_time"
- ",mt.expected"
- ",met.expected_credit_serial"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts mac"
- " USING (account_serial)"
- " LEFT JOIN merchant_expected_transfers met"
- " ON mt.wtid = met.wtid"
- " AND mt.account_serial = met.account_serial"
- " AND mt.exchange_url = met.exchange_url"
- " AND mt.expected"
- " WHERE ( $7 OR "
- " (mt.execution_time < $2 AND"
- " mt.execution_time >= $3) )"
- " AND ( (CAST($6 AS TEXT) IS NULL) OR "
- " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
- " =REGEXP_REPLACE($6,'\\?.*','')) )"
- " AND ( $8 OR "
- " (mt.expected = $9) )"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND (mt.credit_serial > $4)"
- " ORDER BY mt.credit_serial ASC"
- " LIMIT $5");
- PREPARE (pg,
- "lookup_transfers_desc",
- "SELECT"
- " mt.credit_amount"
- ",mt.wtid"
- ",mac.payto_uri"
- ",mt.exchange_url"
- ",mt.credit_serial"
- ",mt.execution_time"
- ",mt.expected"
- ",met.expected_credit_serial"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts mac"
- " USING (account_serial)"
- " LEFT JOIN merchant_expected_transfers met"
- " ON mt.wtid = met.wtid"
- " AND mt.account_serial = met.account_serial"
- " AND mt.exchange_url = met.exchange_url"
- " AND mt.expected"
- " WHERE ( $7 OR "
- " (mt.execution_time < $2 AND"
- " mt.execution_time >= $3) )"
- " AND ( (CAST($6 AS TEXT) IS NULL) OR "
- " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
- " =REGEXP_REPLACE($6,'\\?.*','')) )"
- " AND ( $8 OR "
- " (mt.expected = $9) )"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND (mt.credit_serial < $4)"
- " ORDER BY mt.credit_serial DESC"
- " LIMIT $5");
+ if (limit > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mt.credit_amount"
+ ",mt.wtid"
+ ",mac.payto_uri"
+ ",mt.exchange_url"
+ ",mt.credit_serial"
+ ",mt.execution_time"
+ ",mt.expected"
+ ",met.expected_credit_serial"
+ " FROM merchant_transfers mt"
+ " JOIN merchant_accounts mac"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_expected_transfers met"
+ " ON mt.wtid = met.wtid"
+ " AND mt.account_serial = met.account_serial"
+ " AND mt.exchange_url = met.exchange_url"
+ " AND mt.expected"
+ " WHERE ( $6 OR "
+ " (mt.execution_time < $1 AND"
+ " mt.execution_time >= $2) )"
+ " AND ( (CAST($5 AS TEXT) IS NULL) OR "
+ " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($5,'\\?.*','')) )"
+ " AND ( $7 OR "
+ " (mt.expected = $8) )"
+ " AND (mt.credit_serial > $3)"
+ " ORDER BY mt.credit_serial ASC"
+ " LIMIT $4");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " mt.credit_amount"
+ ",mt.wtid"
+ ",mac.payto_uri"
+ ",mt.exchange_url"
+ ",mt.credit_serial"
+ ",mt.execution_time"
+ ",mt.expected"
+ ",met.expected_credit_serial"
+ " FROM merchant_transfers mt"
+ " JOIN merchant_accounts mac"
+ " USING (account_serial)"
+ " LEFT JOIN merchant_expected_transfers met"
+ " ON mt.wtid = met.wtid"
+ " AND mt.account_serial = met.account_serial"
+ " AND mt.exchange_url = met.exchange_url"
+ " AND mt.expected"
+ " WHERE ( $6 OR "
+ " (mt.execution_time < $1 AND"
+ " mt.execution_time >= $2) )"
+ " AND ( (CAST($5 AS TEXT) IS NULL) OR "
+ " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE($5,'\\?.*','')) )"
+ " AND ( $7 OR "
+ " (mt.expected = $8) )"
+ " AND (mt.credit_serial < $3)"
+ " ORDER BY mt.credit_serial DESC"
+ " LIMIT $4");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- (limit > 0)
- ? "lookup_transfers_asc"
- : "lookup_transfers_desc",
+ "",
params,
&lookup_transfers_cb,
<c);
diff --git a/src/backenddb/lookup_units.c b/src/backenddb/lookup_units.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2025 Taler Systems SA
+ Copyright (C) 2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,8 +19,6 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_units.h"
#include "helper.h"
@@ -89,10 +87,11 @@ lookup_units_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_units (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- TALER_MERCHANTDB_UnitsCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_units (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ TALER_MERCHANTDB_UnitsCallback cb,
+ void *cb_cls)
{
struct LookupUnitsContext luc = {
.cb = cb,
@@ -100,48 +99,43 @@ TALER_MERCHANTDB_lookup_units (struct TALER_MERCHANTDB_PostgresContext *pg,
.extract_failed = false
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_units",
- "WITH mi AS ("
- " SELECT merchant_serial FROM merchant_instances WHERE merchant_id=$1"
- ")"
- "SELECT cu.unit_serial"
- " ,cu.unit"
- " ,cu.unit_name_long"
- " ,cu.unit_name_short"
- " ,cu.unit_name_long_i18n"
- " ,cu.unit_name_short_i18n"
- " ,cu.unit_allow_fraction"
- " ,cu.unit_precision_level"
- " ,cu.unit_active"
- " ,FALSE AS unit_builtin"
- " FROM merchant_custom_units cu"
- " JOIN mi ON cu.merchant_serial = mi.merchant_serial"
- " UNION ALL "
- "SELECT bu.unit_serial"
- " ,bu.unit"
- " ,bu.unit_name_long"
- " ,bu.unit_name_short"
- " ,bu.unit_name_long_i18n"
- " ,bu.unit_name_short_i18n"
- " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)"
- " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)"
- " ,COALESCE(bo.override_active, bu.unit_active)"
- " ,TRUE AS unit_builtin"
- " FROM merchant_builtin_units bu"
- " JOIN mi ON TRUE"
- " LEFT JOIN merchant_builtin_unit_overrides bo"
- " ON bo.builtin_unit_serial = bu.unit_serial"
- " AND bo.merchant_serial = mi.merchant_serial"
- " ORDER BY unit");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT cu.unit_serial"
+ " ,cu.unit"
+ " ,cu.unit_name_long"
+ " ,cu.unit_name_short"
+ " ,cu.unit_name_long_i18n"
+ " ,cu.unit_name_short_i18n"
+ " ,cu.unit_allow_fraction"
+ " ,cu.unit_precision_level"
+ " ,cu.unit_active"
+ " ,FALSE AS unit_builtin"
+ " FROM merchant_custom_units cu"
+ " UNION ALL "
+ "SELECT bu.unit_serial"
+ " ,bu.unit"
+ " ,bu.unit_name_long"
+ " ,bu.unit_name_short"
+ " ,bu.unit_name_long_i18n"
+ " ,bu.unit_name_short_i18n"
+ " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)"
+ " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)"
+ " ,COALESCE(bo.override_active, bu.unit_active)"
+ " ,TRUE AS unit_builtin"
+ " FROM merchant.merchant_builtin_units bu"
+ " LEFT JOIN merchant_builtin_unit_overrides bo"
+ " ON bo.builtin_unit_serial = bu.unit_serial"
+ " ORDER BY unit");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_units",
+ "",
params,
&lookup_units_cb,
&luc);
diff --git a/src/backenddb/lookup_webhook.c b/src/backenddb/lookup_webhook.c
@@ -19,47 +19,45 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_webhook.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *webhook_id,
- struct TALER_MERCHANTDB_WebhookDetails *wb)
+TALER_MERCHANTDB_lookup_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *webhook_id,
+ struct TALER_MERCHANTDB_WebhookDetails *wb)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_webhook",
- "SELECT"
- " event_type"
- ",url"
- ",http_method"
- ",header_template"
- ",body_template"
- " FROM merchant_webhook"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_webhook.webhook_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ " FROM merchant_webhook"
+ " WHERE webhook_id=$1");
- if (NULL == wb)
+ if (NULL == wb) // fIXME: is this case needed?
{
struct GNUNET_PQ_ResultSpec rs_null[] = {
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_webhook",
+ "",
params,
rs_null);
}
@@ -84,7 +82,7 @@ TALER_MERCHANTDB_lookup_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_webhook",
+ "",
params,
rs);
}
diff --git a/src/backenddb/lookup_webhook_by_event.c b/src/backenddb/lookup_webhook_by_event.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_webhook_by_event.h"
#include "helper.h"
@@ -111,11 +109,12 @@ lookup_webhook_by_event_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_webhook_by_event (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *event_type,
- TALER_MERCHANTDB_WebhookDetailCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_webhook_by_event (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *event_type,
+ TALER_MERCHANTDB_WebhookDetailCallback cb,
+ void *cb_cls)
{
struct LookupWebhookDetailContext wlc = {
.cb = cb,
@@ -124,30 +123,28 @@ TALER_MERCHANTDB_lookup_webhook_by_event (struct TALER_MERCHANTDB_PostgresContex
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (event_type),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_webhook_by_event",
- "SELECT"
- " webhook_serial"
- ",event_type"
- ",url"
- ",http_method"
- ",header_template"
- ",body_template"
- " FROM merchant_webhook"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND event_type=$2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " webhook_serial"
+ ",event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ " FROM merchant_webhook"
+ " WHERE event_type=$1");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_webhook_by_event",
+ "",
params,
&lookup_webhook_by_event_cb,
&wlc);
diff --git a/src/backenddb/lookup_webhooks.c b/src/backenddb/lookup_webhooks.c
@@ -19,8 +19,6 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_webhooks.h"
#include "helper.h"
@@ -92,10 +90,11 @@ lookup_webhooks_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_webhooks (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- TALER_MERCHANTDB_WebhooksCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_lookup_webhooks (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ TALER_MERCHANTDB_WebhooksCallback cb,
+ void *cb_cls)
{
struct LookupWebhookContext wlc = {
.cb = cb,
@@ -104,24 +103,22 @@ TALER_MERCHANTDB_lookup_webhooks (struct TALER_MERCHANTDB_PostgresContext *pg,
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_webhooks",
- "SELECT"
- " webhook_id"
- ",event_type"
- " FROM merchant_webhook"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " webhook_id"
+ ",event_type"
+ " FROM merchant_webhook");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_webhooks",
+ "",
params,
&lookup_webhooks_cb,
&wlc);
diff --git a/src/backenddb/lookup_wire_fee.c b/src/backenddb/lookup_wire_fee.c
@@ -19,21 +19,21 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/lookup_wire_fee.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_lookup_wire_fee (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *wire_method,
- struct GNUNET_TIME_Timestamp contract_date,
- struct TALER_WireFeeSet *fees,
- struct GNUNET_TIME_Timestamp *start_date,
- struct GNUNET_TIME_Timestamp *end_date,
- struct TALER_MasterSignatureP *master_sig)
+TALER_MERCHANTDB_lookup_wire_fee (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp contract_date,
+ struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_MasterSignatureP *master_sig)
{
struct GNUNET_HashCode h_wire_method;
struct GNUNET_PQ_QueryParam params[] = {
@@ -68,7 +68,7 @@ TALER_MERCHANTDB_lookup_wire_fee (struct TALER_MERCHANTDB_PostgresContext *pg,
",start_date"
",end_date"
",master_sig"
- " FROM merchant_exchange_wire_fees"
+ " FROM merchant.merchant_exchange_wire_fees"
" WHERE master_pub=$1"
" AND h_wire_method=$2"
" AND start_date <= $3"
diff --git a/src/backenddb/mark_contract_paid.c b/src/backenddb/mark_contract_paid.c
@@ -19,21 +19,20 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/mark_contract_paid.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_mark_contract_paid (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const char *session_id,
- int16_t choice_index)
+TALER_MERCHANTDB_mark_contract_paid (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const char *session_id,
+ int16_t choice_index)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_string (session_id),
(choice_index >= 0)
@@ -42,7 +41,6 @@ TALER_MERCHANTDB_mark_contract_paid (struct TALER_MERCHANTDB_PostgresContext *pg
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_QueryParam uparams[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
@@ -50,72 +48,60 @@ TALER_MERCHANTDB_mark_contract_paid (struct TALER_MERCHANTDB_PostgresContext *pg
/* Session ID must always be given by the caller. */
GNUNET_assert (NULL != session_id);
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ /* FIXME: combine the 3 individual statements into a stored procedure! */
/* no preflight check here, run in transaction by caller! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Marking h_contract_terms '%s' of %s as paid for session `%s'\n",
GNUNET_h2s (&h_contract_terms->hash),
instance_id,
session_id);
- PREPARE (pg,
- "mark_contract_paid",
- "UPDATE merchant_contract_terms SET"
- " paid=TRUE"
- ",session_id=$3"
- ",choice_index=$4"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_contract_terms SET"
+ " paid=TRUE"
+ ",session_id=$2"
+ ",choice_index=$3"
+ " WHERE h_contract_terms=$1");
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_contract_paid",
+ "",
params);
if (qs <= 0)
return qs;
- PREPARE (pg,
- "mark_inventory_sold",
- "UPDATE merchant_inventory SET"
- " total_sold = total_sold"
- " + order_locks.total_locked"
- " + ((merchant_inventory.total_sold_frac::BIGINT"
- " + order_locks.total_locked_frac::BIGINT) / 1000000)"
- " ,total_sold_frac ="
- " ((merchant_inventory.total_sold_frac::BIGINT"
- " + order_locks.total_locked_frac::BIGINT) % 1000000)::INT4"
- " FROM (SELECT total_locked,total_locked_frac,product_serial"
- " FROM merchant_order_locks"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))"
- " ) AS order_locks"
- " WHERE merchant_inventory.product_serial"
- " =order_locks.product_serial");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_inventory SET"
+ " total_sold = total_sold"
+ " + order_locks.total_locked"
+ " + ((merchant_inventory.total_sold_frac::BIGINT"
+ " + order_locks.total_locked_frac::BIGINT) / 1000000)"
+ " ,total_sold_frac ="
+ " ((merchant_inventory.total_sold_frac::BIGINT"
+ " + order_locks.total_locked_frac::BIGINT) % 1000000)::INT4"
+ " FROM (SELECT total_locked,total_locked_frac,product_serial"
+ " FROM merchant_order_locks"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$1)"
+ " ) AS order_locks"
+ " WHERE merchant_inventory.product_serial"
+ " =order_locks.product_serial");
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_inventory_sold",
+ "",
uparams);
if (qs < 0)
return qs; /* 0: no inventory management, that's OK! */
/* ON DELETE CASCADE deletes from merchant_order_locks */
- PREPARE (pg,
- "delete_completed_order",
- "WITH md AS"
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1) "
- "DELETE"
- " FROM merchant_orders"
- " WHERE order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " JOIN md USING (merchant_serial)"
- " WHERE h_contract_terms=$2)");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_orders"
+ " WHERE order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$1)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_completed_order",
+ "",
uparams);
}
diff --git a/src/backenddb/mark_order_wired.c b/src/backenddb/mark_order_wired.c
@@ -19,29 +19,28 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/mark_order_wired.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_mark_order_wired (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t order_serial)
+TALER_MERCHANTDB_mark_order_wired (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t order_serial)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "mark_order_wired",
- "UPDATE merchant_contract_terms SET"
- " wired=TRUE"
- " WHERE order_serial=$1");
-
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_contract_terms SET"
+ " wired=TRUE"
+ " WHERE order_serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_order_wired",
+ "",
params);
}
diff --git a/src/backenddb/meson.build b/src/backenddb/meson.build
@@ -14,6 +14,7 @@ libtalermerchantdb = library(
'event_listen.c',
'event_notify.c',
'preflight.c',
+ 'set_instance.c',
'account_kyc_get_outdated.c',
'account_kyc_get_status.c',
'account_kyc_set_failed.c',
@@ -143,6 +144,7 @@ libtalermerchantdb = library(
'lookup_transfers.c',
'lookup_webhook.c',
'lookup_webhook_by_event.c',
+ 'lookup_all_webhooks.c',
'lookup_webhooks.c',
'lookup_wire_fee.c',
'mark_contract_paid.c',
@@ -153,6 +155,7 @@ libtalermerchantdb = library(
'select_account.c',
'select_account_by_uri.c',
'select_accounts.c',
+ 'select_accounts_by_instance.c',
'select_accounts_by_exchange.c',
'select_category.c',
'select_category_by_name.c',
diff --git a/src/backenddb/pg.c b/src/backenddb/pg.c
@@ -33,8 +33,10 @@
#include "helper.h"
+// FIXME: prefix?
void
-check_connection (struct TALER_MERCHANTDB_PostgresContext *pg)
+check_connection (
+ struct TALER_MERCHANTDB_PostgresContext *pg)
{
if (NULL != pg->transaction_name)
return;
@@ -43,7 +45,8 @@ check_connection (struct TALER_MERCHANTDB_PostgresContext *pg)
struct TALER_MERCHANTDB_PostgresContext *
-TALER_MERCHANTDB_connect (const struct GNUNET_CONFIGURATION_Handle *cfg)
+TALER_MERCHANTDB_connect (
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
{
struct TALER_MERCHANTDB_PostgresContext *pg;
struct GNUNET_PQ_ExecuteStatement es[] = {
@@ -82,9 +85,11 @@ TALER_MERCHANTDB_connect (const struct GNUNET_CONFIGURATION_Handle *cfg)
void
-TALER_MERCHANTDB_disconnect (struct TALER_MERCHANTDB_PostgresContext *pg)
+TALER_MERCHANTDB_disconnect (
+ struct TALER_MERCHANTDB_PostgresContext *pg)
{
GNUNET_PQ_disconnect (pg->conn);
+ GNUNET_free (pg->current_merchant_id);
GNUNET_free (pg);
}
diff --git a/src/backenddb/pg_account_kyc_get_status.sql b/src/backenddb/pg_account_kyc_get_status.sql
@@ -1,124 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2026 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_do_account_kyc_get_status;
-CREATE FUNCTION merchant_do_account_kyc_get_status (
- IN in_merchant_id TEXT,
- IN in_now INT8,
- IN in_exchange_url TEXT, -- can be NULL
- IN in_h_wire BYTEA -- can be NULL
-) RETURNS TABLE (
- out_h_wire BYTEA, -- never NULL
- out_payto_uri TEXT, -- never NULL
- out_exchange_url TEXT,
- out_kyc_timestamp INT8,
- out_kyc_ok BOOLEAN,
- out_access_token BYTEA,
- out_exchange_http_status INT4,
- out_exchange_ec_code INT4,
- out_aml_review BOOLEAN,
- out_jaccount_limits TEXT
-)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_serial INT8;
- my_account_serial INT8;
- my_h_wire BYTEA;
- my_payto_uri TEXT;
- my_kyc_record RECORD;
-
-BEGIN
- -- Get the merchant_serial from merchant_instances
- SELECT merchant_serial
- INTO my_merchant_serial
- FROM merchant_instances
- WHERE merchant_id = in_merchant_id;
- IF NOT FOUND
- THEN
- RETURN;
- END IF;
-
- -- Iterate over merchant_accounts for this merchant
- FOR my_account_serial, my_h_wire, my_payto_uri
- IN SELECT account_serial, h_wire, payto_uri
- FROM merchant_accounts
- WHERE merchant_serial = my_merchant_serial
- AND active
- AND (in_h_wire IS NULL OR h_wire = in_h_wire)
- ORDER BY account_serial ASC
- LOOP
-
- -- Fetch KYC info for this account (can have multiple results)
- FOR my_kyc_record IN
- SELECT
- mk.kyc_serial_id
- ,mk.exchange_url
- ,mk.kyc_timestamp
- ,mk.kyc_ok
- ,mk.access_token
- ,mk.exchange_http_status
- ,mk.exchange_ec_code
- ,mk.aml_review
- ,mk.jaccount_limits::TEXT
- FROM merchant_kyc mk
- WHERE mk.account_serial = my_account_serial
- AND (in_exchange_url IS NULL OR mk.exchange_url = in_exchange_url)
- ORDER BY mk.kyc_serial_id ASC
- LOOP
- -- Ask taler-merchant-kyccheck to get us an update on the status ASAP
- UPDATE merchant_kyc
- SET next_kyc_poll=in_now
- WHERE kyc_serial_id = my_kyc_record.kyc_serial_id;
- NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG; -- MERCHANT_EXCHANGE_KYC_UPDATE_FORCED
- RETURN QUERY
- SELECT
- my_h_wire,
- my_payto_uri,
- my_kyc_record.exchange_url,
- my_kyc_record.kyc_timestamp,
- my_kyc_record.kyc_ok,
- my_kyc_record.access_token,
- my_kyc_record.exchange_http_status,
- my_kyc_record.exchange_ec_code,
- my_kyc_record.aml_review,
- my_kyc_record.jaccount_limits::TEXT;
- END LOOP; -- loop over exchanges with KYC status for the given account
-
- IF NOT FOUND
- THEN
- -- Still return to server that we do NOT know anything
- -- for the given exchange yet (but that the bank account exists)
- RETURN QUERY
- SELECT
- my_h_wire,
- my_payto_uri,
- NULL::TEXT,
- NULL::INT8,
- NULL::BOOLEAN,
- NULL::BYTEA,
- NULL::INT4,
- NULL::INT4,
- NULL::BOOLEAN,
- NULL::TEXT;
- END IF;
-
- END LOOP; -- loop over merchant_accounts
-
-END $$;
-COMMENT ON FUNCTION merchant_do_account_kyc_get_status
- IS 'Returns the KYC status of selected exchanges and accounts, but ALSO resets the next_kyc_check time for all returned data points to the current time (in_now argument)';
diff --git a/src/backenddb/pg_account_kyc_set_failed.sql b/src/backenddb/pg_account_kyc_set_failed.sql
@@ -1,100 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2024 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_do_account_kyc_set_failed;
-
-CREATE FUNCTION merchant_do_account_kyc_set_failed (
- IN in_merchant_id TEXT,
- IN in_h_wire BYTEA,
- IN in_exchange_url TEXT,
- IN in_timestamp INT8,
- IN in_exchange_http_status INT4,
- IN in_kyc_ok BOOL,
- IN in_notify_str TEXT,
- IN in_notify2_str TEXT,
- OUT out_no_instance BOOL,
- OUT out_no_account BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_account_serial INT8;
-BEGIN
-
-out_no_instance=FALSE;
-out_no_account=FALSE;
-
--- Which instance are we using?
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_merchant_id;
-
-IF NOT FOUND
-THEN
- out_no_instance=TRUE;
- RETURN;
-END IF;
-
-SELECT account_serial
- INTO my_account_serial
- FROM merchant_accounts
- WHERE merchant_serial=my_merchant_id
- AND h_wire=in_h_wire;
-
-IF NOT FOUND
-THEN
- out_no_account=TRUE;
- RETURN;
-END IF;
-
-UPDATE merchant_kyc
- SET kyc_timestamp=in_timestamp
- ,kyc_ok=in_kyc_ok
- ,exchange_http_status=in_exchange_http_status
- ,exchange_ec_code=0
- WHERE account_serial=my_account_serial
- AND exchange_url=in_exchange_url;
-
-IF NOT FOUND
-THEN
-
- INSERT INTO merchant_kyc
- (kyc_timestamp
- ,kyc_ok
- ,account_serial
- ,exchange_url
- ,exchange_http_status)
- VALUES
- (in_timestamp
- ,in_kyc_ok
- ,my_account_serial
- ,in_exchange_url
- ,in_exchange_http_status);
-END IF;
-
-EXECUTE FORMAT (
- 'NOTIFY %s'
- ,in_notify_str);
-
-EXECUTE FORMAT (
- 'NOTIFY %s'
- ,in_notify2_str);
-
-
--- Success!
-END $$;
diff --git a/src/backenddb/pg_account_kyc_set_status.sql b/src/backenddb/pg_account_kyc_set_status.sql
@@ -1,127 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2024, 2026 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_do_account_kyc_set_status;
-
-CREATE FUNCTION merchant_do_account_kyc_set_status (
- IN in_merchant_id TEXT,
- IN in_h_wire BYTEA,
- IN in_exchange_url TEXT,
- IN in_timestamp INT8,
- IN in_exchange_http_status INT4,
- IN in_exchange_ec_code INT4,
- IN in_access_token BYTEA, -- can be NULL
- IN in_jlimits JSONB,
- IN in_aml_active BOOL,
- IN in_kyc_ok BOOL,
- IN in_notify_str TEXT,
- IN in_notify2_str TEXT,
- IN in_rule_gen INT8,
- IN in_next_time INT8,
- IN in_kyc_backoff INT8,
- OUT out_no_instance BOOL,
- OUT out_no_account BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_account_serial INT8;
-BEGIN
-
-out_no_instance=FALSE;
-out_no_account=FALSE;
-
--- Which instance are we using?
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_merchant_id;
-
-IF NOT FOUND
-THEN
- out_no_instance=TRUE;
- RETURN;
-END IF;
-
-SELECT account_serial
- INTO my_account_serial
- FROM merchant_accounts
- WHERE merchant_serial=my_merchant_id
- AND h_wire=in_h_wire;
-
-IF NOT FOUND
-THEN
- out_no_account=TRUE;
- RETURN;
-END IF;
-
-UPDATE merchant_kyc
- SET kyc_timestamp=in_timestamp
- ,kyc_ok=in_kyc_ok
- ,jaccount_limits=in_jlimits
- ,aml_review=in_aml_active
- ,exchange_http_status=in_exchange_http_status
- ,exchange_ec_code=in_exchange_ec_code
- ,access_token=in_access_token
- ,last_rule_gen=in_rule_gen
- ,next_kyc_poll=in_next_time
- ,kyc_backoff=in_kyc_backoff
- WHERE account_serial=my_account_serial
- AND exchange_url=in_exchange_url;
-
-IF NOT FOUND
-THEN
-
- INSERT INTO merchant_kyc
- (kyc_timestamp
- ,kyc_ok
- ,account_serial
- ,exchange_url
- ,jaccount_limits
- ,aml_review
- ,exchange_http_status
- ,exchange_ec_code
- ,access_token
- ,last_rule_gen
- ,next_kyc_poll
- ,kyc_backoff)
- VALUES
- (in_timestamp
- ,in_kyc_ok
- ,my_account_serial
- ,in_exchange_url
- ,in_jlimits
- ,in_aml_active
- ,in_exchange_http_status
- ,in_exchange_ec_code
- ,in_access_token
- ,in_rule_gen
- ,in_next_time
- ,in_kyc_backoff);
-END IF;
-
-EXECUTE FORMAT (
- 'NOTIFY %s'
- ,in_notify_str);
-
-EXECUTE FORMAT (
- 'NOTIFY %s'
- ,in_notify2_str);
-
-
--- Success!
-END $$;
diff --git a/src/backenddb/pg_activate_account.sql b/src/backenddb/pg_activate_account.sql
@@ -1,146 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2026 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-DROP FUNCTION IF EXISTS merchant_do_activate_account;
-CREATE FUNCTION merchant_do_activate_account(
- IN in_instance_name TEXT
- ,IN in_h_wire BYTEA
- ,IN in_salt BYTEA
- ,IN in_full_payto TEXT
- ,IN in_credit_facade_url TEXT -- can be NULL
- ,IN in_credit_facade_credentials TEXT -- can be NULL
- ,IN in_extra_wire_subject_metadata TEXT -- can be NULL
- ,OUT out_h_wire BYTEA
- ,OUT out_salt BYTEA
- ,OUT out_not_found BOOL
- ,OUT out_conflict BOOL
-)
-LANGUAGE plpgsql
-AS $$
-DECLARE my_instance INT8;
-DECLARE my_active BOOL;
-DECLARE my_cfu TEXT;
-DECLARE my_cfc TEXT;
-DECLARE my_ewsm TEXT;
-DECLARE my_h_wire BYTEA;
-DECLARE my_salt BYTEA;
-BEGIN
- out_not_found = FALSE;
- out_conflict = FALSE;
- out_h_wire = in_h_wire;
- out_salt = in_salt;
-
- SELECT merchant_serial
- INTO my_instance
- FROM merchant_instances
- WHERE merchant_id=in_instance_name;
- IF NOT FOUND
- THEN
- out_not_found = TRUE;
- RETURN;
- END IF;
-
- INSERT INTO merchant_accounts
- AS ma
- (merchant_serial
- ,h_wire
- ,salt
- ,payto_uri
- ,credit_facade_url
- ,credit_facade_credentials
- ,active
- ,extra_wire_subject_metadata
- ) VALUES (
- my_instance
- ,in_h_wire
- ,in_salt
- ,in_full_payto
- ,in_credit_facade_url
- ,in_credit_facade_credentials::JSONB
- ,TRUE
- ,in_extra_wire_subject_metadata
- ) ON CONFLICT DO NOTHING;
- IF FOUND
- THEN
- -- Notify taler-merchant-kyccheck about the change in
- -- accounts. (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
- NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG;
- RETURN;
- END IF;
-
- SELECT h_wire
- ,salt
- ,active
- ,credit_facade_url
- ,credit_facade_credentials::TEXT
- ,extra_wire_subject_metadata
- INTO my_h_wire
- ,my_salt
- ,my_active
- ,my_cfu
- ,my_cfc
- ,my_ewsm
- FROM merchant_accounts
- WHERE merchant_serial=my_instance
- AND payto_uri=in_full_payto;
- IF NOT FOUND
- THEN
- -- This should never happen (we had a conflict!)
- -- Still, safe way is to return not found.
- out_not_found = TRUE;
- RETURN;
- END IF;
-
- -- Check for conflict
- IF (my_active AND
- (ROW (my_cfu
- ,my_cfc
- ,my_ewsm)
- IS DISTINCT FROM
- ROW (in_credit_facade_url
- ,in_credit_facade_credentials
- ,in_extra_wire_subject_metadata)))
- THEN
- -- Active conflicting account, refuse!
- out_conflict = TRUE;
- RETURN;
- END IF;
-
- -- Equivalent account exists, use its salt instead of the new salt
- -- and just set it to active!
- out_salt = my_salt;
- out_h_wire = my_h_wire;
-
- -- Now check if existing account is already active
- IF my_active
- THEN
- -- nothing to do
- RETURN;
- END IF;
-
- UPDATE merchant_accounts
- SET active=TRUE
- ,credit_facade_url=in_credit_facade_url
- ,credit_facade_credentials=in_credit_facade_credentials::JSONB
- ,extra_wire_subject_metadata=in_extra_wire_subject_metadata
- WHERE h_wire=out_h_wire
- AND merchant_serial=my_instance;
-
- -- Notify taler-merchant-kyccheck about the change in (active)
- -- accounts. (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
- NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG;
-
-END $$;
diff --git a/src/backenddb/pg_base32_crockford.sql b/src/backenddb/pg_base32_crockford.sql
@@ -14,13 +14,13 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
-DROP FUNCTION IF EXISTS base32_crockford;
-CREATE FUNCTION base32_crockford(data BYTEA)
-RETURNS TEXT
-LANGUAGE plpgsql
-IMMUTABLE
-STRICT
-PARALLEL SAFE
+DROP FUNCTION IF EXISTS merchant.base32_crockford;
+CREATE FUNCTION merchant.base32_crockford(data BYTEA)
+ RETURNS TEXT
+ LANGUAGE plpgsql
+ IMMUTABLE
+ STRICT
+ PARALLEL SAFE
AS $$
DECLARE
alphabet TEXT := '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
@@ -62,5 +62,5 @@ BEGIN
RETURN array_to_string(chars, '');
END;
$$;
-COMMENT ON FUNCTION base32_crockford(BYTEA)
+COMMENT ON FUNCTION merchant.base32_crockford(BYTEA)
IS 'Encodes binary data using Crockford Base32';
diff --git a/src/backenddb/pg_create_instance_schema.sql b/src/backenddb/pg_create_instance_schema.sql
@@ -0,0 +1,45 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.create_instance_schema;
+CREATE FUNCTION merchant.create_instance_schema(
+ in_merchant_serial BIGINT
+)
+ RETURNS void
+ LANGUAGE plpgsql
+ AS $$
+DECLARE
+ s TEXT := 'merchant_instance_' || in_merchant_serial::TEXT;
+ r RECORD;
+BEGIN
+ EXECUTE format('CREATE SCHEMA %I', s);
+
+ -- Call fixup functions to initialize all tables
+ FOR r IN
+ SELECT migration_name
+ FROM merchant.instance_fixups
+ ORDER BY version ASC
+ LOOP
+ EXECUTE FORMAT ('CALL merchant.%I(%L)', r.migration_name, s);
+ END LOOP;
+ EXECUTE FORMAT ('CALL merchant.sync_instance_procedures(%s)', in_merchant_serial);
+END $$;
+
+COMMENT ON FUNCTION merchant.create_instance_schema(BIGINT)
+ IS 'Constructs the per-instance schema merchant_instance_<merchant_serial>'
+ ' with all per-instance tables, stored procedures and triggers. Called from'
+ ' merchant_instances_after_insert_trigger() whenever a new instance row'
+ ' is inserted into merchant.merchant_instances.';
diff --git a/src/backenddb/pg_create_instance_trigger.sql b/src/backenddb/pg_create_instance_trigger.sql
@@ -0,0 +1,58 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.merchant_instances_after_insert_trigger() CASCADE;
+CREATE FUNCTION merchant.merchant_instances_after_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ PERFORM merchant.create_instance_schema(NEW.merchant_serial);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION merchant.merchant_instances_after_insert_trigger()
+ IS 'Builds the per-instance schema merchant_instance_<merchant_serial>'
+ ' for the newly inserted row.';
+
+DROP TRIGGER IF EXISTS merchant_instances_on_insert
+ ON merchant.merchant_instances;
+CREATE TRIGGER merchant_instances_on_insert
+ AFTER INSERT
+ ON merchant.merchant_instances
+ FOR EACH ROW
+ EXECUTE FUNCTION merchant.merchant_instances_after_insert_trigger();
+
+
+DROP FUNCTION IF EXISTS merchant.merchant_instances_after_delete_trigger() CASCADE;
+CREATE FUNCTION merchant.merchant_instances_after_delete_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ EXECUTE format('DROP SCHEMA IF EXISTS %I CASCADE',
+ 'merchant_instance_' || OLD.merchant_serial::TEXT);
+ RETURN NULL;
+END $$;
+COMMENT ON FUNCTION merchant.merchant_instances_after_delete_trigger()
+ IS 'Drops the per-instance schema merchant_instance_<merchant_serial>'
+ ' when its row in merchant.merchant_instances is removed.';
+
+DROP TRIGGER IF EXISTS merchant_instances_on_delete
+ ON merchant.merchant_instances;
+CREATE TRIGGER merchant_instances_on_delete
+ AFTER DELETE ON merchant.merchant_instances
+ FOR EACH ROW
+ EXECUTE FUNCTION merchant.merchant_instances_after_delete_trigger();
diff --git a/src/backenddb/pg_do_handle_category_changes.sql b/src/backenddb/pg_do_handle_category_changes.sql
@@ -33,24 +33,23 @@ BEGIN
url,
http_method,
body_template
- FROM merchant.merchant_webhook
+ FROM merchant_webhook
WHERE event_type = 'category_added'
- AND merchant_serial = my_merchant_serial
LOOP
-- Resolve placeholders for the current webhook
resolved_body := webhook.body_template;
- resolved_body := replace_placeholder(resolved_body,
- 'webhook_type',
- 'category_added');
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
+ 'webhook_type',
+ 'category_added');
+ resolved_body := merchant.replace_placeholder(resolved_body,
'category_serial',
NEW.category_serial::TEXT);
- resolved_body := replace_placeholder(resolved_body,
- 'category_name',
- NEW.category_name);
- resolved_body := replace_placeholder(resolved_body,
- 'merchant_serial',
- my_merchant_serial::TEXT);
+ resolved_body := merchant.replace_placeholder(resolved_body,
+ 'category_name',
+ NEW.category_name);
+ resolved_body := merchant.replace_placeholder(resolved_body,
+ 'merchant_serial',
+ my_merchant_serial::TEXT);
-- Insert into pending webhooks for this webhook
INSERT INTO merchant.merchant_pending_webhooks
@@ -75,28 +74,27 @@ BEGIN
url,
http_method,
body_template
- FROM merchant.merchant_webhook
+ FROM merchant_webhook
WHERE event_type = 'category_updated'
- AND merchant_serial = my_merchant_serial
LOOP
-- Resolve placeholders for the current webhook
resolved_body := webhook.body_template;
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'webhook_type',
'category_updated');
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'category_serial',
NEW.category_serial::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_category_name',
OLD.category_name);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'category_name',
NEW.category_name);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'category_name_i18n',
NEW.category_name_i18n::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_category_name_i18n',
OLD.category_name_i18n::TEXT);
@@ -123,19 +121,18 @@ BEGIN
url,
http_method,
body_template
- FROM merchant.merchant_webhook
+ FROM merchant_webhook
WHERE event_type = 'category_deleted'
- AND merchant_serial = my_merchant_serial
LOOP
-- Resolve placeholders for the current webhook
resolved_body := webhook.body_template;
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'webhook_type',
'category_deleted');
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'category_serial',
OLD.category_serial::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'category_name',
OLD.category_name);
diff --git a/src/backenddb/pg_do_handle_inventory_changes.sql b/src/backenddb/pg_do_handle_inventory_changes.sql
@@ -18,12 +18,9 @@
CREATE OR REPLACE FUNCTION handle_inventory_changes()
RETURNS TRIGGER AS $$
DECLARE
- my_merchant_serial BIGINT;
resolved_body TEXT;
webhook RECORD; -- To iterate over all matching webhooks
BEGIN
- -- Fetch the merchant_serial directly from the NEW or OLD row
- my_merchant_serial := COALESCE(OLD.merchant_serial, NEW.merchant_serial);
-- INSERT case: Notify webhooks for inventory addition
IF TG_OP = 'INSERT' THEN
@@ -33,58 +30,57 @@ BEGIN
url,
http_method,
body_template
- FROM merchant.merchant_webhook
+ FROM merchant_webhook
WHERE event_type = 'inventory_added'
- AND merchant_serial = my_merchant_serial
LOOP
-- Resolve placeholders for the current webhook
resolved_body := webhook.body_template;
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'webhook_type',
'inventory_added');
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'product_serial',
NEW.product_serial::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'product_id',
NEW.product_id);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'description',
NEW.description);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'description_i18n',
NEW.description_i18n::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'unit',
NEW.unit);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'image',
NEW.image);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'taxes',
NEW.taxes::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'price',
NEW.price_array[1]::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'unit_price',
NEW.price_array::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_stock',
NEW.total_stock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_sold',
NEW.total_sold::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_lost',
NEW.total_lost::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'address',
NEW.address::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'next_restock',
NEW.next_restock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'minimum_age',
NEW.minimum_age::TEXT);
@@ -111,97 +107,96 @@ BEGIN
url,
http_method,
body_template
- FROM merchant.merchant_webhook
+ FROM merchant_webhook
WHERE event_type = 'inventory_updated'
- AND merchant_serial = my_merchant_serial
LOOP
-- Resolve placeholders for the current webhook
resolved_body := webhook.body_template;
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'webhook_type',
'inventory_updated');
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'product_serial',
NEW.product_serial::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'product_id',
NEW.product_id);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_description',
OLD.description);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'description',
NEW.description);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_description_i18n',
OLD.description_i18n::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'description_i18n',
NEW.description_i18n::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_unit',
OLD.unit);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'unit',
NEW.unit);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_image',
OLD.image);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'image',
NEW.image);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_taxes',
OLD.taxes::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'taxes',
NEW.taxes::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_price',
OLD.price_array[1]::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_unit_price',
OLD.price_array::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'price',
NEW.price_array[1]::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'unit_price',
NEW.price_array::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_total_stock',
OLD.total_stock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_stock',
NEW.total_stock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_total_sold',
OLD.total_sold::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_sold',
NEW.total_sold::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_total_lost',
OLD.total_lost::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_lost',
NEW.total_lost::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_address',
OLD.address::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'address',
NEW.address::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_next_restock',
OLD.next_restock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'next_restock',
NEW.next_restock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'old_minimum_age',
OLD.minimum_age::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'minimum_age',
NEW.minimum_age::TEXT);
@@ -228,58 +223,57 @@ BEGIN
url,
http_method,
body_template
- FROM merchant.merchant_webhook
+ FROM merchant_webhook
WHERE event_type = 'inventory_deleted'
- AND merchant_serial = my_merchant_serial
LOOP
-- Resolve placeholders for the current webhook
resolved_body := webhook.body_template;
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'webhook_type',
'inventory_deleted');
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'product_serial',
OLD.product_serial::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'product_id',
OLD.product_id);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'description',
OLD.description);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'description_i18n',
OLD.description_i18n::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'unit',
OLD.unit);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'image',
OLD.image);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'taxes',
OLD.taxes::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'price',
OLD.price_array[1]::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'unit_price',
OLD.price_array::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_stock',
OLD.total_stock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_sold',
OLD.total_sold::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'total_lost',
OLD.total_lost::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'address',
OLD.address::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'next_restock',
OLD.next_restock::TEXT);
- resolved_body := replace_placeholder(resolved_body,
+ resolved_body := merchant.replace_placeholder(resolved_body,
'minimum_age',
OLD.minimum_age::TEXT);
diff --git a/src/backenddb/pg_fixup_instance_schema.sql b/src/backenddb/pg_fixup_instance_schema.sql
@@ -0,0 +1,48 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.fixup_instance_schema(BIGINT);
+CREATE FUNCTION merchant.fixup_instance_schema(
+ in_min_version BIGINT
+)
+ RETURNS void
+ LANGUAGE plpgsql
+AS $$
+DECLARE
+ rec RECORD;
+ r RECORD;
+ my_schema_name TEXT;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ FROM merchant.merchant_instances
+ LOOP
+ my_schema_name := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ -- Call fixup functions
+ FOR r IN
+ SELECT migration_name
+ FROM merchant.instance_fixups
+ WHERE version >= in_min_version
+ ORDER BY version ASC
+ LOOP
+ EXECUTE format('CALL %I(%I)', r.migration_name, my_schema_name);
+ END LOOP;
+ END LOOP;
+END $$;
+
+COMMENT ON FUNCTION merchant.create_instance_schema(BIGINT)
+ IS 'Updates all schema to the given version';
+
diff --git a/src/backenddb/pg_inactivate_account.sql b/src/backenddb/pg_inactivate_account.sql
@@ -1,49 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2026 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-DROP FUNCTION IF EXISTS merchant_do_inactivate_account;
-CREATE FUNCTION merchant_do_inactivate_account (
- IN in_instance_name TEXT
- ,IN in_h_wire BYTEA
- ,OUT out_found BOOL
-)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_instance INT8;
-BEGIN
- SELECT merchant_serial
- INTO my_instance
- FROM merchant_instances
- WHERE merchant_id=in_instance_name;
- IF NOT FOUND
- THEN
- out_found = FALSE;
- RETURN;
- END IF;
- UPDATE merchant_accounts
- SET active=FALSE
- WHERE h_wire=in_h_wire
- AND merchant_serial=my_instance;
- out_found = FOUND;
- IF out_found
- THEN
- -- Notify taler-merchant-kyccheck about the change in
- -- accounts. (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
- NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG;
- END IF;
-
-END $$;
diff --git a/src/backenddb/pg_increment_money_pots.sql b/src/backenddb/pg_increment_money_pots.sql
@@ -1,109 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-SET search_path TO merchant;
-
-DROP FUNCTION IF EXISTS merchant_do_increment_money_pots;
-CREATE FUNCTION merchant_do_increment_money_pots (
- IN in_instance_id TEXT,
- IN ina_money_pots_ids INT8[],
- IN ina_increments taler_amount_currency[],
- OUT out_not_found BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- i INT;
- ini_current_pot_id INT8;
- ini_current_increment taler_amount_currency;
- my_totals taler_amount_currency[];
- currency_found BOOL;
- j INT;
-BEGIN
-
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-
-IF NOT FOUND
-THEN
- out_not_found = TRUE;
- RETURN;
-END IF;
-
-out_not_found = FALSE;
-
-IF ( COALESCE(array_length(ina_money_pots_ids, 1), 0) !=
- COALESCE(array_length(ina_increments, 1), 0) )
-THEN
- RAISE EXCEPTION 'Array lengths must match';
-END IF;
-
-FOR i IN 1..COALESCE(array_length(ina_money_pots_ids, 1),0)
- LOOP
- ini_current_pot_id = ina_money_pots_ids[i];
- ini_current_increment = ina_increments[i];
-
- SELECT pot_totals
- INTO my_totals
- FROM merchant_money_pots
- WHERE money_pot_serial = ini_current_pot_id
- AND merchant_serial = my_merchant_id;
-
- IF NOT FOUND
- THEN
- -- If pot does not exist, we just ignore the entire
- -- requested increment, but update the return value.
- -- (We may have other pots to update, so we continue
- -- to iterate!).
- out_not_found = TRUE;
- ELSE
- -- Check if currency exists in pot_totals and update
- currency_found = FALSE;
-
- FOR j IN 1..COALESCE(array_length(my_totals, 1), 0)
- LOOP
- IF (my_totals[j]).curr = (ini_current_increment).curr
- THEN
- my_totals[j].frac
- = my_totals[j].frac + ini_current_increment.frac;
- my_totals[j].val
- = my_totals[j].val + ini_current_increment.val;
- IF my_totals[j].frac >= 100000000
- THEN
- my_totals[j].frac = my_totals[j].frac - 100000000;
- my_totals[j].val = my_totals[j].val + 1;
- END IF;
- currency_found = TRUE;
- EXIT; -- break out of loop
- END IF;
- END LOOP;
-
- IF NOT currency_found
- THEN
- my_totals = array_append(my_totals, ini_current_increment);
- END IF;
-
- UPDATE merchant_money_pots
- SET pot_totals = my_totals
- WHERE money_pot_serial = ini_current_pot_id
- AND merchant_serial = my_merchant_id;
-
- END IF;
- END LOOP;
-
-END $$;
diff --git a/src/backenddb/pg_insert_deposit_confirmation.sql b/src/backenddb/pg_insert_deposit_confirmation.sql
@@ -1,171 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2026 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-DROP FUNCTION IF EXISTS merchant_do_insert_deposit_confirmation;
-CREATE FUNCTION merchant_do_insert_deposit_confirmation (
- IN in_instance_id TEXT,
- IN in_h_contract_terms BYTEA,
- IN in_deposit_timestamp INT8,
- IN in_exchange_url TEXT,
- IN in_total_without_fee taler_amount_currency,
- IN in_wire_fee taler_amount_currency,
- IN in_h_wire BYTEA,
- IN in_exchange_sig BYTEA,
- IN in_exchange_pub BYTEA,
- IN in_wire_transfer_deadline INT8,
- IN in_notify_arg_str TEXT,
- OUT out_no_instance BOOL,
- OUT out_no_order BOOL,
- OUT out_no_account BOOL,
- OUT out_no_signkey BOOL,
- OUT out_conflict BOOL,
- OUT out_deposit_confirmation_serial INT8)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_order_serial INT8;
- my_account_serial INT8;
- my_signkey_serial INT8;
- my_record RECORD;
- my_bank_serial_id INT8;
- my_credit_amount taler_amount_currency;
-BEGIN
-
-out_no_instance=TRUE;
-out_no_order=TRUE;
-out_no_account=TRUE;
-out_no_signkey=TRUE;
-out_conflict=FALSE;
-out_deposit_confirmation_serial=0;
-
--- Which instance are we using?
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-IF NOT FOUND
-THEN
- RETURN;
-END IF;
-out_no_instance=FALSE;
-
-SELECT account_serial
- INTO my_account_serial
- FROM merchant_accounts
- WHERE merchant_serial=my_merchant_id
- AND h_wire=in_h_wire;
-IF NOT FOUND
-THEN
- RETURN;
-END IF;
-out_no_account=FALSE;
-
-SELECT signkey_serial
- INTO my_signkey_serial
- FROM merchant_exchange_signing_keys
- WHERE exchange_pub=in_exchange_pub
- ORDER BY start_date DESC
- LIMIT 1;
-IF NOT FOUND
-THEN
- RETURN;
-END IF;
-out_no_signkey=FALSE;
-
-SELECT order_serial
- INTO my_order_serial
- FROM merchant_contract_terms
- WHERE merchant_serial=my_merchant_id
- AND h_contract_terms=in_h_contract_terms;
-IF NOT FOUND
-THEN
- RETURN;
-END IF;
-out_no_order=FALSE;
-
-SELECT deposit_confirmation_serial
- ,deposit_timestamp
- ,exchange_url
- ,total_without_fee
- ,wire_fee
- ,wire_transfer_deadline
- ,account_serial
- INTO my_record
- FROM merchant_deposit_confirmations
- WHERE order_serial=my_order_serial
- AND exchange_url=in_exchange_url;
-IF NOT FOUND
-THEN
- INSERT INTO merchant_deposit_confirmations
- (order_serial
- ,deposit_timestamp
- ,exchange_url
- ,total_without_fee
- ,wire_fee
- ,exchange_sig
- ,wire_transfer_deadline
- ,signkey_serial
- ,account_serial
- ) VALUES (
- my_order_serial
- ,in_deposit_timestamp
- ,in_exchange_url
- ,in_total_without_fee
- ,in_wire_fee
- ,in_exchange_sig
- ,in_wire_transfer_deadline
- ,my_signkey_serial
- ,my_account_serial
- ) RETURNING deposit_confirmation_serial
- INTO out_deposit_confirmation_serial;
-ELSE
- out_deposit_confirmation_serial = my_record.deposit_confirmation_serial;
- IF (in_deposit_timestamp,
- in_wire_transfer_deadline,
- in_wire_fee,
- my_account_serial)
- IS DISTINCT FROM
- (my_record.deposit_timestamp,
- my_record.wire_transfer_deadline,
- my_record.wire_fee,
- my_record.account_serial)
- THEN
- out_conflict = TRUE;
- RETURN;
- END IF;
- IF ( ((in_total_without_fee).val < (my_record.total_without_fee).val) OR
- ( ((in_total_without_fee).val = (my_record.total_without_fee).val) AND
- ((in_total_without_fee).frac <= (my_record.total_without_fee).frac) ) )
- THEN
- -- new amount smaller or did not change, do NOT update.
- RETURN;
- END IF;
-
- -- Same deposit, but total amount increased, store this!
- UPDATE merchant_deposit_confirmations
- SET total_without_fee = in_total_without_fee
- ,exchange_sig = in_exchange_sig
- ,signkey_serial = my_signkey_serial
- WHERE deposit_confirmation_serial = my_record.deposit_confirmation_serial;
-
-END IF;
-
--- Do notify on TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE
-PERFORM pg_notify ('XBZ19D98AK2REYNX93F736A56MT14SCY2EEX7XNXQMNCQ01B121R0',
- in_notify_arg_str);
-
-END $$;
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.sql b/src/backenddb/pg_insert_deposit_to_transfer.sql
@@ -1,136 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2024, 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_insert_deposit_to_transfer;
-CREATE FUNCTION merchant_insert_deposit_to_transfer (
- IN in_deposit_serial INT8,
- IN in_coin_contribution taler_amount_currency,
- IN in_execution_time INT8,
- IN in_exchange_url TEXT,
- IN in_h_wire BYTEA,
- IN in_exchange_sig BYTEA,
- IN in_exchange_pub BYTEA,
- IN in_wtid BYTEA,
- OUT out_dummy BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_signkey_serial INT8;
- my_account_serial INT8;
- my_decose INT8;
- my_expected_credit_serial INT8;
- my_wire_pending_cleared BOOL;
-BEGIN
- -- Just to return something (for now).
- out_dummy=FALSE;
-
--- Find exchange sign key
-SELECT signkey_serial
- INTO my_signkey_serial
- FROM merchant_exchange_signing_keys
- WHERE exchange_pub=in_exchange_pub
- ORDER BY start_date DESC
- LIMIT 1;
-
-IF NOT FOUND
-THEN
- -- Maybe 'keys' is outdated, try again in 8 hours.
- UPDATE merchant_deposits
- SET settlement_last_ec=2029 -- MERCHANT_EXCHANGE_SIGN_PUB_UNKNOWN
- ,settlement_last_http_status=200
- ,settlement_last_detail=ENCODE(in_exchange_pub, 'hex')
- ,settlement_wtid=in_wtid
- ,settlement_retry_needed=TRUE
- ,settlement_retry_time=(EXTRACT(epoch FROM (CURRENT_TIME + interval '8 hours')) * 1000000)::INT8
- WHERE deposit_serial=in_deposit_serial;
- RETURN;
-END IF;
-
--- Find deposit confirmation
-SELECT deposit_confirmation_serial
- INTO my_decose
- FROM merchant_deposits
- WHERE deposit_serial=in_deposit_serial;
-
--- Find merchant account
-SELECT account_serial
- INTO my_account_serial
- FROM merchant_deposit_confirmations mdc
- JOIN merchant_accounts ma
- USING (account_serial)
- WHERE mdc.deposit_confirmation_serial=my_decose
- AND ma.h_wire=in_h_wire;
-
-IF NOT FOUND
-THEN
- -- Merchant account referenced in exchange response is unknown to us.
- -- Remember fatal error and do not try again.
- UPDATE merchant_deposits
- SET settlement_last_ec=2558 -- MERCHANT_EXCHANGE_TRANSFERS_TARGET_ACCOUNT_UNKNOWN
- ,settlement_last_http_status=200
- ,settlement_last_detail=ENCODE(in_h_wire, 'hex')
- ,settlement_wtid=in_wtid
- ,settlement_retry_needed=FALSE
- ,settlement_coin_contribution=in_coin_contribution
- ,signkey_serial=my_signkey_serial
- ,settlement_exchange_sig=in_exchange_sig
- WHERE deposit_serial=in_deposit_serial;
- RETURN;
-END IF;
-
-
--- Make sure wire transfer is expected.
-SELECT expected_credit_serial
- INTO my_expected_credit_serial
- FROM merchant_expected_transfers
- WHERE wtid=in_wtid
- AND exchange_url=in_exchange_url
- AND account_serial=my_account_serial;
-
-IF NOT FOUND
-THEN
- INSERT INTO merchant_expected_transfers
- (exchange_url
- ,wtid
- ,account_serial
- ,expected_time)
- VALUES
- (in_exchange_url
- ,in_wtid
- ,my_account_serial
- ,in_execution_time)
- RETURNING expected_credit_serial
- INTO my_expected_credit_serial;
-END IF;
-
--- Finally, update merchant_deposits so we do not try again.
-UPDATE merchant_deposits
- SET settlement_last_ec=0
- ,settlement_last_http_status=200
- ,settlement_last_detail=NULL
- ,settlement_wtid=in_wtid
- ,settlement_retry_needed=FALSE
- ,settlement_coin_contribution=in_coin_contribution
- ,settlement_expected_credit_serial=my_expected_credit_serial
- ,signkey_serial=my_signkey_serial
- ,settlement_exchange_sig=in_exchange_sig
- WHERE deposit_serial=in_deposit_serial;
-
--- MERCHANT_WIRE_TRANSFER_EXPECTED
-NOTIFY XR6849FMRD2AJFY1E2YY0GWA8GN0YT407Z66WHJB0SAKJWF8G2Q60;
-
-END $$;
diff --git a/src/backenddb/pg_insert_product.sql b/src/backenddb/pg_insert_product.sql
@@ -1,246 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2024, 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-DROP FUNCTION IF EXISTS merchant_do_insert_product;
-CREATE FUNCTION merchant_do_insert_product (
- IN in_instance_id TEXT,
- IN in_product_id TEXT,
- IN in_description TEXT,
- IN in_description_i18n JSONB, -- $4
- IN in_unit TEXT,
- IN in_image TEXT,
- IN in_taxes JSONB, -- $7
- IN ina_price_list taler_amount_currency[],
- IN in_total_stock INT8, -- $9
- IN in_total_stock_frac INT4, --$10
- IN in_allow_fractional_quantity BOOL,
- IN in_fractional_precision_level INT4,
- IN in_address JSONB, -- $13
- IN in_next_restock INT8,
- IN in_minimum_age INT4,
- IN ina_categories INT8[], -- $16
- IN in_product_name TEXT,
- IN in_product_group_id INT8, -- NULL for default
- IN in_money_pot_id INT8, -- NULL for none
- IN in_price_is_net BOOL, -- $20
- OUT out_no_instance BOOL,
- OUT out_conflict BOOL,
- OUT out_no_cat INT8,
- OUT out_no_group BOOL,
- OUT out_no_pot BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_product_serial INT8;
- i INT8;
- ini_cat INT8;
-BEGIN
-
-out_no_group = FALSE;
-out_no_pot = FALSE;
-
--- Which instance are we using?
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-
-IF NOT FOUND
-THEN
- out_no_instance=TRUE;
- out_conflict=FALSE;
- out_no_cat=NULL;
- RETURN;
-END IF;
-out_no_instance=FALSE;
-
-IF in_product_group_id IS NOT NULL
-THEN
- PERFORM FROM merchant_product_groups
- WHERE product_group_serial=in_product_group_id
- AND merchant_serial=my_merchant_id;
- IF NOT FOUND
- THEN
- out_no_group=TRUE;
- out_conflict=FALSE;
- out_no_cat=NULL;
- RETURN;
- END IF;
-END IF;
-
-IF in_money_pot_id IS NOT NULL
-THEN
- PERFORM FROM merchant_money_pots
- WHERE money_pot_serial=in_money_pot_id
- AND merchant_serial=my_merchant_id;
- IF NOT FOUND
- THEN
- out_no_pot=TRUE;
- out_conflict=FALSE;
- out_no_cat=NULL;
- RETURN;
- END IF;
-END IF;
-
-INSERT INTO merchant_inventory
- (merchant_serial
- ,product_id
- ,product_name
- ,description
- ,description_i18n
- ,unit
- ,image
- ,image_hash
- ,taxes
- ,price_array
- ,total_stock
- ,total_stock_frac
- ,allow_fractional_quantity
- ,fractional_precision_level
- ,address
- ,next_restock
- ,minimum_age
- ,product_group_serial
- ,money_pot_serial
- ,price_is_net
-) VALUES (
- my_merchant_id
- ,in_product_id
- ,in_product_name
- ,in_description
- ,in_description_i18n
- ,in_unit
- ,in_image
- ,CASE
- WHEN (in_image IS NULL) OR (in_image = '')
- THEN NULL
- ELSE encode(public.digest(convert_to(in_image, 'UTF8'),
- 'sha256'),
- 'hex')
- END
- ,in_taxes
- ,ina_price_list
- ,in_total_stock
- ,in_total_stock_frac
- ,in_allow_fractional_quantity
- ,in_fractional_precision_level
- ,in_address
- ,in_next_restock
- ,in_minimum_age
- ,in_product_group_id
- ,in_money_pot_id
- ,in_price_is_net
- )
-ON CONFLICT (merchant_serial, product_id) DO NOTHING
- RETURNING product_serial
- INTO my_product_serial;
-
-
-IF NOT FOUND
-THEN
- -- Check for idempotency
- SELECT product_serial
- INTO my_product_serial
- FROM merchant_inventory
- WHERE merchant_serial=my_merchant_id
- AND product_id=in_product_id
- AND product_name=in_product_name
- AND description=in_description
- AND description_i18n=in_description_i18n
- AND unit=in_unit
- AND image=in_image
- AND taxes=in_taxes
- AND to_jsonb(COALESCE(price_array, ARRAY[]::taler_amount_currency[]))
- = to_jsonb(COALESCE(ina_price_list, ARRAY[]::taler_amount_currency[])) -- FIXME: wild. Why so complicated?
- AND total_stock=in_total_stock
- AND total_stock_frac=in_total_stock_frac
- AND allow_fractional_quantity=in_allow_fractional_quantity
- AND fractional_precision_level=in_fractional_precision_level
- AND address=in_address
- AND next_restock=in_next_restock
- AND minimum_age=in_minimum_age
- AND product_group_serial IS NOT DISTINCT FROM in_product_group_id
- AND money_pot_serial IS NOT DISTINCT FROM in_money_pot_id
- AND price_is_net=in_price_is_net;
- IF NOT FOUND
- THEN
- out_conflict=TRUE;
- out_no_cat=NULL;
- RETURN;
- END IF;
-
- -- Check categories match as well
- FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
- LOOP
- ini_cat=ina_categories[i];
-
- PERFORM
- FROM merchant_product_categories
- WHERE product_serial=my_product_serial
- AND category_serial=ini_cat;
- IF NOT FOUND
- THEN
- out_conflict=TRUE;
- out_no_cat=NULL;
- RETURN;
- END IF;
- END LOOP;
-
- -- Also check there are no additional categories
- -- in either set.
- SELECT COUNT(*)
- INTO i
- FROM merchant_product_categories
- WHERE product_serial=my_product_serial;
- IF i != COALESCE(array_length(ina_categories,1),0)
- THEN
- out_conflict=TRUE;
- out_no_cat=NULL;
- RETURN;
- END IF;
-
- -- Is idempotent!
- out_conflict=FALSE;
- out_no_cat=NULL;
- RETURN;
-END IF;
-out_conflict=FALSE;
-
-
--- Add categories
-FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
-LOOP
- ini_cat=ina_categories[i];
-
- INSERT INTO merchant_product_categories
- (product_serial
- ,category_serial)
- VALUES
- (my_product_serial
- ,ini_cat)
- ON CONFLICT DO NOTHING;
-
- IF NOT FOUND
- THEN
- out_no_cat=i;
- RETURN;
- END IF;
-END LOOP;
-
--- Success!
-out_no_cat=NULL;
-END $$;
diff --git a/src/backenddb/pg_insert_transfer.sql b/src/backenddb/pg_insert_transfer.sql
@@ -1,122 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2026 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-DROP FUNCTION IF EXISTS merchant_do_insert_transfer;
-CREATE FUNCTION merchant_do_insert_transfer (
- IN in_instance_id TEXT,
- IN in_exchange_url TEXT,
- IN in_wtid BYTEA,
- IN in_credit_amount taler_amount_currency,
- IN in_credited_account_payto TEXT,
- IN in_bank_serial_id INT8, -- can be NULL if unknown
- IN in_execution_time INT8,
- OUT out_no_instance BOOL,
- OUT out_no_account BOOL,
- OUT out_conflict BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_account_serial INT8;
- my_record RECORD;
- my_bank_serial_id INT8;
- my_credit_amount taler_amount_currency;
-BEGIN
-
-out_conflict=FALSE;
-
--- Which instance are we using?
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-IF NOT FOUND
-THEN
- out_no_instance=TRUE;
- out_no_account=TRUE; -- also true...
- RETURN;
-END IF;
-out_no_instance=FALSE;
-
-SELECT account_serial
- INTO my_account_serial
- FROM merchant_accounts
- WHERE REGEXP_REPLACE(payto_uri,
- '\\?.*','')
- =REGEXP_REPLACE(in_credited_account_payto,
- '\\?.*','')
- AND merchant_serial=my_merchant_id;
-IF NOT FOUND
-THEN
- out_no_account=TRUE;
- RETURN;
-END IF;
-out_no_account=FALSE;
-
-SELECT bank_serial_id
- ,credit_amount
- INTO my_record
- FROM merchant_transfers
- WHERE wtid=in_wtid
- AND account_serial=my_account_serial
- AND exchange_url=in_exchange_url;
-IF NOT FOUND
-THEN
- INSERT INTO merchant_transfers
- (exchange_url
- ,wtid
- ,credit_amount
- ,account_serial
- ,bank_serial_id
- ,execution_time
- ) VALUES
- (in_exchange_url
- ,in_wtid
- ,in_credit_amount
- ,my_account_serial
- ,in_bank_serial_id
- ,in_execution_time);
- -- Do notify on TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED
- NOTIFY XJ5N652FA4TBS2WXGY3S1FMPMQYTD8KAZA9B7HW9JWJ4PZ2DB852G;
- RETURN;
-END IF;
-
-my_bank_serial_id = my_record.bank_serial_id;
-my_credit_amount = my_record.credit_amount;
-
-IF ( (in_credit_amount.val != my_credit_amount.val) OR
- (in_credit_amount.frac != my_credit_amount.frac) OR
- (in_credit_amount.curr != my_credit_amount.curr) )
-THEN
- out_conflict = TRUE; -- amounts differ, not OK!
- RETURN;
-END IF;
-
-IF ( (my_bank_serial_id IS NULL) AND
- (in_bank_serial_id IS NOT NULL) )
-THEN
- -- We learned the bank_bank_serial_id, update that
- UPDATE merchant_transfers
- SET bank_serial_id=in_bank_serial_id
- WHERE wtid=in_wtid
- AND account_serial=my_account_serial
- AND exchange_url=in_exchange_url;
- RETURN;
-END IF;
-
--- idempotent request, success.
-
-END $$;
diff --git a/src/backenddb/pg_insert_transfer_details.sql b/src/backenddb/pg_insert_transfer_details.sql
@@ -1,269 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2024, 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_do_insert_transfer_details;
-CREATE FUNCTION merchant_do_insert_transfer_details (
- IN in_instance_id TEXT,
- IN in_exchange_url TEXT,
- IN in_payto_uri TEXT,
- IN in_wtid BYTEA,
- IN in_execution_time INT8,
- IN in_exchange_pub BYTEA,
- IN in_exchange_sig BYTEA,
- IN in_total_amount taler_amount_currency,
- IN in_wire_fee taler_amount_currency,
- IN ina_coin_values taler_amount_currency[],
- IN ina_deposit_fees taler_amount_currency[],
- IN ina_coin_pubs BYTEA[],
- IN ina_contract_terms BYTEA[],
- OUT out_no_instance BOOL,
- OUT out_no_account BOOL,
- OUT out_no_exchange BOOL,
- OUT out_duplicate BOOL,
- OUT out_conflict BOOL,
- OUT out_order_id TEXT,
- OUT out_merchant_pub BYTEA)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_signkey_serial INT8;
- my_expected_credit_serial INT8;
- my_affected_orders RECORD;
- my_merchant_serial INT8;
- my_decose INT8;
- my_order_id TEXT;
- i INT8;
- curs CURSOR (arg_coin_pub BYTEA) FOR
- SELECT mcon.deposit_confirmation_serial,
- mcon.order_serial
- FROM merchant_deposits dep
- JOIN merchant_deposit_confirmations mcon
- USING (deposit_confirmation_serial)
- WHERE dep.coin_pub=arg_coin_pub;
- ini_coin_pub BYTEA;
- ini_contract_term BYTEA;
- ini_coin_value taler_amount_currency;
- ini_deposit_fee taler_amount_currency;
-BEGIN
-
--- Which instance are we using?
-SELECT merchant_serial, merchant_pub
- INTO my_merchant_id, out_merchant_pub
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-IF NOT FOUND
-THEN
- out_no_instance=TRUE;
- out_no_account=FALSE;
- out_no_exchange=FALSE;
- out_duplicate=FALSE;
- out_conflict=FALSE;
- RETURN;
-END IF;
-out_no_instance=FALSE;
-
--- Determine account that was credited.
-SELECT expected_credit_serial
- INTO my_expected_credit_serial
- FROM merchant_expected_transfers
- WHERE exchange_url=in_exchange_url
- AND wtid=in_wtid
- AND account_serial=
- (SELECT account_serial
- FROM merchant_accounts
- WHERE payto_uri=in_payto_uri
- AND merchant_serial=my_merchant_id);
-
-IF NOT FOUND
-THEN
- out_no_account=TRUE;
- out_no_exchange=FALSE;
- out_duplicate=FALSE;
- out_conflict=FALSE;
- RETURN;
-END IF;
-out_no_account=FALSE;
-
--- Find exchange sign key
-SELECT signkey_serial
- INTO my_signkey_serial
- FROM merchant_exchange_signing_keys
- WHERE exchange_pub=in_exchange_pub
- ORDER BY start_date DESC
- LIMIT 1;
-
-IF NOT FOUND
-THEN
- out_no_exchange=TRUE;
- out_conflict=FALSE;
- out_duplicate=FALSE;
- RETURN;
-END IF;
-out_no_exchange=FALSE;
-
--- Add signature first, check for idempotent request
-INSERT INTO merchant_transfer_signatures
- (expected_credit_serial
- ,signkey_serial
- ,credit_amount
- ,wire_fee
- ,execution_time
- ,exchange_sig)
- VALUES
- (my_expected_credit_serial
- ,my_signkey_serial
- ,in_total_amount
- ,in_wire_fee
- ,in_execution_time
- ,in_exchange_sig)
- ON CONFLICT DO NOTHING;
-
-IF NOT FOUND
-THEN
- PERFORM 1
- FROM merchant_transfer_signatures
- WHERE expected_credit_serial=my_expected_credit_serial
- AND signkey_serial=my_signkey_serial
- AND credit_amount=in_total_amount
- AND wire_fee=in_wire_fee
- AND execution_time=in_execution_time
- AND exchange_sig=in_exchange_sig;
- IF FOUND
- THEN
- -- duplicate case
- out_duplicate=TRUE;
- out_conflict=FALSE;
- RETURN;
- END IF;
- -- conflict case
- out_duplicate=FALSE;
- out_conflict=TRUE;
- RETURN;
-END IF;
-
-out_duplicate=FALSE;
-out_conflict=FALSE;
-
-
-FOR i IN 1..array_length(ina_coin_pubs,1)
-LOOP
- ini_coin_value=ina_coin_values[i];
- ini_deposit_fee=ina_deposit_fees[i];
- ini_coin_pub=ina_coin_pubs[i];
- ini_contract_term=ina_contract_terms[i];
-
- INSERT INTO merchant_expected_transfer_to_coin
- (deposit_serial
- ,expected_credit_serial
- ,offset_in_exchange_list
- ,exchange_deposit_value
- ,exchange_deposit_fee)
- SELECT
- dep.deposit_serial
- ,my_expected_credit_serial
- ,i
- ,ini_coin_value
- ,ini_deposit_fee
- FROM merchant_deposits dep
- JOIN merchant_deposit_confirmations dcon
- USING (deposit_confirmation_serial)
- JOIN merchant_contract_terms cterm
- USING (order_serial)
- WHERE dep.coin_pub=ini_coin_pub
- AND cterm.h_contract_terms=ini_contract_term
- AND cterm.merchant_serial=my_merchant_id;
-
- RAISE NOTICE 'iterating over affected orders';
- OPEN curs (arg_coin_pub:=ini_coin_pub);
- LOOP
- FETCH NEXT FROM curs INTO my_affected_orders;
- EXIT WHEN NOT FOUND;
-
- RAISE NOTICE 'checking affected order for completion';
-
- my_decose=my_affected_orders.deposit_confirmation_serial;
-
- PERFORM FROM merchant_deposits md
- WHERE md.deposit_confirmation_serial=my_decose
- AND (settlement_retry_needed
- OR (settlement_wtid IS NULL) );
- IF NOT FOUND
- THEN
- -- must be all done, clear flag
- UPDATE merchant_deposit_confirmations
- SET wire_pending=FALSE
- WHERE (deposit_confirmation_serial=my_decose);
-
- IF FOUND
- THEN
- -- Also update contract terms, if all (other) associated
- -- deposit_confirmations are also done.
-
- RAISE NOTICE 'checking affected contract for completion';
- PERFORM FROM merchant_deposit_confirmations mdc
- WHERE mdc.wire_pending
- AND mdc.order_serial=my_affected_orders.order_serial;
- IF NOT FOUND
- THEN
-
- UPDATE merchant_contract_terms
- SET wired=TRUE
- WHERE (order_serial=my_affected_orders.order_serial);
-
- -- Select merchant_serial and order_id for webhook
- SELECT merchant_serial, order_id
- INTO my_merchant_serial, my_order_id
- FROM merchant_contract_terms
- WHERE order_serial=my_affected_orders.order_serial;
- out_order_id = my_order_id;
- -- Insert pending webhook if it exists
- INSERT INTO merchant_pending_webhooks
- (merchant_serial
- ,webhook_serial
- ,url
- ,http_method
- ,header
- ,body)
- SELECT mw.merchant_serial
- ,mw.webhook_serial
- ,mw.url
- ,mw.http_method
- ,replace_placeholder(
- replace_placeholder(mw.header_template, 'order_id', my_order_id),
- 'wtid', encode(in_wtid, 'hex')
- )::TEXT
- ,replace_placeholder(
- replace_placeholder(mw.body_template, 'order_id', my_order_id),
- 'wtid', encode(in_wtid, 'hex')
- )::TEXT
- FROM merchant_webhook mw
- WHERE mw.event_type = 'order_settled'
- AND mw.merchant_serial = my_merchant_serial;
- IF FOUND
- THEN
- NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
- END IF; -- found pending order_settled webhooks to deliver
- END IF; -- no more merchant_deposits waiting for wire_pending
- END IF; -- did clear wire_pending flag for deposit confirmation
- END IF; -- no more merchant_deposits wait for settlement
-
- END LOOP; -- END curs LOOP
- CLOSE curs;
-END LOOP; -- END FOR loop
-
-END $$;
diff --git a/src/backenddb/pg_insert_unclaim_signature.sql b/src/backenddb/pg_insert_unclaim_signature.sql
@@ -1,85 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2026 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_do_insert_unclaim_signature;
-CREATE FUNCTION merchant_do_insert_unclaim_signature (
- IN in_instance_id TEXT,
- IN in_order_id TEXT,
- IN in_nonce_str TEXT,
- IN in_notify_str TEXT,
- IN in_h_contract_terms BYTEA,
- IN in_nonce_sig BYTEA,
- OUT out_found BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_serial INT8;
- my_expiration_time INT8;
-BEGIN
-
- SELECT merchant_serial
- INTO my_merchant_serial
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-
- IF NOT FOUND
- THEN
- out_found = FALSE;
- RETURN;
- END IF;
-
- SELECT pay_deadline
- INTO my_expiration_time
- FROM merchant_contract_terms
- WHERE order_id=in_order_id
- AND merchant_serial = my_merchant_serial
- AND contract_terms->>'nonce' = in_nonce_str;
-
- IF NOT FOUND
- THEN
- out_found = FALSE;
- RETURN;
- END IF;
-
- INSERT INTO merchant_unclaim_signatures
- (h_contract_terms
- ,unclaim_sig
- ,expiration_time)
- VALUES
- (in_h_contract_terms
- ,in_nonce_sig
- ,my_expiration_time)
- ON CONFLICT DO NOTHING;
-
- IF FOUND
- THEN
- out_found = TRUE;
-
- -- order status change notification
- EXECUTE FORMAT (
- 'NOTIFY %s'
- ,in_notify_str);
-
- RETURN;
- END IF;
-
- PERFORM FROM merchant_unclaim_signatures
- WHERE h_contract_terms = in_h_contract_terms
- AND unclaim_sig = in_nonce_sig;
- out_found = FOUND;
-
-END $$;
diff --git a/src/backenddb/pg_interval_to_start.sql b/src/backenddb/pg_interval_to_start.sql
@@ -0,0 +1,29 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+DROP FUNCTION IF EXISTS merchant.interval_to_start;
+CREATE OR REPLACE FUNCTION merchant.interval_to_start (
+ IN in_timestamp TIMESTAMP,
+ IN in_range merchant.statistic_range,
+ OUT out_bucket_start INT8
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ out_bucket_start = EXTRACT(EPOCH FROM DATE_TRUNC(in_range::text, in_timestamp));
+END $$;
+
+COMMENT ON FUNCTION merchant.interval_to_start
+ IS 'computes the start time of the bucket for an event at the current time given the desired bucket range';
diff --git a/src/backenddb/pg_merchant_send_kyc_notification.sql b/src/backenddb/pg_merchant_send_kyc_notification.sql
@@ -48,7 +48,7 @@ BEGIN
,notification_language
INTO my_email
,my_notification_language
- FROM merchant_instances
+ FROM merchant.merchant_instances
WHERE merchant_serial=my_instance_serial;
IF NOT FOUND
THEN
diff --git a/src/backenddb/pg_replace_placeholder.sql b/src/backenddb/pg_replace_placeholder.sql
@@ -0,0 +1,41 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS replace_placeholder;
+CREATE FUNCTION replace_placeholder(
+ template TEXT,
+ key TEXT,
+ value TEXT
+)
+ RETURNS TEXT
+ LANGUAGE plpgsql
+ IMMUTABLE
+ STRICT
+ PARALLEL SAFE
+AS $$
+BEGIN
+ RETURN regexp_replace(
+ template,
+ '{{\s*' || key || '\s*}}', -- Match the key with optional spaces
+ value,
+ 'g' -- Global replacement
+ );
+END;
+$$;
+
+COMMENT ON FUNCTION replace_placeholder(TEXT,TEXT,TEXT)
+ IS 'Function to replace placeholders in a string with a given value';
diff --git a/src/backenddb/pg_select_accounts.sql b/src/backenddb/pg_select_accounts.sql
@@ -0,0 +1,73 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.select_accounts;
+CREATE FUNCTION merchant.select_accounts()
+RETURNS TABLE(
+ out_merchant_id TEXT,
+ out_merchant_priv BYTEA,
+ out_h_wire BYTEA,
+ out_salt BYTEA,
+ out_payto_uri TEXT,
+ out_credit_facade_url TEXT,
+ out_credit_facade_credentials JSONB,
+ out_extra_wire_subject_metadata TEXT,
+ out_active BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ rec RECORD;
+BEGIN
+ FOR rec IN
+ SELECT mi.merchant_serial
+ ,mi.merchant_id
+ ,mi.merchant_priv
+ FROM merchant.merchant_instances mi
+ LOOP
+ BEGIN
+ FOR out_h_wire,
+ out_salt,
+ out_payto_uri,
+ out_credit_facade_url,
+ out_credit_facade_credentials,
+ out_extra_wire_subject_metadata,
+ out_active
+ IN EXECUTE format('SELECT'
+ ' h_wire'
+ ',salt'
+ ',payto_uri'
+ ',credit_facade_url'
+ ',credit_facade_credentials'
+ ',extra_wire_subject_metadata'
+ ',active'
+ ' FROM %I.merchant_accounts',
+ 'merchant_instance_' || rec.merchant_serial::TEXT)
+ LOOP
+ out_merchant_id := rec.merchant_id;
+ out_merchant_priv := rec.merchant_priv;
+ RETURN NEXT;
+ END LOOP;
+ EXCEPTION
+ WHEN undefined_table THEN
+ NULL;
+ END;
+ END LOOP;
+END $$;
+
+COMMENT ON FUNCTION merchant.select_accounts()
+ IS 'Returns one row per merchant_account across all (or one) instance schemas;'
+ ' merchant_priv is read from merchant.merchant_instances (NULL if absent).'
+ ' If in_merchant_id is non-NULL, only that instance is scanned.';
diff --git a/src/backenddb/pg_solve_mfa_challenge.sql b/src/backenddb/pg_solve_mfa_challenge.sql
@@ -1,84 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-DROP FUNCTION IF EXISTS merchant_do_solve_mfa_challenge;
-CREATE FUNCTION merchant_do_solve_mfa_challenge (
- IN in_challenge_id INT8,
- IN in_h_body BYTEA,
- IN in_solution TEXT,
- IN in_now INT8,
- OUT out_solved BOOLEAN,
- OUT out_retry_counter INT4
-)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_confirmation_date INT8;
-DECLARE
- my_rec RECORD;
-BEGIN
-
- -- Check if challenge exists and matches
- SELECT
- tc.confirmation_date
- ,tc.retry_counter
- ,(tc.code = in_solution) AS solved
- INTO
- my_rec
- FROM tan_challenges tc
- WHERE tc.challenge_id = in_challenge_id
- AND tc.h_body = in_h_body
- AND tc.expiration_date > in_now;
-
- IF NOT FOUND
- THEN
- out_solved = FALSE;
- RETURN;
- END IF;
-
- my_confirmation_date = my_rec.confirmation_date;
- out_retry_counter = my_rec.retry_counter;
- out_solved = my_rec.solved;
-
- -- Check if already solved before
- IF my_confirmation_date IS NOT NULL
- THEN
- out_solved = TRUE;
- RETURN;
- END IF;
-
- IF (0 = out_retry_counter)
- THEN
- out_solved = FALSE;
- RETURN;
- END IF;
-
- IF out_solved
- THEN
- -- Newly solved, update DB!
- my_confirmation_date = in_now;
- UPDATE tan_challenges
- SET confirmation_date = my_confirmation_date
- WHERE challenge_id = in_challenge_id;
- ELSE
- -- Failed to solve, decrement retry counter
- out_retry_counter = out_retry_counter - 1;
- UPDATE tan_challenges
- SET retry_counter = out_retry_counter
- WHERE challenge_id = in_challenge_id;
- END IF;
-END;
-$$;
diff --git a/src/backenddb/pg_statistics_examples.sql b/src/backenddb/pg_statistics_examples.sql
@@ -21,14 +21,6 @@
-- Everything in one big transaction
BEGIN;
--- Check patch versioning is in place.
--- SELECT _v.register_patch('example-statistics-0001', NULL, NULL);
-
-CREATE SCHEMA IF NOT EXISTS example_statistics;
-
-SET search_path TO example_statistics,merchant;
-
-
-- Setup statistic: what do we want to track for 'deposits'?
-- (Note: this is basically the one "manual" step we might not keep hard-coded)
INSERT INTO merchant_statistic_bucket_meta
@@ -41,7 +33,7 @@ VALUES
('deposits'
,'sales (before refunds)'
,'amount'
- ,ARRAY['second'::statistic_range, 'minute', 'day', 'month', 'quarter', 'year']
+ ,ARRAY['second'::merchant.statistic_range, 'minute', 'day', 'month', 'quarter', 'year']
,ARRAY[120, 120, 95, 36, 40, 100] -- track last 120 s, 120 minutes, 95 days, 36 months, 40 quarters & 100 years
)
ON CONFLICT DO NOTHING;
@@ -72,19 +64,10 @@ CREATE FUNCTION merchant_deposits_statistics_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
-DECLARE
- my_instance INT8;
BEGIN
-- SET search_path TO merchant;
- SELECT mct.merchant_serial
- INTO my_instance
- FROM merchant_contract_terms mct
- JOIN merchant_deposit_confirmations mdc
- USING (order_serial)
- WHERE mdc.deposit_confirmation_serial = NEW.deposit_confirmation_serial;
CALL merchant_do_bump_amount_stat
('deposits'
- ,my_instance
,CURRENT_TIMESTAMP(0)
,NEW.amount_with_fee);
RETURN NEW;
@@ -98,7 +81,6 @@ DECLARE
BEGIN
FOR rec IN
SELECT 'deposits' AS in_slug
- ,mct.merchant_serial AS in_merchant_serial
,TO_TIMESTAMP (mdc.deposit_timestamp / 1000.0 / 1000.0)::TIMESTAMP AS in_timestamp
,mdc.total_without_fee AS in_delta
FROM merchant_deposit_confirmations mdc
@@ -106,7 +88,7 @@ FOR rec IN
USING (order_serial)
WHERE mdc.deposit_timestamp > (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0)) - 365*24*60*60) * 1000000
LOOP
- CALL merchant_do_bump_amount_stat (rec.in_slug, rec.in_merchant_serial, rec.in_timestamp, rec.in_delta);
+ CALL merchant_do_bump_amount_stat (rec.in_slug, rec.in_timestamp, rec.in_delta);
END LOOP;
END $$;
@@ -114,7 +96,8 @@ END $$;
CREATE TRIGGER merchant_deposits_on_insert
AFTER INSERT
ON merchant_deposits
- FOR EACH ROW EXECUTE FUNCTION merchant_deposits_statistics_trigger();
+ FOR EACH ROW
+ EXECUTE FUNCTION merchant_deposits_statistics_trigger();
@@ -131,7 +114,7 @@ VALUES
('products-sold'
,'products sold (only those tracked in inventory)'
,'number'
- ,ARRAY['second'::statistic_range, 'minute', 'day', 'week', 'month', 'quarter', 'year']
+ ,ARRAY['second'::merchant.statistic_range, 'minute', 'day', 'week', 'month', 'quarter', 'year']
,ARRAY[120, 120, 60, 12, 24, 8, 10] -- track last 120s, 120 minutes, 60 days, 12 weeks, 24 months, 8 quarters and 10 years
)
ON CONFLICT DO NOTHING;
@@ -150,7 +133,6 @@ BEGIN
THEN
CALL merchant_do_bump_number_stat
('products-sold'
- ,NEW.merchant_serial
,CURRENT_TIMESTAMP(0)
,my_sold);
END IF;
@@ -163,12 +145,12 @@ CREATE TRIGGER merchant_products_on_sold
ON merchant_inventory
FOR EACH ROW EXECUTE FUNCTION merchant_products_sold_statistics_trigger();
-delete from merchant.merchant_statistic_bucket_counter ;
-delete from merchant.merchant_statistic_bucket_amount ;
-delete from merchant.merchant_statistic_interval_counter;
-delete from merchant.merchant_statistic_interval_amount;
-delete from merchant.merchant_statistic_amount_event;
-delete from merchant.merchant_statistic_counter_event;
+DELETE FROM merchant.merchant_statistic_bucket_counter ;
+DELETE FROM merchant.merchant_statistic_bucket_amount ;
+DELETE FROM merchant.merchant_statistic_interval_counter;
+DELETE FROM merchant.merchant_statistic_interval_amount;
+DELETE FROM merchant.merchant_statistic_amount_event;
+DELETE FROM merchant.merchant_statistic_counter_event;
call merchant_do_bump_number_stat ('products-sold'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP-INTERVAL '2 minutes', 1);
@@ -180,21 +162,21 @@ call merchant_do_bump_amount_stat ('deposits'::text, 6, CURRENT_TIMESTAMP(0)::TI
call merchant_do_bump_number_stat ('products-sold'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP, 4);
call merchant_do_bump_amount_stat ('deposits'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP, (16,16,'EUR')::taler_amount_currency);
-select * from merchant_statistic_interval_number_get ('products-sold', 'default');
+SELECT * FROM merchant_statistic_interval_number_get ('products-sold', 'default');
-select * from merchant_statistic_interval_amount_get ('deposits', 'default');
+SELECT * FROM merchant_statistic_interval_amount_get ('deposits', 'default');
-select * from merchant.merchant_statistic_amount_event;
+SELECT * FROM merchant.merchant_statistic_amount_event;
-select * from merchant.merchant_statistic_counter_event;
+SELECT * FROM merchant.merchant_statistic_counter_event;
-select * from merchant.merchant_statistic_interval_counter;
+SELECT * FROM merchant.merchant_statistic_interval_counter;
-select * from merchant.merchant_statistic_interval_amount;
+SELECT * FROM merchant.merchant_statistic_interval_amount;
-select * from merchant.merchant_statistic_bucket_counter ;
+SELECT * FROM merchant.merchant_statistic_bucket_counter ;
-select * from merchant.merchant_statistic_bucket_amount ;
+SELECT * FROM merchant.merchant_statistic_bucket_amount ;
-- ROLLBACK;
COMMIT;
diff --git a/src/backenddb/pg_statistics_helpers.sql b/src/backenddb/pg_statistics_helpers.sql
@@ -14,26 +14,10 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
-SET search_path TO merchant;
-DROP FUNCTION IF EXISTS interval_to_start;
-CREATE OR REPLACE FUNCTION interval_to_start (
- IN in_timestamp TIMESTAMP,
- IN in_range statistic_range,
- OUT out_bucket_start INT8
-)
-LANGUAGE plpgsql
-AS $$
-BEGIN
- out_bucket_start = EXTRACT(EPOCH FROM DATE_TRUNC(in_range::text, in_timestamp));
-END $$;
-COMMENT ON FUNCTION interval_to_start
- IS 'computes the start time of the bucket for an event at the current time given the desired bucket range';
-
DROP PROCEDURE IF EXISTS merchant_do_bump_number_bucket_stat;
CREATE OR REPLACE PROCEDURE merchant_do_bump_number_bucket_stat(
in_slug TEXT,
- in_merchant_serial BIGINT,
in_timestamp TIMESTAMP,
in_delta INT8
)
@@ -41,7 +25,7 @@ LANGUAGE plpgsql
AS $$
DECLARE
my_meta INT8;
- my_range statistic_range;
+ my_range merchant.statistic_range;
my_bucket_start INT8;
my_curs CURSOR (arg_slug TEXT)
FOR SELECT UNNEST(ranges)
@@ -65,25 +49,22 @@ BEGIN
EXIT WHEN NOT FOUND;
SELECT *
INTO my_bucket_start
- FROM interval_to_start (in_timestamp, my_range);
+ FROM merchant.interval_to_start (in_timestamp, my_range);
UPDATE merchant_statistic_bucket_counter
SET cumulative_number = cumulative_number + in_delta
WHERE bmeta_serial_id=my_meta
- AND merchant_serial=in_merchant_serial
AND bucket_start=my_bucket_start
AND bucket_range=my_range;
IF NOT FOUND
THEN
INSERT INTO merchant_statistic_bucket_counter
(bmeta_serial_id
- ,merchant_serial
,bucket_start
,bucket_range
,cumulative_number
) VALUES (
my_meta
- ,in_merchant_serial
,my_bucket_start
,my_range
,in_delta);
@@ -96,15 +77,14 @@ END $$;
DROP PROCEDURE IF EXISTS merchant_do_bump_amount_bucket_stat;
CREATE OR REPLACE PROCEDURE merchant_do_bump_amount_bucket_stat(
in_slug TEXT,
- in_merchant_serial BIGINT,
in_timestamp TIMESTAMP,
- in_delta taler_amount_currency
+ in_delta merchant.taler_amount_currency
)
LANGUAGE plpgsql
AS $$
DECLARE
my_meta INT8;
- my_range statistic_range;
+ my_range merchant.statistic_range;
my_bucket_start INT8;
my_curs CURSOR (arg_slug TEXT)
FOR SELECT UNNEST(ranges)
@@ -128,7 +108,7 @@ BEGIN
EXIT WHEN NOT FOUND;
SELECT *
INTO my_bucket_start
- FROM interval_to_start (in_timestamp, my_range);
+ FROM merchant.interval_to_start (in_timestamp, my_range);
UPDATE merchant_statistic_bucket_amount
SET
@@ -145,7 +125,6 @@ BEGIN
ELSE 0
END
WHERE bmeta_serial_id=my_meta
- AND merchant_serial=in_merchant_serial
AND curr=(in_delta).curr
AND bucket_start=my_bucket_start
AND bucket_range=my_range;
@@ -153,7 +132,6 @@ BEGIN
THEN
INSERT INTO merchant_statistic_bucket_amount
(bmeta_serial_id
- ,merchant_serial
,bucket_start
,bucket_range
,curr
@@ -161,7 +139,6 @@ BEGIN
,cumulative_frac
) VALUES (
my_meta
- ,in_merchant_serial
,my_bucket_start
,my_range
,(in_delta).curr
@@ -179,7 +156,6 @@ COMMENT ON PROCEDURE merchant_do_bump_amount_bucket_stat
DROP PROCEDURE IF EXISTS merchant_do_bump_number_interval_stat;
CREATE OR REPLACE PROCEDURE merchant_do_bump_number_interval_stat(
in_slug TEXT,
- in_merchant_serial BIGINT,
in_timestamp TIMESTAMP,
in_delta INT8
)
@@ -233,12 +209,10 @@ BEGIN
INSERT INTO merchant_statistic_counter_event AS msce
(imeta_serial_id
- ,merchant_serial
,slot
,delta)
VALUES
(my_meta
- ,in_merchant_serial
,my_start
,in_delta)
ON CONFLICT (imeta_serial_id, merchant_serial, slot)
@@ -250,19 +224,16 @@ BEGIN
UPDATE merchant_statistic_interval_counter
SET cumulative_number = cumulative_number + in_delta
WHERE imeta_serial_id = my_meta
- AND merchant_serial = in_merchant_serial
AND range=my_rangex;
IF NOT FOUND
THEN
INSERT INTO merchant_statistic_interval_counter
(imeta_serial_id
- ,merchant_serial
,range
,event_delimiter
,cumulative_number
) VALUES (
my_meta
- ,in_merchant_serial
,my_rangex
,my_event
,in_delta);
@@ -276,9 +247,8 @@ COMMENT ON PROCEDURE merchant_do_bump_number_interval_stat
DROP PROCEDURE IF EXISTS merchant_do_bump_amount_interval_stat;
CREATE OR REPLACE PROCEDURE merchant_do_bump_amount_interval_stat(
in_slug TEXT,
- in_merchant_serial BIGINT,
in_timestamp TIMESTAMP,
- in_delta taler_amount_currency -- new amount in table that we should add to the tracker
+ in_delta merchant.taler_amount_currency -- new amount in table that we should add to the tracker
)
LANGUAGE plpgsql
AS $$
@@ -330,14 +300,12 @@ BEGIN
INSERT INTO merchant_statistic_amount_event AS msae
(imeta_serial_id
- ,merchant_serial
,slot
,delta_curr
,delta_value
,delta_frac
) VALUES (
my_meta
- ,in_merchant_serial
,my_start
,(in_delta).curr
,(in_delta).val
@@ -375,14 +343,12 @@ BEGIN
ELSE 0
END
WHERE imeta_serial_id=my_meta
- AND merchant_serial=in_merchant_serial
AND range=my_rangex
AND curr=(in_delta).curr;
IF NOT FOUND
THEN
INSERT INTO merchant_statistic_interval_amount
(imeta_serial_id
- ,merchant_serial
,range
,event_delimiter
,curr
@@ -390,7 +356,6 @@ BEGIN
,cumulative_frac
) VALUES (
my_meta
- ,in_merchant_serial
,my_rangex
,my_event
,(in_delta).curr
@@ -405,15 +370,14 @@ COMMENT ON PROCEDURE merchant_do_bump_amount_interval_stat
DROP PROCEDURE IF EXISTS merchant_do_bump_number_stat;
CREATE OR REPLACE PROCEDURE merchant_do_bump_number_stat(
in_slug TEXT,
- in_merchant_serial BIGINT,
in_timestamp TIMESTAMP,
in_delta INT8
)
LANGUAGE plpgsql
AS $$
BEGIN
- CALL merchant_do_bump_number_bucket_stat (in_slug, in_merchant_serial, in_timestamp, in_delta);
- CALL merchant_do_bump_number_interval_stat (in_slug, in_merchant_serial, in_timestamp, in_delta);
+ CALL merchant_do_bump_number_bucket_stat (in_slug, in_timestamp, in_delta);
+ CALL merchant_do_bump_number_interval_stat (in_slug, in_timestamp, in_delta);
END $$;
COMMENT ON PROCEDURE merchant_do_bump_number_stat
IS 'Updates a numeric statistic (bucket or interval)';
@@ -422,373 +386,19 @@ COMMENT ON PROCEDURE merchant_do_bump_number_stat
DROP PROCEDURE IF EXISTS merchant_do_bump_amount_stat;
CREATE OR REPLACE PROCEDURE merchant_do_bump_amount_stat(
in_slug TEXT,
- in_merchant_serial BIGINT,
in_timestamp TIMESTAMP,
- in_delta taler_amount_currency
+ in_delta merchant.taler_amount_currency
)
LANGUAGE plpgsql
AS $$
BEGIN
- CALL merchant_do_bump_amount_bucket_stat (in_slug, in_merchant_serial, in_timestamp, in_delta);
- CALL merchant_do_bump_amount_interval_stat (in_slug, in_merchant_serial, in_timestamp, in_delta);
+ CALL merchant_do_bump_amount_bucket_stat (in_slug, in_timestamp, in_delta);
+ CALL merchant_do_bump_amount_interval_stat (in_slug, in_timestamp, in_delta);
END $$;
COMMENT ON PROCEDURE merchant_do_bump_amount_stat
IS 'Updates an amount statistic (bucket or interval)';
-DROP FUNCTION IF EXISTS merchant_statistic_interval_number_get;
-CREATE FUNCTION merchant_statistic_interval_number_get (
- IN in_slug TEXT,
- IN in_instance_id TEXT
-)
-RETURNS SETOF merchant_statistic_interval_number_get_return_value
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
- my_ranges INT8[];
- my_range INT8;
- my_delta INT8;
- my_meta INT8;
- my_next_max_serial INT8;
- my_instance_id INT8;
- my_rec RECORD;
- my_irec RECORD;
- my_i INT;
- my_min_serial INT8 DEFAULT NULL;
- my_rval merchant_statistic_interval_number_get_return_value;
-BEGIN
- SELECT merchant_serial
- INTO my_instance_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
- IF NOT FOUND
- THEN
- RETURN;
- END IF;
-
- SELECT imeta_serial_id
- ,ranges
- ,precisions
- INTO my_rec
- FROM merchant_statistic_interval_meta
- WHERE slug=in_slug;
- IF NOT FOUND
- THEN
- RETURN;
- END IF;
- my_rval.rvalue = 0;
- my_ranges = my_rec.ranges;
- my_meta = my_rec.imeta_serial_id;
-
- FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
- LOOP
- my_range = my_ranges[my_i];
- SELECT event_delimiter
- ,cumulative_number
- INTO my_irec
- FROM merchant_statistic_interval_counter
- WHERE imeta_serial_id = my_meta
- AND range = my_range
- AND merchant_serial = my_instance_id;
- IF FOUND
- THEN
- my_min_serial = my_irec.event_delimiter;
- my_rval.rvalue = my_rval.rvalue + my_irec.cumulative_number;
-
- -- Check if we have events that left the applicable range
- SELECT SUM(delta) AS delta_sum
- INTO my_irec
- FROM merchant_statistic_counter_event
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND slot < my_time - my_range
- AND nevent_serial_id >= my_min_serial;
-
- IF FOUND AND my_irec.delta_sum IS NOT NULL
- THEN
- my_delta = my_irec.delta_sum;
- my_rval.rvalue = my_rval.rvalue - my_delta;
-
- -- First find out the next event delimiter value
- SELECT nevent_serial_id
- INTO my_next_max_serial
- FROM merchant_statistic_counter_event
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND slot >= my_time - my_range
- AND nevent_serial_id >= my_min_serial
- ORDER BY slot ASC
- LIMIT 1;
-
- IF FOUND
- THEN
- -- remove expired events from the sum of the current slot
-
- UPDATE merchant_statistic_interval_counter
- SET cumulative_number = cumulative_number - my_delta,
- event_delimiter = my_next_max_serial
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND range = my_range;
- ELSE
- -- actually, slot is now empty, remove it entirely
- DELETE FROM merchant_statistic_interval_counter
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND range = my_range;
- END IF;
- IF (my_i < COALESCE(array_length(my_ranges,1),0))
- THEN
- -- carry over all events into the next slot
- UPDATE merchant_statistic_interval_counter AS usic SET
- cumulative_number = cumulative_number + my_delta,
- event_delimiter = LEAST(usic.event_delimiter,my_min_serial)
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND range=my_ranges[my_i+1];
- IF NOT FOUND
- THEN
- INSERT INTO merchant_statistic_interval_counter
- (imeta_serial_id
- ,merchant_serial
- ,range
- ,event_delimiter
- ,cumulative_number
- ) VALUES (
- my_meta
- ,my_instance_id
- ,my_ranges[my_i+1]
- ,my_min_serial
- ,my_delta);
- END IF;
- ELSE
- -- events are obsolete, delete them
- DELETE FROM merchant_statistic_counter_event
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND slot < my_time - my_range;
- END IF;
- END IF;
-
- my_rval.range = my_range;
- RETURN NEXT my_rval;
- END IF;
- END LOOP;
-END $$;
-
-COMMENT ON FUNCTION merchant_statistic_interval_number_get
- IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value for each range';
-
-
-DROP FUNCTION IF EXISTS merchant_statistic_interval_amount_get;
-CREATE FUNCTION merchant_statistic_interval_amount_get (
- IN in_slug TEXT,
- IN in_instance_id TEXT
-)
-RETURNS SETOF merchant_statistic_interval_amount_get_return_value
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
- my_ranges INT8[];
- my_range INT8;
- my_delta_value INT8;
- my_delta_frac INT8;
- my_meta INT8;
- my_instance_id INT8;
- my_next_max_serial INT8;
- my_currency TEXT;
- my_rec RECORD;
- my_irec RECORD;
- my_jrec RECORD;
- my_i INT;
- my_min_serial INT8 DEFAULT NULL;
- my_rval merchant_statistic_interval_amount_get_return_value;
-BEGIN
- SELECT merchant_serial
- INTO my_instance_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
- IF NOT FOUND
- THEN
- RETURN;
- END IF;
-
- SELECT imeta_serial_id
- ,ranges
- ,precisions
- INTO my_rec
- FROM merchant_statistic_interval_meta
- WHERE slug=in_slug;
- IF NOT FOUND
- THEN
- RETURN;
- END IF;
-
- my_meta = my_rec.imeta_serial_id;
- my_ranges = my_rec.ranges;
-
- FOR my_currency IN
- SELECT DISTINCT delta_curr
- FROM merchant_statistic_amount_event
- WHERE imeta_serial_id = my_meta
- LOOP
-
- my_rval.rvalue.val = 0;
- my_rval.rvalue.frac = 0;
- my_rval.rvalue.curr = my_currency;
-
- FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
- LOOP
- my_range = my_ranges[my_i];
- SELECT event_delimiter
- ,cumulative_value
- ,cumulative_frac
- INTO my_irec
- FROM merchant_statistic_interval_amount
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND curr = my_currency
- AND range = my_range;
-
- IF FOUND
- THEN
- my_min_serial = my_irec.event_delimiter;
- my_rval.rvalue.val = (my_rval.rvalue).val + my_irec.cumulative_value + my_irec.cumulative_frac / 100000000;
- my_rval.rvalue.frac = (my_rval.rvalue).frac + my_irec.cumulative_frac % 100000000;
- IF (my_rval.rvalue).frac > 100000000
- THEN
- my_rval.rvalue.frac = (my_rval.rvalue).frac - 100000000;
- my_rval.rvalue.val = (my_rval.rvalue).val + 1;
- END IF;
-
- -- Check if we have events that left the applicable range
- SELECT SUM(delta_value) AS value_sum
- ,SUM(delta_frac) AS frac_sum
- INTO my_jrec
- FROM merchant_statistic_amount_event
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND delta_curr = my_currency
- AND slot < my_time - my_range
- AND aevent_serial_id >= my_min_serial;
-
- IF FOUND AND my_jrec.value_sum IS NOT NULL
- THEN
- -- Normalize sum
- my_delta_value = my_jrec.value_sum + my_jrec.frac_sum / 100000000;
- my_delta_frac = my_jrec.frac_sum % 100000000;
- my_rval.rvalue.val = (my_rval.rvalue).val - my_delta_value;
- IF ((my_rval.rvalue).frac >= my_delta_frac)
- THEN
- my_rval.rvalue.frac = (my_rval.rvalue).frac - my_delta_frac;
- ELSE
- my_rval.rvalue.frac = 100000000 + (my_rval.rvalue).frac - my_delta_frac;
- my_rval.rvalue.val = (my_rval.rvalue).val - 1;
- END IF;
-
- -- First find out the next event delimiter value
- SELECT aevent_serial_id
- INTO my_next_max_serial
- FROM merchant_statistic_amount_event
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND delta_curr = my_currency
- AND slot >= my_time - my_range
- AND aevent_serial_id >= my_min_serial
- ORDER BY slot ASC
- LIMIT 1;
- IF FOUND
- THEN
- -- remove expired events from the sum of the current slot
- UPDATE merchant_statistic_interval_amount SET
- cumulative_value = cumulative_value - my_delta_value
- - CASE
- WHEN cumulative_frac < my_delta_frac
- THEN 1
- ELSE 0
- END,
- cumulative_frac = cumulative_frac - my_delta_frac
- + CASE
- WHEN cumulative_frac < my_delta_frac
- THEN 100000000
- ELSE 0
- END,
- event_delimiter = my_next_max_serial
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND curr = my_currency
- AND range = my_range;
- ELSE
- -- actually, slot is now empty, remove it entirely
- DELETE FROM merchant_statistic_interval_amount
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND curr = my_currency
- AND range = my_range;
- END IF;
- IF (my_i < COALESCE(array_length(my_ranges,1),0))
- THEN
- -- carry over all events into the next (larger) slot
- UPDATE merchant_statistic_interval_amount AS msia SET
- cumulative_value = cumulative_value + my_delta_value
- + CASE
- WHEN cumulative_frac + my_delta_frac > 100000000
- THEN 1
- ELSE 0
- END,
- cumulative_frac = cumulative_frac + my_delta_frac
- - CASE
- WHEN cumulative_frac + my_delta_frac > 100000000
- THEN 100000000
- ELSE 0
- END,
- event_delimiter = LEAST (msia.event_delimiter,my_min_serial)
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND range=my_ranges[my_i+1];
- IF NOT FOUND
- THEN
- INSERT INTO merchant_statistic_interval_amount
- (imeta_serial_id
- ,merchant_serial
- ,event_delimiter
- ,range
- ,curr
- ,cumulative_value
- ,cumulative_frac
- ) VALUES (
- my_meta
- ,my_instance_id
- ,my_min_serial
- ,my_ranges[my_i+1]
- ,my_currency
- ,my_delta_value
- ,my_delta_frac);
- END IF;
- ELSE
- -- events are obsolete, delete them
- DELETE FROM merchant_statistic_amount_event
- WHERE imeta_serial_id = my_meta
- AND merchant_serial = my_instance_id
- AND slot < my_time - my_range;
- END IF;
- END IF;
-
- my_rval.range = my_range;
- RETURN NEXT my_rval;
- END IF;
- END LOOP; -- over my_ranges
- END LOOP; -- over my_currency
-END $$;
-
-COMMENT ON FUNCTION merchant_statistic_interval_amount_get
- IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value; multiple values are returned, one per currency and range';
-
-
-
-
DROP PROCEDURE IF EXISTS merchant_statistic_counter_gc;
CREATE OR REPLACE PROCEDURE merchant_statistic_counter_gc ()
@@ -826,7 +436,7 @@ BEGIN
-- First, we query the current interval statistic to update its counters
SELECT merchant_id
INTO my_instance_name
- FROM merchant_instances
+ FROM merchant.merchant_instances
WHERE merchant_serial = my_instance;
PERFORM FROM merchant_statistic_interval_number_get (my_rec.slug, my_instance_name);
@@ -944,7 +554,7 @@ BEGIN
-- First, we query the current interval statistic to update its counters
SELECT merchant_id
INTO my_instance_name
- FROM merchant_instances
+ FROM merchant.merchant_instances
WHERE merchant_serial = my_instance;
PERFORM FROM merchant_statistic_interval_amount_get (my_rec.slug, my_instance_name);
@@ -1075,7 +685,7 @@ COMMENT ON PROCEDURE merchant_statistic_bucket_gc
DROP FUNCTION IF EXISTS merchant_statistics_bucket_end;
CREATE FUNCTION merchant_statistics_bucket_end (
IN in_bucket_start INT8,
- IN in_range statistic_range,
+ IN in_range merchant.statistic_range,
OUT out_bucket_end INT8
)
LANGUAGE plpgsql
diff --git a/src/backenddb/pg_update_money_pot.sql b/src/backenddb/pg_update_money_pot.sql
@@ -1,82 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_do_update_money_pot;
-CREATE FUNCTION merchant_do_update_money_pot (
- IN in_instance_id TEXT,
- IN in_money_pot_serial INT8,
- IN in_name TEXT,
- IN in_description TEXT,
- 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)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
-BEGIN
-
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-
-IF NOT FOUND
-THEN
- -- If instance does not exist, pot cannot exist
- out_conflict_total = FALSE;
- out_conflict_name = FALSE;
- out_not_found = TRUE;
- RETURN;
-END IF;
-
-BEGIN
- UPDATE merchant_money_pots SET
- money_pot_name=in_name
- ,money_pot_description=in_description
- ,pot_totals=COALESCE(in_new_totals, pot_totals)
- WHERE merchant_serial=my_merchant_id
- AND money_pot_serial=in_money_pot_serial
- AND ( (in_old_totals IS NULL) OR (pot_totals=in_old_totals) );
- IF NOT FOUND
- THEN
- -- Check if pot_total was the problem
- PERFORM FROM merchant_money_pots
- WHERE merchant_serial=my_merchant_id
- AND money_pot_serial=in_money_pot_serial;
- out_conflict_total = FOUND;
- out_not_found = NOT FOUND;
- out_conflict_name = FALSE;
- ELSE
- out_conflict_total = FALSE;
- out_not_found = FALSE;
- out_conflict_name = FALSE;
- END IF;
- RETURN;
-EXCEPTION
- -- money_pot_name already used
- WHEN unique_violation
- THEN
- out_conflict_name = TRUE;
- out_conflict_total = FALSE;
- out_not_found = FALSE;
- RETURN;
-END;
-
-END $$;
diff --git a/src/backenddb/pg_update_product.sql b/src/backenddb/pg_update_product.sql
@@ -1,199 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2024, 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-
-DROP FUNCTION IF EXISTS merchant_do_update_product;
-CREATE FUNCTION merchant_do_update_product (
- IN in_instance_id TEXT,
- IN in_product_id TEXT,
- IN in_description TEXT,
- IN in_description_i18n JSONB, -- $4
- IN in_unit TEXT,
- IN in_image TEXT,
- IN in_taxes JSONB, -- $7
- IN ina_price_list taler_amount_currency[],
- IN in_total_stock INT8,
- IN in_total_stock_frac INT4,
- IN in_allow_fractional_quantity BOOL,
- IN in_fractional_precision_level INT4,
- IN in_total_lost INT8, -- NOTE: not in insert_product
- IN in_address JSONB, -- $14
- IN in_next_restock INT8,
- IN in_minimum_age INT4,
- IN ina_categories INT8[], -- $17
- IN in_product_name TEXT,
- IN in_product_group_id INT8, -- NULL for default
- IN in_money_pot_id INT8, -- NULL for none
- IN in_price_is_net BOOL, -- $21
- OUT out_no_instance BOOL,
- OUT out_no_product BOOL,
- OUT out_lost_reduced BOOL,
- OUT out_sold_reduced BOOL,
- OUT out_stocked_reduced BOOL,
- OUT out_no_cat INT8,
- OUT out_no_group BOOL,
- OUT out_no_pot BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_product_serial INT8;
- i INT8;
- ini_cat INT8;
- rec RECORD;
-BEGIN
-
-out_no_group = FALSE;
-out_no_pot = FALSE;
-out_no_instance=FALSE;
-out_no_product=FALSE;
-out_lost_reduced=FALSE;
-out_sold_reduced=FALSE; -- We currently don't allow updating 'sold', hence always FALSE
-out_stocked_reduced=FALSE;
-out_no_cat=NULL;
-
--- Which instance are we using?
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-
-IF NOT FOUND
-THEN
- out_no_instance=TRUE;
- RETURN;
-END IF;
-
-IF in_product_group_id IS NOT NULL
-THEN
- PERFORM FROM merchant_product_groups
- WHERE product_group_serial=in_product_group_id
- AND merchant_serial=my_merchant_id;
- IF NOT FOUND
- THEN
- out_no_group=TRUE;
- RETURN;
- END IF;
-END IF;
-
-IF in_money_pot_id IS NOT NULL
-THEN
- PERFORM FROM merchant_money_pots
- WHERE money_pot_serial=in_money_pot_id
- AND merchant_serial=my_merchant_id;
- IF NOT FOUND
- THEN
- out_no_pot=TRUE;
- RETURN;
- END IF;
-END IF;
-
--- Check existing entry satisfies constraints
-SELECT total_stock
- ,total_stock_frac
- ,total_lost
- ,allow_fractional_quantity
- ,product_serial
- INTO rec
- FROM merchant_inventory
- WHERE merchant_serial=my_merchant_id
- AND product_id=in_product_id;
-
-IF NOT FOUND
-THEN
- out_no_product=TRUE;
- RETURN;
-END IF;
-
-my_product_serial = rec.product_serial;
-
-IF rec.total_stock > in_total_stock
-THEN
- out_stocked_reduced=TRUE;
- RETURN;
-END IF;
-
-IF rec.total_lost > in_total_lost
-THEN
- out_lost_reduced=TRUE;
- RETURN;
-END IF;
-IF rec.allow_fractional_quantity
- AND (NOT in_allow_fractional_quantity)
-THEN
- DELETE
- FROM merchant_inventory_locks
- WHERE product_serial = my_product_serial
- AND total_locked_frac <> 0;
-END IF;
-
--- Remove old categories
-DELETE FROM merchant_product_categories
- WHERE product_serial=my_product_serial;
-
--- Add new categories
-FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
-LOOP
- ini_cat=ina_categories[i];
-
- INSERT INTO merchant_product_categories
- (product_serial
- ,category_serial)
- VALUES
- (my_product_serial
- ,ini_cat)
- ON CONFLICT DO NOTHING;
-
- IF NOT FOUND
- THEN
- out_no_cat=i;
- RETURN;
- END IF;
-END LOOP;
-
-UPDATE merchant_inventory SET
- description=in_description
- ,description_i18n=in_description_i18n
- ,product_name=in_product_name
- ,unit=in_unit
- ,image=in_image
- ,image_hash=CASE
- WHEN (in_image IS NULL) OR (in_image = '')
- THEN NULL
- ELSE encode(public.digest(convert_to(in_image, 'UTF8'),
- 'sha256'),
- 'hex')
- END
- ,taxes=in_taxes
- ,price_array=ina_price_list
- ,total_stock=in_total_stock
- ,total_stock_frac=in_total_stock_frac
- ,allow_fractional_quantity=in_allow_fractional_quantity
- ,fractional_precision_level=in_fractional_precision_level
- ,total_lost=in_total_lost
- ,address=in_address
- ,next_restock=in_next_restock
- ,minimum_age=in_minimum_age
- ,product_group_serial=in_product_group_id
- ,money_pot_serial=in_money_pot_id
- ,price_is_net=in_price_is_net
- WHERE merchant_serial=my_merchant_id
- AND product_serial=my_product_serial; -- could also match on product_id
-
-ASSERT FOUND,'SELECTED it earlier, should UPDATE it now';
-
--- Success!
-END $$;
diff --git a/src/backenddb/pg_update_product_group.sql b/src/backenddb/pg_update_product_group.sql
@@ -1,55 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2025 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU 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/>
---
-
-SET search_path TO merchant;
-
-DROP FUNCTION IF EXISTS merchant_do_update_product_group;
-CREATE FUNCTION merchant_do_update_product_group (
- IN in_instance_id TEXT,
- IN in_product_group_serial INT8,
- IN in_name TEXT,
- IN in_description TEXT,
- OUT out_conflict BOOL,
- OUT out_not_found BOOL)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
-BEGIN
-
-SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id=in_instance_id;
-
-BEGIN
- UPDATE merchant_product_groups SET
- product_group_name=in_name
- ,product_group_description=in_description
- WHERE merchant_serial=my_merchant_id
- AND product_group_serial=in_product_group_serial;
- out_not_found = NOT FOUND;
- out_conflict = FALSE;
- RETURN;
-EXCEPTION
- WHEN unique_violation
- THEN
- out_conflict = TRUE;
- RETURN;
-END;
-
-
-END $$;
diff --git a/src/backenddb/pg_uri_escape.sql b/src/backenddb/pg_uri_escape.sql
@@ -13,12 +13,14 @@
-- 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/>
--
-DROP FUNCTION IF EXISTS uri_escape;
-CREATE FUNCTION uri_escape(input TEXT)
-RETURNS TEXT
-LANGUAGE sql
-IMMUTABLE
-PARALLEL SAFE
+DROP FUNCTION IF EXISTS merchant.uri_escape;
+CREATE FUNCTION merchant.uri_escape(
+ input TEXT
+)
+ RETURNS TEXT
+ LANGUAGE sql
+ IMMUTABLE
+ PARALLEL SAFE
AS $$
SELECT COALESCE(string_agg(
CASE
@@ -33,5 +35,5 @@ FROM (
) s,
generate_series(0, length(b) - 1) AS i;
$$;
-COMMENT ON FUNCTION uri_escape(TEXT)
+COMMENT ON FUNCTION merchant.uri_escape(TEXT)
IS 'Percent-encodes all characters except RFC 3986 unreserved chars: A-Z a-z 0-9 - . _ ~';
diff --git a/src/backenddb/purge_instance.c b/src/backenddb/purge_instance.c
@@ -19,12 +19,11 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/purge_instance.h"
#include "helper.h"
+
/**
* Purge an instance and all associated information from our database.
* Highly likely to cause undesired data loss. Use with caution.
@@ -34,8 +33,9 @@
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_purge_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *merchant_id)
+TALER_MERCHANTDB_purge_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *merchant_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
@@ -45,8 +45,8 @@ TALER_MERCHANTDB_purge_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
check_connection (pg);
PREPARE (pg,
"purge_instance",
- "DELETE FROM merchant_instances"
- " WHERE merchant_instances.merchant_id = $1");
+ "DELETE FROM merchant.merchant_instances"
+ " WHERE merchant_id = $1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"purge_instance",
params);
diff --git a/src/backenddb/refund_coin.c b/src/backenddb/refund_coin.c
@@ -19,58 +19,56 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/refund_coin.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_refund_coin (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- struct GNUNET_TIME_Timestamp refund_timestamp,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *reason)
+TALER_MERCHANTDB_refund_coin (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp refund_timestamp,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const char *reason)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_timestamp (&refund_timestamp),
GNUNET_PQ_query_param_auto_from_type (coin_pub),
GNUNET_PQ_query_param_string (reason),
GNUNET_PQ_query_param_end
};
- PREPARE (pg,
- "refund_coin",
- "INSERT INTO merchant_refunds"
- "(order_serial"
- ",rtransaction_id"
- ",refund_timestamp"
- ",coin_pub"
- ",reason"
- ",refund_amount"
- ") "
- "SELECT "
- " dcon.order_serial"
- ",0" /* rtransaction_id always 0 for /abort */
- ",$3"
- ",dep.coin_pub"
- ",$5"
- ",dep.amount_with_fee"
- " FROM merchant_deposits dep"
- " JOIN merchant_deposit_confirmations dcon"
- " USING (deposit_confirmation_serial)"
- " WHERE dep.coin_pub=$4"
- " AND dcon.order_serial="
- " (SELECT order_serial"
- " FROM merchant_contract_terms"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1))");
+
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ TMH_PQ_prepare_anon (pg,
+ "INSERT INTO merchant_refunds"
+ "(order_serial"
+ ",rtransaction_id"
+ ",refund_timestamp"
+ ",coin_pub"
+ ",reason"
+ ",refund_amount"
+ ") "
+ "SELECT "
+ " dcon.order_serial"
+ ",0" /* rtransaction_id always 0 for /abort */
+ ",$2"
+ ",dep.coin_pub"
+ ",$4"
+ ",dep.amount_with_fee"
+ " FROM merchant_deposits dep"
+ " JOIN merchant_deposit_confirmations dcon"
+ " USING (deposit_confirmation_serial)"
+ " WHERE dep.coin_pub=$3"
+ " AND dcon.order_serial="
+ " (SELECT order_serial"
+ " FROM merchant_contract_terms"
+ " WHERE h_contract_terms=$1)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "refund_coin",
+ "",
params);
}
diff --git a/src/backenddb/select_account.c b/src/backenddb/select_account.c
@@ -19,21 +19,19 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_account.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_account (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- const struct TALER_MerchantWireHashP *h_wire,
- struct TALER_MERCHANTDB_AccountDetails *ad)
+TALER_MERCHANTDB_select_account (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ struct TALER_MERCHANTDB_AccountDetails *ad)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_end
};
@@ -59,26 +57,24 @@ TALER_MERCHANTDB_select_account (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
ad->h_wire = *h_wire;
ad->instance_id = id;
check_connection (pg);
- PREPARE (pg,
- "select_account",
- "SELECT"
- " salt"
- ",payto_uri"
- ",credit_facade_url"
- ",credit_facade_credentials::TEXT"
- ",active"
- ",extra_wire_subject_metadata"
- " FROM merchant_accounts"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1) "
- " AND (h_wire=$2);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " salt"
+ ",payto_uri"
+ ",credit_facade_url"
+ ",credit_facade_credentials::TEXT"
+ ",active"
+ ",extra_wire_subject_metadata"
+ " FROM merchant_accounts"
+ " WHERE h_wire=$1;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_account",
+ "",
params,
rs);
}
diff --git a/src/backenddb/select_account_by_uri.c b/src/backenddb/select_account_by_uri.c
@@ -19,21 +19,19 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_account_by_uri.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_account_by_uri (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- struct TALER_FullPayto payto_uri,
- struct TALER_MERCHANTDB_AccountDetails *ad)
+TALER_MERCHANTDB_select_account_by_uri (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ struct TALER_FullPayto payto_uri,
+ struct TALER_MERCHANTDB_AccountDetails *ad)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_string (payto_uri.full_payto),
GNUNET_PQ_query_param_end
};
@@ -55,28 +53,26 @@ TALER_MERCHANTDB_select_account_by_uri (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
ad->credit_facade_url = NULL;
ad->credit_facade_credentials = NULL;
ad->payto_uri.full_payto
= GNUNET_strdup (payto_uri.full_payto);
ad->instance_id = id;
check_connection (pg);
- PREPARE (pg,
- "select_account_by_uri",
- "SELECT"
- " salt"
- ",h_wire"
- ",credit_facade_url"
- ",credit_facade_credentials::TEXT"
- ",active"
- " FROM merchant_accounts"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND payto_uri = $2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " salt"
+ ",h_wire"
+ ",credit_facade_url"
+ ",credit_facade_credentials::TEXT"
+ ",active"
+ " FROM merchant_accounts"
+ " WHERE payto_uri = $1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_account_by_uri",
+ "",
params,
rs);
}
diff --git a/src/backenddb/select_accounts.c b/src/backenddb/select_accounts.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_accounts.h"
#include "helper.h"
@@ -80,30 +78,30 @@ select_account_cb (void *cls,
struct TALER_MerchantPrivateKeyP merchant_priv;
bool no_priv;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ GNUNET_PQ_result_spec_auto_from_type ("out_h_wire",
&acc.h_wire),
- GNUNET_PQ_result_spec_auto_from_type ("salt",
+ GNUNET_PQ_result_spec_auto_from_type ("out_salt",
&acc.salt),
- GNUNET_PQ_result_spec_string ("payto_uri",
+ GNUNET_PQ_result_spec_string ("out_payto_uri",
&payto.full_payto),
- GNUNET_PQ_result_spec_string ("merchant_id",
+ GNUNET_PQ_result_spec_string ("out_merchant_id",
&instance_id),
GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("credit_facade_url",
+ GNUNET_PQ_result_spec_string ("out_credit_facade_url",
&facade_url),
NULL),
GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("extra_wire_subject_metadata",
+ GNUNET_PQ_result_spec_string ("out_extra_wire_subject_metadata",
&extra_wire_subject_metadata),
NULL),
GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_result_spec_json ("credit_facade_credentials",
+ TALER_PQ_result_spec_json ("out_credit_facade_credentials",
&credential),
NULL),
- GNUNET_PQ_result_spec_bool ("active",
+ GNUNET_PQ_result_spec_bool ("out_active",
&acc.active),
GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
+ GNUNET_PQ_result_spec_auto_from_type ("out_merchant_priv",
&merchant_priv),
&no_priv),
GNUNET_PQ_result_spec_end
@@ -135,7 +133,6 @@ select_account_cb (void *cls,
enum GNUNET_DB_QueryStatus
TALER_MERCHANTDB_select_accounts (
struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
TALER_MERCHANTDB_AccountCallback cb,
void *cb_cls)
{
@@ -145,9 +142,6 @@ TALER_MERCHANTDB_select_accounts (
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
- NULL == id
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
@@ -156,26 +150,16 @@ TALER_MERCHANTDB_select_accounts (
PREPARE (pg,
"select_accounts",
"SELECT"
- " ma.h_wire"
- ",ma.salt"
- ",ma.payto_uri"
- ",ma.credit_facade_url"
- ",ma.credit_facade_credentials::TEXT"
- ",ma.extra_wire_subject_metadata"
- ",ma.active"
- ",mk.merchant_priv"
- ",mi.merchant_id"
- " FROM merchant_accounts ma"
- " JOIN merchant_instances mi"
- " ON (mi.merchant_serial=ma.merchant_serial)"
- " LEFT JOIN merchant_keys mk"
- " ON (mk.merchant_serial=ma.merchant_serial)"
- " WHERE"
- " ($1::TEXT IS NULL) OR"
- " (ma.merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1));");
+ " out_merchant_id"
+ " ,out_merchant_priv"
+ " ,out_h_wire"
+ " ,out_salt"
+ " ,out_payto_uri"
+ " ,out_credit_facade_url"
+ " ,out_credit_facade_credentials::TEXT"
+ " ,out_extra_wire_subject_metadata"
+ " ,out_active"
+ " FROM merchant.select_accounts()");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"select_accounts",
params,
diff --git a/src/backenddb/select_accounts_by_exchange.c b/src/backenddb/select_accounts_by_exchange.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_accounts_by_exchange.h"
#include "helper.h"
diff --git a/src/backenddb/select_accounts_by_instance.c b/src/backenddb/select_accounts_by_instance.c
@@ -0,0 +1,176 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 src/backenddb/select_accounts_by_instance.c
+ * @brief Implementation of the select_accounts_by_instance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_pq_lib.h>
+#include "merchant-database/select_accounts_by_instance.h"
+#include "helper.h"
+
+
+/**
+ * Context for select_accounts().
+ */
+struct SelectAccountsContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_AccountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Database context.
+ */
+ struct TALER_MERCHANTDB_PostgresContext *pg;
+
+ /**
+ * Merchant instance ID.
+ */
+ const char *id;
+
+ /**
+ * Set to the return value on errors.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param cls of type `struct SelectAccountsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+select_account_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectAccountsContext *lic = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_FullPayto payto;
+ char *facade_url = NULL;
+ char *extra_wire_subject_metadata = NULL;
+ json_t *credential = NULL;
+ struct TALER_MERCHANTDB_AccountDetails acc;
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+ bool no_priv;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &acc.h_wire),
+ GNUNET_PQ_result_spec_auto_from_type ("salt",
+ &acc.salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto.full_payto),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("credit_facade_url",
+ &facade_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("extra_wire_subject_metadata",
+ &extra_wire_subject_metadata),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("credit_facade_credentials",
+ &credential),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("active",
+ &acc.active),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
+ &merchant_priv),
+ &no_priv),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ acc.instance_id = lic->id;
+ acc.payto_uri = payto;
+ acc.credit_facade_url = facade_url;
+ acc.credit_facade_credentials = credential;
+ acc.extra_wire_subject_metadata = extra_wire_subject_metadata;
+ if (NULL != lic->cb)
+ lic->cb (lic->cb_cls,
+ no_priv ? NULL : &merchant_priv,
+ &acc);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TALER_MERCHANTDB_select_accounts_by_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ TALER_MERCHANTDB_AccountCallback cb,
+ void *cb_cls)
+{
+ struct SelectAccountsContext lic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .id = id
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " ma.h_wire"
+ ",ma.salt"
+ ",ma.payto_uri"
+ ",ma.credit_facade_url"
+ ",ma.credit_facade_credentials::TEXT"
+ ",ma.extra_wire_subject_metadata"
+ ",ma.active"
+ ",mi.merchant_priv"
+ " FROM merchant_accounts ma"
+ " JOIN merchant.merchant_instances mi"
+ " ON (mi.merchant_id=$1)");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "",
+ params,
+ &select_account_cb,
+ &lic);
+ if (0 > lic.qs)
+ return lic.qs;
+ return qs;
+}
diff --git a/src/backenddb/select_all_donau_instances.c b/src/backenddb/select_all_donau_instances.c
@@ -21,8 +21,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_all_donau_instances.h"
#include "helper.h"
@@ -76,24 +74,24 @@ select_donau_instance_cb (void *cls,
int64_t current_year;
json_t *donau_keys_json = NULL;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("donau_instances_serial",
+ GNUNET_PQ_result_spec_uint64 ("out_donau_instances_serial",
&donau_instance_serial),
- GNUNET_PQ_result_spec_string ("donau_url",
+ GNUNET_PQ_result_spec_string ("out_donau_url",
&donau_url),
- GNUNET_PQ_result_spec_string ("charity_name",
+ GNUNET_PQ_result_spec_string ("out_charity_name",
&charity_name),
- GNUNET_PQ_result_spec_auto_from_type ("charity_pub_key",
+ GNUNET_PQ_result_spec_auto_from_type ("out_charity_pub_key",
&charity_pub_key),
- GNUNET_PQ_result_spec_uint64 ("charity_id",
+ GNUNET_PQ_result_spec_uint64 ("out_charity_id",
&charity_id),
- TALER_PQ_result_spec_amount_with_currency ("charity_max_per_year",
+ TALER_PQ_result_spec_amount_with_currency ("out_charity_max_per_year",
&charity_max_per_year),
- TALER_PQ_result_spec_amount_with_currency ("charity_receipts_to_date",
+ TALER_PQ_result_spec_amount_with_currency ("out_charity_receipts_to_date",
&charity_receipts_to_date),
- GNUNET_PQ_result_spec_int64 ("current_year",
+ GNUNET_PQ_result_spec_int64 ("out_current_year",
¤t_year),
GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_result_spec_json ("keys_json",
+ TALER_PQ_result_spec_json ("out_keys_json",
&donau_keys_json),
NULL),
GNUNET_PQ_result_spec_end
@@ -144,20 +142,16 @@ TALER_MERCHANTDB_select_all_donau_instances (
PREPARE (pg,
"select_all_donau_instances",
"SELECT"
- " di.donau_instances_serial"
- ",di.donau_url"
- ",di.charity_name"
- ",mi.merchant_pub AS charity_pub_key"
- ",di.charity_id"
- ",di.charity_max_per_year"
- ",di.charity_receipts_to_date"
- ",di.current_year"
- ",dk.keys_json::TEXT"
- " FROM merchant_donau_instances di"
- " LEFT JOIN merchant_donau_keys dk"
- " ON di.donau_url = dk.donau_url"
- " JOIN merchant_instances mi"
- " ON di.merchant_instance_serial = mi.merchant_serial");
+ " out_donau_instances_serial"
+ " ,out_donau_url"
+ " ,out_charity_name"
+ " ,out_charity_pub_key"
+ " ,out_charity_id"
+ " ,out_charity_max_per_year"
+ " ,out_charity_receipts_to_date"
+ " ,out_current_year"
+ " ,out_keys_json::TEXT"
+ " FROM merchant.select_all_donau_instances()");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"select_all_donau_instances",
params,
diff --git a/src/backenddb/select_all_donau_instances.sql b/src/backenddb/select_all_donau_instances.sql
@@ -0,0 +1,82 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.select_all_donau_instances();
+CREATE FUNCTION merchant.select_all_donau_instances()
+RETURNS TABLE(
+ out_donau_instances_serial INT8,
+ out_donau_url TEXT,
+ out_charity_name TEXT,
+ out_charity_pub_key BYTEA,
+ out_charity_id INT8,
+ out_charity_max_per_year merchant.taler_amount_currency,
+ out_charity_receipts_to_date merchant.taler_amount_currency,
+ out_current_year INT8,
+ out_keys_json JSONB)
+LANGUAGE plpgsql
+AS $FN$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ inner_rec RECORD;
+BEGIN
+ FOR rec
+ IN
+ SELECT merchant_serial
+ ,merchant_pub
+ FROM merchant.merchant_instances
+ LOOP
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ FOR inner_rec
+ IN
+ EXECUTE format(
+ 'SELECT'
+ ' di.donau_instances_serial AS dis'
+ ' ,di.donau_url AS du'
+ ' ,di.charity_name AS cn'
+ ' ,di.charity_id AS ci'
+ ' ,di.charity_max_per_year AS cmp'
+ ' ,di.charity_receipts_to_date AS crt'
+ ' ,di.current_year AS cy'
+ ' ,dk.keys_json AS kj'
+ ' FROM %I.merchant_donau_instances di'
+ ' LEFT JOIN merchant.merchant_donau_keys dk'
+ ' ON di.donau_url = dk.donau_url', s)
+ LOOP
+ out_donau_instances_serial := inner_rec.dis;
+ out_donau_url := inner_rec.du;
+ out_charity_name := inner_rec.cn;
+ out_charity_pub_key := rec.merchant_pub;
+ out_charity_id := inner_rec.ci;
+ out_charity_max_per_year := inner_rec.cmp;
+ out_charity_receipts_to_date := inner_rec.crt;
+ out_current_year := inner_rec.cy;
+ out_keys_json := inner_rec.kj;
+ RETURN NEXT;
+ END LOOP;
+ EXCEPTION
+ WHEN undefined_table
+ THEN
+ NULL;
+ END;
+ END LOOP;
+END
+$FN$;
+COMMENT ON FUNCTION merchant.select_all_donau_instances()
+ IS 'Returns all donau-instance configurations across every per-instance'
+ ' schema, joined with merchant.merchant_donau_keys for the keys json'
+ ' and with merchant.merchant_instances for the charity public key.';
diff --git a/src/backenddb/select_category.c b/src/backenddb/select_category.c
@@ -19,80 +19,60 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_category.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_category (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t category_id,
- struct TALER_MERCHANTDB_CategoryDetails *cd,
- size_t *num_products,
- char **products)
+TALER_MERCHANTDB_select_category (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t category_id,
+ struct TALER_MERCHANTDB_CategoryDetails *cd,
+ size_t *num_products,
+ char **products)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&category_id),
GNUNET_PQ_query_param_end
};
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("category_name",
+ &cd->category_name),
+ TALER_PQ_result_spec_json ("category_name_i18n",
+ &cd->category_name_i18n),
+ GNUNET_PQ_result_spec_array_string (pg->conn,
+ "products",
+ num_products,
+ products),
+ GNUNET_PQ_result_spec_end
+ };
- if (NULL == cd)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (
- pg->conn,
- "select_category",
- params,
- rs_null);
- }
- else
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("category_name",
- &cd->category_name),
- TALER_PQ_result_spec_json ("category_name_i18n",
- &cd->category_name_i18n),
- GNUNET_PQ_result_spec_array_string (pg->conn,
- "products",
- num_products,
- products),
- GNUNET_PQ_result_spec_end
- };
-
- PREPARE (pg,
- "select_category",
- "SELECT"
- " category_name"
- ",category_name_i18n::TEXT"
- ",t.product_array AS products"
- " FROM merchant_categories mc"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- ",LATERAL ("
- " SELECT ARRAY ("
- " SELECT "
- " mi.product_id AS product_id"
- " FROM merchant_product_categories mpc"
- " JOIN merchant_inventory mi"
- " USING (product_serial)"
- " WHERE mpc.category_serial = mc.category_serial"
- " ) AS product_array"
- " ) t"
- " WHERE inst.merchant_id=$1"
- " AND mc.category_serial=$2");
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (
- pg->conn,
- "select_category",
- params,
- rs);
- }
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ check_connection (pg);
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " category_name"
+ ",category_name_i18n::TEXT"
+ ",t.product_array AS products"
+ " FROM merchant_categories mc"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT "
+ " mi.product_id AS product_id"
+ " FROM merchant_product_categories mpc"
+ " JOIN merchant_inventory mi"
+ " USING (product_serial)"
+ " WHERE mpc.category_serial = mc.category_serial"
+ " ) AS product_array"
+ " ) t"
+ " WHERE mc.category_serial=$1");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "",
+ params,
+ rs);
}
diff --git a/src/backenddb/select_category_by_name.c b/src/backenddb/select_category_by_name.c
@@ -19,22 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_category_by_name.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_category_by_name (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *category_name,
- json_t **name_i18n,
- uint64_t *category_id)
+TALER_MERCHANTDB_select_category_by_name (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *category_name,
+ json_t **name_i18n,
+ uint64_t *category_id)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (category_name),
GNUNET_PQ_query_param_end
};
@@ -46,20 +44,19 @@ TALER_MERCHANTDB_select_category_by_name (struct TALER_MERCHANTDB_PostgresContex
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "select_category_by_name",
- "SELECT"
- " category_serial"
- ",category_name_i18n::TEXT"
- " FROM merchant_categories mc"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE mi.merchant_id=$1"
- " AND mc.category_name=$2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " category_serial"
+ ",category_name_i18n::TEXT"
+ " FROM merchant_categories"
+ " WHERE category_name=$1");
return GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "select_category_by_name",
+ "",
params,
rs);
}
diff --git a/src/backenddb/select_donau_instance_by_serial.c b/src/backenddb/select_donau_instance_by_serial.c
@@ -20,18 +20,17 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_donau_instance_by_serial.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_donau_instance_by_serial (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t serial,
- char **donau_url,
- uint64_t *charity_id)
+TALER_MERCHANTDB_select_donau_instance_by_serial (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t serial,
+ char **donau_url,
+ uint64_t *charity_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&serial),
@@ -45,17 +44,17 @@ TALER_MERCHANTDB_select_donau_instance_by_serial (struct TALER_MERCHANTDB_Postgr
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "select_donau_instance_by_serial",
- "SELECT donau_url"
- " ,charity_id"
- " FROM merchant_donau_instances"
- " WHERE donau_instances_serial = $1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT donau_url"
+ " ,charity_id"
+ " FROM merchant_donau_instances"
+ " WHERE donau_instances_serial = $1");
return GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "select_donau_instance_by_serial",
+ "",
params,
rs);
}
diff --git a/src/backenddb/select_donau_instances.c b/src/backenddb/select_donau_instances.c
@@ -20,8 +20,6 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include <donau/donau_util.h>
#include "merchant-database/select_donau_instances.h"
@@ -33,6 +31,11 @@
struct SelectDonauInstanceContext
{
/**
+ * Database connection.
+ */
+ struct TALER_MERCHANTDB_PostgresContext *pg;
+
+ /**
* Function to call with the results.
*/
TALER_MERCHANTDB_DonauInstanceCallback cb;
@@ -63,13 +66,19 @@ select_donau_instance_cb (void *cls,
unsigned int num_results)
{
struct SelectDonauInstanceContext *sdc = cls;
+ struct TALER_MERCHANTDB_PostgresContext *pg = sdc->pg;
+ struct DONAU_CharityPublicKeyP charity_pub_key;
+ GNUNET_static_assert (sizeof (charity_pub_key) ==
+ sizeof (pg->current_merchant_pub));
+ memcpy (&charity_pub_key,
+ &pg->current_merchant_pub,
+ sizeof (charity_pub_key));
for (unsigned int i = 0; i < num_results; i++)
{
uint64_t donau_instance_serial;
char *donau_url;
char *charity_name;
- struct DONAU_CharityPublicKeyP charity_pub_key;
uint64_t charity_id;
struct TALER_Amount charity_max_per_year;
struct TALER_Amount charity_receipts_to_date;
@@ -82,8 +91,6 @@ select_donau_instance_cb (void *cls,
&donau_url),
GNUNET_PQ_result_spec_string ("charity_name",
&charity_name),
- GNUNET_PQ_result_spec_auto_from_type ("charity_pub_key",
- &charity_pub_key),
GNUNET_PQ_result_spec_uint64 ("charity_id",
&charity_id),
TALER_PQ_result_spec_amount_with_currency ("charity_max_per_year",
@@ -131,43 +138,41 @@ TALER_MERCHANTDB_select_donau_instances (
void *cb_cls)
{
struct SelectDonauInstanceContext sdc = {
+ .pg = pg,
.cb = cb,
.cb_cls = cb_cls,
/* Can be overwritten by the select_donau_instance_cb */
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "select_donau_instances",
- "SELECT"
- " di.donau_instances_serial"
- ",di.donau_url"
- ",di.charity_name"
- ",mi.merchant_pub AS charity_pub_key"
- ",di.charity_id"
- ",di.charity_max_per_year"
- ",di.charity_receipts_to_date"
- ",di.current_year"
- ",dk.keys_json::TEXT"
- " FROM merchant_donau_instances di"
- " LEFT JOIN merchant_donau_keys dk"
- " ON di.donau_url = dk.donau_url"
- " JOIN merchant_instances mi"
- " ON di.merchant_instance_serial = mi.merchant_serial"
- " WHERE mi.merchant_id = $1");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " di.donau_instances_serial"
+ ",di.donau_url"
+ ",di.charity_name"
+ ",di.charity_id"
+ ",di.charity_max_per_year"
+ ",di.charity_receipts_to_date"
+ ",di.current_year"
+ ",dk.keys_json::TEXT"
+ " FROM merchant_donau_instances di"
+ " LEFT JOIN merchant_donau_keys dk"
+ " ON di.donau_url = dk.donau_url");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_donau_instances",
+ "",
params,
&select_donau_instance_cb,
&sdc);
-/* If there was an error inside select_donau_instance_cb, return a hard error. */
+ /* If there was an error inside select_donau_instance_cb, return a hard error. */
if (sdc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
diff --git a/src/backenddb/select_donau_instances_filtered.c b/src/backenddb/select_donau_instances_filtered.c
@@ -20,12 +20,11 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_donau_instances_filtered.h"
#include "helper.h"
+
/**
* Context for select_donau_instances_filtered().
*/
@@ -101,7 +100,6 @@ TALER_MERCHANTDB_select_donau_instances_filtered (
/* Can be overwritten by the select_donau_instance_cb */
.extract_failed = false,
};
-
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (currency),
GNUNET_PQ_query_param_end
@@ -109,15 +107,12 @@ TALER_MERCHANTDB_select_donau_instances_filtered (
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
- PREPARE (pg,
- "select_donau_instances_filtered",
- "SELECT"
- " donau_url"
- " FROM merchant_donau_instances"
- " WHERE (charity_max_per_year).curr = $1");
-
+ TMH_PQ_prepare_anon (pg,
+ "SELECT donau_url"
+ " FROM merchant_donau_instances"
+ " WHERE (charity_max_per_year).curr = $1");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_donau_instances_filtered",
+ "",
params,
&select_donau_instance_cb,
&sdc);
@@ -125,6 +120,5 @@ TALER_MERCHANTDB_select_donau_instances_filtered (
/* If there was an error inside select_donau_instance_cb, return a hard error. */
if (sdc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
-
return qs;
}
diff --git a/src/backenddb/select_exchange_keys.c b/src/backenddb/select_exchange_keys.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include <taler/taler_exchange_service.h>
#include "merchant-database/select_exchange_keys.h"
@@ -57,7 +55,7 @@ TALER_MERCHANTDB_select_exchange_keys (
"SELECT"
" first_retry"
",keys_json::TEXT"
- " FROM merchant_exchange_keys"
+ " FROM merchant.merchant_exchange_keys"
" WHERE exchange_url=$1;");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"select_exchange_keys",
diff --git a/src/backenddb/select_exchanges.c b/src/backenddb/select_exchanges.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_exchanges.h"
#include "helper.h"
@@ -136,7 +134,7 @@ TALER_MERCHANTDB_select_exchanges (
" ,keys_json::TEXT"
" ,exchange_http_status"
" ,exchange_ec_code"
- " FROM merchant_exchange_keys");
+ " FROM merchant.merchant_exchange_keys");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"select_exchanges",
diff --git a/src/backenddb/select_login_token.c b/src/backenddb/select_login_token.c
@@ -19,22 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_login_token.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_login_token (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- const struct TALER_MERCHANTDB_LoginTokenP *token,
- struct GNUNET_TIME_Timestamp *expiration_time,
- uint32_t *validity_scope)
+TALER_MERCHANTDB_select_login_token (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ const struct TALER_MERCHANTDB_LoginTokenP *token,
+ struct GNUNET_TIME_Timestamp *expiration_time,
+ uint32_t *validity_scope)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_auto_from_type (token),
GNUNET_PQ_query_param_end
};
@@ -46,20 +44,18 @@ TALER_MERCHANTDB_select_login_token (struct TALER_MERCHANTDB_PostgresContext *pg
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "select_login_token",
- "SELECT"
- " expiration_time"
- ",validity_scope"
- " FROM merchant_login_tokens"
- " WHERE token=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " expiration_time"
+ ",validity_scope"
+ " FROM merchant_login_tokens"
+ " WHERE token=$1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_login_token",
+ "",
params,
rs);
}
diff --git a/src/backenddb/select_money_pot.c b/src/backenddb/select_money_pot.c
@@ -19,24 +19,22 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_money_pot.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t money_pot_id,
- char **name,
- char **description,
- size_t *pot_total_len,
- struct TALER_Amount **pot_totals)
+TALER_MERCHANTDB_select_money_pot (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t money_pot_id,
+ char **name,
+ char **description,
+ size_t *pot_total_len,
+ struct TALER_Amount **pot_totals)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&money_pot_id),
GNUNET_PQ_query_param_end
};
@@ -53,21 +51,20 @@ TALER_MERCHANTDB_select_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "select_money_pot",
- "SELECT"
- " mp.money_pot_name"
- " ,mp.money_pot_description"
- " ,mp.pot_totals"
- " FROM merchant_money_pots mp"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE merchant_id=$1"
- " AND money_pot_serial=$2;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " money_pot_name"
+ " ,money_pot_description"
+ " ,pot_totals"
+ " FROM merchant_money_pots"
+ " WHERE money_pot_serial=$1;");
return GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "select_money_pot",
+ "",
params,
rs);
}
diff --git a/src/backenddb/select_money_pots.c b/src/backenddb/select_money_pots.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_money_pots.h"
#include "helper.h"
@@ -107,12 +105,13 @@ lookup_money_pots_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANTDB_MoneyPotsCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_select_money_pots (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ int64_t limit,
+ uint64_t offset,
+ TALER_MERCHANTDB_MoneyPotsCallback cb,
+ void *cb_cls)
{
uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
struct LookupMoneyPotsContext plc = {
@@ -123,45 +122,43 @@ TALER_MERCHANTDB_select_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg,
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_money_pots_asc",
- "SELECT"
- " money_pot_serial"
- " ,money_pot_name"
- " ,pot_totals"
- " FROM merchant_money_pots"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND money_pot_serial > $2"
- " ORDER BY money_pot_serial ASC"
- " LIMIT $3");
- PREPARE (pg,
- "lookup_money_pots_desc",
- "SELECT"
- " money_pot_serial"
- " ,money_pot_name"
- " ,pot_totals"
- " FROM merchant_money_pots"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND money_pot_serial < $2"
- " ORDER BY money_pot_serial DESC"
- " LIMIT $3");
+ if (limit > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " money_pot_serial"
+ " ,money_pot_name"
+ " ,pot_totals"
+ " FROM merchant_money_pots"
+ " WHERE money_pot_serial > $1"
+ " ORDER BY money_pot_serial ASC"
+ " LIMIT $2");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " money_pot_serial"
+ " ,money_pot_name"
+ " ,pot_totals"
+ " FROM merchant_money_pots"
+ " WHERE money_pot_serial < $1"
+ " ORDER BY money_pot_serial DESC"
+ " LIMIT $2");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- (limit > 0)
- ? "lookup_money_pots_asc"
- : "lookup_money_pots_desc",
+ "",
params,
&lookup_money_pots_cb,
&plc);
diff --git a/src/backenddb/select_open_transfers.c b/src/backenddb/select_open_transfers.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_open_transfers.h"
#include "helper.h"
@@ -77,17 +75,17 @@ open_transfers_cb (void *cls,
struct TALER_WireTransferIdentifierRawP wtid;
struct GNUNET_TIME_Absolute retry_time;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("expected_credit_serial",
+ GNUNET_PQ_result_spec_uint64 ("out_expected_credit_serial",
&rowid),
- GNUNET_PQ_result_spec_string ("instance_id",
+ GNUNET_PQ_result_spec_string ("out_instance_id",
&instance_id),
- GNUNET_PQ_result_spec_string ("exchange_url",
+ GNUNET_PQ_result_spec_string ("out_exchange_url",
&exchange_url),
- GNUNET_PQ_result_spec_string ("payto_uri",
+ GNUNET_PQ_result_spec_string ("out_payto_uri",
&payto_uri.full_payto),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ GNUNET_PQ_result_spec_auto_from_type ("out_wtid",
&wtid),
- GNUNET_PQ_result_spec_absolute_time ("retry_time",
+ GNUNET_PQ_result_spec_absolute_time ("out_retry_time",
&retry_time),
GNUNET_PQ_result_spec_end
};
@@ -134,20 +132,13 @@ TALER_MERCHANTDB_select_open_transfers (
PREPARE (pg,
"select_open_transfers",
"SELECT"
- " met.expected_credit_serial"
- ",mi.merchant_id AS instance_id"
- ",met.exchange_url"
- ",ma.payto_uri"
- ",met.wtid"
- ",met.retry_time"
- " FROM merchant_expected_transfers met"
- " JOIN merchant_accounts ma"
- " USING (account_serial)"
- " JOIN merchant_instances mi"
- " ON (ma.merchant_serial=mi.merchant_serial)"
- " WHERE retry_needed"
- " ORDER BY retry_time ASC"
- " LIMIT $1;");
+ " out_expected_credit_serial"
+ " ,out_instance_id"
+ " ,out_exchange_url"
+ " ,out_payto_uri"
+ " ,out_wtid"
+ " ,out_retry_time"
+ " FROM merchant.select_open_transfers($1)");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"select_open_transfers",
diff --git a/src/backenddb/select_open_transfers.sql b/src/backenddb/select_open_transfers.sql
@@ -0,0 +1,77 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.select_open_transfers;
+CREATE FUNCTION merchant.select_open_transfers(IN p_limit INT8)
+RETURNS TABLE(
+ out_expected_credit_serial INT8,
+ out_instance_id TEXT,
+ out_exchange_url TEXT,
+ out_payto_uri TEXT,
+ out_wtid BYTEA,
+ out_retry_time INT8)
+LANGUAGE plpgsql
+AS $FN$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ inner_rec RECORD;
+ remaining INT8 := p_limit;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ ,merchant_id
+ FROM merchant.merchant_instances
+ LOOP
+ EXIT WHEN remaining <= 0;
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ FOR inner_rec IN
+ EXECUTE format(
+ 'SELECT'
+ ' met.expected_credit_serial AS ecs'
+ ' ,met.exchange_url AS exu'
+ ' ,ma.payto_uri AS pu'
+ ' ,met.wtid AS wt'
+ ' ,met.retry_time AS rt'
+ ' FROM %I.merchant_expected_transfers met'
+ ' JOIN %I.merchant_accounts ma USING (account_serial)'
+ ' WHERE retry_needed'
+ ' ORDER BY retry_time ASC'
+ ' LIMIT $1', s, s)
+ USING remaining
+ LOOP
+ out_expected_credit_serial := inner_rec.ecs;
+ out_instance_id := rec.merchant_id;
+ out_exchange_url := inner_rec.exu;
+ out_payto_uri := inner_rec.pu;
+ out_wtid := inner_rec.wt;
+ out_retry_time := inner_rec.rt;
+ RETURN NEXT;
+ remaining := remaining - 1;
+ EXIT WHEN remaining <= 0;
+ END LOOP;
+ EXCEPTION
+ WHEN undefined_table
+ THEN
+ NULL;
+ END;
+ END LOOP;
+END
+$FN$;
+COMMENT ON FUNCTION merchant.select_open_transfers(INT8)
+ IS 'Returns up to p_limit retry-needed expected_transfer rows across all'
+ ' instance schemas, joined with the per-instance merchant_accounts.';
diff --git a/src/backenddb/select_order_blinded_sigs.c b/src/backenddb/select_order_blinded_sigs.c
@@ -19,8 +19,6 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_order_blinded_sigs.h"
#include "helper.h"
@@ -90,35 +88,35 @@ restore_sig_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_order_blinded_sigs (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *order_id,
- TALER_MERCHANTDB_BlindedSigCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_select_order_blinded_sigs (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *order_id,
+ TALER_MERCHANTDB_BlindedSigCallback cb,
+ void *cb_cls)
{
struct SelectBlindedSigsContext ctx = {
.cb = cb,
.cb_cls = cb_cls,
.qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
};
-
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "select_blinded_sigs",
- "SELECT"
- " motbs.token_blinded_signature"
- " ,motbs.token_hash"
- " FROM merchant_order_token_blinded_sigs AS motbs"
- " JOIN merchant_contract_terms AS mct USING (order_serial)"
- " WHERE mct.order_id = $1"
- " ORDER BY motbs.token_index ASC");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " motbs.token_blinded_signature"
+ " ,motbs.token_hash"
+ " FROM merchant_order_token_blinded_sigs AS motbs"
+ " JOIN merchant_contract_terms AS mct USING (order_serial)"
+ " WHERE mct.order_id = $1"
+ " ORDER BY motbs.token_index ASC");
return GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- "select_blinded_sigs",
+ "",
params,
&restore_sig_cb,
&ctx);
diff --git a/src/backenddb/select_otp.c b/src/backenddb/select_otp.c
@@ -19,71 +19,52 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_otp.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_otp (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *otp_id,
- struct TALER_MERCHANTDB_OtpDeviceDetails *td)
+TALER_MERCHANTDB_select_otp (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *otp_id,
+ struct TALER_MERCHANTDB_OtpDeviceDetails *td)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (otp_id),
GNUNET_PQ_query_param_end
};
+ uint32_t pos32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("otp_description",
+ &td->otp_description),
+ GNUNET_PQ_result_spec_uint64 ("otp_ctr",
+ &td->otp_ctr),
+ GNUNET_PQ_result_spec_string ("otp_key",
+ &td->otp_key),
+ GNUNET_PQ_result_spec_uint32 ("otp_algorithm",
+ &pos32),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
- PREPARE (pg,
- "select_otp",
- "SELECT"
- " otp_description"
- ",otp_ctr"
- ",otp_key"
- ",otp_algorithm"
- " FROM merchant_otp_devices"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_otp_devices.otp_id=$2");
- if (NULL == td)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_otp",
- params,
- rs_null);
- }
- else
- {
- uint32_t pos32;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("otp_description",
- &td->otp_description),
- GNUNET_PQ_result_spec_uint64 ("otp_ctr",
- &td->otp_ctr),
- GNUNET_PQ_result_spec_string ("otp_key",
- &td->otp_key),
- GNUNET_PQ_result_spec_uint32 ("otp_algorithm",
- &pos32),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_otp",
- params,
- rs);
- td->otp_algorithm = (enum TALER_MerchantConfirmationAlgorithm) pos32;
- return qs;
- }
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ check_connection (pg);
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " otp_description"
+ ",otp_ctr"
+ ",otp_key"
+ ",otp_algorithm"
+ " FROM merchant_otp_devices"
+ " WHERE otp_id=$1");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
+ td->otp_algorithm = (enum TALER_MerchantConfirmationAlgorithm) pos32;
+ return qs;
}
diff --git a/src/backenddb/select_otp_serial.c b/src/backenddb/select_otp_serial.c
@@ -19,21 +19,19 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_otp_serial.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_otp_serial (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *otp_id,
- uint64_t *serial)
+TALER_MERCHANTDB_select_otp_serial (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *otp_id,
+ uint64_t *serial)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (otp_id),
GNUNET_PQ_query_param_end
};
@@ -43,18 +41,17 @@ TALER_MERCHANTDB_select_otp_serial (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_result_spec_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "select_otp_serial",
- "SELECT"
- " otp_serial"
- " FROM merchant_otp_devices"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_otp_devices.otp_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " otp_serial"
+ " FROM merchant_otp_devices"
+ " WHERE otp_id=$1");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_otp_serial",
+ "",
params,
rs);
}
diff --git a/src/backenddb/select_product_groups.c b/src/backenddb/select_product_groups.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_product_groups.h"
#include "helper.h"
@@ -97,12 +95,13 @@ lookup_product_groups_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_product_groups (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANTDB_ProductGroupsCallback cb,
- void *cb_cls)
+TALER_MERCHANTDB_select_product_groups (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ int64_t limit,
+ uint64_t offset,
+ TALER_MERCHANTDB_ProductGroupsCallback cb,
+ void *cb_cls)
{
uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
struct LookupProductGroupsContext plc = {
@@ -112,45 +111,43 @@ TALER_MERCHANTDB_select_product_groups (struct TALER_MERCHANTDB_PostgresContext
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "lookup_product_groups_asc",
- "SELECT"
- " product_group_serial"
- " ,product_group_name"
- " ,product_group_description"
- " FROM merchant_product_groups"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND product_group_serial > $2"
- " ORDER BY product_group_serial ASC"
- " LIMIT $3");
- PREPARE (pg,
- "lookup_product_groups_desc",
- "SELECT"
- " product_group_serial"
- " ,product_group_name"
- " ,product_group_description"
- " FROM merchant_product_groups"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND product_group_serial < $2"
- " ORDER BY product_group_serial DESC"
- " LIMIT $3");
+ if (limit > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " product_group_serial"
+ " ,product_group_name"
+ " ,product_group_description"
+ " FROM merchant_product_groups"
+ " WHERE product_group_serial > $1"
+ " ORDER BY product_group_serial ASC"
+ " LIMIT $2");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " product_group_serial"
+ " ,product_group_name"
+ " ,product_group_description"
+ " FROM merchant_product_groups"
+ " WHERE product_group_serial < $1"
+ " ORDER BY product_group_serial DESC"
+ " LIMIT $2");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- (limit > 0)
- ? "lookup_product_groups_asc"
- : "lookup_product_groups_desc",
+ "",
params,
&lookup_product_groups_cb,
&plc);
diff --git a/src/backenddb/select_report.c b/src/backenddb/select_report.c
@@ -19,29 +19,28 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_report.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_report (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t report_id,
- char **report_program_section,
- char **report_description,
- char **mime_type,
- char **data_source,
- char **target_address,
- struct GNUNET_TIME_Relative *frequency,
- struct GNUNET_TIME_Relative *frequency_shift,
- struct GNUNET_TIME_Absolute *next_transmission,
- enum TALER_ErrorCode *last_error_code,
- char **last_error_detail)
+TALER_MERCHANTDB_select_report (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t report_id,
+ char **report_program_section,
+ char **report_description,
+ char **mime_type,
+ char **data_source,
+ char **target_address,
+ struct GNUNET_TIME_Relative *frequency,
+ struct GNUNET_TIME_Relative *frequency_shift,
+ struct GNUNET_TIME_Absolute *next_transmission,
+ enum TALER_ErrorCode *last_error_code,
+ char **last_error_detail)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&report_id),
GNUNET_PQ_query_param_end
};
@@ -75,30 +74,29 @@ TALER_MERCHANTDB_select_report (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
*last_error_detail = NULL;
code = TALER_EC_NONE;
check_connection (pg);
- PREPARE (pg,
- "select_report",
- "SELECT"
- " mr.report_program_section"
- " ,mr.report_description"
- " ,mr.mime_type"
- " ,mr.data_source"
- " ,mr.target_address"
- " ,mr.frequency"
- " ,mr.frequency_shift"
- " ,mr.next_transmission"
- " ,mr.last_error_code"
- " ,mr.last_error_detail"
- " FROM merchant_reports mr"
- " JOIN merchant_instances mi"
- " USING (merchant_serial)"
- " WHERE merchant_id=$1"
- " AND report_serial=$2;");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " report_program_section"
+ " ,report_description"
+ " ,mime_type"
+ " ,data_source"
+ " ,target_address"
+ " ,frequency"
+ " ,frequency_shift"
+ " ,next_transmission"
+ " ,last_error_code"
+ " ,last_error_detail"
+ " FROM merchant_reports"
+ " WHERE report_serial=$1;");
qs = GNUNET_PQ_eval_prepared_singleton_select (
pg->conn,
- "select_report",
+ "",
params,
rs);
*last_error_code = (enum TALER_ErrorCode) code;
diff --git a/src/backenddb/select_reports.c b/src/backenddb/select_reports.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_reports.h"
#include "helper.h"
@@ -113,47 +111,45 @@ TALER_MERCHANTDB_select_reports (
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "select_reports_asc",
- "SELECT"
- " report_serial"
- " ,report_description"
- " ,frequency"
- " FROM merchant_reports"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND report_serial > $2"
- " AND NOT one_shot_hidden"
- " ORDER BY report_serial ASC"
- " LIMIT $3");
- PREPARE (pg,
- "select_reports_desc",
- "SELECT"
- " report_serial"
- " ,report_description"
- " ,frequency"
- " FROM merchant_reports"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND report_serial < $2"
- " AND NOT one_shot_hidden"
- " ORDER BY report_serial DESC"
- " LIMIT $3");
+ if (limit > 0)
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " report_serial"
+ " ,report_description"
+ " ,frequency"
+ " FROM merchant_reports"
+ " WHERE report_serial > $1"
+ " AND NOT one_shot_hidden"
+ " ORDER BY report_serial ASC"
+ " LIMIT $2");
+ }
+ else
+ {
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " report_serial"
+ " ,report_description"
+ " ,frequency"
+ " FROM merchant_reports"
+ " WHERE report_serial < $1"
+ " AND NOT one_shot_hidden"
+ " ORDER BY report_serial DESC"
+ " LIMIT $2");
+ }
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
- (limit > 0)
- ? "select_reports_asc"
- : "select_reports_desc",
+ "",
params,
&select_reports_cb,
&plc);
diff --git a/src/backenddb/select_unit.c b/src/backenddb/select_unit.c
@@ -19,116 +19,90 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_unit.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *unit_id,
- struct TALER_MERCHANTDB_UnitDetails *ud)
+TALER_MERCHANTDB_select_unit (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *unit_id,
+ struct TALER_MERCHANTDB_UnitDetails *ud)
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (unit_id),
GNUNET_PQ_query_param_end
};
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("unit_serial",
+ &ud->unit_serial),
+ GNUNET_PQ_result_spec_string ("unit",
+ &ud->unit),
+ GNUNET_PQ_result_spec_string ("unit_name_long",
+ &ud->unit_name_long),
+ GNUNET_PQ_result_spec_string ("unit_name_short",
+ &ud->unit_name_short),
+ TALER_PQ_result_spec_json ("unit_name_long_i18n",
+ &ud->unit_name_long_i18n),
+ TALER_PQ_result_spec_json ("unit_name_short_i18n",
+ &ud->unit_name_short_i18n),
+ GNUNET_PQ_result_spec_bool ("unit_allow_fraction",
+ &ud->unit_allow_fraction),
+ GNUNET_PQ_result_spec_uint32 ("unit_precision_level",
+ &ud->unit_precision_level),
+ GNUNET_PQ_result_spec_bool ("unit_active",
+ &ud->unit_active),
+ GNUNET_PQ_result_spec_bool ("unit_builtin",
+ &ud->unit_builtin),
+ GNUNET_PQ_result_spec_end
+ };
- if (NULL == ud)
- {
- struct GNUNET_PQ_ResultSpec rs_null[] = {
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
- return GNUNET_PQ_eval_prepared_singleton_select (
- pg->conn,
- "select_unit",
- params,
- rs_null);
- }
- else
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("unit_serial",
- &ud->unit_serial),
- GNUNET_PQ_result_spec_string ("unit",
- &ud->unit),
- GNUNET_PQ_result_spec_string ("unit_name_long",
- &ud->unit_name_long),
- GNUNET_PQ_result_spec_string ("unit_name_short",
- &ud->unit_name_short),
- TALER_PQ_result_spec_json ("unit_name_long_i18n",
- &ud->unit_name_long_i18n),
- TALER_PQ_result_spec_json ("unit_name_short_i18n",
- &ud->unit_name_short_i18n),
- GNUNET_PQ_result_spec_bool ("unit_allow_fraction",
- &ud->unit_allow_fraction),
- GNUNET_PQ_result_spec_uint32 ("unit_precision_level",
- &ud->unit_precision_level),
- GNUNET_PQ_result_spec_bool ("unit_active",
- &ud->unit_active),
- GNUNET_PQ_result_spec_bool ("unit_builtin",
- &ud->unit_builtin),
- GNUNET_PQ_result_spec_end
- };
-
- check_connection (pg);
-
- PREPARE (pg,
- "select_unit_custom",
- "SELECT"
- " cu.unit_serial"
- " ,cu.unit"
- " ,cu.unit_name_long"
- " ,cu.unit_name_short"
- " ,cu.unit_name_long_i18n"
- " ,cu.unit_name_short_i18n"
- " ,cu.unit_allow_fraction"
- " ,cu.unit_precision_level"
- " ,cu.unit_active"
- " ,FALSE AS unit_builtin"
- " FROM merchant_custom_units cu"
- " JOIN merchant_instances inst"
- " USING (merchant_serial)"
- " WHERE inst.merchant_id=$1"
- " AND cu.unit=$2");
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_unit_custom",
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
+ check_connection (pg);
+ // FIXME: combine both SELECT statements into a single statement!
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " cu.unit_serial"
+ " ,cu.unit"
+ " ,cu.unit_name_long"
+ " ,cu.unit_name_short"
+ " ,cu.unit_name_long_i18n"
+ " ,cu.unit_name_short_i18n"
+ " ,cu.unit_allow_fraction"
+ " ,cu.unit_precision_level"
+ " ,cu.unit_active"
+ " ,FALSE AS unit_builtin"
+ " FROM merchant_custom_units cu"
+ " WHERE cu.unit=$1");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ return qs;
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " bu.unit_serial"
+ " ,bu.unit"
+ " ,bu.unit_name_long"
+ " ,bu.unit_name_short"
+ " ,bu.unit_name_long_i18n"
+ " ,bu.unit_name_short_i18n"
+ " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)"
+ " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)"
+ " ,COALESCE(bo.override_active, bu.unit_active)"
+ " ,TRUE AS unit_builtin"
+ " FROM merchant.merchant_builtin_units bu"
+ " LEFT JOIN merchant_builtin_unit_overrides bo"
+ " ON bo.builtin_unit_serial = bu.unit_serial"
+ " WHERE bu.unit=$1");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
params,
rs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
- return qs;
-
- PREPARE (pg,
- "select_unit_builtin",
- "SELECT"
- " bu.unit_serial"
- " ,bu.unit"
- " ,bu.unit_name_long"
- " ,bu.unit_name_short"
- " ,bu.unit_name_long_i18n"
- " ,bu.unit_name_short_i18n"
- " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)"
- " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)"
- " ,COALESCE(bo.override_active, bu.unit_active)"
- " ,TRUE AS unit_builtin"
- " FROM merchant_builtin_units bu"
- " JOIN merchant_instances inst"
- " ON TRUE"
- " LEFT JOIN merchant_builtin_unit_overrides bo"
- " ON bo.builtin_unit_serial = bu.unit_serial"
- " AND bo.merchant_serial = inst.merchant_serial"
- " WHERE inst.merchant_id=$1"
- " AND bu.unit=$2");
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_unit_builtin",
- params,
- rs);
- }
}
diff --git a/src/backenddb/select_wirewatch_accounts.c b/src/backenddb/select_wirewatch_accounts.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/select_wirewatch_accounts.h"
#include "helper.h"
@@ -71,17 +69,17 @@ handle_results (void *cls,
json_t *credential;
uint64_t last_serial;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("merchant_id",
+ GNUNET_PQ_result_spec_string ("out_merchant_id",
&instance),
- GNUNET_PQ_result_spec_string ("payto_uri",
+ GNUNET_PQ_result_spec_string ("out_payto_uri",
&payto_uri.full_payto),
- GNUNET_PQ_result_spec_string ("credit_facade_url",
+ GNUNET_PQ_result_spec_string ("out_credit_facade_url",
&facade_url),
GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_result_spec_json ("credit_facade_credentials",
+ TALER_PQ_result_spec_json ("out_credit_facade_credentials",
&credential),
NULL),
- GNUNET_PQ_result_spec_uint64 ("last_bank_serial",
+ GNUNET_PQ_result_spec_uint64 ("out_last_bank_serial",
&last_serial),
GNUNET_PQ_result_spec_end
};
@@ -124,16 +122,12 @@ TALER_MERCHANTDB_select_wirewatch_accounts (
PREPARE (pg,
"select_wirewatch_progress",
"SELECT"
- " last_bank_serial"
- ",merchant_id"
- ",payto_uri"
- ",credit_facade_url"
- ",credit_facade_credentials::TEXT"
- " FROM merchant_accounts"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE active"
- " AND credit_facade_url IS NOT NULL");
+ " out_last_bank_serial"
+ " ,out_merchant_id"
+ " ,out_payto_uri"
+ " ,out_credit_facade_url"
+ " ,out_credit_facade_credentials::TEXT"
+ " FROM merchant.select_wirewatch_accounts()");
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"select_wirewatch_progress",
diff --git a/src/backenddb/select_wirewatch_accounts.sql b/src/backenddb/select_wirewatch_accounts.sql
@@ -0,0 +1,67 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant.select_wirewatch_accounts();
+CREATE FUNCTION merchant.select_wirewatch_accounts()
+RETURNS TABLE(
+ out_merchant_id TEXT,
+ out_payto_uri TEXT,
+ out_credit_facade_url TEXT,
+ out_credit_facade_credentials JSONB,
+ out_last_bank_serial INT8)
+LANGUAGE plpgsql
+AS $FN$
+DECLARE
+ rec RECORD;
+ s TEXT;
+ inner_rec RECORD;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ ,merchant_id
+ FROM merchant.merchant_instances
+ LOOP
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+ BEGIN
+ FOR inner_rec IN
+ EXECUTE format(
+ 'SELECT'
+ ' payto_uri AS pu'
+ ' ,credit_facade_url AS cfu'
+ ' ,credit_facade_credentials AS cfc'
+ ' ,last_bank_serial AS lbs'
+ ' FROM %I.merchant_accounts'
+ ' WHERE active'
+ ' AND credit_facade_url IS NOT NULL', s)
+ LOOP
+ out_merchant_id := rec.merchant_id;
+ out_payto_uri := inner_rec.pu;
+ out_credit_facade_url := inner_rec.cfu;
+ out_credit_facade_credentials := inner_rec.cfc;
+ out_last_bank_serial := inner_rec.lbs;
+ RETURN NEXT;
+ END LOOP;
+ EXCEPTION
+ WHEN undefined_table
+ THEN
+ NULL;
+ END;
+ END LOOP;
+END
+$FN$;
+COMMENT ON FUNCTION merchant.select_wirewatch_accounts()
+ IS 'Returns one row per active credit-facade-enabled merchant_account'
+ ' across all instance schemas.';
diff --git a/src/backenddb/set_instance.c b/src/backenddb/set_instance.c
@@ -0,0 +1,99 @@
+/*
+ This file is part of TALER
+ (C) 2026 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 src/backenddb/set_instance.c
+ * @brief Implementation of TALER_MERCHANTDB_set_instance
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_pq_lib.h>
+#include <taler/taler_pq_lib.h>
+#include "merchant-database/set_instance.h"
+#include "helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TALER_MERCHANTDB_set_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id)
+{
+ uint64_t serial;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("merchant_serial",
+ &serial),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &merchant_pub),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ char sp_sql[128];
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute (sp_sql),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (NULL == instance_id)
+ {
+ pg->current_merchant_id = 0;
+ pg->current_merchant_serial = 0;
+ memset (&pg->current_merchant_pub,
+ 0,
+ sizeof (pg->current_merchant_pub));
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ if ( (NULL != pg->current_merchant_id) &&
+ (0 == strcmp (pg->current_merchant_id,
+ instance_id)) )
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ check_connection (pg);
+ PREPARE (pg,
+ "set_instance_lookup_serial",
+ "SELECT merchant_serial"
+ " ,merchant_pub"
+ " FROM merchant.merchant_instances"
+ " WHERE merchant_id=$1");
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "set_instance_lookup_serial",
+ params,
+ rs);
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return qs;
+ GNUNET_snprintf (sp_sql,
+ sizeof (sp_sql),
+ "SET search_path TO merchant_instance_%llu",
+ (unsigned long long) serial);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_free (pg->current_merchant_id);
+ pg->current_merchant_id = GNUNET_strdup (instance_id);
+ pg->current_merchant_serial = serial;
+ pg->current_merchant_pub = merchant_pub;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
diff --git a/src/backenddb/solve_mfa_challenge.c b/src/backenddb/solve_mfa_challenge.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "taler/taler_merchant_util.h"
#include "merchant-database/solve_mfa_challenge.h"
@@ -60,15 +58,14 @@ TALER_MERCHANTDB_solve_mfa_challenge (
safe values, even though these should not be used with qs <= 0 */
*solved = false;
*retry_counter = 0;
- PREPARE (pg,
- "solve_mfa_challenge",
- "SELECT"
- " out_solved"
- " ,out_retry_counter"
- " FROM merchant_do_solve_mfa_challenge"
- " ($1, $2, $3, $4);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_solved"
+ " ,out_retry_counter"
+ " FROM merchant.merchant_do_solve_mfa_challenge"
+ " ($1, $2, $3, $4);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "solve_mfa_challenge",
+ "",
params,
rs);
if (qs <= 0)
diff --git a/src/backenddb/solve_mfa_challenge.sql b/src/backenddb/solve_mfa_challenge.sql
@@ -0,0 +1,84 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant_do_solve_mfa_challenge;
+CREATE FUNCTION merchant_do_solve_mfa_challenge (
+ IN in_challenge_id INT8,
+ IN in_h_body BYTEA,
+ IN in_solution TEXT,
+ IN in_now INT8,
+ OUT out_solved BOOLEAN,
+ OUT out_retry_counter INT4
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_confirmation_date INT8;
+DECLARE
+ my_rec RECORD;
+BEGIN
+
+ -- Check if challenge exists and matches
+ SELECT
+ tc.confirmation_date
+ ,tc.retry_counter
+ ,(tc.code = in_solution) AS solved
+ INTO
+ my_rec
+ FROM merchant.tan_challenges tc
+ WHERE tc.challenge_id = in_challenge_id
+ AND tc.h_body = in_h_body
+ AND tc.expiration_date > in_now;
+
+ IF NOT FOUND
+ THEN
+ out_solved = FALSE;
+ RETURN;
+ END IF;
+
+ my_confirmation_date = my_rec.confirmation_date;
+ out_retry_counter = my_rec.retry_counter;
+ out_solved = my_rec.solved;
+
+ -- Check if already solved before
+ IF my_confirmation_date IS NOT NULL
+ THEN
+ out_solved = TRUE;
+ RETURN;
+ END IF;
+
+ IF (0 = out_retry_counter)
+ THEN
+ out_solved = FALSE;
+ RETURN;
+ END IF;
+
+ IF out_solved
+ THEN
+ -- Newly solved, update DB!
+ my_confirmation_date = in_now;
+ UPDATE merchant.tan_challenges
+ SET confirmation_date = my_confirmation_date
+ WHERE challenge_id = in_challenge_id;
+ ELSE
+ -- Failed to solve, decrement retry counter
+ out_retry_counter = out_retry_counter - 1;
+ UPDATE merchant.tan_challenges
+ SET retry_counter = out_retry_counter
+ WHERE challenge_id = in_challenge_id;
+ END IF;
+END;
+$$;
diff --git a/src/backenddb/sql-schema/amalgamate-sql.sh b/src/backenddb/sql-schema/amalgamate-sql.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+# This file is part of TALER
+# Copyright (C) 2026 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU 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/>
+
+set -eu
+
+if [ $# -lt 1 ]; then
+ echo "Usage: $0 SQLFILES..." >&2
+ exit 1
+fi
+
+cat <<'EOF'
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+EOF
+
+for x in $@; do
+ echo -- generated from $x
+ # Remove SQL comments and empty lines
+ cat $x | sed -e "s/--.*//" | awk 'NF'
+done
diff --git a/src/backenddb/sql-schema/drop.sql b/src/backenddb/sql-schema/drop.sql
@@ -28,6 +28,21 @@ WITH xpatches AS (
FROM xpatches;
+-- Drop all per-instance schemas created by the per-instance trigger.
+DO $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN
+ SELECT nspname
+ FROM pg_namespace
+ WHERE nspname LIKE 'merchant_instance_%'
+ LOOP
+ EXECUTE format('DROP SCHEMA %I CASCADE', r.nspname);
+ END LOOP;
+END
+$$;
+
DROP SCHEMA merchant CASCADE;
-- And we're out of here...
diff --git a/src/backenddb/sql-schema/gen-procedures.sh b/src/backenddb/sql-schema/gen-procedures.sh
@@ -16,8 +16,8 @@
set -eu
-if [ $# -lt 1 ]; then
- echo "Usage: $0 SQLFILES..." >&2
+if [ $# -lt 2 ]; then
+ echo "Usage: $0 SCHEMANAME SQLFILES..." >&2
exit 1
fi
@@ -41,10 +41,12 @@ cat <<'EOF'
--
BEGIN;
+EOF
-SET search_path TO merchant;
+echo "CREATE SCHEMA IF NOT EXISTS $1;"
+echo "SET search_path TO $1;"
+shift 1
-EOF
# Output procedures, stripping comments
@@ -54,29 +56,4 @@ for x in $@; do
cat $x | sed -e "s/--.*//" | awk 'NF'
done
-# Output epilogue
-
-cat <<'EOF'
--- Epilogue
-DROP PROCEDURE IF EXISTS merchant_do_gc;
-CREATE PROCEDURE merchant_do_gc(in_now INT8)
-LANGUAGE plpgsql
-AS $$
-BEGIN
- DELETE FROM merchant_instances
- WHERE validation_needed
- AND validation_expiration < in_now;
- CALL merchant_statistic_amount_gc ();
- CALL merchant_statistic_bucket_gc ();
- CALL merchant_statistic_counter_gc ();
-
- DELETE FROM tan_challenges
- WHERE expiration_date < in_now;
- DELETE FROM merchant_unclaim_signatures
- WHERE expiration_time < in_now;
-END $$;
-COMMENT ON PROCEDURE merchant_do_gc
- IS 'calls all other garbage collection subroutines';
-
-COMMIT;
-EOF
+echo "COMMIT;"
diff --git a/src/backenddb/sql-schema/merchant-0013.sql b/src/backenddb/sql-schema/merchant-0013.sql
@@ -68,22 +68,6 @@ COMMENT ON COLUMN merchant_token_family_keys.private_key_deleted_at
COMMENT ON COLUMN merchant_token_family_keys.private_key_created_at
IS 'Specifies when the private key was created. Not terribly useful, mostly for debugging.';
--- Function to replace placeholders in a string with a given value
-CREATE FUNCTION replace_placeholder(
- template TEXT,
- key TEXT,
- value TEXT
-) RETURNS TEXT AS $$
-BEGIN
- RETURN regexp_replace(
- template,
- '{{\s*' || key || '\s*}}', -- Match the key with optional spaces
- value,
- 'g' -- Global replacement
- );
-END;
-$$ LANGUAGE plpgsql;
-
CREATE FUNCTION handle_inventory_changes()
RETURNS TRIGGER AS $$
BEGIN
diff --git a/src/backenddb/sql-schema/merchant-0027.sql b/src/backenddb/sql-schema/merchant-0027.sql
@@ -148,308 +148,4 @@ VALUES
('WeightUnitPound', 'pound', 'lb', TRUE, 3, TRUE),
('WeightUnitTon', 'metric tonne', 't', TRUE, 3, TRUE);
-DROP FUNCTION IF EXISTS merchant_do_insert_unit;
-CREATE FUNCTION merchant_do_insert_unit (
- IN in_instance_id TEXT,
- IN in_unit TEXT,
- IN in_unit_name_long TEXT,
- IN in_unit_name_short TEXT,
- IN in_unit_name_long_i18n BYTEA,
- IN in_unit_name_short_i18n BYTEA,
- IN in_unit_allow_fraction BOOL,
- IN in_unit_precision_level INT4,
- IN in_unit_active BOOL,
- OUT out_no_instance BOOL,
- OUT out_conflict BOOL,
- OUT out_unit_serial INT8)
- LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
-BEGIN
- SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id = in_instance_id;
-
- IF NOT FOUND THEN
- out_no_instance := TRUE;
- out_conflict := FALSE;
- out_unit_serial := NULL;
- RETURN;
- END IF;
-
- out_no_instance := FALSE;
-
- -- Reject attempts to shadow builtin identifiers.
- IF EXISTS (
- SELECT 1 FROM merchant_builtin_units bu WHERE bu.unit = in_unit
- ) THEN
- out_conflict := TRUE;
- out_unit_serial := NULL;
- RETURN;
- END IF;
-
- INSERT INTO merchant_custom_units (
- merchant_serial,
- unit,
- unit_name_long,
- unit_name_short,
- unit_name_long_i18n,
- unit_name_short_i18n,
- unit_allow_fraction,
- unit_precision_level,
- unit_active)
- VALUES (
- my_merchant_id,
- in_unit,
- in_unit_name_long,
- in_unit_name_short,
- in_unit_name_long_i18n,
- in_unit_name_short_i18n,
- in_unit_allow_fraction,
- in_unit_precision_level,
- in_unit_active)
- ON CONFLICT (merchant_serial, unit) DO NOTHING
- RETURNING unit_serial
- INTO out_unit_serial;
-
- IF FOUND THEN
- out_conflict := FALSE;
- RETURN;
- END IF;
-
- -- Conflict: custom unit already exists.
- SELECT unit_serial
- INTO out_unit_serial
- FROM merchant_custom_units
- WHERE merchant_serial = my_merchant_id
- AND unit = in_unit;
-
- out_conflict := TRUE;
-END $$;
-
-DROP FUNCTION IF EXISTS merchant_do_update_unit;
-CREATE FUNCTION merchant_do_update_unit (
- IN in_instance_id TEXT,
- IN in_unit_id TEXT,
- IN in_unit_name_long TEXT,
- IN in_unit_name_long_i18n BYTEA,
- IN in_unit_name_short TEXT,
- IN in_unit_name_short_i18n BYTEA,
- IN in_unit_allow_fraction BOOL,
- IN in_unit_precision_level INT4,
- IN in_unit_active BOOL,
- OUT out_no_instance BOOL,
- OUT out_no_unit BOOL,
- OUT out_builtin_conflict BOOL)
- LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_custom merchant_custom_units%ROWTYPE;
- my_builtin merchant_builtin_units%ROWTYPE;
- my_override merchant_builtin_unit_overrides%ROWTYPE;
- new_unit_name_long TEXT;
- new_unit_name_short TEXT;
- new_unit_name_long_i18n BYTEA;
- new_unit_name_short_i18n BYTEA;
- new_unit_allow_fraction BOOL;
- new_unit_precision_level INT4;
- new_unit_active BOOL;
- old_unit_allow_fraction BOOL;
- old_unit_precision_level INT4;
- old_unit_active BOOL;
-BEGIN
- out_no_instance := FALSE;
- out_no_unit := FALSE;
- out_builtin_conflict := FALSE;
-
- SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id = in_instance_id;
-
- IF NOT FOUND THEN
- out_no_instance := TRUE;
- RETURN;
- END IF;
-
- SELECT *
- INTO my_custom
- FROM merchant_custom_units
- WHERE merchant_serial = my_merchant_id
- AND unit = in_unit_id
- FOR UPDATE;
-
- IF FOUND THEN
- old_unit_allow_fraction := my_custom.unit_allow_fraction;
- old_unit_precision_level := my_custom.unit_precision_level;
- old_unit_active := my_custom.unit_active;
-
- new_unit_name_long := COALESCE (in_unit_name_long, my_custom.unit_name_long);
- new_unit_name_short := COALESCE (in_unit_name_short, my_custom.unit_name_short);
- new_unit_name_long_i18n := COALESCE (in_unit_name_long_i18n,
- my_custom.unit_name_long_i18n);
- new_unit_name_short_i18n := COALESCE (in_unit_name_short_i18n,
- my_custom.unit_name_short_i18n);
- new_unit_allow_fraction := COALESCE (in_unit_allow_fraction,
- my_custom.unit_allow_fraction);
- new_unit_precision_level := COALESCE (in_unit_precision_level,
- my_custom.unit_precision_level);
- IF NOT new_unit_allow_fraction THEN
- new_unit_precision_level := 0;
- END IF;
-
- new_unit_active := COALESCE (in_unit_active, my_custom.unit_active);
-
- UPDATE merchant_custom_units SET
- unit_name_long = new_unit_name_long
- ,unit_name_long_i18n = new_unit_name_long_i18n
- ,unit_name_short = new_unit_name_short
- ,unit_name_short_i18n = new_unit_name_short_i18n
- ,unit_allow_fraction = new_unit_allow_fraction
- ,unit_precision_level = new_unit_precision_level
- ,unit_active = new_unit_active
- WHERE unit_serial = my_custom.unit_serial;
-
- ASSERT FOUND,'SELECTED it earlier, should UPDATE it now';
-
- IF old_unit_allow_fraction IS DISTINCT FROM new_unit_allow_fraction
- OR old_unit_precision_level IS DISTINCT FROM new_unit_precision_level
- THEN
- UPDATE merchant_inventory SET
- allow_fractional_quantity = new_unit_allow_fraction
- , fractional_precision_level = new_unit_precision_level
- WHERE merchant_serial = my_merchant_id
- AND unit = in_unit_id
- AND allow_fractional_quantity = old_unit_allow_fraction
- AND fractional_precision_level = old_unit_precision_level;
- END IF;
- RETURN;
- END IF;
-
- -- Try builtin with overrides.
- SELECT *
- INTO my_builtin
- FROM merchant_builtin_units
- WHERE unit = in_unit_id;
-
- IF NOT FOUND THEN
- out_no_unit := TRUE;
- RETURN;
- END IF;
-
- SELECT *
- INTO my_override
- FROM merchant_builtin_unit_overrides
- WHERE merchant_serial = my_merchant_id
- AND builtin_unit_serial = my_builtin.unit_serial
- FOR UPDATE;
-
- old_unit_allow_fraction := COALESCE (my_override.override_allow_fraction,
- my_builtin.unit_allow_fraction);
- old_unit_precision_level := COALESCE (my_override.override_precision_level,
- my_builtin.unit_precision_level);
- old_unit_active := COALESCE (my_override.override_active,
- my_builtin.unit_active);
-
- -- Only policy flags can change for builtin units.
- IF in_unit_name_long IS NOT NULL
- OR in_unit_name_short IS NOT NULL
- OR in_unit_name_long_i18n IS NOT NULL
- OR in_unit_name_short_i18n IS NOT NULL THEN
- out_builtin_conflict := TRUE;
- RETURN;
- END IF;
-
- new_unit_allow_fraction := COALESCE (in_unit_allow_fraction,
- old_unit_allow_fraction);
- new_unit_precision_level := COALESCE (in_unit_precision_level,
- old_unit_precision_level);
- IF NOT new_unit_allow_fraction THEN
- new_unit_precision_level := 0;
- END IF;
- new_unit_active := COALESCE (in_unit_active, old_unit_active);
-
- INSERT INTO merchant_builtin_unit_overrides (
- merchant_serial,
- builtin_unit_serial,
- override_allow_fraction,
- override_precision_level,
- override_active)
- VALUES (my_merchant_id,
- my_builtin.unit_serial,
- new_unit_allow_fraction,
- new_unit_precision_level,
- new_unit_active)
- ON CONFLICT (merchant_serial, builtin_unit_serial)
- DO UPDATE SET override_allow_fraction = EXCLUDED.override_allow_fraction
- , override_precision_level = EXCLUDED.override_precision_level
- , override_active = EXCLUDED.override_active;
-
- IF old_unit_allow_fraction IS DISTINCT FROM new_unit_allow_fraction
- OR old_unit_precision_level IS DISTINCT FROM new_unit_precision_level
- THEN
- UPDATE merchant_inventory SET
- allow_fractional_quantity = new_unit_allow_fraction
- , fractional_precision_level = new_unit_precision_level
- WHERE merchant_serial = my_merchant_id
- AND unit = in_unit_id
- AND allow_fractional_quantity = old_unit_allow_fraction
- AND fractional_precision_level = old_unit_precision_level;
- END IF;
-
- RETURN;
-END $$;
-
-DROP FUNCTION IF EXISTS merchant_do_delete_unit;
-CREATE FUNCTION merchant_do_delete_unit (
- IN in_instance_id TEXT,
- IN in_unit_id TEXT,
- OUT out_no_instance BOOL,
- OUT out_no_unit BOOL,
- OUT out_builtin_conflict BOOL)
- LANGUAGE plpgsql
-AS $$
-DECLARE
- my_merchant_id INT8;
- my_unit merchant_custom_units%ROWTYPE;
-BEGIN
- out_no_instance := FALSE;
- out_no_unit := FALSE;
- out_builtin_conflict := FALSE;
-
- SELECT merchant_serial
- INTO my_merchant_id
- FROM merchant_instances
- WHERE merchant_id = in_instance_id;
-
- IF NOT FOUND THEN
- out_no_instance := TRUE;
- RETURN;
- END IF;
-
- SELECT *
- INTO my_unit
- FROM merchant_custom_units
- WHERE merchant_serial = my_merchant_id
- AND unit = in_unit_id
- FOR UPDATE;
-
- IF NOT FOUND THEN
- IF EXISTS (SELECT 1 FROM merchant_builtin_units bu WHERE bu.unit = in_unit_id) THEN
- out_builtin_conflict := TRUE;
- ELSE
- out_no_unit := TRUE;
- END IF;
- RETURN;
- END IF;
-
- DELETE FROM merchant_custom_units
- WHERE unit_serial = my_unit.unit_serial;
-
- RETURN;
-END $$;
-
COMMIT;
diff --git a/src/backenddb/sql-schema/merchant-0036-copy.sql.fragment b/src/backenddb/sql-schema/merchant-0036-copy.sql.fragment
@@ -0,0 +1,477 @@
+ -- =================================================================
+ -- Copy per-instance row data from merchant.* into the new schema.
+ -- Tables are listed in FK-dependency order so plain INSERT works
+ -- (no DEFERRABLE games, no WITH ORDINALITY). $1 is bound to
+ -- rec.merchant_serial via USING. The merchant_serial column is
+ -- dropped from every column list — the schema name is the
+ -- discriminator. All target tables use GENERATED BY DEFAULT AS
+ -- IDENTITY (so we can keep the source serial values), except
+ -- merchant_login_tokens whose `serial` is GENERATED ALWAYS — that
+ -- one needs OVERRIDING SYSTEM VALUE.
+ --
+ -- Two stat-meta tables (merchant_statistic_bucket_meta,
+ -- merchant_statistic_interval_meta) have no merchant_serial in the
+ -- source schema — they are global slug catalogs and are copied
+ -- verbatim into every per-instance schema.
+ -- =================================================================
+
+ -- ---------------- direct merchant_serial (no JOIN) --------------
+
+ EXECUTE format('INSERT INTO %I.merchant_accounts'
+ || ' (account_serial, h_wire, salt, credit_facade_url,'
+ || ' credit_facade_credentials, last_bank_serial, payto_uri,'
+ || ' active, extra_wire_subject_metadata)'
+ || ' SELECT account_serial, h_wire, salt, credit_facade_url,'
+ || ' credit_facade_credentials, last_bank_serial, payto_uri,'
+ || ' active, extra_wire_subject_metadata'
+ || ' FROM merchant.merchant_accounts'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_categories'
+ || ' (category_serial, category_name, category_name_i18n)'
+ || ' SELECT category_serial, category_name, category_name_i18n'
+ || ' FROM merchant.merchant_categories'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_contract_terms'
+ || ' (order_serial, order_id, contract_terms, wallet_data,'
+ || ' h_contract_terms, creation_time, pay_deadline, refund_deadline,'
+ || ' paid, wired, fulfillment_url, session_id, pos_key, pos_algorithm,'
+ || ' claim_token, choice_index)'
+ || ' SELECT order_serial, order_id, contract_terms, wallet_data,'
+ || ' h_contract_terms, creation_time, pay_deadline, refund_deadline,'
+ || ' paid, wired, fulfillment_url, session_id, pos_key, pos_algorithm,'
+ || ' claim_token, choice_index'
+ || ' FROM merchant.merchant_contract_terms'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_custom_units'
+ || ' (unit_serial, unit, unit_name_long, unit_name_short,'
+ || ' unit_name_long_i18n, unit_name_short_i18n,'
+ || ' unit_allow_fraction, unit_precision_level, unit_active)'
+ || ' SELECT unit_serial, unit, unit_name_long, unit_name_short,'
+ || ' unit_name_long_i18n, unit_name_short_i18n,'
+ || ' unit_allow_fraction, unit_precision_level, unit_active'
+ || ' FROM merchant.merchant_custom_units'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- merchant_donau_instances: source filter column is named
+ -- merchant_instance_serial (not merchant_serial).
+ EXECUTE format('INSERT INTO %I.merchant_donau_instances'
+ || ' (donau_instances_serial, donau_url, charity_name, charity_id,'
+ || ' charity_max_per_year, charity_receipts_to_date, current_year)'
+ || ' SELECT donau_instances_serial, donau_url, charity_name, charity_id,'
+ || ' charity_max_per_year, charity_receipts_to_date, current_year'
+ || ' FROM merchant.merchant_donau_instances'
+ || ' WHERE merchant_instance_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- merchant_login_tokens: target `serial` column is GENERATED ALWAYS
+ -- → must use OVERRIDING SYSTEM VALUE to preserve serial values.
+ EXECUTE format('INSERT INTO %I.merchant_login_tokens'
+ || ' (token, creation_time, expiration_time, validity_scope,'
+ || ' description, serial)'
+ || ' OVERRIDING SYSTEM VALUE'
+ || ' SELECT token, creation_time, expiration_time, validity_scope,'
+ || ' description, serial'
+ || ' FROM merchant.merchant_login_tokens'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_money_pots'
+ || ' (money_pot_serial, money_pot_name, money_pot_description, pot_totals)'
+ || ' SELECT money_pot_serial, money_pot_name, money_pot_description, pot_totals'
+ || ' FROM merchant.merchant_money_pots'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_otp_devices'
+ || ' (otp_serial, otp_id, otp_description, otp_key, otp_algorithm, otp_ctr)'
+ || ' SELECT otp_serial, otp_id, otp_description, otp_key, otp_algorithm, otp_ctr'
+ || ' FROM merchant.merchant_otp_devices'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_orders'
+ || ' (order_serial, order_id, claim_token, h_post_data, pay_deadline,'
+ || ' creation_time, contract_terms, pos_key, pos_algorithm,'
+ || ' fulfillment_url, session_id)'
+ || ' SELECT order_serial, order_id, claim_token, h_post_data, pay_deadline,'
+ || ' creation_time, contract_terms, pos_key, pos_algorithm,'
+ || ' fulfillment_url, session_id'
+ || ' FROM merchant.merchant_orders'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_product_groups'
+ || ' (product_group_serial, product_group_name, product_group_description)'
+ || ' SELECT product_group_serial, product_group_name, product_group_description'
+ || ' FROM merchant.merchant_product_groups'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- merchant_inventory references merchant_product_groups and merchant_money_pots
+ -- (already copied above).
+ EXECUTE format('INSERT INTO %I.merchant_inventory'
+ || ' (product_serial, product_id, description, description_i18n,'
+ || ' unit, image, taxes, total_stock, total_sold, total_lost,'
+ || ' address, next_restock, minimum_age, product_name, image_hash,'
+ || ' price_array, total_stock_frac, total_sold_frac, total_lost_frac,'
+ || ' allow_fractional_quantity, fractional_precision_level,'
+ || ' product_group_serial, money_pot_serial, price_is_net)'
+ || ' SELECT product_serial, product_id, description, description_i18n,'
+ || ' unit, image, taxes, total_stock, total_sold, total_lost,'
+ || ' address, next_restock, minimum_age, product_name, image_hash,'
+ || ' price_array, total_stock_frac, total_sold_frac, total_lost_frac,'
+ || ' allow_fractional_quantity, fractional_precision_level,'
+ || ' product_group_serial, money_pot_serial, price_is_net'
+ || ' FROM merchant.merchant_inventory'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_reports'
+ || ' (report_serial, report_program_section, report_description,'
+ || ' mime_type, report_token, data_source, target_address,'
+ || ' frequency, frequency_shift, next_transmission,'
+ || ' last_error_code, last_error_detail, one_shot_hidden)'
+ || ' SELECT report_serial, report_program_section, report_description,'
+ || ' mime_type, report_token, data_source, target_address,'
+ || ' frequency, frequency_shift, next_transmission,'
+ || ' last_error_code, last_error_detail, one_shot_hidden'
+ || ' FROM merchant.merchant_reports'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- merchant_template references merchant_otp_devices (already copied).
+ EXECUTE format('INSERT INTO %I.merchant_template'
+ || ' (template_serial, template_id, template_description,'
+ || ' otp_device_id, template_contract, editable_defaults)'
+ || ' SELECT template_serial, template_id, template_description,'
+ || ' otp_device_id, template_contract, editable_defaults'
+ || ' FROM merchant.merchant_template'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_token_families'
+ || ' (token_family_serial, slug, name, description, description_i18n,'
+ || ' valid_after, valid_before, duration, kind, issued, used,'
+ || ' validity_granularity, start_offset, cipher_choice, extra_data)'
+ || ' SELECT token_family_serial, slug, name, description, description_i18n,'
+ || ' valid_after, valid_before, duration, kind, issued, used,'
+ || ' validity_granularity, start_offset, cipher_choice, extra_data'
+ || ' FROM merchant.merchant_token_families'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_webhook'
+ || ' (webhook_serial, webhook_id, event_type, url, http_method,'
+ || ' header_template, body_template)'
+ || ' SELECT webhook_serial, webhook_id, event_type, url, http_method,'
+ || ' header_template, body_template'
+ || ' FROM merchant.merchant_webhook'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+
+ EXECUTE format('INSERT INTO %I.merchant_builtin_unit_overrides'
+ || ' (builtin_unit_serial, override_allow_fraction,'
+ || ' override_precision_level, override_active)'
+ || ' SELECT builtin_unit_serial, override_allow_fraction,'
+ || ' override_precision_level, override_active'
+ || ' FROM merchant.merchant_builtin_unit_overrides'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- ---------------- statistics: meta tables are GLOBAL ------------
+ -- Copied unfiltered (every instance gets a full slug catalog).
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_bucket_meta'
+ || ' (bmeta_serial_id, slug, description, stype, ranges, ages)'
+ || ' SELECT bmeta_serial_id, slug, description, stype, ranges, ages'
+ || ' FROM merchant.merchant_statistic_bucket_meta', s);
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_interval_meta'
+ || ' (imeta_serial_id, slug, description, stype, ranges, precisions)'
+ || ' SELECT imeta_serial_id, slug, description, stype, ranges, precisions'
+ || ' FROM merchant.merchant_statistic_interval_meta', s);
+
+ -- ---------------- statistics: per-instance event/bucket tables --
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_amount_event'
+ || ' (aevent_serial_id, imeta_serial_id, slot,'
+ || ' delta_curr, delta_value, delta_frac)'
+ || ' SELECT aevent_serial_id, imeta_serial_id, slot,'
+ || ' delta_curr, delta_value, delta_frac'
+ || ' FROM merchant.merchant_statistic_amount_event'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_counter_event'
+ || ' (nevent_serial_id, imeta_serial_id, slot, delta)'
+ || ' SELECT nevent_serial_id, imeta_serial_id, slot, delta'
+ || ' FROM merchant.merchant_statistic_counter_event'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_bucket_amount'
+ || ' (bmeta_serial_id, bucket_start, bucket_range, curr,'
+ || ' cumulative_value, cumulative_frac)'
+ || ' SELECT bmeta_serial_id, bucket_start, bucket_range, curr,'
+ || ' cumulative_value, cumulative_frac'
+ || ' FROM merchant.merchant_statistic_bucket_amount'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_bucket_counter'
+ || ' (bmeta_serial_id, bucket_start, bucket_range, cumulative_number)'
+ || ' SELECT bmeta_serial_id, bucket_start, bucket_range, cumulative_number'
+ || ' FROM merchant.merchant_statistic_bucket_counter'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_interval_amount'
+ || ' (imeta_serial_id, event_delimiter, range, curr,'
+ || ' cumulative_value, cumulative_frac)'
+ || ' SELECT imeta_serial_id, event_delimiter, range, curr,'
+ || ' cumulative_value, cumulative_frac'
+ || ' FROM merchant.merchant_statistic_interval_amount'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_statistic_interval_counter'
+ || ' (imeta_serial_id, range, event_delimiter, cumulative_number)'
+ || ' SELECT imeta_serial_id, range, event_delimiter, cumulative_number'
+ || ' FROM merchant.merchant_statistic_interval_counter'
+ || ' WHERE merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- ---------------- account-linked (JOIN via account_serial) ------
+
+ EXECUTE format('INSERT INTO %I.merchant_kyc'
+ || ' (kyc_serial_id, kyc_timestamp, kyc_ok, account_serial,'
+ || ' exchange_url, access_token, exchange_http_status,'
+ || ' exchange_ec_code, aml_review, jaccount_limits,'
+ || ' last_rule_gen, next_kyc_poll, kyc_backoff)'
+ || ' SELECT k.kyc_serial_id, k.kyc_timestamp, k.kyc_ok, k.account_serial,'
+ || ' k.exchange_url, k.access_token, k.exchange_http_status,'
+ || ' k.exchange_ec_code, k.aml_review, k.jaccount_limits,'
+ || ' k.last_rule_gen, k.next_kyc_poll, k.kyc_backoff'
+ || ' FROM merchant.merchant_kyc k'
+ || ' JOIN merchant.merchant_accounts a'
+ || ' ON k.account_serial = a.account_serial'
+ || ' WHERE a.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_deposit_confirmations'
+ || ' (deposit_confirmation_serial, order_serial, deposit_timestamp,'
+ || ' exchange_url, total_without_fee, wire_fee, signkey_serial,'
+ || ' exchange_sig, account_serial, wire_transfer_deadline,'
+ || ' wire_pending, exchange_failure, retry_backoff)'
+ || ' SELECT dc.deposit_confirmation_serial, dc.order_serial, dc.deposit_timestamp,'
+ || ' dc.exchange_url, dc.total_without_fee, dc.wire_fee, dc.signkey_serial,'
+ || ' dc.exchange_sig, dc.account_serial, dc.wire_transfer_deadline,'
+ || ' dc.wire_pending, dc.exchange_failure, dc.retry_backoff'
+ || ' FROM merchant.merchant_deposit_confirmations dc'
+ || ' JOIN merchant.merchant_accounts a'
+ || ' ON dc.account_serial = a.account_serial'
+ || ' WHERE a.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_expected_transfers'
+ || ' (expected_credit_serial, exchange_url, wtid, expected_credit_amount,'
+ || ' wire_fee, account_serial, expected_time, retry_time, last_http_status,'
+ || ' last_ec, last_detail, retry_needed, signkey_serial, exchange_sig,'
+ || ' h_details, confirmed)'
+ || ' SELECT et.expected_credit_serial, et.exchange_url, et.wtid, et.expected_credit_amount,'
+ || ' et.wire_fee, et.account_serial, et.expected_time, et.retry_time, et.last_http_status,'
+ || ' et.last_ec, et.last_detail, et.retry_needed, et.signkey_serial, et.exchange_sig,'
+ || ' et.h_details, et.confirmed'
+ || ' FROM merchant.merchant_expected_transfers et'
+ || ' JOIN merchant.merchant_accounts a'
+ || ' ON et.account_serial = a.account_serial'
+ || ' WHERE a.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_transfers'
+ || ' (credit_serial, exchange_url, wtid, credit_amount, account_serial,'
+ || ' bank_serial_id, expected, execution_time)'
+ || ' SELECT t.credit_serial, t.exchange_url, t.wtid, t.credit_amount, t.account_serial,'
+ || ' t.bank_serial_id, t.expected, t.execution_time'
+ || ' FROM merchant.merchant_transfers t'
+ || ' JOIN merchant.merchant_accounts a'
+ || ' ON t.account_serial = a.account_serial'
+ || ' WHERE a.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- ---------------- deposits: deeper join (dc → accounts) ---------
+
+ EXECUTE format('INSERT INTO %I.merchant_deposits'
+ || ' (deposit_serial, coin_offset, deposit_confirmation_serial,'
+ || ' coin_pub, coin_sig, amount_with_fee, deposit_fee, refund_fee,'
+ || ' settlement_retry_needed, settlement_retry_time,'
+ || ' settlement_last_http_status, settlement_last_ec,'
+ || ' settlement_last_detail, settlement_wtid,'
+ || ' settlement_coin_contribution, settlement_expected_credit_serial,'
+ || ' signkey_serial, settlement_exchange_sig)'
+ || ' SELECT d.deposit_serial, d.coin_offset, d.deposit_confirmation_serial,'
+ || ' d.coin_pub, d.coin_sig, d.amount_with_fee, d.deposit_fee, d.refund_fee,'
+ || ' d.settlement_retry_needed, d.settlement_retry_time,'
+ || ' d.settlement_last_http_status, d.settlement_last_ec,'
+ || ' d.settlement_last_detail, d.settlement_wtid,'
+ || ' d.settlement_coin_contribution, d.settlement_expected_credit_serial,'
+ || ' d.signkey_serial, d.settlement_exchange_sig'
+ || ' FROM merchant.merchant_deposits d'
+ || ' JOIN merchant.merchant_deposit_confirmations dc'
+ || ' ON d.deposit_confirmation_serial = dc.deposit_confirmation_serial'
+ || ' JOIN merchant.merchant_accounts a'
+ || ' ON dc.account_serial = a.account_serial'
+ || ' WHERE a.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- expected_transfer_to_coin: deposit_serial → deposits → dc → accounts.
+ EXECUTE format('INSERT INTO %I.merchant_expected_transfer_to_coin'
+ || ' (deposit_serial, expected_credit_serial, offset_in_exchange_list,'
+ || ' exchange_deposit_value, exchange_deposit_fee)'
+ || ' SELECT ettc.deposit_serial, ettc.expected_credit_serial, ettc.offset_in_exchange_list,'
+ || ' ettc.exchange_deposit_value, ettc.exchange_deposit_fee'
+ || ' FROM merchant.merchant_expected_transfer_to_coin ettc'
+ || ' JOIN merchant.merchant_deposits d'
+ || ' ON ettc.deposit_serial = d.deposit_serial'
+ || ' JOIN merchant.merchant_deposit_confirmations dc'
+ || ' ON d.deposit_confirmation_serial = dc.deposit_confirmation_serial'
+ || ' JOIN merchant.merchant_accounts a'
+ || ' ON dc.account_serial = a.account_serial'
+ || ' WHERE a.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- transfer_signatures: expected_credit_serial → expected_transfers → accounts.
+ EXECUTE format('INSERT INTO %I.merchant_transfer_signatures'
+ || ' (expected_credit_serial, signkey_serial, wire_fee, credit_amount,'
+ || ' execution_time, exchange_sig)'
+ || ' SELECT ts.expected_credit_serial, ts.signkey_serial, ts.wire_fee, ts.credit_amount,'
+ || ' ts.execution_time, ts.exchange_sig'
+ || ' FROM merchant.merchant_transfer_signatures ts'
+ || ' JOIN merchant.merchant_expected_transfers et'
+ || ' ON ts.expected_credit_serial = et.expected_credit_serial'
+ || ' JOIN merchant.merchant_accounts a'
+ || ' ON et.account_serial = a.account_serial'
+ || ' WHERE a.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- ---------------- order/contract-linked (JOIN via order_serial) ---
+
+ EXECUTE format('INSERT INTO %I.merchant_refunds'
+ || ' (refund_serial, order_serial, rtransaction_id, refund_timestamp,'
+ || ' coin_pub, reason, refund_amount)'
+ || ' SELECT r.refund_serial, r.order_serial, r.rtransaction_id, r.refund_timestamp,'
+ || ' r.coin_pub, r.reason, r.refund_amount'
+ || ' FROM merchant.merchant_refunds r'
+ || ' JOIN merchant.merchant_contract_terms ct'
+ || ' ON r.order_serial = ct.order_serial'
+ || ' WHERE ct.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_refund_proofs'
+ || ' (refund_serial, exchange_sig, signkey_serial)'
+ || ' SELECT rp.refund_serial, rp.exchange_sig, rp.signkey_serial'
+ || ' FROM merchant.merchant_refund_proofs rp'
+ || ' JOIN merchant.merchant_refunds r'
+ || ' ON rp.refund_serial = r.refund_serial'
+ || ' JOIN merchant.merchant_contract_terms ct'
+ || ' ON r.order_serial = ct.order_serial'
+ || ' WHERE ct.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_order_token_blinded_sigs'
+ || ' (order_token_bs_serial, order_serial, token_index,'
+ || ' token_blinded_signature, token_hash)'
+ || ' SELECT otbs.order_token_bs_serial, otbs.order_serial, otbs.token_index,'
+ || ' otbs.token_blinded_signature, otbs.token_hash'
+ || ' FROM merchant.merchant_order_token_blinded_sigs otbs'
+ || ' JOIN merchant.merchant_contract_terms ct'
+ || ' ON otbs.order_serial = ct.order_serial'
+ || ' WHERE ct.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- unclaim_signatures: linked via h_contract_terms.
+ EXECUTE format('INSERT INTO %I.merchant_unclaim_signatures'
+ || ' (unclaim_serial, h_contract_terms, unclaim_sig, expiration_time)'
+ || ' SELECT us.unclaim_serial, us.h_contract_terms, us.unclaim_sig, us.expiration_time'
+ || ' FROM merchant.merchant_unclaim_signatures us'
+ || ' JOIN merchant.merchant_contract_terms ct'
+ || ' ON us.h_contract_terms = ct.h_contract_terms'
+ || ' WHERE ct.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- order_locks: order_serial → contract_terms.
+ EXECUTE format('INSERT INTO %I.merchant_order_locks'
+ || ' (product_serial, total_locked, order_serial, total_locked_frac)'
+ || ' SELECT ol.product_serial, ol.total_locked, ol.order_serial, ol.total_locked_frac'
+ || ' FROM merchant.merchant_order_locks ol'
+ || ' JOIN merchant.merchant_contract_terms ct'
+ || ' ON ol.order_serial = ct.order_serial'
+ || ' WHERE ct.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- inventory_locks: product_serial → inventory.
+ EXECUTE format('INSERT INTO %I.merchant_inventory_locks'
+ || ' (product_serial, lock_uuid, total_locked, expiration, total_locked_frac)'
+ || ' SELECT il.product_serial, il.lock_uuid, il.total_locked, il.expiration, il.total_locked_frac'
+ || ' FROM merchant.merchant_inventory_locks il'
+ || ' JOIN merchant.merchant_inventory i'
+ || ' ON il.product_serial = i.product_serial'
+ || ' WHERE i.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- product_categories: junction; pivot on product_serial → inventory.
+ EXECUTE format('INSERT INTO %I.merchant_product_categories'
+ || ' (category_serial, product_serial)'
+ || ' SELECT pc.category_serial, pc.product_serial'
+ || ' FROM merchant.merchant_product_categories pc'
+ || ' JOIN merchant.merchant_inventory i'
+ || ' ON pc.product_serial = i.product_serial'
+ || ' WHERE i.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ -- ---------------- token chain (token_families → keys → tokens) --
+
+ EXECUTE format('INSERT INTO %I.merchant_token_family_keys'
+ || ' (token_family_key_serial, token_family_serial, pub, h_pub,'
+ || ' priv, cipher, signature_validity_start, signature_validity_end,'
+ || ' private_key_deleted_at, private_key_created_at)'
+ || ' SELECT tfk.token_family_key_serial, tfk.token_family_serial, tfk.pub, tfk.h_pub,'
+ || ' tfk.priv, tfk.cipher, tfk.signature_validity_start, tfk.signature_validity_end,'
+ || ' tfk.private_key_deleted_at, tfk.private_key_created_at'
+ || ' FROM merchant.merchant_token_family_keys tfk'
+ || ' JOIN merchant.merchant_token_families tf'
+ || ' ON tfk.token_family_serial = tf.token_family_serial'
+ || ' WHERE tf.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_issued_tokens'
+ || ' (issued_token_serial, h_contract_terms, token_family_key_serial, blind_sig)'
+ || ' SELECT it.issued_token_serial, it.h_contract_terms, it.token_family_key_serial, it.blind_sig'
+ || ' FROM merchant.merchant_issued_tokens it'
+ || ' JOIN merchant.merchant_token_family_keys tfk'
+ || ' ON it.token_family_key_serial = tfk.token_family_key_serial'
+ || ' JOIN merchant.merchant_token_families tf'
+ || ' ON tfk.token_family_serial = tf.token_family_serial'
+ || ' WHERE tf.merchant_serial = $1', s)
+ USING rec.merchant_serial;
+
+ EXECUTE format('INSERT INTO %I.merchant_used_tokens'
+ || ' (spent_token_serial, h_contract_terms, token_family_key_serial,'
+ || ' token_pub, token_sig, blind_sig)'
+ || ' SELECT ut.spent_token_serial, ut.h_contract_terms, ut.token_family_key_serial,'
+ || ' ut.token_pub, ut.token_sig, ut.blind_sig'
+ || ' FROM merchant.merchant_used_tokens ut'
+ || ' JOIN merchant.merchant_token_family_keys tfk'
+ || ' ON ut.token_family_key_serial = tfk.token_family_key_serial'
+ || ' JOIN merchant.merchant_token_families tf'
+ || ' ON tfk.token_family_serial = tf.token_family_serial'
+ || ' WHERE tf.merchant_serial = $1', s)
+ USING rec.merchant_serial;
diff --git a/src/backenddb/sql-schema/merchant-0036-drop.sql.fragment b/src/backenddb/sql-schema/merchant-0036-drop.sql.fragment
@@ -0,0 +1,63 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+
+-- Drop the now-empty per-instance tables from the merchant schema.
+-- CASCADE removes the (now obsolete) trigger attachments along with them.
+-- The per-instance trigger functions in merchant.* (handle_category_changes,
+-- merchant_*_statistics_trigger, ...) are kept around — they will be replaced
+-- by the procedures.sql reload after the patch finishes.
+
+DROP TABLE merchant.merchant_unclaim_signatures CASCADE;
+DROP TABLE merchant.merchant_used_tokens CASCADE;
+DROP TABLE merchant.merchant_issued_tokens CASCADE;
+DROP TABLE merchant.merchant_token_family_keys CASCADE;
+DROP TABLE merchant.merchant_token_families CASCADE;
+DROP TABLE merchant.merchant_webhook CASCADE;
+DROP TABLE merchant.merchant_transfer_signatures CASCADE;
+DROP TABLE merchant.merchant_expected_transfer_to_coin CASCADE;
+DROP TABLE merchant.merchant_transfers CASCADE;
+DROP TABLE merchant.merchant_expected_transfers CASCADE;
+DROP TABLE merchant.merchant_kyc CASCADE;
+DROP TABLE merchant.merchant_deposits CASCADE;
+DROP TABLE merchant.merchant_deposit_confirmations CASCADE;
+DROP TABLE merchant.merchant_refund_proofs CASCADE;
+DROP TABLE merchant.merchant_refunds CASCADE;
+DROP TABLE merchant.merchant_order_token_blinded_sigs CASCADE;
+DROP TABLE merchant.merchant_order_locks CASCADE;
+DROP TABLE merchant.merchant_inventory_locks CASCADE;
+DROP TABLE merchant.merchant_product_categories CASCADE;
+DROP TABLE merchant.merchant_template CASCADE;
+DROP TABLE merchant.merchant_otp_devices CASCADE;
+DROP TABLE merchant.merchant_inventory CASCADE;
+DROP TABLE merchant.merchant_product_groups CASCADE;
+DROP TABLE merchant.merchant_money_pots CASCADE;
+DROP TABLE merchant.merchant_categories CASCADE;
+DROP TABLE merchant.merchant_contract_terms CASCADE;
+DROP TABLE merchant.merchant_orders CASCADE;
+DROP TABLE merchant.merchant_custom_units CASCADE;
+DROP TABLE merchant.merchant_builtin_unit_overrides CASCADE;
+DROP TABLE merchant.merchant_login_tokens CASCADE;
+DROP TABLE merchant.merchant_donau_instances CASCADE;
+DROP TABLE merchant.merchant_reports CASCADE;
+DROP TABLE merchant.merchant_keys CASCADE;
+DROP TABLE merchant.merchant_accounts CASCADE;
+DROP TABLE merchant.merchant_statistic_amount_event CASCADE;
+DROP TABLE merchant.merchant_statistic_counter_event CASCADE;
+DROP TABLE merchant.merchant_statistic_interval_amount CASCADE;
+DROP TABLE merchant.merchant_statistic_interval_counter CASCADE;
+DROP TABLE merchant.merchant_statistic_interval_meta CASCADE;
+DROP TABLE merchant.merchant_statistic_bucket_amount CASCADE;
+DROP TABLE merchant.merchant_statistic_bucket_counter CASCADE;
+DROP TABLE merchant.merchant_statistic_bucket_meta CASCADE;
diff --git a/src/backenddb/sql-schema/merchant-0036-init.sql.fragment b/src/backenddb/sql-schema/merchant-0036-init.sql.fragment
@@ -0,0 +1,964 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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 merchant-0036-init.sql.fragment
+-- @brief Create initial set of per-instance tables and indices
+-- @author Christian Grothoff
+
+CREATE PROCEDURE merchant.merchant_0036_init(s TEXT)
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ -- -------------------------------------------------------------------
+ -- 2. CREATE TABLE statements for all per-instance tables.
+ -- The merchant_serial column has been dropped everywhere; PK,
+ -- UNIQUE and CHECK constraints have been adjusted accordingly.
+ -- -------------------------------------------------------------------
+
+ -- merchant_accounts
+ EXECUTE format('CREATE TABLE %I.merchant_accounts ('
+ || ' account_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' h_wire BYTEA NOT NULL UNIQUE,'
+ || ' salt BYTEA NOT NULL,'
+ || ' credit_facade_url TEXT,'
+ || ' credit_facade_credentials JSONB,'
+ || ' last_bank_serial INT8 DEFAULT 0 NOT NULL,'
+ || ' payto_uri TEXT NOT NULL UNIQUE,'
+ || ' active BOOLEAN NOT NULL,'
+ || ' extra_wire_subject_metadata TEXT,'
+ || ' CONSTRAINT merchant_accounts_h_wire_check CHECK ((LENGTH(h_wire) = 64)),'
+ || ' CONSTRAINT merchant_accounts_salt_check CHECK ((LENGTH(salt) = 16))'
+ || ')', s);
+
+ -- merchant_builtin_unit_overrides
+ EXECUTE format('CREATE TABLE %I.merchant_builtin_unit_overrides ('
+ || ' builtin_unit_serial INT8 NOT NULL PRIMARY KEY,'
+ || ' override_allow_fraction BOOLEAN,'
+ || ' override_precision_level INT4,'
+ || ' override_active BOOLEAN,'
+ || ' CONSTRAINT merchant_builtin_unit_overrides_override_precision_level_check'
+ || ' CHECK (((override_precision_level >= 0) AND (override_precision_level <= 6)))'
+ || ')', s);
+
+ -- merchant_categories
+ EXECUTE format('CREATE TABLE %I.merchant_categories ('
+ || ' category_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' category_name TEXT NOT NULL UNIQUE,'
+ || ' category_name_i18n JSONB NOT NULL'
+ || ')', s);
+
+ -- merchant_contract_terms
+ EXECUTE format('CREATE TABLE %I.merchant_contract_terms ('
+ || ' order_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' order_id TEXT NOT NULL UNIQUE,'
+ || ' contract_terms JSONB NOT NULL,'
+ || ' wallet_data TEXT,'
+ || ' h_contract_terms BYTEA NOT NULL UNIQUE,'
+ || ' creation_time INT8 NOT NULL,'
+ || ' pay_deadline INT8 NOT NULL,'
+ || ' refund_deadline INT8 NOT NULL,'
+ || ' paid BOOLEAN DEFAULT FALSE NOT NULL,'
+ || ' wired BOOLEAN DEFAULT FALSE NOT NULL,'
+ || ' fulfillment_url TEXT,'
+ || $DDL$ session_id TEXT DEFAULT ''::text NOT NULL,$DDL$
+ || ' pos_key TEXT,'
+ || ' pos_algorithm INT4 DEFAULT 0 NOT NULL,'
+ || ' claim_token BYTEA NOT NULL,'
+ || ' choice_index INT2,'
+ || ' CONSTRAINT merchant_contract_terms_claim_token_check CHECK ((LENGTH(claim_token) = 16)),'
+ || ' CONSTRAINT merchant_contract_terms_h_contract_terms_check CHECK ((LENGTH(h_contract_terms) = 64))'
+ || ')', s);
+
+ -- merchant_custom_units
+ EXECUTE format($DDL$CREATE TABLE %I.merchant_custom_units (
+ unit_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ unit TEXT NOT NULL UNIQUE,
+ unit_name_long TEXT NOT NULL,
+ unit_name_short TEXT NOT NULL,
+ unit_name_long_i18n BYTEA DEFAULT convert_to('{}'::text, 'UTF8'::name) NOT NULL,
+ unit_name_short_i18n BYTEA DEFAULT convert_to('{}'::text, 'UTF8'::name) NOT NULL,
+ unit_allow_fraction BOOLEAN DEFAULT FALSE NOT NULL,
+ unit_precision_level INT4 DEFAULT 0 NOT NULL,
+ unit_active BOOLEAN DEFAULT TRUE NOT NULL,
+ CONSTRAINT merchant_custom_units_unit_precision_level_check
+ CHECK (((unit_precision_level >= 0) AND (unit_precision_level <= 6)))
+ )$DDL$, s);
+
+ -- merchant_deposit_confirmations
+ EXECUTE format('CREATE TABLE %I.merchant_deposit_confirmations ('
+ || ' deposit_confirmation_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' order_serial INT8,'
+ || ' deposit_timestamp INT8 NOT NULL,'
+ || ' exchange_url TEXT NOT NULL,'
+ || ' total_without_fee merchant.taler_amount_currency NOT NULL,'
+ || ' wire_fee merchant.taler_amount_currency NOT NULL,'
+ || ' signkey_serial INT8 NOT NULL,'
+ || ' exchange_sig BYTEA NOT NULL,'
+ || ' account_serial INT8 NOT NULL,'
+ || ' wire_transfer_deadline INT8 DEFAULT 0 NOT NULL,'
+ || ' wire_pending BOOLEAN DEFAULT TRUE NOT NULL,'
+ || ' exchange_failure TEXT,'
+ || ' retry_backoff INT8 DEFAULT 0 NOT NULL,'
+ || ' CONSTRAINT merchant_deposit_confirmations_exchange_sig_check CHECK ((LENGTH(exchange_sig) = 64)),'
+ || ' UNIQUE (order_serial, exchange_sig)'
+ || ')', s);
+
+ -- merchant_deposits
+ EXECUTE format('CREATE TABLE %I.merchant_deposits ('
+ || ' deposit_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' coin_offset INT4 NOT NULL,'
+ || ' deposit_confirmation_serial INT8 NOT NULL,'
+ || ' coin_pub BYTEA NOT NULL,'
+ || ' coin_sig BYTEA NOT NULL,'
+ || ' amount_with_fee merchant.taler_amount_currency NOT NULL,'
+ || ' deposit_fee merchant.taler_amount_currency NOT NULL,'
+ || ' refund_fee merchant.taler_amount_currency NOT NULL,'
+ || ' settlement_retry_needed BOOLEAN DEFAULT TRUE,'
+ || ' settlement_retry_time INT8 DEFAULT 0,'
+ || ' settlement_last_http_status INT4,'
+ || ' settlement_last_ec INT4,'
+ || ' settlement_last_detail TEXT,'
+ || ' settlement_wtid BYTEA,'
+ || ' settlement_coin_contribution merchant.taler_amount_currency,'
+ || ' settlement_expected_credit_serial INT8,'
+ || ' signkey_serial INT8,'
+ || ' settlement_exchange_sig BYTEA,'
+ || ' CONSTRAINT merchant_deposits_coin_pub_check CHECK ((LENGTH(coin_pub) = 32)),'
+ || ' CONSTRAINT merchant_deposits_coin_sig_check CHECK ((LENGTH(coin_sig) = 64)),'
+ || ' CONSTRAINT merchant_deposits_settlement_exchange_sig_check CHECK ((LENGTH(settlement_exchange_sig) = 64)),'
+ || ' CONSTRAINT merchant_deposits_settlement_wtid_check CHECK ((LENGTH(settlement_wtid) = 32)),'
+ || ' UNIQUE (deposit_confirmation_serial, coin_pub)'
+ || ')', s);
+
+ -- merchant_donau_instances
+ EXECUTE format('CREATE TABLE %I.merchant_donau_instances ('
+ || ' donau_instances_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' donau_url TEXT NOT NULL,'
+ || ' charity_name TEXT NOT NULL,'
+ || ' charity_id INT8 NOT NULL,'
+ || ' charity_max_per_year merchant.taler_amount_currency NOT NULL,'
+ || ' charity_receipts_to_date merchant.taler_amount_currency NOT NULL,'
+ || ' current_year INT8 NOT NULL,'
+ || ' UNIQUE (donau_url, charity_id)'
+ || ')', s);
+
+ -- merchant_expected_transfer_to_coin
+ EXECUTE format('CREATE TABLE %I.merchant_expected_transfer_to_coin ('
+ || ' deposit_serial INT8 NOT NULL UNIQUE,'
+ || ' expected_credit_serial INT8 NOT NULL,'
+ || ' offset_in_exchange_list INT8 NOT NULL,'
+ || ' exchange_deposit_value merchant.taler_amount_currency NOT NULL,'
+ || ' exchange_deposit_fee merchant.taler_amount_currency NOT NULL'
+ || ')', s);
+
+ -- merchant_expected_transfers
+ EXECUTE format('CREATE TABLE %I.merchant_expected_transfers ('
+ || ' expected_credit_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' exchange_url TEXT NOT NULL,'
+ || ' wtid BYTEA NOT NULL,'
+ || ' expected_credit_amount merchant.taler_amount_currency,'
+ || ' wire_fee merchant.taler_amount_currency,'
+ || ' account_serial INT8 NOT NULL,'
+ || ' expected_time INT8 NOT NULL,'
+ || ' retry_time INT8 DEFAULT 0 NOT NULL,'
+ || ' last_http_status INT4,'
+ || ' last_ec INT4,'
+ || ' last_detail TEXT,'
+ || ' retry_needed BOOLEAN DEFAULT TRUE NOT NULL,'
+ || ' signkey_serial INT8,'
+ || ' exchange_sig BYTEA,'
+ || ' h_details BYTEA,'
+ || ' confirmed BOOLEAN DEFAULT FALSE NOT NULL,'
+ || ' CONSTRAINT merchant_expected_transfers_exchange_sig_check CHECK ((LENGTH(exchange_sig) = 64)),'
+ || ' CONSTRAINT merchant_expected_transfers_h_details_check CHECK ((LENGTH(h_details) = 64)),'
+ || ' CONSTRAINT merchant_expected_transfers_wtid_check CHECK ((LENGTH(wtid) = 32)),'
+ || ' UNIQUE (wtid, exchange_url, account_serial)'
+ || ')', s);
+
+ -- merchant_inventory
+ EXECUTE format($DDL$CREATE TABLE %I.merchant_inventory (
+ product_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ product_id TEXT NOT NULL UNIQUE,
+ description TEXT NOT NULL,
+ description_i18n JSONB NOT NULL,
+ unit TEXT NOT NULL,
+ image TEXT NOT NULL,
+ taxes JSONB NOT NULL,
+ total_stock INT8 NOT NULL,
+ total_sold INT8 DEFAULT 0 NOT NULL,
+ total_lost INT8 DEFAULT 0 NOT NULL,
+ address JSONB NOT NULL,
+ next_restock INT8 NOT NULL,
+ minimum_age INT4 DEFAULT 0 NOT NULL,
+ product_name TEXT NOT NULL,
+ image_hash TEXT,
+ price_array merchant.taler_amount_currency[]
+ DEFAULT ARRAY[]::merchant.taler_amount_currency[] NOT NULL,
+ total_stock_frac INT4 DEFAULT 0 NOT NULL,
+ total_sold_frac INT4 DEFAULT 0 NOT NULL,
+ total_lost_frac INT4 DEFAULT 0 NOT NULL,
+ allow_fractional_quantity BOOLEAN DEFAULT FALSE NOT NULL,
+ fractional_precision_level INT4 DEFAULT 0 NOT NULL,
+ product_group_serial INT8,
+ money_pot_serial INT8,
+ price_is_net BOOLEAN DEFAULT FALSE
+ )$DDL$, s);
+
+ -- merchant_inventory_locks
+ EXECUTE format('CREATE TABLE %I.merchant_inventory_locks ('
+ || ' product_serial INT8 NOT NULL,'
+ || ' lock_uuid BYTEA NOT NULL,'
+ || ' total_locked INT8 NOT NULL,'
+ || ' expiration INT8 NOT NULL,'
+ || ' total_locked_frac INT4 DEFAULT 0 NOT NULL,'
+ || ' CONSTRAINT merchant_inventory_locks_lock_uuid_check CHECK ((LENGTH(lock_uuid) = 16))'
+ || ')', s);
+
+ -- merchant_issued_tokens
+ EXECUTE format('CREATE TABLE %I.merchant_issued_tokens ('
+ || ' issued_token_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' h_contract_terms BYTEA NOT NULL,'
+ || ' token_family_key_serial INT8,'
+ || ' blind_sig BYTEA NOT NULL,'
+ || ' CONSTRAINT merchant_issued_tokens_h_contract_terms_check CHECK ((LENGTH(h_contract_terms) = 64))'
+ || ')', s);
+
+ -- merchant_kyc (PK survives unchanged: (account_serial, exchange_url))
+ EXECUTE format('CREATE TABLE %I.merchant_kyc ('
+ || ' kyc_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE,'
+ || ' kyc_timestamp INT8 NOT NULL,'
+ || ' kyc_ok BOOLEAN DEFAULT FALSE NOT NULL,'
+ || ' account_serial INT8 NOT NULL,'
+ || ' exchange_url TEXT NOT NULL,'
+ || ' access_token BYTEA,'
+ || ' exchange_http_status INT4 DEFAULT 0,'
+ || ' exchange_ec_code INT4 DEFAULT 0,'
+ || ' aml_review BOOLEAN DEFAULT FALSE,'
+ || ' jaccount_limits JSONB,'
+ || ' last_rule_gen INT8 DEFAULT 0 NOT NULL,'
+ || ' next_kyc_poll INT8 DEFAULT 0 NOT NULL,'
+ || ' kyc_backoff INT8 DEFAULT 0 NOT NULL,'
+ || ' CONSTRAINT access_token_LENGTH_check CHECK ((LENGTH(access_token) = 32)),'
+ || ' PRIMARY KEY (account_serial, exchange_url)'
+ || ')', s);
+
+ -- merchant_login_tokens (note: serial is GENERATED ALWAYS)
+ EXECUTE format('CREATE TABLE %I.merchant_login_tokens ('
+ || ' serial INT8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,'
+ || ' token BYTEA NOT NULL UNIQUE,'
+ || ' creation_time INT8 NOT NULL,'
+ || ' expiration_time INT8 NOT NULL,'
+ || ' validity_scope INT4 NOT NULL,'
+ || ' description TEXT NOT NULL,'
+ || ' CONSTRAINT merchant_login_tokens_token_check CHECK ((LENGTH(token) = 32))'
+ || ')', s);
+
+ -- merchant_money_pots
+ EXECUTE format('CREATE TABLE %I.merchant_money_pots ('
+ || ' money_pot_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' money_pot_name TEXT NOT NULL UNIQUE,'
+ || ' money_pot_description TEXT NOT NULL,'
+ || ' pot_totals merchant.taler_amount_currency[]'
+ || ' DEFAULT ARRAY[]::merchant.taler_amount_currency[] NOT NULL'
+ || ')', s);
+
+ -- merchant_order_locks
+ EXECUTE format('CREATE TABLE %I.merchant_order_locks ('
+ || ' product_serial INT8 NOT NULL,'
+ || ' total_locked INT8 NOT NULL,'
+ || ' order_serial INT8 NOT NULL,'
+ || ' total_locked_frac INT4 DEFAULT 0 NOT NULL'
+ || ')', s);
+
+ -- merchant_order_token_blinded_sigs
+ EXECUTE format('CREATE TABLE %I.merchant_order_token_blinded_sigs ('
+ || ' order_token_bs_serial INT8 GENERATED BY DEFAULT AS IDENTITY,'
+ || ' order_serial INT8 NOT NULL,'
+ || ' token_index INT4 NOT NULL,'
+ || ' token_blinded_signature BYTEA NOT NULL,'
+ || ' token_hash BYTEA NOT NULL,'
+ || ' CONSTRAINT merchant_order_token_blinded_sigs_token_hash_check CHECK ((LENGTH(token_hash) = 64)),'
+ || ' PRIMARY KEY (order_serial, token_index)'
+ || ')', s);
+
+ -- merchant_orders
+ EXECUTE format($DDL$CREATE TABLE %I.merchant_orders (
+ order_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ order_id TEXT NOT NULL UNIQUE,
+ claim_token BYTEA NOT NULL,
+ h_post_data BYTEA NOT NULL,
+ pay_deadline INT8 NOT NULL,
+ creation_time INT8 NOT NULL,
+ contract_terms JSONB NOT NULL,
+ pos_key TEXT,
+ pos_algorithm INT4 DEFAULT 0 NOT NULL,
+ fulfillment_url TEXT,
+ session_id TEXT DEFAULT ''::text NOT NULL,
+ CONSTRAINT merchant_orders_claim_token_check CHECK ((LENGTH(claim_token) = 16)),
+ CONSTRAINT merchant_orders_h_post_data_check CHECK ((LENGTH(h_post_data) = 64))
+ )$DDL$, s);
+
+ -- merchant_otp_devices
+ EXECUTE format('CREATE TABLE %I.merchant_otp_devices ('
+ || ' otp_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' otp_id TEXT NOT NULL UNIQUE,'
+ || ' otp_description TEXT NOT NULL,'
+ || ' otp_key TEXT,'
+ || ' otp_algorithm INT4 DEFAULT 0 NOT NULL,'
+ || ' otp_ctr INT8 DEFAULT 0 NOT NULL'
+ || ')', s);
+
+ -- merchant_pending_webhooks
+ -- The original (merchant_serial, webhook_pending_serial) UNIQUE
+ -- collapses to just (webhook_pending_serial), which is already the
+ -- PK, so no extra UNIQUE constraint is needed.
+ EXECUTE format('CREATE TABLE %I.merchant_pending_webhooks ('
+ || ' webhook_pending_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' webhook_serial INT8 NOT NULL,'
+ || ' next_attempt INT8 DEFAULT 0 NOT NULL,'
+ || ' retries INT4 DEFAULT 0 NOT NULL,'
+ || ' url TEXT NOT NULL,'
+ || ' http_method TEXT NOT NULL,'
+ || ' header TEXT,'
+ || ' body TEXT'
+ || ')', s);
+
+ -- merchant_product_categories
+ EXECUTE format('CREATE TABLE %I.merchant_product_categories ('
+ || ' category_serial INT8 NOT NULL,'
+ || ' product_serial INT8 NOT NULL'
+ || ')', s);
+
+ -- merchant_product_groups
+ EXECUTE format('CREATE TABLE %I.merchant_product_groups ('
+ || ' product_group_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' product_group_name TEXT NOT NULL UNIQUE,'
+ || ' product_group_description TEXT NOT NULL'
+ || ')', s);
+
+ -- merchant_refund_proofs
+ EXECUTE format('CREATE TABLE %I.merchant_refund_proofs ('
+ || ' refund_serial INT8 NOT NULL PRIMARY KEY,'
+ || ' exchange_sig BYTEA NOT NULL,'
+ || ' signkey_serial INT8 NOT NULL,'
+ || ' CONSTRAINT merchant_refund_proofs_exchange_sig_check CHECK ((LENGTH(exchange_sig) = 64))'
+ || ')', s);
+
+ -- merchant_refunds
+ EXECUTE format('CREATE TABLE %I.merchant_refunds ('
+ || ' refund_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' order_serial INT8 NOT NULL,'
+ || ' rtransaction_id INT8 NOT NULL,'
+ || ' refund_timestamp INT8 NOT NULL,'
+ || ' coin_pub BYTEA NOT NULL,'
+ || ' reason TEXT NOT NULL,'
+ || ' refund_amount merchant.taler_amount_currency NOT NULL,'
+ || ' UNIQUE (order_serial, coin_pub, rtransaction_id)'
+ || ')', s);
+
+ -- merchant_reports
+ EXECUTE format('CREATE TABLE %I.merchant_reports ('
+ || ' report_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' report_program_section TEXT NOT NULL,'
+ || ' report_description TEXT NOT NULL,'
+ || ' mime_type TEXT NOT NULL,'
+ || ' report_token BYTEA NOT NULL,'
+ || ' data_source TEXT NOT NULL,'
+ || ' target_address TEXT NOT NULL,'
+ || ' frequency INT8 NOT NULL,'
+ || ' frequency_shift INT8 NOT NULL,'
+ || ' next_transmission INT8 NOT NULL,'
+ || ' last_error_code INT4,'
+ || ' last_error_detail TEXT,'
+ || ' one_shot_hidden BOOLEAN DEFAULT FALSE,'
+ || ' CONSTRAINT merchant_reports_report_token_check CHECK ((LENGTH(report_token) = 32))'
+ || ')', s);
+
+ -- merchant_used_tokens
+ EXECUTE format('CREATE TABLE %I.merchant_used_tokens ('
+ || ' spent_token_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' h_contract_terms BYTEA NOT NULL,'
+ || ' token_family_key_serial INT8,'
+ || ' token_pub BYTEA NOT NULL UNIQUE,'
+ || ' token_sig BYTEA NOT NULL,'
+ || ' blind_sig BYTEA NOT NULL,'
+ || ' CONSTRAINT merchant_spent_tokens_h_contract_terms_check CHECK ((LENGTH(h_contract_terms) = 64)),'
+ || ' CONSTRAINT merchant_spent_tokens_token_pub_check CHECK ((LENGTH(token_pub) = 32)),'
+ || ' CONSTRAINT merchant_spent_tokens_token_sig_check CHECK ((LENGTH(token_sig) = 64))'
+ || ')', s);
+
+ -- merchant_statistic_amount_event
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_amount_event ('
+ || ' aevent_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' imeta_serial_id INT8,'
+ || ' slot INT8 NOT NULL,'
+ || ' delta_curr character varying(12) NOT NULL,'
+ || ' delta_value INT8 NOT NULL,'
+ || ' delta_frac INT4 NOT NULL,'
+ || ' CONSTRAINT event_key UNIQUE (imeta_serial_id, delta_curr, slot)'
+ || ')', s);
+
+ -- merchant_statistic_bucket_amount
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_bucket_amount ('
+ || ' bmeta_serial_id INT8 NOT NULL,'
+ || ' bucket_start INT8 NOT NULL,'
+ || ' bucket_range merchant.statistic_range NOT NULL,'
+ || ' curr character varying(12) NOT NULL,'
+ || ' cumulative_value INT8 NOT NULL,'
+ || ' cumulative_frac INT4 NOT NULL,'
+ || ' PRIMARY KEY (bmeta_serial_id, curr, bucket_start, bucket_range)'
+ || ')', s);
+
+ -- merchant_statistic_bucket_counter
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_bucket_counter ('
+ || ' bmeta_serial_id INT8 NOT NULL,'
+ || ' bucket_start INT8 NOT NULL,'
+ || ' bucket_range merchant.statistic_range NOT NULL,'
+ || ' cumulative_number INT8 NOT NULL,'
+ || ' PRIMARY KEY (bmeta_serial_id, bucket_start, bucket_range)'
+ || ')', s);
+
+ -- merchant_statistic_bucket_meta
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_bucket_meta ('
+ || ' bmeta_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' slug TEXT NOT NULL,'
+ || ' description TEXT NOT NULL,'
+ || ' stype merchant.statistic_type NOT NULL,'
+ || ' ranges merchant.statistic_range[] NOT NULL,'
+ || ' ages INT4[] NOT NULL,'
+ || ' CONSTRAINT equal_array_LENGTH CHECK ((array_LENGTH(ranges, 1) = array_LENGTH(ages, 1))),'
+ || ' UNIQUE (slug, stype)'
+ || ')', s);
+
+ -- merchant_statistic_counter_event
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_counter_event ('
+ || ' nevent_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' imeta_serial_id INT8,'
+ || ' slot INT8 NOT NULL,'
+ || ' delta INT8 NOT NULL,'
+ || ' UNIQUE (imeta_serial_id, slot)'
+ || ')', s);
+
+ -- merchant_statistic_interval_amount
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_interval_amount ('
+ || ' imeta_serial_id INT8 NOT NULL,'
+ || ' event_delimiter INT8 NOT NULL,'
+ || ' range INT8 NOT NULL,'
+ || ' curr character varying(12) NOT NULL,'
+ || ' cumulative_value INT8 NOT NULL,'
+ || ' cumulative_frac INT4 NOT NULL,'
+ || ' PRIMARY KEY (imeta_serial_id, curr, range)'
+ || ')', s);
+
+ -- merchant_statistic_interval_counter
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_interval_counter ('
+ || ' imeta_serial_id INT8 NOT NULL,'
+ || ' range INT8 NOT NULL,'
+ || ' event_delimiter INT8 NOT NULL,'
+ || ' cumulative_number INT8 NOT NULL,'
+ || ' PRIMARY KEY (imeta_serial_id, range)'
+ || ')', s);
+
+ -- merchant_statistic_interval_meta
+ EXECUTE format('CREATE TABLE %I.merchant_statistic_interval_meta ('
+ || ' imeta_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' slug TEXT NOT NULL,'
+ || ' description TEXT NOT NULL,'
+ || ' stype merchant.statistic_type NOT NULL,'
+ || ' ranges INT8[] NOT NULL,'
+ || ' precisions INT8[] NOT NULL,'
+ || ' CONSTRAINT equal_array_LENGTH CHECK ((array_LENGTH(ranges, 1) = array_LENGTH(precisions, 1))),'
+ || ' CONSTRAINT merchant_statistic_interval_meta_precisions_check CHECK ((array_LENGTH(precisions, 1) > 0)),'
+ || ' CONSTRAINT merchant_statistic_interval_meta_ranges_check CHECK ((array_LENGTH(ranges, 1) > 0)),'
+ || ' UNIQUE (slug, stype)'
+ || ')', s);
+
+ -- merchant_template
+ EXECUTE format('CREATE TABLE %I.merchant_template ('
+ || ' template_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' template_id TEXT NOT NULL UNIQUE,'
+ || ' template_description TEXT NOT NULL,'
+ || ' otp_device_id INT8,'
+ || ' template_contract JSONB NOT NULL,'
+ || ' editable_defaults JSONB'
+ || ')', s);
+
+ -- merchant_token_families
+ EXECUTE format($DDL$CREATE TABLE %I.merchant_token_families (
+ token_family_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ slug TEXT NOT NULL UNIQUE,
+ name TEXT NOT NULL,
+ description TEXT,
+ description_i18n JSONB NOT NULL,
+ valid_after INT8 NOT NULL,
+ valid_before INT8 NOT NULL,
+ duration INT8 NOT NULL,
+ kind TEXT NOT NULL,
+ issued INT8 DEFAULT 0,
+ used INT8 DEFAULT 0,
+ validity_granularity INT8 DEFAULT '2592000000000'::INT8 NOT NULL,
+ start_offset INT8 DEFAULT 0 NOT NULL,
+ cipher_choice TEXT DEFAULT 'rsa(2048)'::text NOT NULL,
+ extra_data JSONB,
+ CONSTRAINT merchant_token_families_kind_check
+ CHECK ((kind = ANY (ARRAY['subscription'::text, 'discount'::text]))),
+ CONSTRAINT merchant_token_families_validity_granularity_check
+ CHECK ((validity_granularity = ANY (ARRAY[(60000000)::INT8,
+ '3600000000'::INT8, '86400000000'::INT8, '604800000000'::INT8,
+ '2592000000000'::INT8, '7776000000000'::INT8,
+ '31536000000000'::INT8])))
+ )$DDL$, s);
+
+ -- merchant_token_family_keys
+ EXECUTE format('CREATE TABLE %I.merchant_token_family_keys ('
+ || ' token_family_key_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' token_family_serial INT8,'
+ || ' pub BYTEA NOT NULL,'
+ || ' h_pub BYTEA NOT NULL UNIQUE,'
+ || ' priv BYTEA,'
+ || ' cipher TEXT NOT NULL,'
+ || ' signature_validity_start INT8 DEFAULT 0 NOT NULL,'
+ || ' signature_validity_end INT8 DEFAULT 0 NOT NULL,'
+ || ' private_key_deleted_at INT8 DEFAULT 0 NOT NULL,'
+ || ' private_key_created_at INT8 DEFAULT 0 NOT NULL,'
+ || ' CONSTRAINT h_pub_LENGTH_check CHECK ((LENGTH(h_pub) = 64)),'
+ || $DDL$ CONSTRAINT merchant_token_family_keys_cipher_check CHECK ((cipher = ANY (ARRAY['rsa'::text, 'cs'::text])))$DDL$
+ || ')', s);
+
+ -- merchant_transfer_signatures
+ EXECUTE format('CREATE TABLE %I.merchant_transfer_signatures ('
+ || ' expected_credit_serial INT8 NOT NULL PRIMARY KEY,'
+ || ' signkey_serial INT8 NOT NULL,'
+ || ' wire_fee merchant.taler_amount_currency NOT NULL,'
+ || ' credit_amount merchant.taler_amount_currency NOT NULL,'
+ || ' execution_time INT8 NOT NULL,'
+ || ' exchange_sig BYTEA NOT NULL,'
+ || ' CONSTRAINT merchant_transfer_signatures_exchange_sig_check CHECK ((LENGTH(exchange_sig) = 64))'
+ || ')', s);
+
+ -- merchant_transfers
+ EXECUTE format('CREATE TABLE %I.merchant_transfers ('
+ || ' credit_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' exchange_url TEXT NOT NULL,'
+ || ' wtid BYTEA,'
+ || ' credit_amount merchant.taler_amount_currency NOT NULL,'
+ || ' account_serial INT8 NOT NULL,'
+ || ' bank_serial_id INT8,'
+ || ' expected BOOLEAN DEFAULT FALSE,'
+ || ' execution_time INT8 DEFAULT 0,'
+ || ' CONSTRAINT merchant_transfers_wtid_check CHECK ((LENGTH(wtid) = 32)),'
+ || ' CONSTRAINT merchant_transfers_unique UNIQUE (wtid, exchange_url, account_serial, bank_serial_id)'
+ || ')', s);
+
+ -- merchant_unclaim_signatures
+ EXECUTE format('CREATE TABLE %I.merchant_unclaim_signatures ('
+ || ' unclaim_serial INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE,'
+ || ' h_contract_terms BYTEA NOT NULL,'
+ || ' unclaim_sig BYTEA NOT NULL PRIMARY KEY,'
+ || ' expiration_time INT8 NOT NULL,'
+ || ' CONSTRAINT merchant_unclaim_signatures_h_contract_terms_check CHECK ((LENGTH(h_contract_terms) = 64)),'
+ || ' CONSTRAINT merchant_unclaim_signatures_unclaim_sig_check CHECK ((LENGTH(unclaim_sig) = 64))'
+ || ')', s);
+
+ -- merchant_webhook
+ EXECUTE format('CREATE TABLE %I.merchant_webhook ('
+ || ' webhook_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,'
+ || ' webhook_id TEXT NOT NULL UNIQUE,'
+ || ' event_type TEXT NOT NULL,'
+ || ' url TEXT NOT NULL,'
+ || ' http_method TEXT NOT NULL,'
+ || ' header_template TEXT,'
+ || ' body_template TEXT'
+ || ')', s);
+
+ -- tan_challenges
+ EXECUTE format('CREATE TABLE %I.tan_challenges ('
+ || ' challenge_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE,'
+ || ' h_body BYTEA NOT NULL,'
+ || ' salt BYTEA NOT NULL,'
+ || ' op merchant.op_enum NOT NULL,'
+ || ' code TEXT NOT NULL,'
+ || ' creation_date INT8 NOT NULL,'
+ || ' expiration_date INT8 NOT NULL,'
+ || ' retransmission_date INT8 DEFAULT 0 NOT NULL,'
+ || ' confirmation_date INT8,'
+ || ' retry_counter INT4 NOT NULL,'
+ || ' tan_channel merchant.tan_enum NOT NULL,'
+ || ' required_address TEXT NOT NULL,'
+ || ' CONSTRAINT tan_challenges_h_body_check CHECK ((LENGTH(h_body) = 32)),'
+ || ' CONSTRAINT tan_challenges_salt_check CHECK ((LENGTH(salt) = 16))'
+ || ')', s);
+
+ -- -------------------------------------------------------------------
+ -- 3. CREATE INDEX statements (per-instance only).
+ -- Index names are bare; PostgreSQL scopes them by schema, so the
+ -- same logical name in each instance schema is fine.
+ -- merchant_serial leading columns are dropped from the column list.
+ -- -------------------------------------------------------------------
+
+ -- merchant_product_categories
+ EXECUTE format('CREATE INDEX merchant_categories_by_category'
+ || ' ON %I.merchant_product_categories USING btree (category_serial)', s);
+ EXECUTE format('CREATE INDEX merchant_categories_by_product'
+ || ' ON %I.merchant_product_categories USING btree (product_serial)', s);
+
+ -- merchant_contract_terms
+ EXECUTE format('CREATE INDEX merchant_contract_terms_by_expiration'
+ || ' ON %I.merchant_contract_terms USING btree (paid, pay_deadline)', s);
+ EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_and_expiration'
+ || ' ON %I.merchant_contract_terms USING btree (pay_deadline)', s);
+ EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_and_payment'
+ || ' ON %I.merchant_contract_terms USING btree (paid)', s);
+ EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_and_session'
+ || ' ON %I.merchant_contract_terms USING btree (session_id)', s);
+ EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_session_and_fulfillment'
+ || ' ON %I.merchant_contract_terms USING btree (fulfillment_url, session_id)', s);
+
+ -- merchant_deposit_confirmations
+ EXECUTE format('CREATE INDEX merchant_deposit_confirmations_by_pending_wire'
+ || ' ON %I.merchant_deposit_confirmations USING btree (exchange_url, wire_transfer_deadline)'
+ || ' WHERE wire_pending', s);
+
+ -- merchant_deposits
+ EXECUTE format('CREATE INDEX merchant_deposits_by_deposit_confirmation'
+ || ' ON %I.merchant_deposits USING btree (deposit_confirmation_serial)', s);
+
+ EXECUTE format('CREATE INDEX merchant_deposits_by_settlement_open'
+ || ' ON %I.merchant_deposits USING btree (settlement_retry_time)'
+ || ' WHERE settlement_retry_needed', s);
+
+ -- merchant_expected_transfers
+ EXECUTE format('CREATE INDEX merchant_expected_transfers_by_open'
+ || ' ON %I.merchant_expected_transfers USING btree (retry_time)'
+ || ' WHERE ((NOT confirmed) OR retry_needed)', s);
+
+ -- merchant_inventory
+ EXECUTE format('CREATE INDEX merchant_inventory_by_image_hash'
+ || ' ON %I.merchant_inventory USING btree (image_hash)', s);
+
+ -- merchant_inventory_locks
+ EXECUTE format('CREATE INDEX merchant_inventory_locks_by_expiration'
+ || ' ON %I.merchant_inventory_locks USING btree (expiration)', s);
+ EXECUTE format('CREATE INDEX merchant_inventory_locks_by_uuid'
+ || ' ON %I.merchant_inventory_locks USING btree (lock_uuid)', s);
+
+ -- merchant_kyc
+ EXECUTE format('CREATE INDEX merchant_kyc_by_next_kyc_poll'
+ || ' ON %I.merchant_kyc USING btree (next_kyc_poll)', s);
+
+ -- merchant_login_tokens
+ EXECUTE format('CREATE INDEX merchant_login_tokens_by_expiration'
+ || ' ON %I.merchant_login_tokens USING btree (expiration_time)', s);
+
+ -- merchant_orders
+ EXECUTE format('CREATE INDEX merchant_orders_by_creation_time'
+ || ' ON %I.merchant_orders USING btree (creation_time)', s);
+ EXECUTE format('CREATE INDEX merchant_orders_by_expiration'
+ || ' ON %I.merchant_orders USING btree (pay_deadline)', s);
+ EXECUTE format('CREATE INDEX merchant_orders_by_merchant_and_fullfilment_and_session'
+ || ' ON %I.merchant_orders USING btree (fulfillment_url, session_id)', s);
+ EXECUTE format('CREATE INDEX merchant_orders_by_merchant_and_session'
+ || ' ON %I.merchant_orders USING btree (session_id)', s);
+
+ -- merchant_order_locks
+ EXECUTE format('CREATE INDEX merchant_orders_locks_by_order_and_product'
+ || ' ON %I.merchant_order_locks USING btree (order_serial, product_serial)', s);
+
+ -- merchant_refunds
+ EXECUTE format('CREATE INDEX merchant_refunds_by_coin_and_order'
+ || ' ON %I.merchant_refunds USING btree (coin_pub, order_serial)', s);
+
+ -- merchant_expected_transfer_to_coin
+ EXECUTE format('CREATE INDEX merchant_transfers_by_credit'
+ || ' ON %I.merchant_expected_transfer_to_coin USING btree (expected_credit_serial)', s);
+
+ -- merchant_unclaim_signatures
+ EXECUTE format('CREATE INDEX merchant_unclaim_signatures_by_expiration'
+ || ' ON %I.merchant_unclaim_signatures USING btree (expiration_time)', s);
+
+ -- tan_challenges
+ EXECUTE format('CREATE INDEX tan_challenges_expiration_index'
+ || ' ON %I.tan_challenges USING btree (expiration_date)', s);
+
+ -- trigram indexes
+ EXECUTE format('CREATE INDEX trgm_idx_categories_by_name'
+ || ' ON %I.merchant_categories USING gin (lower(category_name) public.gin_trgm_ops)', s);
+ EXECUTE format($DDL$CREATE INDEX trgm_idx_contract_summaries
+ ON %I.merchant_contract_terms USING gin (lower((contract_terms ->> 'summary'::text)) public.gin_trgm_ops)$DDL$, s);
+ EXECUTE format($DDL$CREATE INDEX trgm_idx_order_summaries
+ ON %I.merchant_orders USING gin (lower((contract_terms ->> 'summary'::text)) public.gin_trgm_ops)$DDL$, s);
+ EXECUTE format('CREATE INDEX trgm_idx_products_by_description'
+ || ' ON %I.merchant_inventory USING gin (lower(description) public.gin_trgm_ops)', s);
+ EXECUTE format('CREATE INDEX trgm_idx_products_by_name'
+ || ' ON %I.merchant_inventory USING gin (lower(product_name) public.gin_trgm_ops)', s);
+
+ -- -------------------------------------------------------------------
+ -- 4. ALTER TABLE for FKs *between per-instance tables* (intra-schema).
+ -- Both sides live in `s`; use format(..., s, s).
+ -- -------------------------------------------------------------------
+
+ -- merchant_deposit_confirmations -> merchant_accounts
+ EXECUTE format('ALTER TABLE %I.merchant_deposit_confirmations'
+ || ' ADD CONSTRAINT merchant_deposit_confirmations_account_serial_fkey'
+ || ' FOREIGN KEY (account_serial)'
+ || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_deposit_confirmations -> merchant_contract_terms
+ EXECUTE format('ALTER TABLE %I.merchant_deposit_confirmations'
+ || ' ADD CONSTRAINT merchant_deposit_confirmations_order_serial_fkey'
+ || ' FOREIGN KEY (order_serial)'
+ || ' REFERENCES %I.merchant_contract_terms(order_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_deposits -> merchant_deposit_confirmations
+ EXECUTE format('ALTER TABLE %I.merchant_deposits'
+ || ' ADD CONSTRAINT merchant_deposits_deposit_confirmation_serial_fkey'
+ || ' FOREIGN KEY (deposit_confirmation_serial)'
+ || ' REFERENCES %I.merchant_deposit_confirmations(deposit_confirmation_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_deposits -> merchant_expected_transfers (no ON DELETE clause originally)
+ EXECUTE format('ALTER TABLE %I.merchant_deposits'
+ || ' ADD CONSTRAINT merchant_deposits_settlement_expected_credit_serial_fkey'
+ || ' FOREIGN KEY (settlement_expected_credit_serial)'
+ || ' REFERENCES %I.merchant_expected_transfers(expected_credit_serial)', s, s);
+
+ -- merchant_expected_transfer_to_coin -> merchant_deposits
+ EXECUTE format('ALTER TABLE %I.merchant_expected_transfer_to_coin'
+ || ' ADD CONSTRAINT merchant_expected_transfer_to_coin_deposit_serial_fkey'
+ || ' FOREIGN KEY (deposit_serial)'
+ || ' REFERENCES %I.merchant_deposits(deposit_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_expected_transfer_to_coin -> merchant_expected_transfers
+ EXECUTE format('ALTER TABLE %I.merchant_expected_transfer_to_coin'
+ || ' ADD CONSTRAINT merchant_expected_transfer_to_coin_expected_credit_serial_fkey'
+ || ' FOREIGN KEY (expected_credit_serial)'
+ || ' REFERENCES %I.merchant_expected_transfers(expected_credit_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_expected_transfers -> merchant_accounts
+ EXECUTE format('ALTER TABLE %I.merchant_expected_transfers'
+ || ' ADD CONSTRAINT merchant_expected_transfers_account_serial_fkey'
+ || ' FOREIGN KEY (account_serial)'
+ || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_inventory -> merchant_money_pots
+ EXECUTE format('ALTER TABLE %I.merchant_inventory'
+ || ' ADD CONSTRAINT merchant_inventory_money_pot_serial_fkey'
+ || ' FOREIGN KEY (money_pot_serial)'
+ || ' REFERENCES %I.merchant_money_pots(money_pot_serial) ON DELETE SET NULL', s, s);
+
+ -- merchant_inventory -> merchant_product_groups
+ EXECUTE format('ALTER TABLE %I.merchant_inventory'
+ || ' ADD CONSTRAINT merchant_inventory_product_group_serial_fkey'
+ || ' FOREIGN KEY (product_group_serial)'
+ || ' REFERENCES %I.merchant_product_groups(product_group_serial) ON DELETE SET NULL', s, s);
+
+ -- merchant_inventory_locks -> merchant_inventory
+ EXECUTE format('ALTER TABLE %I.merchant_inventory_locks'
+ || ' ADD CONSTRAINT merchant_inventory_locks_product_serial_fkey'
+ || ' FOREIGN KEY (product_serial)'
+ || ' REFERENCES %I.merchant_inventory(product_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_issued_tokens -> merchant_token_family_keys
+ EXECUTE format('ALTER TABLE %I.merchant_issued_tokens'
+ || ' ADD CONSTRAINT merchant_issued_tokens_token_family_key_serial_fkey'
+ || ' FOREIGN KEY (token_family_key_serial)'
+ || ' REFERENCES %I.merchant_token_family_keys(token_family_key_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_kyc -> merchant_accounts
+ EXECUTE format('ALTER TABLE %I.merchant_kyc'
+ || ' ADD CONSTRAINT merchant_kyc_account_serial_fkey'
+ || ' FOREIGN KEY (account_serial)'
+ || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_order_locks -> merchant_orders
+ EXECUTE format('ALTER TABLE %I.merchant_order_locks'
+ || ' ADD CONSTRAINT merchant_order_locks_order_serial_fkey'
+ || ' FOREIGN KEY (order_serial)'
+ || ' REFERENCES %I.merchant_orders(order_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_order_locks -> merchant_inventory (no ON DELETE clause originally)
+ EXECUTE format('ALTER TABLE %I.merchant_order_locks'
+ || ' ADD CONSTRAINT merchant_order_locks_product_serial_fkey'
+ || ' FOREIGN KEY (product_serial)'
+ || ' REFERENCES %I.merchant_inventory(product_serial)', s, s);
+
+ -- merchant_order_token_blinded_sigs -> merchant_contract_terms
+ EXECUTE format('ALTER TABLE %I.merchant_order_token_blinded_sigs'
+ || ' ADD CONSTRAINT merchant_order_token_blinded_sigs_order_serial_fkey'
+ || ' FOREIGN KEY (order_serial)'
+ || ' REFERENCES %I.merchant_contract_terms(order_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_pending_webhooks -> merchant_webhook
+ EXECUTE format('ALTER TABLE %I.merchant_pending_webhooks'
+ || ' ADD CONSTRAINT merchant_pending_webhooks_webhook_serial_fkey'
+ || ' FOREIGN KEY (webhook_serial)'
+ || ' REFERENCES %I.merchant_webhook(webhook_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_product_categories -> merchant_categories
+ EXECUTE format('ALTER TABLE %I.merchant_product_categories'
+ || ' ADD CONSTRAINT merchant_product_categories_category_serial_fkey'
+ || ' FOREIGN KEY (category_serial)'
+ || ' REFERENCES %I.merchant_categories(category_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_product_categories -> merchant_inventory
+ EXECUTE format('ALTER TABLE %I.merchant_product_categories'
+ || ' ADD CONSTRAINT merchant_product_categories_product_serial_fkey'
+ || ' FOREIGN KEY (product_serial)'
+ || ' REFERENCES %I.merchant_inventory(product_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_refund_proofs -> merchant_refunds
+ EXECUTE format('ALTER TABLE %I.merchant_refund_proofs'
+ || ' ADD CONSTRAINT merchant_refund_proofs_refund_serial_fkey'
+ || ' FOREIGN KEY (refund_serial)'
+ || ' REFERENCES %I.merchant_refunds(refund_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_refunds -> merchant_contract_terms
+ EXECUTE format('ALTER TABLE %I.merchant_refunds'
+ || ' ADD CONSTRAINT merchant_refunds_order_serial_fkey'
+ || ' FOREIGN KEY (order_serial)'
+ || ' REFERENCES %I.merchant_contract_terms(order_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_used_tokens -> merchant_token_family_keys
+ EXECUTE format('ALTER TABLE %I.merchant_used_tokens'
+ || ' ADD CONSTRAINT merchant_spent_tokens_token_family_key_serial_fkey'
+ || ' FOREIGN KEY (token_family_key_serial)'
+ || ' REFERENCES %I.merchant_token_family_keys(token_family_key_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_statistic_amount_event -> merchant_statistic_interval_meta
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_amount_event'
+ || ' ADD CONSTRAINT merchant_statistic_amount_event_imeta_serial_id_fkey'
+ || ' FOREIGN KEY (imeta_serial_id)'
+ || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s);
+
+ -- merchant_statistic_bucket_amount -> merchant_statistic_bucket_meta
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_bucket_amount'
+ || ' ADD CONSTRAINT merchant_statistic_bucket_amount_bmeta_serial_id_fkey'
+ || ' FOREIGN KEY (bmeta_serial_id)'
+ || ' REFERENCES %I.merchant_statistic_bucket_meta(bmeta_serial_id) ON DELETE CASCADE', s, s);
+
+ -- merchant_statistic_bucket_counter -> merchant_statistic_bucket_meta
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_bucket_counter'
+ || ' ADD CONSTRAINT merchant_statistic_bucket_counter_bmeta_serial_id_fkey'
+ || ' FOREIGN KEY (bmeta_serial_id)'
+ || ' REFERENCES %I.merchant_statistic_bucket_meta(bmeta_serial_id) ON DELETE CASCADE', s, s);
+
+ -- merchant_statistic_counter_event -> merchant_statistic_interval_meta
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_counter_event'
+ || ' ADD CONSTRAINT merchant_statistic_counter_event_imeta_serial_id_fkey'
+ || ' FOREIGN KEY (imeta_serial_id)'
+ || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s);
+
+ -- merchant_statistic_interval_amount -> merchant_statistic_amount_event
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_amount'
+ || ' ADD CONSTRAINT merchant_statistic_interval_amount_event_delimiter_fkey'
+ || ' FOREIGN KEY (event_delimiter)'
+ || ' REFERENCES %I.merchant_statistic_amount_event(aevent_serial_id) ON DELETE RESTRICT', s, s);
+
+ -- merchant_statistic_interval_amount -> merchant_statistic_interval_meta
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_amount'
+ || ' ADD CONSTRAINT merchant_statistic_interval_amount_imeta_serial_id_fkey'
+ || ' FOREIGN KEY (imeta_serial_id)'
+ || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s);
+
+ -- merchant_statistic_interval_counter -> merchant_statistic_counter_event
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_counter'
+ || ' ADD CONSTRAINT merchant_statistic_interval_counter_event_delimiter_fkey'
+ || ' FOREIGN KEY (event_delimiter)'
+ || ' REFERENCES %I.merchant_statistic_counter_event(nevent_serial_id) ON DELETE RESTRICT', s, s);
+
+ -- merchant_statistic_interval_counter -> merchant_statistic_interval_meta
+ EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_counter'
+ || ' ADD CONSTRAINT merchant_statistic_interval_counter_imeta_serial_id_fkey'
+ || ' FOREIGN KEY (imeta_serial_id)'
+ || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s);
+
+ -- merchant_template -> merchant_otp_devices
+ EXECUTE format('ALTER TABLE %I.merchant_template'
+ || ' ADD CONSTRAINT merchant_template_otp_device_id_fkey'
+ || ' FOREIGN KEY (otp_device_id)'
+ || ' REFERENCES %I.merchant_otp_devices(otp_serial) ON DELETE SET NULL', s, s);
+
+ -- merchant_token_family_keys -> merchant_token_families
+ EXECUTE format('ALTER TABLE %I.merchant_token_family_keys'
+ || ' ADD CONSTRAINT merchant_token_family_keys_token_family_serial_fkey'
+ || ' FOREIGN KEY (token_family_serial)'
+ || ' REFERENCES %I.merchant_token_families(token_family_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_transfer_signatures -> merchant_expected_transfers
+ EXECUTE format('ALTER TABLE %I.merchant_transfer_signatures'
+ || ' ADD CONSTRAINT merchant_transfer_signatures_expected_credit_serial_fkey'
+ || ' FOREIGN KEY (expected_credit_serial)'
+ || ' REFERENCES %I.merchant_expected_transfers(expected_credit_serial) ON DELETE CASCADE', s, s);
+
+ -- merchant_transfers -> merchant_accounts
+ EXECUTE format('ALTER TABLE %I.merchant_transfers'
+ || ' ADD CONSTRAINT merchant_transfers_account_serial_fkey'
+ || ' FOREIGN KEY (account_serial)'
+ || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s);
+
+ -- -------------------------------------------------------------------
+ -- 5. ALTER TABLE for FKs to *global* merchant.* tables.
+ -- Right-hand side keeps `merchant.` qualification.
+ -- -------------------------------------------------------------------
+
+ -- merchant_builtin_unit_overrides -> merchant.merchant_builtin_units
+ EXECUTE format('ALTER TABLE %I.merchant_builtin_unit_overrides'
+ || ' ADD CONSTRAINT merchant_builtin_unit_overrides_builtin_unit_serial_fkey'
+ || ' FOREIGN KEY (builtin_unit_serial)'
+ || ' REFERENCES merchant.merchant_builtin_units(unit_serial) ON DELETE CASCADE', s);
+
+ -- merchant_deposit_confirmations -> merchant.merchant_exchange_signing_keys
+ EXECUTE format('ALTER TABLE %I.merchant_deposit_confirmations'
+ || ' ADD CONSTRAINT merchant_deposit_confirmations_signkey_serial_fkey'
+ || ' FOREIGN KEY (signkey_serial)'
+ || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s);
+
+ -- merchant_deposits -> merchant.merchant_exchange_signing_keys
+ EXECUTE format('ALTER TABLE %I.merchant_deposits'
+ || ' ADD CONSTRAINT merchant_deposits_signkey_serial_fkey'
+ || ' FOREIGN KEY (signkey_serial)'
+ || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s);
+
+ -- merchant_expected_transfers -> merchant.merchant_exchange_signing_keys
+ EXECUTE format('ALTER TABLE %I.merchant_expected_transfers'
+ || ' ADD CONSTRAINT merchant_expected_transfers_signkey_serial_fkey'
+ || ' FOREIGN KEY (signkey_serial)'
+ || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s);
+
+ -- merchant_refund_proofs -> merchant.merchant_exchange_signing_keys
+ EXECUTE format('ALTER TABLE %I.merchant_refund_proofs'
+ || ' ADD CONSTRAINT merchant_refund_proofs_signkey_serial_fkey'
+ || ' FOREIGN KEY (signkey_serial)'
+ || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s);
+
+ -- merchant_transfer_signatures -> merchant.merchant_exchange_signing_keys
+ EXECUTE format('ALTER TABLE %I.merchant_transfer_signatures'
+ || ' ADD CONSTRAINT merchant_transfer_signatures_signkey_serial_fkey'
+ || ' FOREIGN KEY (signkey_serial)'
+ || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s);
+
+ -- =====================================================================
+ -- (FKs back to merchant.merchant_instances are intentionally dropped:
+ -- the existence of the per-instance schema implies that relationship.)
+ -- =====================================================================
+
+END
+$$;
+
+INSERT INTO merchant.instance_fixups
+ (migration_name
+ ,version)
+ VALUES
+ ('merchant_0036_init'
+ ,36);
diff --git a/src/backenddb/sql-schema/merchant-0036-setval.sql.fragment b/src/backenddb/sql-schema/merchant-0036-setval.sql.fragment
@@ -0,0 +1,33 @@
+ -- Advance every IDENTITY sequence in the new instance schema past the
+ -- maximum value just copied so future inserts do not collide.
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_accounts', 'account_serial'), COALESCE((SELECT MAX(account_serial) FROM merchant.merchant_accounts WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_categories', 'category_serial'), COALESCE((SELECT MAX(category_serial) FROM merchant.merchant_categories WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_contract_terms', 'order_serial'), COALESCE((SELECT MAX(order_serial) FROM merchant.merchant_contract_terms WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_custom_units', 'unit_serial'), COALESCE((SELECT MAX(unit_serial) FROM merchant.merchant_custom_units WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_donau_instances', 'donau_instances_serial'), COALESCE((SELECT MAX(donau_instances_serial) FROM merchant.merchant_donau_instances WHERE merchant_instance_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_inventory', 'product_serial'), COALESCE((SELECT MAX(product_serial) FROM merchant.merchant_inventory WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_login_tokens', 'serial'), COALESCE((SELECT MAX(serial) FROM merchant.merchant_login_tokens WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_money_pots', 'money_pot_serial'), COALESCE((SELECT MAX(money_pot_serial) FROM merchant.merchant_money_pots WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_orders', 'order_serial'), COALESCE((SELECT MAX(order_serial) FROM merchant.merchant_orders WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_otp_devices', 'otp_serial'), COALESCE((SELECT MAX(otp_serial) FROM merchant.merchant_otp_devices WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_product_groups', 'product_group_serial'), COALESCE((SELECT MAX(product_group_serial) FROM merchant.merchant_product_groups WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_reports', 'report_serial'), COALESCE((SELECT MAX(report_serial) FROM merchant.merchant_reports WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_amount_event', 'aevent_serial_id'), COALESCE((SELECT MAX(aevent_serial_id) FROM merchant.merchant_statistic_amount_event WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_bucket_meta', 'bmeta_serial_id'), COALESCE((SELECT MAX(bmeta_serial_id) FROM merchant.merchant_statistic_bucket_meta), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_counter_event', 'nevent_serial_id'), COALESCE((SELECT MAX(nevent_serial_id) FROM merchant.merchant_statistic_counter_event WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_interval_meta', 'imeta_serial_id'), COALESCE((SELECT MAX(imeta_serial_id) FROM merchant.merchant_statistic_interval_meta), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_template', 'template_serial'), COALESCE((SELECT MAX(template_serial) FROM merchant.merchant_template WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_token_families', 'token_family_serial'), COALESCE((SELECT MAX(token_family_serial) FROM merchant.merchant_token_families WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_webhook', 'webhook_serial'), COALESCE((SELECT MAX(webhook_serial) FROM merchant.merchant_webhook WHERE merchant_serial = rec.merchant_serial), 0) + 1, false);
+ -- Indirect-FK tables (owning instance via JOIN)
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_kyc', 'kyc_serial_id'), COALESCE((SELECT MAX(k.kyc_serial_id) FROM merchant.merchant_kyc k JOIN merchant.merchant_accounts a ON k.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_deposit_confirmations', 'deposit_confirmation_serial'), COALESCE((SELECT MAX(dc.deposit_confirmation_serial) FROM merchant.merchant_deposit_confirmations dc JOIN merchant.merchant_accounts a ON dc.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_expected_transfers', 'expected_credit_serial'), COALESCE((SELECT MAX(et.expected_credit_serial) FROM merchant.merchant_expected_transfers et JOIN merchant.merchant_accounts a ON et.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_transfers', 'credit_serial'), COALESCE((SELECT MAX(t.credit_serial) FROM merchant.merchant_transfers t JOIN merchant.merchant_accounts a ON t.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_deposits', 'deposit_serial'), COALESCE((SELECT MAX(d.deposit_serial) FROM merchant.merchant_deposits d JOIN merchant.merchant_deposit_confirmations dc ON d.deposit_confirmation_serial = dc.deposit_confirmation_serial JOIN merchant.merchant_accounts a ON dc.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_refunds', 'refund_serial'), COALESCE((SELECT MAX(r.refund_serial) FROM merchant.merchant_refunds r JOIN merchant.merchant_contract_terms ct ON r.order_serial = ct.order_serial WHERE ct.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_token_family_keys', 'token_family_key_serial'), COALESCE((SELECT MAX(tfk.token_family_key_serial) FROM merchant.merchant_token_family_keys tfk JOIN merchant.merchant_token_families tf ON tfk.token_family_serial = tf.token_family_serial WHERE tf.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_issued_tokens', 'issued_token_serial'), COALESCE((SELECT MAX(it.issued_token_serial) FROM merchant.merchant_issued_tokens it JOIN merchant.merchant_token_family_keys tfk ON it.token_family_key_serial = tfk.token_family_key_serial JOIN merchant.merchant_token_families tf ON tfk.token_family_serial = tf.token_family_serial WHERE tf.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_used_tokens', 'spent_token_serial'), COALESCE((SELECT MAX(ut.spent_token_serial) FROM merchant.merchant_used_tokens ut JOIN merchant.merchant_token_family_keys tfk ON ut.token_family_key_serial = tfk.token_family_key_serial JOIN merchant.merchant_token_families tf ON tfk.token_family_serial = tf.token_family_serial WHERE tf.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_unclaim_signatures', 'unclaim_serial'), COALESCE((SELECT MAX(us.unclaim_serial) FROM merchant.merchant_unclaim_signatures us JOIN merchant.merchant_contract_terms ct ON us.h_contract_terms = ct.h_contract_terms WHERE ct.merchant_serial = rec.merchant_serial), 0) + 1, false);
+ PERFORM setval(pg_get_serial_sequence(s || '.merchant_order_token_blinded_sigs','order_token_bs_serial'), COALESCE((SELECT MAX(otbs.order_token_bs_serial) FROM merchant.merchant_order_token_blinded_sigs otbs JOIN merchant.merchant_contract_terms ct ON otbs.order_serial = ct.order_serial WHERE ct.merchant_serial = rec.merchant_serial), 0) + 1, false);
diff --git a/src/backenddb/sql-schema/merchant-0036.sql.in b/src/backenddb/sql-schema/merchant-0036.sql.in
@@ -0,0 +1,124 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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 merchant-0036.sql.in
+-- @brief Move per-instance tables into per-instance schemas
+-- merchant_instance_<merchant_serial>. Each existing row of
+-- merchant_instances is converted by calling
+-- merchant.create_instance_schema(serial), copying the rows of every
+-- per-instance table into the new schema (dropping the merchant_serial
+-- column), and finally dropping the now-empty merchant.<table>.
+-- @author Christian Grothoff
+
+BEGIN;
+
+SELECT _v.register_patch('merchant-0036', NULL, NULL);
+
+SET search_path TO merchant;
+
+-- ---------------------------------------------------------------------
+-- Step 0: Fold merchant_keys.merchant_priv into merchant_instances as a
+-- nullable column. Setting merchant_priv to NULL replaces deleting the
+-- key row. The merchant_keys table itself is dropped in step 4.
+-- ---------------------------------------------------------------------
+ALTER TABLE merchant.merchant_instances
+ ADD COLUMN merchant_priv BYTEA
+ CHECK (merchant_priv IS NULL OR LENGTH(merchant_priv) = 32);
+COMMENT ON COLUMN merchant.merchant_instances.merchant_priv
+ IS 'private key of the instance, NULL once the key has been "deleted"';
+UPDATE merchant.merchant_instances mi
+ SET merchant_priv = mk.merchant_priv
+ FROM merchant.merchant_keys mk
+ WHERE mk.merchant_serial = mi.merchant_serial;
+
+CREATE TABLE merchant.instance_fixups
+ (instance_fixup_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,migration_name TEXT NOT NULL
+ ,version INT8 NOT NULL);
+COMMENT ON TABLE instance_fixups
+ IS 'Fixups to setup the per-instance tables';
+COMMENT ON COLUMN instance_fixups.migration_name
+ IS 'Name of the stored procedure to run to fix up the schema of the instance';
+COMMENT ON COLUMN instance_fixups.version
+ IS 'Version of the DB in which the given fix up should happen';
+
+CREATE INDEX instance_fixups_by_version
+ ON instance_fixups (version);
+COMMENT ON INDEX instance_fixups_by_version
+ IS 'Used by create_instance_schema (and in the future on import)';
+
+-- ---------------------------------------------------------------------
+-- Step 1: Install the schema constructor and actual fix-ups.
+-- procedures.sql will re-install the same definition (DROP IF EXISTS),
+-- so installing it here is harmless and lets the migration call it.
+-- ---------------------------------------------------------------------
+#include "../create_tables.sql"
+#include "../pg_create_instance_schema.sql"
+#include "merchant-0036-init.sql.fragment"
+
+-- ---------------------------------------------------------------------
+-- Step 2: Build a per-instance schema for every existing instance.
+-- ---------------------------------------------------------------------
+DO $MIG$
+DECLARE
+ rec RECORD;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ FROM merchant.merchant_instances
+ LOOP
+ PERFORM merchant.create_instance_schema(rec.merchant_serial);
+ END LOOP;
+END $MIG$;
+
+-- ---------------------------------------------------------------------
+-- Step 3: Migrate row data per instance.
+-- Tables are copied in FK-dependency order. Every copy drops the
+-- merchant_serial column. After each table is copied, its IDENTITY
+-- sequence (if any) is advanced past MAX(serial).
+-- Indirect-FK tables (merchant_kyc, merchant_deposits, ...) are copied
+-- by JOINing back to the table that owns the merchant_serial.
+-- ---------------------------------------------------------------------
+-- Suppress USER triggers during the bulk copy: per-instance triggers
+-- (e.g. merchant_kyc_insert_trigger) would otherwise fire on every
+-- inserted row and CALL into per-instance procedures that may rely on
+-- runtime invariants we do not have during migration. This setting is
+-- transaction-local and does not affect normal callers.
+SET session_replication_role = replica;
+
+DO $MIG$
+DECLARE
+ rec RECORD;
+ s TEXT;
+BEGIN
+ FOR rec IN
+ SELECT merchant_serial
+ FROM merchant.merchant_instances
+ LOOP
+ s := 'merchant_instance_' || rec.merchant_serial::TEXT;
+#include "merchant-0036-copy.sql.fragment"
+#include "merchant-0036-setval.sql.fragment"
+ END LOOP;
+END $MIG$;
+
+SET session_replication_role = origin;
+
+-- ---------------------------------------------------------------------
+-- Step 4: Drop old per-instance tables from the merchant schema, in
+-- reverse FK-dependency order so CASCADE is not needed.
+-- ---------------------------------------------------------------------
+#include "merchant-0036-drop.sql.fragment"
+
+COMMIT;
diff --git a/src/backenddb/sql-schema/meson.build b/src/backenddb/sql-schema/meson.build
@@ -1,86 +1,138 @@
sqldir = get_option('datadir') / 'taler-merchant' / 'sql'
-# Input files for the amalgamated procedures.sql.
+# Input files for the amalgamated global_procedures.sql.
# The order matters.
-sql_procedures = [
- '../pg_insert_deposit_to_transfer.sql',
- '../pg_insert_product.sql',
- '../pg_insert_issued_token.sql',
- '../pg_insert_spent_token.sql',
- '../pg_insert_transfer_details.sql',
- '../pg_update_product.sql',
- '../pg_solve_mfa_challenge.sql',
- '../pg_account_kyc_set_status.sql',
- '../pg_account_kyc_set_failed.sql',
- '../pg_statistics_helpers.sql',
- '../pg_do_handle_inventory_changes.sql',
- '../pg_do_handle_category_changes.sql',
- '../pg_update_product_group.sql',
- '../pg_update_money_pot.sql',
- '../pg_increment_money_pots.sql',
- '../pg_account_kyc_get_status.sql',
- '../pg_insert_transfer.sql',
- '../pg_insert_deposit_confirmation.sql',
+sql_global_procedures = [
+ '../pg_interval_to_start.sql',
'../pg_base32_crockford.sql',
'../pg_uri_escape.sql',
- '../pg_merchant_kyc_trigger.sql',
- '../pg_merchant_send_kyc_notification.sql',
- '../pg_activate_account.sql',
- '../pg_inactivate_account.sql',
+ '../pg_replace_placeholder.sql',
+ '../check_report.sql',
+ '../create_tables.sql',
+ '../pg_fixup_instance_schema.sql',
+ '../account_kyc_get_outdated.sql',
+ '../select_wirewatch_accounts.sql',
+ '../select_open_transfers.sql',
+ '../select_all_donau_instances.sql',
+ '../solve_mfa_challenge.sql',
+ '../lookup_pending_deposits.sql',
+ '../lookup_reports_pending.sql',
+ '../pg_select_accounts.sql',
+ '../pg_create_instance_schema.sql',
+ '../pg_create_instance_trigger.sql',
+ '../gc.sql',
]
-sqlfiles = [
- 'drop.sql',
- 'merchant-0001.sql',
- 'merchant-0002.sql',
- 'merchant-0003.sql',
- 'merchant-0004.sql',
- 'merchant-0005.sql',
- 'merchant-0006.sql',
- 'merchant-0007.sql',
- 'merchant-0008.sql',
- 'merchant-0009.sql',
- 'merchant-0010.sql',
- 'merchant-0011.sql',
- 'merchant-0012.sql',
- 'merchant-0013.sql',
- 'merchant-0014.sql',
- 'merchant-0015.sql',
- 'merchant-0016.sql',
- 'merchant-0017.sql',
- 'merchant-0018.sql',
- 'merchant-0019.sql',
- 'merchant-0020.sql',
- 'merchant-0021.sql',
- 'merchant-0022.sql',
- 'merchant-0023.sql',
- 'merchant-0024.sql',
- 'merchant-0025.sql',
- 'merchant-0026.sql',
- 'merchant-0027.sql',
- 'merchant-0028.sql',
- 'merchant-0029.sql',
- 'merchant-0030.sql',
- 'merchant-0031.sql',
- 'merchant-0032.sql',
- 'merchant-0033.sql',
- 'merchant-0034.sql',
- 'merchant-0035.sql',
- 'versioning.sql',
-]
+gprocedures_sql = custom_target('sql_global_procedures',
+ input: sql_global_procedures,
+ output: 'global_procedures.sql',
+ capture: true,
+ command: ['./gen-procedures.sh', 'merchant', '@INPUT@'],
+ install: true,
+ install_dir: sqldir)
+sql_instance_procedures = [
+ '../pg_statistics_helpers.sql',
+ '../pg_do_handle_inventory_changes.sql',
+ '../pg_do_handle_category_changes.sql',
+ '../delete_unit.sql',
+ '../insert_unit.sql',
+ '../update_unit.sql',
+ '../lookup_statistics_amount_by_interval.sql',
+ '../lookup_statistics_counter_by_interval.sql',
+ '../insert_deposit_to_transfer.sql',
+ '../insert_product.sql',
+ '../insert_issued_token.sql',
+ '../insert_spent_token.sql',
+ '../insert_transfer_details.sql',
+ '../update_product.sql',
+ '../account_kyc_set_status.sql',
+ '../account_kyc_set_failed.sql',
+ '../update_product_group.sql',
+ '../update_money_pot.sql',
+ '../increment_money_pots.sql',
+ '../account_kyc_get_status.sql',
+ '../insert_transfer.sql',
+ '../insert_unclaim_signature.sql',
+ '../insert_deposit_confirmation.sql',
+ '../activate_account.sql',
+ '../inactivate_account.sql',
+ '../expire_locks.sql',
+ '../pg_merchant_send_kyc_notification.sql',
+ '../pg_merchant_kyc_trigger.sql',
+]
-sql_procs = custom_target('sql_procedures',
- input: sql_procedures,
- output: 'procedures.sql',
+iprocedures_sql = custom_target('instance_procedures',
+ input: sql_instance_procedures,
+ output: 'instance_procedures.sql',
capture: true,
- command: ['./gen-procedures.sh', '@INPUT@'],
+ command: ['./gen-procedures.sh', 'merchant_instances', '@INPUT@'],
install: true,
install_dir: sqldir)
-install_data(sources: sqlfiles, install_dir: sqldir)
+generated_sql = [
+ ['drop.sql'],
+ ['versioning.sql'],
+ ['merchant-0001.sql'],
+ ['merchant-0002.sql'],
+ ['merchant-0003.sql'],
+ ['merchant-0004.sql'],
+ ['merchant-0005.sql'],
+ ['merchant-0006.sql'],
+ ['merchant-0007.sql'],
+ ['merchant-0008.sql'],
+ ['merchant-0009.sql'],
+ ['merchant-0010.sql'],
+ ['merchant-0011.sql'],
+ ['merchant-0012.sql'],
+ ['merchant-0013.sql'],
+ ['merchant-0014.sql'],
+ ['merchant-0015.sql'],
+ ['merchant-0016.sql'],
+ ['merchant-0017.sql'],
+ ['merchant-0018.sql'],
+ ['merchant-0019.sql'],
+ ['merchant-0020.sql'],
+ ['merchant-0021.sql'],
+ ['merchant-0022.sql'],
+ ['merchant-0023.sql'],
+ ['merchant-0024.sql'],
+ ['merchant-0025.sql'],
+ ['merchant-0026.sql'],
+ ['merchant-0027.sql'],
+ ['merchant-0028.sql'],
+ ['merchant-0029.sql'],
+ ['merchant-0030.sql'],
+ ['merchant-0031.sql'],
+ ['merchant-0032.sql'],
+ ['merchant-0033.sql'],
+ ['merchant-0034.sql'],
+ ['merchant-0035.sql'],
+]
+
+foreach g : generated_sql
+ custom_target(
+ 'gen-merchantdb-' + g[0],
+ input: g[0],
+ output: g[0],
+ capture: true,
+ command: ['./amalgamate-sql.sh', '@INPUT@'],
+ install: true,
+ install_dir: sqldir,
+ )
+endforeach
+
-# This makes meson copy the files into the build directory for testing
-#foreach f : sqlfiles
-# configure_file(input: f, output: f, copy: true, install_mode: 'rw-r--r--')
-#endforeach
+custom_target(
+ 'gen-merchantdb-merchant_0036.sql',
+ input: ['merchant-0036.sql.in',
+ 'merchant-0036-init.sql.fragment',
+ 'merchant-0036-copy.sql.fragment',
+ 'merchant-0036-drop.sql.fragment',
+ 'merchant-0036-setval.sql.fragment'],
+ output: ['merchant-0036.sql'],
+ capture: true,
+ command: ['./preprocess-sql.sh', '@INPUT@'],
+ install: true,
+ install_dir: sqldir,
+ )
+\ No newline at end of file
diff --git a/src/backenddb/sql-schema/preprocess-sql.sh b/src/backenddb/sql-schema/preprocess-sql.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+# This file is part of TALER
+# Copyright (C) 2026 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU 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/>
+
+set -eu
+
+if [ $# -lt 1 ]; then
+ echo "Usage: $0 SQLFILES..." >&2
+ exit 1
+fi
+
+cat <<'EOF'
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+EOF
+
+echo "-- generated by" $(basename $0)
+for n in $@
+do
+ echo "-- generated from" $(basename $n)
+done
+echo "--"
+
+# Pre-process with cpp and remove SQL comments and empty lines
+gcc -I $(dirname $1) -E -P -undef - < $1 2>/tmp/null | sed -e "s/--.*//" | awk 'NF'
diff --git a/src/backenddb/start.c b/src/backenddb/start.c
@@ -105,7 +105,8 @@ TALER_MERCHANTDB_rollback (
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_commit (struct TALER_MERCHANTDB_PostgresContext *pg)
+TALER_MERCHANTDB_commit (
+ struct TALER_MERCHANTDB_PostgresContext *pg)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_end
diff --git a/src/backenddb/store_wire_fee_by_exchange.c b/src/backenddb/store_wire_fee_by_exchange.c
@@ -19,21 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/store_wire_fee_by_exchange.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_store_wire_fee_by_exchange (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct GNUNET_HashCode *h_wire_method,
- const struct TALER_WireFeeSet *fees,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *master_sig)
+TALER_MERCHANTDB_store_wire_fee_by_exchange (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const struct GNUNET_HashCode *h_wire_method,
+ const struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_pub),
@@ -51,7 +50,7 @@ TALER_MERCHANTDB_store_wire_fee_by_exchange (struct TALER_MERCHANTDB_PostgresCon
/* no preflight check here, run in its own transaction by the caller */
PREPARE (pg,
"insert_wire_fee",
- "INSERT INTO merchant_exchange_wire_fees"
+ "INSERT INTO merchant.merchant_exchange_wire_fees"
"(master_pub"
",h_wire_method"
",wire_fee"
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -67,6 +67,7 @@
#include "merchant-database/lookup_order_by_fulfillment.h"
#include "merchant-database/lookup_order_status.h"
#include "merchant-database/lookup_orders.h"
+#include "merchant-database/lookup_all_webhooks.h"
#include "merchant-database/lookup_pending_webhooks.h"
#include "merchant-database/lookup_product.h"
#include "merchant-database/lookup_products.h"
@@ -86,6 +87,7 @@
#include "merchant-database/mark_contract_paid.h"
#include "merchant-database/mark_order_wired.h"
#include "merchant-database/refund_coin.h"
+#include "merchant-database/set_instance.h"
#include "merchant-database/store_wire_fee_by_exchange.h"
#include "merchant-database/unlock_inventory.h"
#include "merchant-database/update_contract_terms.h"
@@ -138,6 +140,46 @@ static struct TALER_MERCHANTDB_PostgresContext *pg;
return 1; \
)
+/**
+ * Activate the per-instance schema for @a iid before invoking any
+ * per-instance MERCHANTDB function. Returns 1 from the enclosing
+ * test helper if routing fails for an unexpected reason.
+ *
+ * If @a iid does not resolve to an existing instance, set_instance
+ * returns NO_RESULTS. In that case the underlying per-instance call
+ * could never have run, so we treat it as the test outcome: when the
+ * caller expected NO_RESULTS, return 0 (pass); otherwise return 1.
+ *
+ * @param iid C-string instance identifier
+ * @param expected expected GNUNET_DB_QueryStatus from the wrapped call
+ */
+#define TEST_SET_INSTANCE(iid, expected) \
+ do { \
+ enum GNUNET_DB_QueryStatus _si_qs; \
+ _si_qs = TALER_MERCHANTDB_set_instance (pg, (iid)); \
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == _si_qs) \
+ { \
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == (expected)) \
+ return 0; \
+ GNUNET_break (0); \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "set_instance(%s): instance missing, " \
+ "expected qs=%d\n", \
+ (iid), \
+ (int) (expected)); \
+ return 1; \
+ } \
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != _si_qs) \
+ { \
+ GNUNET_break (0); \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "set_instance(%s) failed (qs=%d)\n", \
+ (iid), \
+ (int) _si_qs); \
+ return 1; \
+ } \
+ } while (0)
+
/* ********** Instances ********** */
@@ -475,6 +517,7 @@ static int
test_delete_instance_private_key (const struct InstanceData *instance,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_delete_instance_private_key (
pg,
@@ -495,6 +538,7 @@ static int
test_purge_instance (const struct InstanceData *instance,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_purge_instance (pg,
instance->instance.id),
@@ -516,6 +560,7 @@ test_insert_account (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_account (pg,
account),
@@ -536,6 +581,7 @@ test_inactivate_account (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_inactivate_account (pg,
instance->instance.id,
@@ -901,11 +947,19 @@ test_insert_product (const struct InstanceData *instance,
ssize_t expected_no_cat)
{
bool conflict;
- bool no_instance;
ssize_t no_cat;
bool no_group;
bool no_pot;
+ if (expect_no_instance)
+ {
+ TEST_COND_RET_ON_FAIL (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ TALER_MERCHANTDB_set_instance (pg, instance->instance.id),
+ "Expected missing instance, but set_instance succeeded\n");
+ return 0;
+ }
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_product (pg,
instance->instance.id,
@@ -913,7 +967,6 @@ test_insert_product (const struct InstanceData *instance,
&product->product,
num_cats,
cats,
- &no_instance,
&conflict,
&no_cat,
&no_group,
@@ -921,8 +974,6 @@ test_insert_product (const struct InstanceData *instance,
"Insert product failed\n");
if (expected_result > 0)
{
- TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
- "No instance wrong");
TEST_COND_RET_ON_FAIL (conflict == expect_conflict,
"Conflict wrong");
TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
@@ -954,7 +1005,6 @@ test_update_product (const struct InstanceData *instance,
ssize_t expected_no_cat)
{
- bool no_instance;
ssize_t no_cat;
bool no_product;
bool lost_reduced;
@@ -963,6 +1013,15 @@ test_update_product (const struct InstanceData *instance,
bool no_group;
bool no_pot;
+ if (expect_no_instance)
+ {
+ TEST_COND_RET_ON_FAIL (
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+ TALER_MERCHANTDB_set_instance (pg, instance->instance.id),
+ "Expected missing instance, but set_instance succeeded\n");
+ return 0;
+ }
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (
expected_result ==
TALER_MERCHANTDB_update_product (pg,
@@ -971,7 +1030,6 @@ test_update_product (const struct InstanceData *instance,
&product->product,
num_cats,
cats,
- &no_instance,
&no_cat,
&no_product,
&lost_reduced,
@@ -982,8 +1040,6 @@ test_update_product (const struct InstanceData *instance,
"Update product failed\n");
if (expected_result > 0)
{
- TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
- "No instance wrong");
TEST_COND_RET_ON_FAIL (no_product == expect_no_product,
"No product wrong");
TEST_COND_RET_ON_FAIL (lost_reduced == expect_lost_reduced,
@@ -1014,6 +1070,7 @@ test_lookup_product (const struct InstanceData *instance,
size_t num_categories = 0;
uint64_t *categories = NULL;
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_product (pg,
instance->instance.id,
product->id,
@@ -1120,6 +1177,7 @@ test_lookup_products (const struct InstanceData *instance,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * products_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_products (pg,
instance->instance.id,
0,
@@ -1167,6 +1225,7 @@ test_delete_product (const struct InstanceData *instance,
const struct ProductData *product,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_delete_product (pg,
instance->instance.id,
@@ -1608,6 +1667,7 @@ test_insert_order (const struct InstanceData *instance,
memset (&h_post,
42,
sizeof (h_post));
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_order (pg,
instance->instance.id,
@@ -1643,6 +1703,7 @@ test_lookup_order (const struct InstanceData *instance,
memset (&wh,
42,
sizeof (wh));
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
TALER_MERCHANTDB_lookup_order (pg,
instance->instance.id,
@@ -1760,6 +1821,7 @@ test_lookup_orders (const struct InstanceData *instance,
memset (results_match,
0,
sizeof (bool) * orders_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_orders (pg,
instance->instance.id,
filter,
@@ -1855,6 +1917,9 @@ get_order_serial (const struct InstanceData *instance,
.delta = 256
};
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (pg,
+ instance->instance.id));
GNUNET_assert (0 < TALER_MERCHANTDB_lookup_orders (pg,
instance->instance.id,
&filter,
@@ -1879,6 +1944,7 @@ test_delete_order (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_delete_order (pg,
instance->instance.id,
@@ -1904,6 +1970,7 @@ test_insert_contract_terms (const struct InstanceData *instance,
{
uint64_t os;
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_contract_terms (pg,
instance->instance.id,
@@ -1928,6 +1995,7 @@ test_update_contract_terms (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_update_contract_terms (pg,
instance->instance.id,
@@ -1952,6 +2020,7 @@ test_lookup_contract_terms (const struct InstanceData *instance,
json_t *contract = NULL;
uint64_t order_serial;
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
TALER_MERCHANTDB_lookup_contract_terms (pg,
instance->instance.id,
@@ -1993,6 +2062,7 @@ test_delete_contract_terms (const struct InstanceData *instance,
struct GNUNET_TIME_Relative legal_expiration,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_delete_contract_terms (pg,
instance->instance.id,
@@ -2021,6 +2091,7 @@ test_mark_contract_paid (const struct InstanceData *instance,
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_hash (order->contract,
&h_contract_terms));
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_mark_contract_paid (pg,
instance->instance.id,
@@ -2049,6 +2120,7 @@ test_lookup_order_status (const struct InstanceData *instance,
struct TALER_PrivateContractHashP h_contract_terms;
bool order_paid = false;
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
TALER_MERCHANTDB_lookup_order_status (pg,
instance->instance.id,
@@ -2093,6 +2165,7 @@ test_lookup_order_by_fulfillment (const struct InstanceData *instance,
json_string_value (json_object_get (order->contract,
"fulfillment_url"));
GNUNET_assert (NULL != fulfillment_url);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
TALER_MERCHANTDB_lookup_order_by_fulfillment (pg,
instance->instance.id,
@@ -2141,6 +2214,7 @@ test_lookup_payment_status (const char *instance_id,
uint64_t os;
int16_t choice_index;
+ TEST_SET_INSTANCE (instance_id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
TALER_MERCHANTDB_lookup_contract_terms3 (pg,
instance_id,
@@ -2810,6 +2884,7 @@ test_insert_deposit (const struct InstanceData *instance,
TALER_amount_subtract (&awf,
&deposit->amount_with_fee,
&deposit->deposit_fee));
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
TALER_MERCHANTDB_insert_deposit_confirmation (pg,
@@ -2945,6 +3020,7 @@ test_lookup_deposits (const struct InstanceData *instance,
memset (results_matching,
0,
sizeof (unsigned int) * deposits_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
TEST_COND_RET_ON_FAIL (0 <=
TALER_MERCHANTDB_lookup_deposits (pg,
instance->instance.id,
@@ -3072,6 +3148,7 @@ test_lookup_deposits_contract_and_coin (
memset (results_matching,
0,
sizeof (unsigned int) * deposits_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
TEST_COND_RET_ON_FAIL (
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin (
@@ -4211,6 +4288,7 @@ test_lookup_transfers (const struct InstanceData *instance,
memset (results_matching,
0,
sizeof (unsigned int) * transfers_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (1 != TALER_MERCHANTDB_lookup_transfers (pg,
instance->instance.id,
account->payto_uri,
@@ -4260,21 +4338,21 @@ test_insert_transfer (const struct InstanceData *instance,
const struct TransferData *transfer,
enum GNUNET_DB_QueryStatus expected_result)
{
- bool no_instance;
bool no_account;
bool conflict;
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
- TALER_MERCHANTDB_insert_transfer (pg,
- instance->instance.id,
- transfer->exchange_url,
- &transfer->wtid,
- &transfer->data.total_amount,
- account->payto_uri,
- transfer->confirmed,
- &no_instance,
- &no_account,
- &conflict),
+ TALER_MERCHANTDB_insert_transfer (
+ pg,
+ instance->instance.id,
+ transfer->exchange_url,
+ &transfer->wtid,
+ &transfer->data.total_amount,
+ account->payto_uri,
+ transfer->confirmed,
+ &no_account,
+ &conflict),
"Insert transfer failed\n");
return 0;
}
@@ -4307,9 +4385,12 @@ test_insert_deposit_to_transfer (const struct InstanceData *instance,
.execution_time = transfer->data.execution_time,
.coin_contribution = deposit->amount_with_fee
};
- uint64_t deposit_serial = get_deposit_serial (instance,
- order,
- deposit);
+ uint64_t deposit_serial;
+
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
+ deposit_serial = get_deposit_serial (instance,
+ order,
+ deposit);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_deposit_to_transfer (
@@ -4339,6 +4420,7 @@ test_insert_transfer_details (
const struct TransferData *transfer,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_transfer_details (
pg,
@@ -4699,6 +4781,7 @@ test_lookup_refunds (const struct InstanceData *instance,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (1 != TALER_MERCHANTDB_lookup_refunds (pg,
instance->instance.id,
h_contract_terms,
@@ -4908,6 +4991,7 @@ test_lookup_refunds_detailed (
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_refunds_detailed (pg,
instance->instance.id,
h_contract_terms,
@@ -5018,6 +5102,9 @@ get_refund_serial (const struct InstanceData *instance,
.serial = 0
};
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (pg,
+ instance->instance.id));
GNUNET_assert (0 < TALER_MERCHANTDB_lookup_refunds_detailed (pg,
instance->instance.id,
h_contract_terms,
@@ -5899,6 +5986,7 @@ test_insert_template (const struct InstanceData *instance,
const struct TemplateData *template,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_template (pg,
instance->instance.id,
@@ -5923,6 +6011,7 @@ test_update_template (const struct InstanceData *instance,
const struct TemplateData *template,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_update_template (pg,
instance->instance.id,
@@ -5948,6 +6037,7 @@ test_lookup_template (const struct InstanceData *instance,
= &template->template;
struct TALER_MERCHANTDB_TemplateDetails lookup_result;
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_template (pg,
instance->instance.id,
template->id,
@@ -6050,6 +6140,7 @@ test_lookup_templates (const struct InstanceData *instance,
memset (results_matching,
0,
sizeof (unsigned int) * templates_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_templates (pg,
instance->instance.id,
&lookup_templates_cb,
@@ -6091,6 +6182,7 @@ test_delete_template (const struct InstanceData *instance,
const struct TemplateData *template,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_delete_template (pg,
instance->instance.id,
@@ -6346,6 +6438,7 @@ test_insert_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_webhook (pg,
instance->instance.id,
@@ -6369,6 +6462,7 @@ test_update_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_update_webhook (pg,
instance->instance.id,
@@ -6393,6 +6487,7 @@ test_lookup_webhook (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
struct TALER_MERCHANTDB_WebhookDetails lookup_result;
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_webhook (pg,
instance->instance.id,
webhook->id,
@@ -6491,6 +6586,7 @@ test_lookup_webhooks (const struct InstanceData *instance,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_webhooks (pg,
instance->instance.id,
&lookup_webhooks_cb,
@@ -6576,6 +6672,7 @@ test_lookup_webhook_by_event (const struct InstanceData *instance,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_webhook_by_event (pg,
instance->instance.id,
webhooks->webhook.event_type,
@@ -6619,6 +6716,7 @@ test_delete_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_delete_webhook (pg,
instance->instance.id,
@@ -6854,6 +6952,7 @@ test_insert_pending_webhook (const struct InstanceData *instance,
enum GNUNET_DB_QueryStatus expected_result)
{
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_insert_pending_webhook (pg,
instance->instance.id,
@@ -6887,6 +6986,7 @@ test_update_pending_webhook (const struct InstanceData *instance,
pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_UNIT_HOURS);
pwebhook->pwebhook.retries++;
+ TEST_SET_INSTANCE (instance->instance.id, expected_result);
TEST_COND_RET_ON_FAIL (expected_result ==
TALER_MERCHANTDB_update_pending_webhook (pg,
pwebhook->
@@ -6973,6 +7073,9 @@ get_pending_serial (const struct InstanceData *instance,
.webhook_pending_serial = 0
};
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ TALER_MERCHANTDB_set_instance (pg,
+ instance->instance.id));
GNUNET_assert (0 <
TALER_MERCHANTDB_lookup_all_webhooks (pg,
instance->instance.id,
@@ -7174,6 +7277,7 @@ test_lookup_all_webhooks (const struct InstanceData *instance,
memset (results_matching,
0,
sizeof (results_matching));
+ TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
if (0 > TALER_MERCHANTDB_lookup_all_webhooks (pg,
instance->instance.id,
min_row,
@@ -7236,6 +7340,14 @@ struct TestPendingWebhooks_Closure
struct InstanceData instance;
/**
+ * Webhooks that the pending webhooks reference via foreign key.
+ * Each per-instance schema has its own webhook_serial sequence
+ * starting at 1, so we must populate webhooks before inserting
+ * pending webhooks.
+ */
+ struct WebhookData webhooks[4];
+
+ /**
* The array of pending webhooks.
*/
struct PendingWebhookData pwebhooks[2];
@@ -7254,6 +7366,12 @@ pre_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
make_instance ("test_inst_pending_webhooks",
&cls->instance);
+ /* Webhooks the pending webhooks reference (serials 1..4) */
+ make_webhook ("test_pwh_wb_0", &cls->webhooks[0]);
+ make_webhook ("test_pwh_wb_1", &cls->webhooks[1]);
+ make_webhook ("test_pwh_wb_2", &cls->webhooks[2]);
+ make_webhook ("test_pwh_wb_3", &cls->webhooks[3]);
+
/* Webhooks */
make_pending_webhook (1,
&cls->pwebhooks[0]);
@@ -7300,6 +7418,13 @@ run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Insert the referenced webhooks so pending webhook FKs resolve. */
+ for (unsigned int i = 0; i < 4; i++)
+ TEST_RET_ON_FAIL (
+ test_insert_webhook (&cls->instance,
+ &cls->webhooks[i],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+
/* Test inserting a pending webhook */
TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
&cls->pwebhooks[0],
diff --git a/src/backenddb/unlock_inventory.c b/src/backenddb/unlock_inventory.c
@@ -19,28 +19,28 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/unlock_inventory.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_unlock_inventory (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct GNUNET_Uuid *uuid)
+TALER_MERCHANTDB_unlock_inventory (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct GNUNET_Uuid *uuid)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (uuid),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "unlock_inventory",
- "DELETE"
- " FROM merchant_inventory_locks"
- " WHERE lock_uuid=$1");
+ TMH_PQ_prepare_anon (pg,
+ "DELETE"
+ " FROM merchant_inventory_locks"
+ " WHERE lock_uuid=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "unlock_inventory",
+ "",
params);
}
diff --git a/src/backenddb/update_account.c b/src/backenddb/update_account.c
@@ -19,23 +19,21 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_account.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_account (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *extra_wire_subject_metadata,
- const char *credit_facade_url,
- const json_t *credit_facade_credentials)
+TALER_MERCHANTDB_update_account (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *extra_wire_subject_metadata,
+ const char *credit_facade_url,
+ const json_t *credit_facade_credentials)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
NULL == credit_facade_url
? GNUNET_PQ_query_param_null ()
@@ -49,20 +47,18 @@ TALER_MERCHANTDB_update_account (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_account",
- "UPDATE merchant_accounts SET"
- " credit_facade_url=$3"
- ",credit_facade_credentials="
- " COALESCE($4::TEXT::JSONB, credit_facade_credentials)"
- ",extra_wire_subject_metadata=$5"
- " WHERE h_wire=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1);");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_accounts SET"
+ " credit_facade_url=$2"
+ ",credit_facade_credentials="
+ " COALESCE($3::TEXT::JSONB, credit_facade_credentials)"
+ ",extra_wire_subject_metadata=$4"
+ " WHERE h_wire=$1;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_account",
+ "",
params);
}
diff --git a/src/backenddb/update_category.c b/src/backenddb/update_category.c
@@ -19,40 +19,36 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_category.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_category (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t category_id,
- const char *category_name,
- const json_t *category_name_i18n)
+TALER_MERCHANTDB_update_category (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&category_id),
GNUNET_PQ_query_param_string (category_name),
TALER_PQ_query_param_json (category_name_i18n),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_category",
- "UPDATE merchant_categories SET"
- " category_name=$3"
- ",category_name_i18n=$4::TEXT::JSONB"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND category_serial=$2");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_categories SET"
+ " category_name=$2"
+ ",category_name_i18n=$3::TEXT::JSONB"
+ " WHERE category_serial=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_category",
+ "",
params);
}
diff --git a/src/backenddb/update_contract_session.c b/src/backenddb/update_contract_session.c
@@ -19,23 +19,21 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_contract_session.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_contract_session (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const char *session_id,
- char **fulfillment_url,
- bool *refunded)
+TALER_MERCHANTDB_update_contract_session (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const char *session_id,
+ char **fulfillment_url,
+ bool *refunded)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_string (session_id),
GNUNET_PQ_query_param_end
@@ -52,6 +50,9 @@ TALER_MERCHANTDB_update_contract_session (struct TALER_MERCHANTDB_PostgresContex
/* Session ID must always be given by the caller. */
GNUNET_assert (NULL != session_id);
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
/* no preflight check here, run in transaction by caller! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -59,24 +60,19 @@ TALER_MERCHANTDB_update_contract_session (struct TALER_MERCHANTDB_PostgresContex
GNUNET_h2s (&h_contract_terms->hash),
instance_id,
session_id);
- PREPARE (pg,
- "update_contract_session",
- "UPDATE merchant_contract_terms mct SET"
- " session_id=$3"
- " WHERE h_contract_terms=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " RETURNING"
- " contract_terms->>'fulfillment_url' AS fulfillment_url"
- " ,EXISTS (SELECT 1"
- " FROM merchant_refunds mr"
- " WHERE order_serial=mct.order_serial"
- " ) AS refunded");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_contract_terms mct SET"
+ " session_id=$2"
+ " WHERE h_contract_terms=$1"
+ " RETURNING"
+ " contract_terms->>'fulfillment_url' AS fulfillment_url"
+ " ,EXISTS (SELECT 1"
+ " FROM merchant_refunds mr"
+ " WHERE order_serial=mct.order_serial"
+ " ) AS refunded");
*fulfillment_url = NULL;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "update_contract_session",
+ "",
params,
rs);
}
diff --git a/src/backenddb/update_contract_terms.c b/src/backenddb/update_contract_terms.c
@@ -19,17 +19,17 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_contract_terms.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_contract_terms (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *order_id,
- json_t *contract_terms)
+TALER_MERCHANTDB_update_contract_terms (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms)
{
struct GNUNET_TIME_Timestamp pay_deadline;
struct GNUNET_TIME_Timestamp refund_deadline;
@@ -71,10 +71,12 @@ TALER_MERCHANTDB_update_contract_terms (struct TALER_MERCHANTDB_PostgresContext
}
}
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
TALER_PQ_query_param_json (contract_terms),
GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
@@ -85,21 +87,17 @@ TALER_MERCHANTDB_update_contract_terms (struct TALER_MERCHANTDB_PostgresContext
: GNUNET_PQ_query_param_string (fulfillment_url),
GNUNET_PQ_query_param_end
};
- PREPARE (pg,
- "update_contract_terms",
- "UPDATE merchant_contract_terms SET"
- " contract_terms=$3::TEXT::JSONB"
- ",h_contract_terms=$4"
- ",pay_deadline=$5"
- ",refund_deadline=$6"
- ",fulfillment_url=$7"
- " WHERE order_id=$2"
- " AND merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_contract_terms SET"
+ " contract_terms=$2::TEXT::JSONB"
+ ",h_contract_terms=$3"
+ ",pay_deadline=$4"
+ ",refund_deadline=$5"
+ ",fulfillment_url=$6"
+ " WHERE order_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_contract_terms",
+ "",
params);
}
}
diff --git a/src/backenddb/update_deposit_confirmation_status.c b/src/backenddb/update_deposit_confirmation_status.c
@@ -19,21 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_deposit_confirmation_status.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_deposit_confirmation_status (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t deposit_serial,
- bool retry_needed,
- struct GNUNET_TIME_Timestamp retry_time,
- uint32_t last_http_status,
- enum TALER_ErrorCode last_ec,
- const char *last_detail)
+TALER_MERCHANTDB_update_deposit_confirmation_status (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t deposit_serial,
+ bool retry_needed,
+ struct GNUNET_TIME_Timestamp retry_time,
+ uint32_t last_http_status,
+ enum TALER_ErrorCode last_ec,
+ const char *last_detail)
{
uint32_t ec32 = (uint32_t) last_ec;
struct GNUNET_PQ_QueryParam params[] = {
@@ -48,18 +47,18 @@ TALER_MERCHANTDB_update_deposit_confirmation_status (struct TALER_MERCHANTDB_Pos
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "update_deposit_confirmation_status",
- "UPDATE merchant_deposits SET"
- " settlement_retry_needed=$2"
- ",settlement_retry_time=$3"
- ",settlement_last_http_status=$4"
- ",settlement_last_ec=$5"
- ",settlement_last_detail=$6"
- " WHERE deposit_serial=$1;");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_deposits SET"
+ " settlement_retry_needed=$2"
+ ",settlement_retry_time=$3"
+ ",settlement_last_http_status=$4"
+ ",settlement_last_ec=$5"
+ ",settlement_last_detail=$6"
+ " WHERE deposit_serial=$1;");
return GNUNET_PQ_eval_prepared_non_select (
pg->conn,
- "update_deposit_confirmation_status",
+ "",
params);
}
diff --git a/src/backenddb/update_donau_instance.c b/src/backenddb/update_donau_instance.c
@@ -20,8 +20,6 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "donau/donau_service.h"
#include "merchant-database/update_donau_instance.h"
@@ -29,15 +27,15 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_donau_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *donau_url,
- const struct DONAU_Charity *charity,
- uint64_t charity_id)
+TALER_MERCHANTDB_update_donau_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ uint64_t charity_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (donau_url),
GNUNET_PQ_query_param_string (charity->name),
- GNUNET_PQ_query_param_auto_from_type (&charity->charity_pub),
GNUNET_PQ_query_param_uint64 (&charity_id),
TALER_PQ_query_param_amount_with_currency (pg->conn,
&charity->max_per_year),
@@ -47,23 +45,24 @@ TALER_MERCHANTDB_update_donau_instance (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_static_assert (sizeof (charity->charity_pub) ==
+ sizeof (pg->current_merchant_pub));
+ GNUNET_assert (0 ==
+ memcmp (&charity->charity_pub,
+ &pg->current_merchant_pub,
+ sizeof (charity->charity_pub)));
check_connection (pg);
- PREPARE (pg,
- "update_existing_donau_instance",
- "UPDATE merchant_donau_instances SET"
- " charity_name = $2,"
- " charity_max_per_year = $5,"
- " charity_receipts_to_date = $6,"
- " current_year = $7"
- " WHERE charity_id = $4"
- " AND merchant_instance_serial "
- " = (SELECT merchant_serial"
- " FROM merchant_instances mi"
- " WHERE mi.merchant_pub = $3)"
- " AND donau_url = $1;");
-
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_donau_instances SET"
+ " charity_name = $2,"
+ " charity_max_per_year = $4,"
+ " charity_receipts_to_date = $5,"
+ " current_year = $6"
+ " WHERE charity_id = $3"
+ " AND donau_url = $1;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_existing_donau_instance",
+ "",
params);
}
diff --git a/src/backenddb/update_donau_instance_receipts_amount.c b/src/backenddb/update_donau_instance_receipts_amount.c
@@ -21,17 +21,16 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_donau_instance_receipts_amount.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_donau_instance_receipts_amount (struct TALER_MERCHANTDB_PostgresContext *pg
- ,
- uint64_t *donau_instances_serial,
- const struct TALER_Amount *new_amount)
+TALER_MERCHANTDB_update_donau_instance_receipts_amount (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t *donau_instances_serial,
+ const struct TALER_Amount *new_amount)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (donau_instances_serial),
@@ -40,15 +39,12 @@ TALER_MERCHANTDB_update_donau_instance_receipts_amount (struct TALER_MERCHANTDB_
GNUNET_PQ_query_param_end
};
- check_connection (pg);
-
- PREPARE (pg,
- "update_donau_instance_receipts",
- "UPDATE merchant_donau_instances "
- "SET charity_receipts_to_date = $2 "
- "WHERE donau_instances_serial = $1;");
-
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_donau_instances "
+ "SET charity_receipts_to_date = $2 "
+ "WHERE donau_instances_serial = $1;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_donau_instance_receipts",
+ "",
params);
}
diff --git a/src/backenddb/update_instance.c b/src/backenddb/update_instance.c
@@ -20,16 +20,15 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_instance.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct TALER_MERCHANTDB_InstanceSettings *is)
+TALER_MERCHANTDB_update_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct TALER_MERCHANTDB_InstanceSettings *is)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (is->id),
@@ -66,7 +65,7 @@ TALER_MERCHANTDB_update_instance (struct TALER_MERCHANTDB_PostgresContext *pg,
check_connection (pg);
PREPARE (pg,
"update_instance",
- "UPDATE merchant_instances SET"
+ "UPDATE merchant.merchant_instances SET"
" merchant_name=$2"
",address=$3::TEXT::JSONB"
",jurisdiction=$4::TEXT::JSONB"
diff --git a/src/backenddb/update_instance_auth.c b/src/backenddb/update_instance_auth.c
@@ -19,16 +19,16 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_instance_auth.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_instance_auth (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *merchant_id,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *is)
+TALER_MERCHANTDB_update_instance_auth (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *merchant_id,
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *is)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
@@ -40,7 +40,7 @@ TALER_MERCHANTDB_update_instance_auth (struct TALER_MERCHANTDB_PostgresContext *
check_connection (pg);
PREPARE (pg,
"update_instance_auth",
- "UPDATE merchant_instances SET"
+ "UPDATE merchant.merchant_instances SET"
" auth_hash=$2"
",auth_salt=$3"
" WHERE merchant_id=$1");
diff --git a/src/backenddb/update_mfa_challenge.c b/src/backenddb/update_mfa_challenge.c
@@ -19,20 +19,19 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_mfa_challenge.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_mfa_challenge (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t challenge_id,
- const char *code,
- uint32_t retry_counter,
- struct GNUNET_TIME_Absolute expiration_date,
- struct GNUNET_TIME_Absolute retransmission_date)
+TALER_MERCHANTDB_update_mfa_challenge (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t challenge_id,
+ const char *code,
+ uint32_t retry_counter,
+ struct GNUNET_TIME_Absolute expiration_date,
+ struct GNUNET_TIME_Absolute retransmission_date)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&challenge_id),
@@ -45,7 +44,7 @@ TALER_MERCHANTDB_update_mfa_challenge (struct TALER_MERCHANTDB_PostgresContext *
PREPARE (pg,
"update_mfa_challenge",
- "UPDATE tan_challenges"
+ "UPDATE merchant.tan_challenges"
" SET"
" code=$2"
" ,retry_counter=$3"
diff --git a/src/backenddb/update_money_pot.c b/src/backenddb/update_money_pot.c
@@ -19,28 +19,26 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_money_pot.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t money_pot_id,
- const char *name,
- const char *description,
- 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)
+TALER_MERCHANTDB_update_money_pot (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t money_pot_id,
+ const char *name,
+ const char *description,
+ 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)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&money_pot_id),
GNUNET_PQ_query_param_string (name),
GNUNET_PQ_query_param_string (description),
@@ -68,17 +66,19 @@ TALER_MERCHANTDB_update_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_money_pot",
- "SELECT"
- " out_conflict_name AS conflict_name"
- ",out_conflict_total AS conflict_total"
- ",out_not_found AS not_found"
- " FROM merchant_do_update_money_pot"
- "($1,$2,$3,$4,$5,$6);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_conflict_name AS conflict_name"
+ ",out_conflict_total AS conflict_total"
+ ",out_not_found AS not_found"
+ " FROM merchant_do_update_money_pot"
+ "($1,$2,$3,$4,$5);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "update_money_pot",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
diff --git a/src/backenddb/update_money_pot.sql b/src/backenddb/update_money_pot.sql
@@ -0,0 +1,63 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_update_money_pot;
+CREATE FUNCTION merchant_do_update_money_pot (
+ IN in_money_pot_serial INT8,
+ IN in_name TEXT,
+ IN in_description TEXT,
+ IN in_old_totals merchant.taler_amount_currency[], -- can be NULL!
+ IN in_new_totals merchant.taler_amount_currency[], -- can be NULL!
+ OUT out_conflict_total BOOL,
+ OUT out_conflict_name BOOL,
+ OUT out_not_found BOOL)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+BEGIN
+ UPDATE merchant_money_pots SET
+ money_pot_name=in_name
+ ,money_pot_description=in_description
+ ,pot_totals=COALESCE(in_new_totals, pot_totals)
+ WHERE money_pot_serial=in_money_pot_serial
+ AND ( (in_old_totals IS NULL) OR (pot_totals=in_old_totals) );
+ IF NOT FOUND
+ THEN
+ -- Check if pot_total was the problem
+ PERFORM FROM merchant_money_pots
+ WHERE money_pot_serial=in_money_pot_serial;
+ out_conflict_total = FOUND;
+ out_not_found = NOT FOUND;
+ out_conflict_name = FALSE;
+ ELSE
+ out_conflict_total = FALSE;
+ out_not_found = FALSE;
+ out_conflict_name = FALSE;
+ END IF;
+ RETURN;
+EXCEPTION
+ -- money_pot_name already used
+ WHEN unique_violation
+ THEN
+ out_conflict_name = TRUE;
+ out_conflict_total = FALSE;
+ out_not_found = FALSE;
+ RETURN;
+END;
+
+END $$;
diff --git a/src/backenddb/update_otp.c b/src/backenddb/update_otp.c
@@ -19,22 +19,20 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_otp.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_otp (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *otp_id,
- const struct TALER_MERCHANTDB_OtpDeviceDetails *td)
+TALER_MERCHANTDB_update_otp (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td)
{
uint32_t pos32 = (uint32_t) td->otp_algorithm;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (otp_id),
GNUNET_PQ_query_param_string (td->otp_description),
GNUNET_PQ_query_param_uint32 (&pos32),
@@ -45,20 +43,18 @@ TALER_MERCHANTDB_update_otp (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_otp",
- "UPDATE merchant_otp_devices SET"
- " otp_description=$3"
- ",otp_algorithm=$4"
- ",otp_ctr=$5"
- ",otp_key=COALESCE($6,otp_key)"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND otp_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_otp_devices SET"
+ " otp_description=$2"
+ ",otp_algorithm=$3"
+ ",otp_ctr=$4"
+ ",otp_key=COALESCE($5,otp_key)"
+ " WHERE otp_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_otp",
+ "",
params);
}
diff --git a/src/backenddb/update_pending_webhook.c b/src/backenddb/update_pending_webhook.c
@@ -19,16 +19,16 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_pending_webhook.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_pending_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- uint64_t webhook_pending_serial,
- struct GNUNET_TIME_Absolute next_attempt)
+TALER_MERCHANTDB_update_pending_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ uint64_t webhook_pending_serial,
+ struct GNUNET_TIME_Absolute next_attempt)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&webhook_pending_serial),
@@ -39,11 +39,10 @@ TALER_MERCHANTDB_update_pending_webhook (struct TALER_MERCHANTDB_PostgresContext
check_connection (pg);
PREPARE (pg,
"update_pending_webhook",
- "UPDATE merchant_pending_webhooks SET"
+ "UPDATE merchant.merchant_pending_webhooks SET"
" retries=retries+1"
",next_attempt=$2"
" WHERE webhook_pending_serial=$1");
-
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_pending_webhook",
params);
diff --git a/src/backenddb/update_product.c b/src/backenddb/update_product.c
@@ -20,67 +20,62 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_product.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_product (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t num_cats,
- const uint64_t *cats,
- bool *no_instance,
- ssize_t *no_cat,
- bool *no_product,
- bool *lost_reduced,
- bool *sold_reduced,
- bool *stocked_reduced,
- bool *no_group,
- bool *no_pot)
+TALER_MERCHANTDB_update_product (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ ssize_t *no_cat,
+ bool *no_product,
+ bool *lost_reduced,
+ bool *sold_reduced,
+ bool *stocked_reduced,
+ bool *no_group,
+ bool *no_pot)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id), /* $1 */
- GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_string (product_id), /* $1 */
GNUNET_PQ_query_param_string (pd->description),
- TALER_PQ_query_param_json (pd->description_i18n), /* $4 */
+ TALER_PQ_query_param_json (pd->description_i18n), /* $3 */
GNUNET_PQ_query_param_string (pd->unit),
- GNUNET_PQ_query_param_string (pd->image), /* $6 */
- TALER_PQ_query_param_json (pd->taxes), /* $7 */
+ GNUNET_PQ_query_param_string (pd->image), /* $5 */
+ TALER_PQ_query_param_json (pd->taxes), /* $6 */
TALER_PQ_query_param_array_amount_with_currency (
pd->price_array_length,
pd->price_array,
- pg->conn), /* $8 */
- GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $9 */
- GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $10 */
- GNUNET_PQ_query_param_bool (pd->allow_fractional_quantity), /* $11 */
+ pg->conn), /* $7 */
+ GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $8 */
+ GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $9 */
+ GNUNET_PQ_query_param_bool (pd->allow_fractional_quantity), /* $10 */
GNUNET_PQ_query_param_uint32 (&pd->fractional_precision_level),
GNUNET_PQ_query_param_uint64 (&pd->total_lost),
- TALER_PQ_query_param_json (pd->address), /* $14 */
+ TALER_PQ_query_param_json (pd->address), /* $13 */
GNUNET_PQ_query_param_timestamp (&pd->next_restock),
GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
GNUNET_PQ_query_param_array_uint64 (num_cats,
cats,
- pg->conn), /* $17 */
- GNUNET_PQ_query_param_string (pd->product_name), /* $18 */
+ pg->conn), /* $16 */
+ GNUNET_PQ_query_param_string (pd->product_name), /* $17 */
(0 == pd->product_group_id)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_uint64 (&pd->product_group_id),
(0 == pd->money_pot_id)
? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $20 */
+ : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $19 */
GNUNET_PQ_query_param_bool (pd->price_is_net),
GNUNET_PQ_query_param_end
};
uint64_t ncat;
bool cats_found = true;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("no_instance",
- no_instance),
GNUNET_PQ_result_spec_bool ("no_product",
no_product),
GNUNET_PQ_result_spec_bool ("lost_reduced",
@@ -108,24 +103,25 @@ TALER_MERCHANTDB_update_product (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_product",
- "SELECT"
- " out_lost_reduced AS lost_reduced"
- ",out_sold_reduced AS sold_reduced"
- ",out_stocked_reduced AS stocked_reduced"
- ",out_no_product AS no_product"
- ",out_no_cat AS no_cat"
- ",out_no_instance AS no_instance"
- ",out_no_group AS no_group"
- ",out_no_pot AS no_pot"
- " FROM merchant_do_update_product"
- "($1,$2,$3,$4::TEXT::JSONB,$5,$6,$7::TEXT::JSONB,$8,$9"
- ",$10,$11,$12,$13,$14::TEXT::JSONB,$15,$16,$17,$18"
- ",$19,$20,$21);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_lost_reduced AS lost_reduced"
+ ",out_sold_reduced AS sold_reduced"
+ ",out_stocked_reduced AS stocked_reduced"
+ ",out_no_product AS no_product"
+ ",out_no_cat AS no_cat"
+ ",out_no_group AS no_group"
+ ",out_no_pot AS no_pot"
+ " FROM merchant_do_update_product"
+ "($1,$2,$3::TEXT::JSONB,$4,$5,$6::TEXT::JSONB,$7,$8"
+ ",$9,$10,$11,$12,$13::TEXT::JSONB,$14,$15,$16,$17"
+ ",$18,$19,$20);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "update_product",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
diff --git a/src/backenddb/update_product.sql b/src/backenddb/update_product.sql
@@ -0,0 +1,179 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024, 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+DROP FUNCTION IF EXISTS merchant_do_update_product;
+CREATE FUNCTION merchant_do_update_product (
+ IN in_product_id TEXT,
+ IN in_description TEXT,
+ IN in_description_i18n JSONB, -- $3
+ IN in_unit TEXT,
+ IN in_image TEXT,
+ IN in_taxes JSONB, -- $6
+ IN ina_price_list merchant.taler_amount_currency[],
+ IN in_total_stock INT8,
+ IN in_total_stock_frac INT4,
+ IN in_allow_fractional_quantity BOOL,
+ IN in_fractional_precision_level INT4,
+ IN in_total_lost INT8, -- NOTE: not in insert_product
+ IN in_address JSONB, -- $13
+ IN in_next_restock INT8,
+ IN in_minimum_age INT4,
+ IN ina_categories INT8[], -- $16
+ IN in_product_name TEXT,
+ IN in_product_group_id INT8, -- NULL for default
+ IN in_money_pot_id INT8, -- NULL for none
+ IN in_price_is_net BOOL, -- $20
+ OUT out_no_product BOOL,
+ OUT out_lost_reduced BOOL,
+ OUT out_sold_reduced BOOL,
+ OUT out_stocked_reduced BOOL,
+ OUT out_no_cat INT8,
+ OUT out_no_group BOOL,
+ OUT out_no_pot BOOL)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_product_serial INT8;
+ i INT8;
+ ini_cat INT8;
+ rec RECORD;
+BEGIN
+
+out_no_group = FALSE;
+out_no_pot = FALSE;
+out_no_product=FALSE;
+out_lost_reduced=FALSE;
+out_sold_reduced=FALSE; -- We currently don't allow updating 'sold', hence always FALSE
+out_stocked_reduced=FALSE;
+out_no_cat=NULL;
+
+IF in_product_group_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_product_groups
+ WHERE product_group_serial=in_product_group_id;
+ IF NOT FOUND
+ THEN
+ out_no_group=TRUE;
+ RETURN;
+ END IF;
+END IF;
+
+IF in_money_pot_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_money_pots
+ WHERE money_pot_serial=in_money_pot_id;
+ IF NOT FOUND
+ THEN
+ out_no_pot=TRUE;
+ RETURN;
+ END IF;
+END IF;
+
+-- Check existing entry satisfies constraints
+SELECT total_stock
+ ,total_stock_frac
+ ,total_lost
+ ,allow_fractional_quantity
+ ,product_serial
+ INTO rec
+ FROM merchant_inventory
+ WHERE product_id=in_product_id;
+
+IF NOT FOUND
+THEN
+ out_no_product=TRUE;
+ RETURN;
+END IF;
+
+my_product_serial = rec.product_serial;
+
+IF rec.total_stock > in_total_stock
+THEN
+ out_stocked_reduced=TRUE;
+ RETURN;
+END IF;
+
+IF rec.total_lost > in_total_lost
+THEN
+ out_lost_reduced=TRUE;
+ RETURN;
+END IF;
+IF rec.allow_fractional_quantity
+ AND (NOT in_allow_fractional_quantity)
+THEN
+ DELETE
+ FROM merchant_inventory_locks
+ WHERE product_serial = my_product_serial
+ AND total_locked_frac <> 0;
+END IF;
+
+-- Remove old categories
+DELETE FROM merchant_product_categories
+ WHERE product_serial=my_product_serial;
+
+-- Add new categories
+FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
+LOOP
+ ini_cat=ina_categories[i];
+
+ INSERT INTO merchant_product_categories
+ (product_serial
+ ,category_serial)
+ VALUES
+ (my_product_serial
+ ,ini_cat)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ out_no_cat=i;
+ RETURN;
+ END IF;
+END LOOP;
+
+UPDATE merchant_inventory SET
+ description=in_description
+ ,description_i18n=in_description_i18n
+ ,product_name=in_product_name
+ ,unit=in_unit
+ ,image=in_image
+ ,image_hash=CASE
+ WHEN (in_image IS NULL) OR (in_image = '')
+ THEN NULL
+ ELSE encode(public.digest(convert_to(in_image, 'UTF8'),
+ 'sha256'),
+ 'hex')
+ END
+ ,taxes=in_taxes
+ ,price_array=ina_price_list
+ ,total_stock=in_total_stock
+ ,total_stock_frac=in_total_stock_frac
+ ,allow_fractional_quantity=in_allow_fractional_quantity
+ ,fractional_precision_level=in_fractional_precision_level
+ ,total_lost=in_total_lost
+ ,address=in_address
+ ,next_restock=in_next_restock
+ ,minimum_age=in_minimum_age
+ ,product_group_serial=in_product_group_id
+ ,money_pot_serial=in_money_pot_id
+ ,price_is_net=in_price_is_net
+ WHERE product_serial=my_product_serial; -- could also match on product_id
+
+ASSERT FOUND,'SELECTED it earlier, should UPDATE it now';
+
+-- Success!
+END $$;
diff --git a/src/backenddb/update_product_group.c b/src/backenddb/update_product_group.c
@@ -19,8 +19,6 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_product_group.h"
#include "helper.h"
@@ -36,7 +34,6 @@ TALER_MERCHANTDB_update_product_group (
bool *conflict)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&product_group_id),
GNUNET_PQ_query_param_string (name),
GNUNET_PQ_query_param_string (description),
@@ -52,17 +49,19 @@ TALER_MERCHANTDB_update_product_group (
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_product_group",
- "SELECT"
- " out_conflict AS conflict"
- ",out_not_found AS not_found"
- " FROM merchant_do_update_product_group"
- "($1,$2,$3,$4);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_conflict AS conflict"
+ ",out_not_found AS not_found"
+ " FROM merchant_do_update_product_group"
+ "($1,$2,$3);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "update_product_group",
+ "",
params,
rs);
if (qs <= 0)
diff --git a/src/backenddb/update_product_group.sql b/src/backenddb/update_product_group.sql
@@ -0,0 +1,44 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+DROP FUNCTION IF EXISTS merchant_do_update_product_group;
+CREATE FUNCTION merchant_do_update_product_group (
+ IN in_product_group_serial INT8,
+ IN in_name TEXT,
+ IN in_description TEXT,
+ OUT out_conflict BOOL,
+ OUT out_not_found BOOL)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+BEGIN
+ UPDATE merchant_product_groups SET
+ product_group_name=in_name
+ ,product_group_description=in_description
+ WHERE product_group_serial=in_product_group_serial;
+ out_not_found = NOT FOUND;
+ out_conflict = FALSE;
+ RETURN;
+EXCEPTION
+ WHEN unique_violation
+ THEN
+ out_conflict = TRUE;
+ RETURN;
+END;
+
+
+END $$;
diff --git a/src/backenddb/update_report.c b/src/backenddb/update_report.c
@@ -19,28 +19,26 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_report.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_report (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t report_id,
- const char *report_program_section,
- const char *report_description,
- const char *mime_type,
- const char *data_source,
- const char *target_address,
- struct GNUNET_TIME_Relative frequency,
- struct GNUNET_TIME_Relative frequency_shift)
+TALER_MERCHANTDB_update_report (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t report_id,
+ const char *report_program_section,
+ const char *report_description,
+ const char *mime_type,
+ const char *data_source,
+ const char *target_address,
+ struct GNUNET_TIME_Relative frequency,
+ struct GNUNET_TIME_Relative frequency_shift)
{
struct GNUNET_TIME_Timestamp start;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&report_id),
GNUNET_PQ_query_param_string (report_program_section),
GNUNET_PQ_query_param_string (report_description),
@@ -53,6 +51,9 @@ TALER_MERCHANTDB_update_report (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
start =
GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_absolute_add (
@@ -62,23 +63,18 @@ TALER_MERCHANTDB_update_report (struct TALER_MERCHANTDB_PostgresContext *pg,
frequency_shift)));
check_connection (pg);
- PREPARE (pg,
- "update_report",
- "UPDATE merchant_reports SET"
- " report_program_section=$3"
- " ,report_description=$4"
- " ,mime_type=$5"
- " ,data_source=$6"
- " ,target_address=$7"
- " ,frequency=$8"
- " ,frequency_shift=$9"
- " ,next_transmission=$10"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND report_serial=$2;");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_reports SET"
+ " report_program_section=$2"
+ " ,report_description=$3"
+ " ,mime_type=$4"
+ " ,data_source=$5"
+ " ,target_address=$6"
+ " ,frequency=$7"
+ " ,frequency_shift=$8"
+ " ,next_transmission=$9"
+ " WHERE report_serial=$1;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_report",
+ "",
params);
}
diff --git a/src/backenddb/update_report_status.c b/src/backenddb/update_report_status.c
@@ -19,24 +19,22 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_report_status.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_report_status (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- uint64_t report_id,
- struct GNUNET_TIME_Timestamp next_transmission,
- enum TALER_ErrorCode last_error_code,
- const char *last_error_detail)
+TALER_MERCHANTDB_update_report_status (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t report_id,
+ struct GNUNET_TIME_Timestamp next_transmission,
+ enum TALER_ErrorCode last_error_code,
+ const char *last_error_detail)
{
uint32_t ec = (uint32_t) last_error_code;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&report_id),
GNUNET_PQ_query_param_timestamp (&next_transmission),
TALER_EC_NONE == ec
@@ -48,19 +46,17 @@ TALER_MERCHANTDB_update_report_status (struct TALER_MERCHANTDB_PostgresContext *
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_report_status",
- "UPDATE merchant_reports SET"
- " next_transmission=$3"
- ",last_error_code=$4"
- ",last_error_detail=$5"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND report_serial=$2;");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_reports SET"
+ " next_transmission=$2"
+ ",last_error_code=$3"
+ ",last_error_detail=$4"
+ " WHERE report_serial=$1;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_report_status",
+ "",
params);
}
diff --git a/src/backenddb/update_template.c b/src/backenddb/update_template.c
@@ -19,21 +19,19 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_template.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_template (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td)
+TALER_MERCHANTDB_update_template (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *template_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_string (td->template_description),
(NULL == td->otp_id)
@@ -47,31 +45,25 @@ TALER_MERCHANTDB_update_template (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_template",
- "WITH mid AS ("
- " SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- ",otp AS ("
- " SELECT otp_serial"
- " FROM merchant_otp_devices"
- " JOIN mid USING (merchant_serial)"
- " WHERE otp_id=$4)"
- "UPDATE merchant_template SET"
- " template_description=$3"
- ",otp_device_id="
- " COALESCE((SELECT otp_serial"
- " FROM otp), NULL)"
- ",template_contract=$5::TEXT::JSONB"
- ",editable_defaults=$6::TEXT::JSONB"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM mid)"
- " AND template_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "WITH otp AS ("
+ " SELECT otp_serial"
+ " FROM merchant_otp_devices"
+ " WHERE otp_id=$3)"
+ "UPDATE merchant_template SET"
+ " template_description=$2"
+ ",otp_device_id="
+ " COALESCE((SELECT otp_serial"
+ " FROM otp), NULL)"
+ ",template_contract=$4::TEXT::JSONB"
+ ",editable_defaults=$5::TEXT::JSONB"
+ " WHERE template_id=$1");
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_template",
+ "",
params);
GNUNET_PQ_cleanup_query_params_closures (params);
return qs;
diff --git a/src/backenddb/update_token_family.c b/src/backenddb/update_token_family.c
@@ -27,13 +27,13 @@
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_token_family (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *token_family_slug,
- const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
+TALER_MERCHANTDB_update_token_family (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *token_family_slug,
+ const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (token_family_slug),
GNUNET_PQ_query_param_string (details->name),
GNUNET_PQ_query_param_string (details->description),
@@ -46,22 +46,20 @@ TALER_MERCHANTDB_update_token_family (struct TALER_MERCHANTDB_PostgresContext *p
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_token_family",
- "UPDATE merchant_token_families SET"
- " name=$3"
- ",description=$4"
- ",description_i18n=$5::TEXT::JSONB"
- ",extra_data=$6::TEXT::JSONB"
- ",valid_after=$7"
- ",valid_before=$8"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND slug=$2");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_token_families SET"
+ " name=$2"
+ ",description=$3"
+ ",description_i18n=$4::TEXT::JSONB"
+ ",extra_data=$5::TEXT::JSONB"
+ ",valid_after=$6"
+ ",valid_before=$7"
+ " WHERE slug=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_token_family",
+ "",
params);
}
diff --git a/src/backenddb/update_transfer_status.c b/src/backenddb/update_transfer_status.c
@@ -19,22 +19,21 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_transfer_status.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_transfer_status (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct GNUNET_TIME_Absolute next_attempt,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const char *detail,
- bool needs_retry)
+TALER_MERCHANTDB_update_transfer_status (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Absolute next_attempt,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail,
+ bool needs_retry)
{
uint32_t hs32 = (uint32_t) http_status;
uint32_t ec32 = (uint32_t) ec;
@@ -51,19 +50,19 @@ TALER_MERCHANTDB_update_transfer_status (struct TALER_MERCHANTDB_PostgresContext
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
check_connection (pg);
- PREPARE (pg,
- "update_transfer_status",
- "UPDATE merchant_expected_transfers SET"
- " last_http_status=$3"
- ",last_ec=$4"
- ",last_detail=$5"
- ",retry_needed=$6"
- ",retry_time=$7"
- " WHERE wtid=$1"
- " AND exchange_url=$2");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_expected_transfers SET"
+ " last_http_status=$3"
+ ",last_ec=$4"
+ ",last_detail=$5"
+ ",retry_needed=$6"
+ ",retry_time=$7"
+ " WHERE wtid=$1"
+ " AND exchange_url=$2");
return GNUNET_PQ_eval_prepared_non_select (
pg->conn,
- "update_transfer_status",
+ "",
params);
}
diff --git a/src/backenddb/update_unit.c b/src/backenddb/update_unit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2025 Taler Systems SA
+ Copyright (C) 2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -19,30 +19,27 @@
* @author Bohdan Potuzhnyi
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_unit.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *unit_id,
- const char *unit_name_long,
- const json_t *unit_name_long_i18n,
- const char *unit_name_short,
- const json_t *unit_name_short_i18n,
- const bool *unit_allow_fraction,
- const uint32_t *unit_precision_level,
- const bool *unit_active,
- bool *no_instance,
- bool *no_unit,
- bool *builtin_conflict)
+TALER_MERCHANTDB_update_unit (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *unit_id,
+ const char *unit_name_long,
+ const json_t *unit_name_long_i18n,
+ const char *unit_name_short,
+ const json_t *unit_name_short_i18n,
+ const bool *unit_allow_fraction,
+ const uint32_t *unit_precision_level,
+ const bool *unit_active,
+ bool *no_unit,
+ bool *builtin_conflict)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (unit_id),
(NULL == unit_name_long)
? GNUNET_PQ_query_param_null ()
@@ -68,8 +65,6 @@ TALER_MERCHANTDB_update_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("out_no_instance",
- no_instance),
GNUNET_PQ_result_spec_bool ("out_no_unit",
no_unit),
GNUNET_PQ_result_spec_bool ("out_builtin_conflict",
@@ -78,17 +73,18 @@ TALER_MERCHANTDB_update_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
};
enum GNUNET_DB_QueryStatus qs;
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_unit",
- "SELECT"
- " out_no_instance"
- " ,out_no_unit"
- " ,out_builtin_conflict"
- " FROM merchant_do_update_unit"
- "($1,$2,$3,$4,$5,$6,$7,$8,$9);");
+ TMH_PQ_prepare_anon (pg,
+ "SELECT"
+ " out_no_unit"
+ " ,out_builtin_conflict"
+ " FROM merchant_do_update_unit"
+ "($1,$2,$3,$4,$5,$6,$7,$8);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "update_unit",
+ "",
params,
rs);
GNUNET_PQ_cleanup_query_params_closures (params);
diff --git a/src/backenddb/update_unit.sql b/src/backenddb/update_unit.sql
@@ -0,0 +1,172 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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 update_unit.sql
+-- @brief SQL for updating units
+-- @author Bohdan Potuzhnyi
+
+DROP FUNCTION IF EXISTS merchant_do_update_unit;
+CREATE FUNCTION merchant_do_update_unit (
+ IN in_unit_id TEXT,
+ IN in_unit_name_long TEXT,
+ IN in_unit_name_long_i18n BYTEA,
+ IN in_unit_name_short TEXT,
+ IN in_unit_name_short_i18n BYTEA,
+ IN in_unit_allow_fraction BOOL,
+ IN in_unit_precision_level INT4,
+ IN in_unit_active BOOL,
+ OUT out_no_unit BOOL,
+ OUT out_builtin_conflict BOOL)
+ LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_custom RECORD;
+ my_builtin RECORD;
+ my_override RECORD;
+ new_unit_name_long TEXT;
+ new_unit_name_short TEXT;
+ new_unit_name_long_i18n BYTEA;
+ new_unit_name_short_i18n BYTEA;
+ new_unit_allow_fraction BOOL;
+ new_unit_precision_level INT4;
+ new_unit_active BOOL;
+ old_unit_allow_fraction BOOL;
+ old_unit_precision_level INT4;
+ old_unit_active BOOL;
+BEGIN
+ out_no_unit := FALSE;
+ out_builtin_conflict := FALSE;
+
+ SELECT *
+ INTO my_custom
+ FROM merchant_custom_units
+ WHERE unit = in_unit_id
+ FOR UPDATE;
+
+ IF FOUND THEN
+ old_unit_allow_fraction := my_custom.unit_allow_fraction;
+ old_unit_precision_level := my_custom.unit_precision_level;
+ old_unit_active := my_custom.unit_active;
+
+ new_unit_name_long := COALESCE (in_unit_name_long, my_custom.unit_name_long);
+ new_unit_name_short := COALESCE (in_unit_name_short, my_custom.unit_name_short);
+ new_unit_name_long_i18n := COALESCE (in_unit_name_long_i18n,
+ my_custom.unit_name_long_i18n);
+ new_unit_name_short_i18n := COALESCE (in_unit_name_short_i18n,
+ my_custom.unit_name_short_i18n);
+ new_unit_allow_fraction := COALESCE (in_unit_allow_fraction,
+ my_custom.unit_allow_fraction);
+ new_unit_precision_level := COALESCE (in_unit_precision_level,
+ my_custom.unit_precision_level);
+ IF NOT new_unit_allow_fraction THEN
+ new_unit_precision_level := 0;
+ END IF;
+
+ new_unit_active := COALESCE (in_unit_active, my_custom.unit_active);
+
+ UPDATE merchant_custom_units SET
+ unit_name_long = new_unit_name_long
+ ,unit_name_long_i18n = new_unit_name_long_i18n
+ ,unit_name_short = new_unit_name_short
+ ,unit_name_short_i18n = new_unit_name_short_i18n
+ ,unit_allow_fraction = new_unit_allow_fraction
+ ,unit_precision_level = new_unit_precision_level
+ ,unit_active = new_unit_active
+ WHERE unit_serial = my_custom.unit_serial;
+
+ ASSERT FOUND,'SELECTED it earlier, should UPDATE it now';
+
+ IF old_unit_allow_fraction IS DISTINCT FROM new_unit_allow_fraction
+ OR old_unit_precision_level IS DISTINCT FROM new_unit_precision_level
+ THEN
+ UPDATE merchant_inventory SET
+ allow_fractional_quantity = new_unit_allow_fraction
+ , fractional_precision_level = new_unit_precision_level
+ WHERE unit = in_unit_id
+ AND allow_fractional_quantity = old_unit_allow_fraction
+ AND fractional_precision_level = old_unit_precision_level;
+ END IF;
+ RETURN;
+ END IF;
+
+ -- Try builtin with overrides.
+ SELECT *
+ INTO my_builtin
+ FROM merchant.merchant_builtin_units
+ WHERE unit = in_unit_id;
+
+ IF NOT FOUND THEN
+ out_no_unit := TRUE;
+ RETURN;
+ END IF;
+
+ SELECT *
+ INTO my_override
+ FROM merchant_builtin_unit_overrides
+ WHERE builtin_unit_serial = my_builtin.unit_serial
+ FOR UPDATE;
+
+ old_unit_allow_fraction := COALESCE (my_override.override_allow_fraction,
+ my_builtin.unit_allow_fraction);
+ old_unit_precision_level := COALESCE (my_override.override_precision_level,
+ my_builtin.unit_precision_level);
+ old_unit_active := COALESCE (my_override.override_active,
+ my_builtin.unit_active);
+
+ -- Only policy flags can change for builtin units.
+ IF in_unit_name_long IS NOT NULL
+ OR in_unit_name_short IS NOT NULL
+ OR in_unit_name_long_i18n IS NOT NULL
+ OR in_unit_name_short_i18n IS NOT NULL THEN
+ out_builtin_conflict := TRUE;
+ RETURN;
+ END IF;
+
+ new_unit_allow_fraction := COALESCE (in_unit_allow_fraction,
+ old_unit_allow_fraction);
+ new_unit_precision_level := COALESCE (in_unit_precision_level,
+ old_unit_precision_level);
+ IF NOT new_unit_allow_fraction THEN
+ new_unit_precision_level := 0;
+ END IF;
+ new_unit_active := COALESCE (in_unit_active, old_unit_active);
+
+ INSERT INTO merchant_builtin_unit_overrides (
+ builtin_unit_serial,
+ override_allow_fraction,
+ override_precision_level,
+ override_active)
+ VALUES (my_builtin.unit_serial,
+ new_unit_allow_fraction,
+ new_unit_precision_level,
+ new_unit_active)
+ ON CONFLICT (merchant_serial, builtin_unit_serial)
+ DO UPDATE SET override_allow_fraction = EXCLUDED.override_allow_fraction
+ , override_precision_level = EXCLUDED.override_precision_level
+ , override_active = EXCLUDED.override_active;
+
+ IF old_unit_allow_fraction IS DISTINCT FROM new_unit_allow_fraction
+ OR old_unit_precision_level IS DISTINCT FROM new_unit_precision_level
+ THEN
+ UPDATE merchant_inventory SET
+ allow_fractional_quantity = new_unit_allow_fraction
+ , fractional_precision_level = new_unit_precision_level
+ WHERE unit = in_unit_id
+ AND allow_fractional_quantity = old_unit_allow_fraction
+ AND fractional_precision_level = old_unit_precision_level;
+ END IF;
+
+ RETURN;
+END $$;
diff --git a/src/backenddb/update_webhook.c b/src/backenddb/update_webhook.c
@@ -19,20 +19,19 @@
* @author Iván Ávalos
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_webhook.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *webhook_id,
- const struct TALER_MERCHANTDB_WebhookDetails *wb)
+TALER_MERCHANTDB_update_webhook (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_string (wb->event_type),
GNUNET_PQ_query_param_string (wb->url),
@@ -46,22 +45,20 @@ TALER_MERCHANTDB_update_webhook (struct TALER_MERCHANTDB_PostgresContext *pg,
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance_id,
+ pg->current_merchant_id));
check_connection (pg);
- PREPARE (pg,
- "update_webhook",
- "UPDATE merchant_webhook SET"
- " event_type=$3"
- ",url=$4"
- ",http_method=$5"
- ",header_template=$6"
- ",body_template=$7"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND webhook_id=$2");
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_webhook SET"
+ " event_type=$2"
+ ",url=$3"
+ ",http_method=$4"
+ ",header_template=$5"
+ ",body_template=$6"
+ " WHERE webhook_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_webhook",
+ "",
params);
}
diff --git a/src/backenddb/update_wirewatch_progress.c b/src/backenddb/update_wirewatch_progress.c
@@ -19,38 +19,34 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "merchant-database/update_wirewatch_progress.h"
#include "helper.h"
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_wirewatch_progress (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance,
- struct TALER_FullPayto payto_uri,
- uint64_t last_serial)
+TALER_MERCHANTDB_update_wirewatch_progress (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance,
+ struct TALER_FullPayto payto_uri,
+ uint64_t last_serial)
{
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance),
GNUNET_PQ_query_param_string (payto_uri.full_payto),
GNUNET_PQ_query_param_uint64 (&last_serial),
GNUNET_PQ_query_param_end
};
- PREPARE (pg,
- "update_wirewatch_progress",
- "UPDATE merchant_accounts"
- " SET last_bank_serial=$3"
- " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
- " =REGEXP_REPLACE(CAST ($2 AS TEXT),'\\?.*','')"
- " AND merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)");
+ GNUNET_assert (NULL != pg->current_merchant_id);
+ GNUNET_assert (0 == strcmp (instance,
+ pg->current_merchant_id));
check_connection (pg);
+ TMH_PQ_prepare_anon (pg,
+ "UPDATE merchant_accounts"
+ " SET last_bank_serial=$2"
+ " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
+ " =REGEXP_REPLACE(CAST ($1 AS TEXT),'\\?.*','')");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_wirewatch_progress",
+ "",
params);
}
diff --git a/src/backenddb/upsert_donau_keys.c b/src/backenddb/upsert_donau_keys.c
@@ -20,17 +20,17 @@
* @author Vlada Svirsh
*/
#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "donau/donau_service.h"
#include "merchant-database/upsert_donau_keys.h"
#include "helper.h"
+
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_upsert_donau_keys (struct TALER_MERCHANTDB_PostgresContext *pg,
- const struct DONAU_Keys *keys,
- struct GNUNET_TIME_Absolute first_retry)
+TALER_MERCHANTDB_upsert_donau_keys (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const struct DONAU_Keys *keys,
+ struct GNUNET_TIME_Absolute first_retry)
{
enum GNUNET_DB_QueryStatus qs;
json_t *jkeys = DONAU_keys_to_json (keys);
@@ -47,6 +47,7 @@ TALER_MERCHANTDB_upsert_donau_keys (struct TALER_MERCHANTDB_PostgresContext *pg,
};
check_connection (pg);
+ // FIXME: combine these into a stored procedure
PREPARE (pg,
"insert_donau_keys",
"INSERT INTO merchant_donau_keys"
diff --git a/src/include/merchant-database/delete_unit.h b/src/include/merchant-database/delete_unit.h
@@ -24,14 +24,12 @@
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
/**
* Delete a measurement unit.
*
* @param pg database context
* @param instance_id instance to delete unit from
* @param unit_id symbolic identifier
- * @param[out] no_instance set to true if @a instance_id is unknown
* @param[out] no_unit set to true if the unit does not exist
* @param[out] builtin_conflict set to true if the unit cannot be deleted
* @return database result code
@@ -40,7 +38,6 @@ enum GNUNET_DB_QueryStatus
TALER_MERCHANTDB_delete_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
const char *instance_id,
const char *unit_id,
- bool *no_instance,
bool *no_unit,
bool *builtin_conflict);
diff --git a/src/include/merchant-database/insert_product.h b/src/include/merchant-database/insert_product.h
@@ -22,11 +22,9 @@
#define MERCHANT_DATABASE_INSERT_PRODUCT_H
#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
/**
* Insert details about a particular product.
*
@@ -36,7 +34,6 @@ struct TALER_MERCHANTDB_PostgresContext;
* @param pd the product details to insert
* @param num_cats length of @a cats array
* @param cats array of categories the product is in
- * @param[out] no_instance set to true if @a instance_id is unknown
* @param[out] conflict set to true if a conflicting
* product already exists in the database
* @param[out] no_cat set to index of non-existing category from @a cats, or -1 if all @a cats were found
@@ -45,17 +42,17 @@ struct TALER_MERCHANTDB_PostgresContext;
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_product (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t num_cats,
- const uint64_t *cats,
- bool *no_instance,
- bool *conflict,
- ssize_t *no_cat,
- bool *no_group,
- bool *no_pot);
+TALER_MERCHANTDB_insert_product (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *conflict,
+ ssize_t *no_cat,
+ bool *no_group,
+ bool *no_pot);
#endif
diff --git a/src/include/merchant-database/insert_transfer.h b/src/include/merchant-database/insert_transfer.h
@@ -22,11 +22,9 @@
#define MERCHANT_DATABASE_INSERT_TRANSFER_H
#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
/**
* Insert information about a wire transfer the merchant has received.
* Idempotent, will do nothing if the same wire transfer is already known.
@@ -47,16 +45,16 @@ struct TALER_MERCHANTDB_PostgresContext;
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_transfer (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *credit_amount,
- struct TALER_FullPayto payto_uri,
- uint64_t bank_serial_id,
- bool *no_instance,
- bool *no_account,
- bool *conflict);
+TALER_MERCHANTDB_insert_transfer (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *credit_amount,
+ struct TALER_FullPayto payto_uri,
+ uint64_t bank_serial_id,
+ bool *no_account,
+ bool *conflict);
#endif
diff --git a/src/include/merchant-database/insert_unclaim_signature.h b/src/include/merchant-database/insert_unclaim_signature.h
@@ -22,17 +22,14 @@
#define MERCHANT_DATABASE_INSERT_UNCLAIM_SIGNATURE_H
#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
/**
* Insert unclaim signature into DB and unclaim the order.
*
* @param pg database context
* @param instance_id instance of the operation (checked)
- * @param merchant_pub public key of the instance (for notifications)
* @param order_id identifies the order to unclaim
* @param nonce claim nonce of the original claim
* @param h_contract hash of the contract to unclaim
@@ -41,12 +38,12 @@ struct TALER_MERCHANTDB_PostgresContext;
* and @a h_contract was not found; 1 if the request is idempotent
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_unclaim_signature (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const char *order_id,
- const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
- const struct GNUNET_HashCode *h_contract,
- const struct GNUNET_CRYPTO_EddsaSignature *nsig);
+TALER_MERCHANTDB_insert_unclaim_signature (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *order_id,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
+ const struct GNUNET_HashCode *h_contract,
+ const struct GNUNET_CRYPTO_EddsaSignature *nsig);
#endif
diff --git a/src/include/merchant-database/insert_unit.h b/src/include/merchant-database/insert_unit.h
@@ -22,28 +22,25 @@
#define MERCHANT_DATABASE_INSERT_UNIT_H
#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
/**
* Insert a measurement unit definition.
*
* @param pg database context
* @param instance_id instance to insert unit for
* @param ud unit definition to store (unit_serial ignored)
- * @param[out] no_instance set to true if the instance is unknown
* @param[out] conflict set to true if a conflicting unit already exists
* @param[out] unit_serial set to the generated serial on success (or the existing serial for idempotent inserts)
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_insert_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const struct TALER_MERCHANTDB_UnitDetails *ud,
- bool *no_instance,
- bool *conflict,
- uint64_t *unit_serial);
+TALER_MERCHANTDB_insert_unit (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const struct TALER_MERCHANTDB_UnitDetails *ud,
+ bool *conflict,
+ uint64_t *unit_serial);
#endif
diff --git a/src/include/merchant-database/lookup_all_webhooks.h b/src/include/merchant-database/lookup_all_webhooks.h
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 src/include/merchant-database/lookup_all_webhooks.h
+ * @brief implementation of the lookup_all_webhooks function for Postgres
+ * @author Iván Ávalos
+ */
+#ifndef MERCHANT_DATABASE_LOOKUP_ALL_WEBHOOKS_H
+#define MERCHANT_DATABASE_LOOKUP_ALL_WEBHOOKS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "merchantdb_lib.h"
+
+
+/**
+ * Typically called by `lookup_all_webhooks`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param webhook_serial reference to the configured webhook template.
+ * @param next_attempt is the time we should make the next request to the webhook.
+ * @param retries how often have we tried this request to the webhook.
+ * @param url to make request to
+ * @param http_method use for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ */
+typedef void
+(*TALER_MERCHANTDB_AllWebhooksCallback)(
+ void *cls,
+ uint64_t webhook_serial,
+ struct GNUNET_TIME_Absolute
+ next_attempt,
+ uint32_t retries,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body);
+
+
+/**
+ * Lookup all the webhooks in the ALL webhook.
+ * Use by the administrator
+ *
+ * @param pg database context
+ * @param instance_id to lookup webhooks for this instance particularly
+ * @param min_row to see the list of the ALL webhook that it is started with this minimum row.
+ * @param max_results to see the list of the ALL webhook that it is end with this max results.
+ * @param cb ALL webhook callback
+ * @param cb_cls callback closure
+ */
+enum GNUNET_DB_QueryStatus
+TALER_MERCHANTDB_lookup_all_webhooks (struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_AllWebhooksCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/include/merchant-database/lookup_order_charity.h b/src/include/merchant-database/lookup_order_charity.h
@@ -42,7 +42,6 @@
* @param instance_id the instance ID of the merchant
* @param donau_url base URL of the Donau instance
* @param[out] charity_id set to the `charity_id` from `merchant_donau_instances`
- * @param[out] charity_priv set to the private key (32 bytes)
* @param[out] charity_max_per_year set to the maximum amount that can be donated
* per year to this charity
* @param[out] charity_receipts_to_date set to the total amount of receipts
@@ -56,7 +55,6 @@ TALER_MERCHANTDB_lookup_order_charity (
const char *instance_id,
const char *donau_url,
uint64_t *charity_id,
- struct DONAU_CharityPrivateKeyP *charity_priv,
struct TALER_Amount *charity_max_per_year,
struct TALER_Amount *charity_receipts_to_date,
json_t **donau_keys_json,
diff --git a/src/include/merchant-database/select_accounts.h b/src/include/merchant-database/select_accounts.h
@@ -26,8 +26,6 @@
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
-/* Callback typedefs */
/**
* Callback invoked with information about a bank account.
*
@@ -41,19 +39,19 @@ typedef void
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const struct TALER_MERCHANTDB_AccountDetails *ad);
+
/**
* Obtain information about an instance's accounts.
*
* @param pg database context
- * @param id identifier of the instance
* @param cb function to call on each account
* @param cb_cls closure for @a cb
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_select_accounts (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *id,
- TALER_MERCHANTDB_AccountCallback cb,
- void *cb_cls);
+TALER_MERCHANTDB_select_accounts (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ TALER_MERCHANTDB_AccountCallback cb,
+ void *cb_cls);
#endif
diff --git a/src/include/merchant-database/select_accounts_by_instance.h b/src/include/merchant-database/select_accounts_by_instance.h
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 src/include/merchant-database/select_accounts_by_instance.h
+ * @brief implementation of the select_accounts_by_instance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef MERCHANT_DATABASE_SELECT_ACCOUNTS_BY_INSTANCE_H
+#define MERCHANT_DATABASE_SELECT_ACCOUNTS_BY_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "merchantdb_lib.h"
+
+
+/**
+ * Callback invoked with information about a bank account.
+ *
+ * @param cls closure
+ * @param merchant_priv private key of the merchant instance
+ * @param ad details about the account
+ */
+typedef void
+(*TALER_MERCHANTDB_AccountCallback)(
+ void *cls,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const struct TALER_MERCHANTDB_AccountDetails *ad);
+
+/**
+ * Obtain information about an instance's accounts.
+ *
+ * @param pg database context
+ * @param id identifier of the instance
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TALER_MERCHANTDB_select_accounts_by_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *id,
+ TALER_MERCHANTDB_AccountCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/include/merchant-database/set_instance.h b/src/include/merchant-database/set_instance.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 src/include/merchant-database/set_instance.h
+ * @brief Select the per-instance schema active on the connection
+ * @author Christian Grothoff
+ */
+#ifndef MERCHANT_DATABASE_SET_INSTANCE_H
+#define MERCHANT_DATABASE_SET_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include "merchantdb_lib.h"
+
+
+/**
+ * Resolve @a instance_id to its merchant_serial and route subsequent
+ * per-instance queries on @a pg to the corresponding
+ * `merchant_instance_<serial>` schema. Updates the connection's
+ * search_path to "merchant_instance_<serial>, merchant" and stores
+ * @a instance_id / serial in the context for use by per-instance
+ * call sites and the #PREPARE_INSTANCE macro.
+ *
+ * Idempotent: a call with an already-active @a instance_id is a no-op.
+ *
+ * @param pg database connection
+ * @param instance_id instance to activate (must be NUL-terminated)
+ * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the instance was found
+ * and the search_path was updated;
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if no such instance exists;
+ * a hard or soft error code otherwise
+ */
+enum GNUNET_DB_QueryStatus
+TALER_MERCHANTDB_set_instance (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id);
+
+
+#endif
diff --git a/src/include/merchant-database/update_product.h b/src/include/merchant-database/update_product.h
@@ -22,11 +22,9 @@
#define MERCHANT_DATABASE_UPDATE_PRODUCT_H
#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
/**
* Update details about a particular product. Note that the
* transaction must enforce that the sold/stocked/lost counters
@@ -37,7 +35,6 @@ struct TALER_MERCHANTDB_PostgresContext;
* @param product_id product to lookup
* @param num_cats length of @a cats array
* @param cats number of categories the product is in
- * @param[out] no_instance the update failed as the instance is unknown
* @param[out] no_cat set to -1 on success, otherwise the update failed and this is set
* to the index of a category in @a cats that is unknown
* @param[out] no_product the @a product_id is unknown
@@ -55,19 +52,19 @@ struct TALER_MERCHANTDB_PostgresContext;
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_product (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t num_cats,
- const uint64_t *cats,
- bool *no_instance,
- ssize_t *no_cat,
- bool *no_product,
- bool *lost_reduced,
- bool *sold_reduced,
- bool *stocked_reduced,
- bool *no_group,
- bool *no_pot);
+TALER_MERCHANTDB_update_product (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ ssize_t *no_cat,
+ bool *no_product,
+ bool *lost_reduced,
+ bool *sold_reduced,
+ bool *stocked_reduced,
+ bool *no_group,
+ bool *no_pot);
#endif
diff --git a/src/include/merchant-database/update_unit.h b/src/include/merchant-database/update_unit.h
@@ -22,11 +22,9 @@
#define MERCHANT_DATABASE_UPDATE_UNIT_H
#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
#include "merchantdb_lib.h"
-struct TALER_MERCHANTDB_PostgresContext;
/**
* Update an existing measurement unit definition.
*
@@ -40,24 +38,23 @@ struct TALER_MERCHANTDB_PostgresContext;
* @param unit_allow_fraction optional new fractional toggle
* @param unit_precision_level optional new fractional precision
* @param unit_active optional new visibility flag
- * @param[out] no_instance set if instance unknown
* @param[out] no_unit set if unit unknown
* @param[out] builtin_conflict set if immutable builtin fields touched
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_unit (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *instance_id,
- const char *unit_id,
- const char *unit_name_long,
- const json_t *unit_name_long_i18n,
- const char *unit_name_short,
- const json_t *unit_name_short_i18n,
- const bool *unit_allow_fraction,
- const uint32_t *unit_precision_level,
- const bool *unit_active,
- bool *no_instance,
- bool *no_unit,
- bool *builtin_conflict);
+TALER_MERCHANTDB_update_unit (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *instance_id,
+ const char *unit_id,
+ const char *unit_name_long,
+ const json_t *unit_name_long_i18n,
+ const char *unit_name_short,
+ const json_t *unit_name_short_i18n,
+ const bool *unit_allow_fraction,
+ const uint32_t *unit_precision_level,
+ const bool *unit_active,
+ bool *no_unit,
+ bool *builtin_conflict);
#endif
diff --git a/src/testing/test_merchant_accounts.sh b/src/testing/test_merchant_accounts.sh
@@ -42,10 +42,11 @@ echo -n "Configuring 'admin' instance ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X POST \
http://localhost:9966/management/instances \
-d '{"auth":{"method":"token","password":"secret-token:new_value"},"id":"admin","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
- -w "%{http_code}" -s -o /dev/null)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 204, instance created. got: $STATUS" >&2
fi
@@ -67,6 +68,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
if [ "$STATUS" != "200" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 200 OK. Got: $STATUS"
fi
@@ -82,6 +84,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X GET \
if [ "$STATUS" != "200" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 200 OK. Got: $STATUS"
fi
@@ -89,6 +92,7 @@ ACTIVE=$(jq -r .active $LAST_RESPONSE)
if [ "$ACTIVE" != "true" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected account active."
fi
@@ -97,10 +101,11 @@ echo -n "deleting account ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
-H 'Authorization: Bearer secret-token:new_value' \
http://localhost:9966/private/accounts/$ACCOUNT_ID \
- -w "%{http_code}" -s )
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 204 OK. Got: $STATUS"
fi
@@ -117,6 +122,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
if [ "$STATUS" != "200" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 200 OK. Got: $STATUS"
fi
@@ -129,15 +135,18 @@ ACTIVE=$(jq -r .active $LAST_RESPONSE)
if [ "$ACTIVE" != "true" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected account active."
fi
echo "OK" >&2
##
-# Using different name should not conflict with previous account.
+# Using different name should conflict with previous account.
##
+ACCOUNT_ID=$(jq -r .h_wire $LAST_RESPONSE)
+
echo -n "creating same account with different name ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X POST \
@@ -147,14 +156,12 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-w "%{http_code}" -s -o "$LAST_RESPONSE")
-if [ "$STATUS" != "200" ]
+if [ "$STATUS" != "409" ]
then
- exit_fail "Expected 200 OK. Got: $STATUS"
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 409 Conflict. Got: $STATUS"
fi
-ACCOUNT_ID=$(jq -r .h_wire $LAST_RESPONSE)
-
-
STATUS=$(curl -H "Content-Type: application/json" -X GET \
-H 'Authorization: Bearer secret-token:new_value' \
http://localhost:9966/private/accounts/$ACCOUNT_ID \
@@ -164,11 +171,14 @@ ACTIVE=$(jq -r .active $LAST_RESPONSE)
if [ "$ACTIVE" != "true" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected account active."
fi
echo "OK" >&2
+
+
echo -n "deleting the account ..." >&2
STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
@@ -178,6 +188,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 204 OK. Got: $STATUS"
fi
@@ -194,9 +205,12 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
if [ "$STATUS" != "200" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 200 OK. Got: $STATUS"
fi
+ACCOUNT_ID=$(jq -r .h_wire $LAST_RESPONSE)
+
STATUS=$(curl -H "Content-Type: application/json" -X GET \
-H 'Authorization: Bearer secret-token:new_value' \
http://localhost:9966/private/accounts/$ACCOUNT_ID \
@@ -206,6 +220,7 @@ ACTIVE=$(jq -r .active $LAST_RESPONSE)
if [ "$ACTIVE" != "true" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected account active."
fi
@@ -227,6 +242,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
if [ "$STATUS" != "200" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 200 OK. Got: $STATUS"
fi
@@ -242,6 +258,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X GET \
if [ "$STATUS" != "200" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 200 OK. Got: $STATUS"
fi
@@ -249,6 +266,7 @@ ACTIVE=$(jq -r .active $LAST_RESPONSE)
if [ "$ACTIVE" != "true" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected account active."
fi
@@ -261,6 +279,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X DELETE \
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 204 OK. Got: $STATUS"
fi
@@ -277,6 +296,7 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
if [ "$STATUS" != "200" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 200 OK. Got: $STATUS"
fi
@@ -285,17 +305,19 @@ STATUS=$(curl -H "Content-Type: application/json" -X GET \
http://localhost:9966/private/accounts/$ACCOUNT_ID \
-w "%{http_code}" -s -o "$LAST_RESPONSE")
-ACTIVE=$(jq -r .active $LAST_RESPONSE)
+ACTIVE=$(jq -r .active "$LAST_RESPONSE")
if [ "$ACTIVE" != "true" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected account active."
fi
-FACADE=$(jq -r .credit_facade_url $LAST_RESPONSE)
+FACADE=$(jq -r .credit_facade_url "$LAST_RESPONSE")
if [ "$FACADE" != "http://asd.com/" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected account with facade http://asd.com/."
fi
@@ -317,7 +339,12 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
if [ "$STATUS" != "409" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 409 Conflict. Got: $STATUS"
fi
echo "OK" >&2
+
+echo "Test PASSED"
+
+exit 0
diff --git a/src/testing/test_merchant_instance_auth.sh b/src/testing/test_merchant_instance_auth.sh
@@ -100,7 +100,7 @@ taler-merchant-exchangekeyupdate \
-c "${CONF}" \
-L DEBUG \
-t \
- 2> taler-merchant-exchangekeyupdate2.log
+ 2> taler-merchant-exchangekeyupdate2.log
taler-merchant-passwd \
-c "${CONF}" \
-L DEBUG \
@@ -196,15 +196,15 @@ PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
echo "OK order ${ORDER_ID} with ${ORD_TOKEN} and ${PAY_URL}" >&2
echo -n "Configuring 'second' instance ..." >&2
-
STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer '"$TOKEN" \
http://localhost:9966/management/instances \
-d '{"auth":{"method":"token","password":"second"},"id":"second","name":"second","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
- -w "%{http_code}" -s -o /dev/null)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 204, instance created. got: $STATUS"
fi
@@ -216,10 +216,11 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer '"$TOKEN" \
http://localhost:9966/management/instances \
-d '{"auth":{"method":"token","password":"third"},"id":"third","name":"third","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
- -w "%{http_code}" -s -o /dev/null)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 204, instance created. got: $STATUS"
fi
@@ -231,10 +232,11 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer '"$TOKEN" \
http://localhost:9966/management/instances/second/auth \
-d '{"method":"token","password":"new_one"}' \
- -w "%{http_code}" -s -o /dev/null)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected 204, instance auth token changed. got: $STATUS"
fi
NEW_SECRET="new_one"
@@ -304,7 +306,7 @@ BASIC_AUTH2=$(echo -n second:again | base64)
if [ "$STATUS" != "204" ]
then
- cat $LAST_RESPONSE
+ cat $LAST_RESPONSE >&2
exit_fail "Expected 204, instance not authorized. got: $STATUS"
fi
@@ -316,10 +318,11 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer '"$RWTOKEN" \
http://localhost:9966/management/instances/third/auth \
-d '{"method":"token","password":"new_one"}' \
- -w "%{http_code}" -s -o /dev/null)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "401" ]
then
+ cat $LAST_RESPONSE >&2
exit_fail "Expected 401, instance not authorized. got: $STATUS"
fi
diff --git a/src/testing/test_merchant_mfa.sh b/src/testing/test_merchant_mfa.sh
@@ -56,6 +56,7 @@ then
exit_fail "Expected 200 OK. Got: $STATUS"
fi
+echo " OK"
echo -n "Self-provision instance ..."
STATUS=$(curl -H "Content-Type: application/json" -X POST \
@@ -69,6 +70,7 @@ then
jq < "$LAST_RESPONSE"
exit_fail "Expected 202 Accepted. Got: $STATUS"
fi
+echo " OK"
C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE")
C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE")
diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh
@@ -723,4 +723,5 @@ then
fi
echo " OK"
+echo "Test PASSED"
exit 0
diff --git a/src/testing/test_merchant_product_creation.sh b/src/testing/test_merchant_product_creation.sh
@@ -316,4 +316,6 @@ then
fi
echo " OK"
+echo "Test PASSED"
+
exit 0
diff --git a/src/testing/test_merchant_wirewatch.sh b/src/testing/test_merchant_wirewatch.sh
@@ -391,4 +391,6 @@ then
fi
echo " OK"
+echo "Test PASSED"
+
exit 0