merchant

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

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:
Msrc/backend/taler-merchant-donaukeyupdate.c | 2+-
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 337+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/backend/taler-merchant-httpd_private-post-orders.c | 5+++++
Msrc/backenddb/Makefile.am | 2++
Msrc/backenddb/merchant-0022.sql | 12+++++++-----
Asrc/backenddb/pg_insert_order_blinded_sigs.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_order_blinded_sigs.h | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_order_blinded_sigs.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_order_blinded_sigs.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/plugin_merchantdb_postgres.c | 6++++++
Msrc/include/taler_merchantdb_plugin.h | 28++++++++++++++++++++++++++++
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