commit 531169bde6d5e88efeffd67c755a8087e686eb4e
parent 86cab216c4a68dfc5be8dfaea7e1c81d9c110201
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Fri, 25 Jul 2025 16:26:59 +0200
cleanin up orders id pay endpoint
Diffstat:
11 files changed, 495 insertions(+), 106 deletions(-)
diff --git a/src/backend/taler-merchant-donaukeyupdate.c b/src/backend/taler-merchant-donaukeyupdate.c
@@ -346,7 +346,7 @@ donau_cert_cb (
/* limit retry */
d->first_retry = first_retry;
- /* Might be good to reference some key_data_expiration and not first sign_key*/
+ /* FIXME: Might be good to reference some key_data_expiration and not first sign_key*/
n = GNUNET_TIME_absolute_max (d->first_retry,
keys->sign_keys[0].expire_sign.abs_time);
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -128,7 +128,12 @@ enum PayPhase
/**
* Communicate with DONAU to generate a donation receipt from the donor BUDIs.
*/
- PP_GENERATE_DONATION_RECEIPT,
+ PP_REQUEST_DONATION_RECEIPT,
+
+ /**
+ * Process the donation receipt response from DONAU (save the donau_sigs to the db).
+ */
+ PP_PROCESS_DONATION_RECEIPT,
/**
* Notify other processes about successful payment.
@@ -164,7 +169,7 @@ enum PayPhase
/**
* Return #MHD_NO to end processing.
*/
- PP_END_NO,
+ PP_END_NO
};
@@ -541,7 +546,7 @@ struct PayContext
#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
- * Number of the blinded key pairs
+ * Number of the blinded key pairs @e bkps
*/
unsigned int num_bkps;
@@ -734,7 +739,7 @@ struct PayContext
#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
- * Struct for #phase_generate_donation_receipt()
+ * Struct for #phase_request_donation_receipt()
*/
struct
{
@@ -749,7 +754,7 @@ struct PayContext
struct DONAU_BlindedDonationUnitSignature *sigs;
/**
- * Number of receipts for the donations from the Donau.
+ * Number of receipts @e sigs for the donations from the Donau.
*/
unsigned int num_sigs;
@@ -1812,6 +1817,9 @@ phase_success_response (struct PayContext *pc)
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_array_steal ("token_sigs",
token_sigs)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("donau_sigs",
+ pc->donau_receipt.donau_sigs_json)),
GNUNET_JSON_pack_data_auto ("sig",
&sig)));
GNUNET_free (pos_confirmation);
@@ -1874,8 +1882,109 @@ phase_payment_notification (struct PayContext *pc)
}
-#ifdef HAVE_DONAU_DONAU_SERVICE_H
+/**
+ * Phase to process received donation receipts and store them into the database.
+ *
+ * @param pc payment context
+ */
+static void
+phase_process_donation_receipt (struct PayContext *pc)
+{
+#ifndef HAVE_DONAU_DONAU_SERVICE_H
+ /* No Donau support compiled in – just continue the FSM. */
+ pc->phase = PP_PAYMENT_NOTIFICATION;
+ return;
+#else
+ /* Attaching first sig from the donau as output_token signature,
+ * to reference the output_token of donau */
+ const struct TALER_MERCHANT_ContractChoice *choice =
+ &pc->check_contract.contract_terms->details.v1
+ .choices[pc->parse_wallet_data.choice_index];
+
+ for (size_t i = 0; i < pc->validate_tokens.output_tokens_len; i++)
+ if (i < choice->outputs_len &&
+ TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT ==
+ choice->outputs[i].type)
+ pc->validate_tokens.output_tokens[i].sig.signature =
+ pc->donau_receipt.sigs[0].blinded_sig;
+
+ /* To have the possibility to fetch the donau_sigs
+ * in case of contract_paid, we need to save
+ * the sigs[0].blinded_sig and donau_receipt.donau_sigs_json
+ * to db */
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned int retry;
+ TMH_db->preflight (TMH_db->cls);
+ if (GNUNET_OK != TMH_db->start (TMH_db->cls,
+ "insert_order_blinded_sigs"))
+ {
+ resume_pay_with_error (pc,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "start insert blinded donation receipts failed");
+ return;
+ }
+ for (retry = 0; retry < MAX_RETRIES; retry++)
+ {
+ qs = TMH_db->insert_order_blinded_sigs (
+ TMH_db->cls,
+ pc->order_id,
+ pc->donau_receipt.donau_sigs_json,
+ pc->donau_receipt.sigs[0].blinded_sig);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Retrying insert blinded donation receipts");
+ continue;
+ }
+ if (0 >= qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert blinded donation receipts"));
+ return;
+ }
+
+ qs = TMH_db->commit (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Retrying commit blinded donation receipts");
+ continue;
+ }
+ if (0 > qs)
+ {
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "commit blinded donation receipts"));
+ return;
+ }
+ break;
+ }
+
+ if (retry == MAX_RETRIES)
+ {
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "insert blinded donation receipts retry exhausted"));
+ return;
+ }
+
+ pc->phase = PP_PAYMENT_NOTIFICATION;
+#endif
+}
+
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
* Parse signatures to JSON.
*
@@ -1886,9 +1995,9 @@ phase_payment_notification (struct PayContext *pc)
* is malformed.
*/
static void
-signature_to_json (const size_t num_sig,
- struct DONAU_BlindedDonationUnitSignature *signatures,
- json_t *j_signatures)
+signatures_to_json (const size_t num_sig,
+ struct DONAU_BlindedDonationUnitSignature *signatures,
+ json_t *j_signatures)
{
for (size_t i = 0; i < num_sig; i++)
{
@@ -1914,87 +2023,65 @@ merchant_donau_issue_receipt_cb (void *cls,
const struct DONAU_BatchIssueResponse *resp)
{
struct PayContext *pc = cls;
-
/* Donau replies asynchronously, so we expect the PayContext
* to be suspended. */
GNUNET_assert (GNUNET_YES == pc->suspended);
+
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Donau responded with status=%u, ec=%u\n",
resp->hr.http_status,
resp->hr.ec);
-
- /* If Donau gave 0 => means the reply was malformed or invalid. */
- if (0 == resp->hr.http_status)
+ switch (resp->hr.http_status)
{
+ case 0:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Donau replied with status 0 => invalid or parse error\n");
- /* FIXME: TALER_EC_MERCHANT_DONAU_TALKING_ISSUE_
- * resume_pay_with_donau_error so include everything as possible from donau
- * 502 for return
- * PayResponse new bad_gateway part with donau hint error, so on
- * change to switch*/
- resume_pay_with_error (pc,
- TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
- "Donau parse error");
- return;
- }
-
- /* If Taler error code is non-zero => Donau signaled an error. */
- if (TALER_EC_NONE != resp->hr.ec)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Donau responded with Taler error code %u\n",
- resp->hr.ec);
+ "Donau batch issue request from merchant-httpd failed (http_status==0)\n");
resume_pay_with_error (pc,
resp->hr.ec,
- "Donau returned an error");
+ "Donau batch issue request failed");
return;
- }
-
- if (MHD_HTTP_CREATED == resp->hr.http_status)
- {
- pc->donau_receipt.sigs =
- resp->details.ok.blinded_sigs;
- for (size_t i = 0; i<pc->validate_tokens.output_tokens_len; i++)
- {
- const struct TALER_MERCHANT_ContractChoice *choice =
- &pc->check_contract.contract_terms->details.v1
- .choices[pc->parse_wallet_data.choice_index];
-
- if (i < choice->outputs_len &&
- TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT ==
- choice->outputs[i].type)
- {
- pc->validate_tokens.output_tokens[i].sig.signature =
- pc->donau_receipt.sigs->blinded_sig;
- }
- }
-
- pc->donau_receipt.donau_sigs_json = json_array ();
- signature_to_json (pc->donau_receipt.num_sigs,
- pc->donau_receipt.sigs,
- pc->donau_receipt.donau_sigs_json);
+ case MHD_HTTP_OK:
+ case MHD_HTTP_CREATED:
+ /* Most probably, it is just some small flaw from
+ * donau so no point in failing, yet we have to display it*/
+ if (TALER_EC_NONE != resp->hr.ec)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Donau signalled error %u despite HTTP %u\n",
+ resp->hr.ec,
+ resp->hr.http_status);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Donau accepted donation receipts with total_issued=%s\n",
TALER_amount2s (&resp->details.ok.issued_amount));
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Donau responded with HTTP code %u\n",
- resp->hr.http_status);
+ break; /* continue normal handling after switch*/
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ default: /* make sure that everything except 200/201 will end up here*/
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Donau replied with HTTP %u (ec=%u)\n",
+ resp->hr.http_status,
+ resp->hr.ec);
resume_pay_with_error (pc,
- TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
- "Donau HTTP error (non-OK)");
+ resp->hr.ec,
+ "Donau HTTP error");
return;
}
- /* Everything OK, continue the normal /pay flow */
- pc->phase = PP_PAYMENT_NOTIFICATION; /* go to next phase */
+ pc->donau_receipt.sigs =
+ resp->details.ok.blinded_sigs;
+ pc->donau_receipt.num_sigs =
+ resp->details.ok.num_blinded_sigs;
+ pc->donau_receipt.donau_sigs_json =
+ json_array ();
+ signatures_to_json (pc->donau_receipt.num_sigs,
+ pc->donau_receipt.sigs,
+ pc->donau_receipt.donau_sigs_json);
+ pc->phase = PP_PROCESS_DONATION_RECEIPT;
pay_resume (pc);
}
@@ -2040,14 +2127,13 @@ merchant_parse_json_bkp (struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
* @param pc payment context containing the charity and bkps
*/
static void
-phase_generate_donation_receipt (struct PayContext *pc)
+phase_request_donation_receipt (struct PayContext *pc)
{
#ifndef HAVE_DONAU_DONAU_SERVICE_H
/* If Donau is disabled at compile-time, skip. */
pc->phase = PP_PAYMENT_NOTIFICATION;
return;
#else
- /* Call DONAU_charity_issue_receipt() asynchronously. */
pc->donau_receipt.birh =
DONAU_charity_issue_receipt (TMH_curl_ctx,
pc->parse_wallet_data.donau.donau_url,
@@ -2060,16 +2146,15 @@ phase_generate_donation_receipt (struct PayContext *pc)
pc);
if (NULL == pc->donau_receipt.birh)
{
- /* The request was invalid from the library’s perspective. */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to create Donau receipt request\n");
- resume_pay_with_error (pc,
- TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
- "Donau error");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ "Donau request creation error"));
return;
}
-
- /* As we do in the batch_issue for the exchange*/
MHD_suspend_connection (pc->connection);
pc->suspended = GNUNET_YES;
#endif
@@ -2683,10 +2768,6 @@ phase_execute_pay_transaction (struct PayContext *pc)
&pc->check_contract.contract_terms->details.v1
.choices[pc->parse_wallet_data.choice_index];
- // GNUNET_assert (pc->validate_tokens.output_tokens_len ==
- // choice->outputs_len);
-
- /* Store signed output tokens in database. */
for (size_t i = 0; i<pc->validate_tokens.output_tokens_len; i++)
{
switch (choice->outputs[i].type)
@@ -2702,25 +2783,21 @@ phase_execute_pay_transaction (struct PayContext *pc)
break;
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
/* We skip outputs of donation receipts here, as they are handled in the
- * phase_generate_donation_receipt() */
+ * phase_process_donation_receipt() callback from donau */
break;
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
struct SignedOutputToken *output =
&pc->validate_tokens.output_tokens[i];
enum GNUNET_DB_QueryStatus qs;
-
qs = TMH_db->insert_issued_token (TMH_db->cls,
&pc->check_contract.h_contract_terms,
&output->h_issue,
&output->sig);
- /* FIXME: Lovely switch :) is missing */
- if (0 >= qs)
+ switch (qs)
{
+ case GNUNET_DB_STATUS_HARD_ERROR:
TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return; /* do it again */
-
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
pay_end (pc,
TALER_MHD_reply_with_error (pc->connection,
@@ -2728,6 +2805,21 @@ phase_execute_pay_transaction (struct PayContext *pc)
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert output token"));
return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* Serialization failure, retry */
+ TMH_db->rollback (TMH_db->cls);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* UNIQUE constraint violation, meaning this token was already used. */
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "duplicate output token"));
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
}
break;
}
@@ -2792,7 +2884,7 @@ phase_execute_pay_transaction (struct PayContext *pc)
if (NULL != pc->parse_wallet_data.donau.donau_url)
{
/* We have a Donau URL => we do the new donation receipt phase. */
- pc->phase = PP_GENERATE_DONATION_RECEIPT;
+ pc->phase = PP_REQUEST_DONATION_RECEIPT;
}
else
{
@@ -3124,18 +3216,13 @@ handle_output_token (struct PayContext *pc,
pc->check_contract.contract_terms->timestamp,
pc->check_contract.contract_terms->pay_deadline,
&details);
- /* FIXME: change to lovely switch NO_RESPONSE 404, SOFT/HARD ERRORs 500 */
- if (qs <= 0)
+ switch (qs)
{
- GNUNET_log (
- GNUNET_ERROR_TYPE_ERROR,
- "Did not find key for %s at [%llu,%llu]\n",
- family->slug,
- (unsigned long
- long) pc->check_contract.contract_terms->timestamp.abs_time.abs_value_us,
- (unsigned long
- long) pc->check_contract.contract_terms->pay_deadline.abs_time.
- abs_value_us);
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database error looking up token-family key for %s\n",
+ family->slug);
GNUNET_break (0);
pay_end (pc,
TALER_MHD_reply_with_error (
@@ -3144,6 +3231,27 @@ handle_output_token (struct PayContext *pc,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL));
return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Token-family key for %s not found at [%llu,%llu]\n",
+ family->slug,
+ (unsigned long
+ long) pc->check_contract.contract_terms->timestamp.abs_time.
+ abs_value_us,
+ (unsigned long
+ long) pc->check_contract.contract_terms->pay_deadline.abs_time.
+ abs_value_us);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL));
+ return GNUNET_NO;
+
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
}
GNUNET_assert (NULL != details.priv.private_key);
@@ -3536,6 +3644,32 @@ phase_contract_paid (struct PayContext *pc)
if (! tuc->found_in_db)
unmatched = true;
}
+
+ /* In this part we are fetching donau related outputs */
+ {
+ /* Can be replaced with reference to the output_token,
+ * if output_tokens also are returned in contract_paid case*/
+ struct GNUNET_CRYPTO_BlindedSignature *stored_sig =
+ GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ enum GNUNET_DB_QueryStatus qs;
+ qs = TMH_db->select_order_blinded_sigs (
+ TMH_db->cls,
+ pc->order_id,
+ &pc->donau_receipt.donau_sigs_json,
+ stored_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_order_blinded_sigs"));
+ return;
+ }
+ GNUNET_free (stored_sig);
+ }
if (! unmatched)
{
/* Everything fine, idempotent request, generate response immediately */
@@ -4497,8 +4631,11 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
case PP_PAY_TRANSACTION:
phase_execute_pay_transaction (pc);
break;
- case PP_GENERATE_DONATION_RECEIPT:
- phase_generate_donation_receipt (pc);
+ case PP_REQUEST_DONATION_RECEIPT:
+ phase_request_donation_receipt (pc);
+ break;
+ case PP_PROCESS_DONATION_RECEIPT:
+ phase_process_donation_receipt (pc);
break;
case PP_PAYMENT_NOTIFICATION:
phase_payment_notification (pc);
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -3227,6 +3227,8 @@ phase_merge_inventory (struct OrderContext *oc)
/**
* Callback function that is called for each donau instance.
* It simply adds the provided donau_url to the json.
+ *
+ * @param cls closure with our `struct TALER_MERCHANT_ContractOutput *`
*/
static void
add_donau_url (void *cls,
@@ -3247,6 +3249,9 @@ add_donau_url (void *cls,
}
+/**
+ *
+ */
static void
add_donau_output (struct OrderContext *oc,
struct TALER_MERCHANT_ContractOutput *output)
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
@@ -222,8 +222,10 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES += \
pg_lookup_order_charity.h pg_lookup_order_charity.c \
pg_upsert_donau_keys.h pg_upsert_donau_keys.c \
pg_insert_donau_instance.h pg_insert_donau_instance.c \
+ pg_insert_order_blinded_sigs.h pg_insert_order_blinded_sigs.c \
pg_select_donau_instances.h pg_select_donau_instances.c \
pg_select_donau_instances_filtered.h pg_select_donau_instances_filtered.c \
+ pg_select_order_blinded_sigs.h pg_select_order_blinded_sigs.c \
pg_delete_donau_instance.h pg_delete_donau_instance.c
endif
diff --git a/src/backenddb/merchant-0022.sql b/src/backenddb/merchant-0022.sql
@@ -75,8 +75,9 @@ COMMENT ON COLUMN merchant_donau_instances.current_year
CREATE TABLE IF NOT EXISTS merchant_order_donau
(order_donau_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,order_serial BIGINT NOT NULL
- REFERENCES merchant_orders (order_serial) ON DELETE CASCADE
- ,donau_budis TEXT NOT NULL
+ REFERENCES merchant_contract_terms (order_serial) ON DELETE CASCADE
+ ,donau_blinded_sigs_json TEXT NOT NULL
+ ,donau_blinded_signature BYTEA NOT NULL CHECK (LENGTH(donau_blinded_signature)=48)
);
COMMENT ON TABLE merchant_order_donau
@@ -85,7 +86,8 @@ COMMENT ON COLUMN merchant_order_donau.order_donau_serial
IS 'Unique serial identifier for Donau order linkage';
COMMENT ON COLUMN merchant_order_donau.order_serial
IS 'Foreign key linking to the corresponding merchant order';
-COMMENT ON COLUMN merchant_order_donau.donau_budis
- IS 'Donau BUDIs json associated with the order';
-
+COMMENT ON COLUMN merchant_order_donau.donau_blinded_sigs_json
+ IS 'Donau blinded sigs json associated with the order';
+COMMENT ON COLUMN merchant_order_donau.donau_blinded_signature
+ IS 'First blinded signature returned by Donau, raw 64-byte value';
COMMIT;
\ No newline at end of file
diff --git a/src/backenddb/pg_insert_order_blinded_sigs.c b/src/backenddb/pg_insert_order_blinded_sigs.c
@@ -0,0 +1,55 @@
+/*
+ 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 backenddb/insert_order_blinded_sigs.c
+ * @brief implementation of insert_order_blinded_sigs() for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_order_blinded_sigs.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order_blinded_sigs (void *cls,
+ const char *order_id,
+ const json_t *blinded_sigs,
+ const struct
+ GNUNET_CRYPTO_BlindedSignature *blind_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (order_id),
+ TALER_PQ_query_param_json (blinded_sigs),
+ GNUNET_PQ_query_param_auto_from_type (blind_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_blinded_sigs",
+ "INSERT INTO merchant_order_donau"
+ " (order_serial, donau_blinded_sigs_json, donau_blinded_signature)"
+ " SELECT order_serial, $2, $3"
+ " 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/pg_insert_order_blinded_sigs.h b/src/backenddb/pg_insert_order_blinded_sigs.h
@@ -0,0 +1,44 @@
+/*
+ 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 backenddb/pg_insert_order_blinded_sigs.h
+ * @brief Implementation of the insert blinded sigs by order_id function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_INSERT_BLIND_SIGS_ORD_H
+#define PG_INSERT_BLIND_SIGS_ORD_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Store (or update) the Donau *blinded* signature JSON for an order.
+ *
+ * @param cls closure (our `struct PostgresClosure *`)
+ * @param order_id business-level order identifier
+ * @param blinded_sigs JSON with Donau blinded signature(s)
+ * @return GNUNET_DB status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order_blinded_sigs (void *cls,
+ const char *order_id,
+ const json_t *blinded_sigs,
+ const struct
+ GNUNET_CRYPTO_BlindedSignature *blind_sig);
+
+#endif
diff --git a/src/backenddb/pg_select_order_blinded_sigs.c b/src/backenddb/pg_select_order_blinded_sigs.c
@@ -0,0 +1,64 @@
+/*
+ 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 backenddb/pg_select_order_blinded_sigs.c
+ * @brief Implementation of the select blinded sigs by order_id function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_order_blinded_sigs.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_order_blinded_sigs (
+ void *cls,
+ const char *order_id,
+ json_t **blinded_sigs,
+ struct GNUNET_CRYPTO_BlindedSignature *blind_sig_out)
+{
+ struct PostgresClosure *pg = cls;
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_json ("donau_blinded_sigs_json",
+ blinded_sigs),
+ GNUNET_PQ_result_spec_auto_from_type ("donau_blinded_signature",
+ blind_sig_out),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_blinded_sigs",
+ "SELECT"
+ " mod.donau_blinded_sigs_json"
+ " ,mod.donau_blinded_signature"
+ " FROM merchant_order_donau AS mod"
+ " JOIN merchant_contract_terms AS mct USING (order_serial)"
+ " WHERE mct.order_id = $1");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_blinded_sigs",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_select_order_blinded_sigs.h b/src/backenddb/pg_select_order_blinded_sigs.h
@@ -0,0 +1,46 @@
+/*
+ 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 backenddb/pg_select_order_blinded_sigs.h
+ * @brief Implementation of the select blinded sigs by order_id function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_SELECT_BLIND_SIGS_ORD_H
+#define PG_SELECT_BLIND_SIGS_ORD_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Look up the Donau *blinded‐signature* blob we already stored for an
+ * order (identified by its human-readable `order_id`) and hand it back
+ * to the caller.
+ *
+ * @param cls closure (our `struct PostgresClosure *`)
+ * @param order_id business-level order identifier
+ * @param[out] blinded_sigs where to write the JSON object (refcount +1)
+ * @return GNUNET_DB status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_order_blinded_sigs (
+ void *cls,
+ const char *order_id,
+ json_t **blinded_sigs,
+ struct GNUNET_CRYPTO_BlindedSignature *blind_sig_out);
+
+#endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
@@ -171,6 +171,8 @@
#include "pg_lookup_donau_keys.h"
#include "pg_lookup_order_charity.h"
#include "pg_upsert_donau_keys.h"
+#include "pg_insert_order_blinded_sigs.h"
+#include "pg_select_order_blinded_sigs.h"
#endif
/**
@@ -678,6 +680,10 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_lookup_order_charity;
plugin->upsert_donau_keys
= &TMH_PG_upsert_donau_keys;
+ plugin->insert_order_blinded_sigs
+ = &TMH_PG_insert_order_blinded_sigs;
+ plugin->select_order_blinded_sigs
+ = &TMH_PG_select_order_blinded_sigs;
#endif
return plugin;
}
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -4167,6 +4167,20 @@ struct TALER_MERCHANTDB_Plugin
);
/**
+ * Insert blinded signatures for an order.
+ *
+ * @param cls closure
+ * @param order_id order ID to insert blinded signatures for
+ * @param blinded_sigs JSON array of blinded signatures
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_order_blinded_sigs)(
+ void *cls,
+ const char *order_id,
+ const json_t *blinded_sigs,
+ const struct GNUNET_CRYPTO_BlindedSignature *blind_sig);
+
+ /**
* Select all Donau instances.
*
* @param cls closure
@@ -4196,6 +4210,20 @@ struct TALER_MERCHANTDB_Plugin
);
/**
+ * Select blinded signatures for an order.
+ *
+ * @param cls closure
+ * @param order_id order ID to select blinded signatures for
+ * @param blinded_sigs set to the JSON array of blinded signatures on success
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_order_blinded_sigs)(
+ void *cls,
+ const char *order_id,
+ json_t **blinded_sigs,
+ struct GNUNET_CRYPTO_BlindedSignature *blind_sig_out);
+
+ /**
* Delete information about a Donau instance.
*
* @param cls closure