merchant

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

commit cb9b9098d63dfbbe908cdbdef5f2e0c31856f158
parent ca30b4e11c847d6082555ae69628e5e169999be9
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon,  9 Mar 2026 17:15:55 +0100

need to return accumulated_total_without_fee in /batch-deposit

Diffstat:
Msrc/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c | 28+---------------------------
Msrc/backenddb/pg_insert_deposit.c | 3++-
Msrc/backenddb/pg_insert_deposit_confirmation.c | 108++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/backenddb/pg_insert_deposit_confirmation.sql | 36++++++++++++++++++++----------------
Msrc/backenddb/test_merchantdb.c | 4++--
5 files changed, 85 insertions(+), 94 deletions(-)

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 @@ -1036,35 +1036,9 @@ batch_deposit_transaction ( { const struct PayContext *pc = eg->pc; enum GNUNET_DB_QueryStatus qs; - struct TALER_Amount total_without_fees; uint64_t b_dep_serial; uint32_t off = 0; - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (pc->validate_tokens.brutto.currency, - &total_without_fees)); - for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) - { - struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; - struct TALER_Amount amount_without_fees; - - /* might want to group deposits by batch more explicitly ... */ - if (0 != strcmp (eg->exchange_url, - dc->exchange_url)) - continue; - if (dc->found_in_db) - continue; - if (! dc->in_batch) - continue; - GNUNET_assert (0 <= - TALER_amount_subtract (&amount_without_fees, - &dc->cdd.amount, - &dc->deposit_fee)); - GNUNET_assert (0 <= - TALER_amount_add (&total_without_fees, - &total_without_fees, - &amount_without_fees)); - } qs = TMH_db->insert_deposit_confirmation ( TMH_db->cls, pc->hc->instance->settings.id, @@ -1072,7 +1046,7 @@ batch_deposit_transaction ( &pc->check_contract.h_contract_terms, eg->exchange_url, pc->check_contract.contract_terms->wire_deadline, - &total_without_fees, + &dr->details.ok.accumulated_total_without_fee, &eg->wire_fee, &pc->check_contract.wm->h_wire, dr->details.ok.exchange_sig, diff --git a/src/backenddb/pg_insert_deposit.c b/src/backenddb/pg_insert_deposit.c @@ -72,7 +72,8 @@ TMH_PG_insert_deposit ( ",deposit_fee" ",refund_fee" ",settlement_retry_time" - ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8)"); + ") 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/pg_insert_deposit_confirmation.c b/src/backenddb/pg_insert_deposit_confirmation.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022, 2023, 2024 Taler Systems SA + Copyright (C) 2022, 2023, 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 @@ -41,11 +41,12 @@ TMH_PG_insert_deposit_confirmation ( const struct TALER_ExchangePublicKeyP *exchange_pub, uint64_t *deposit_confirmation_serial_id) { - struct GNUNET_DB_EventHeaderP es = { - .size = htons (sizeof (es)), - .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE) - }; struct PostgresClosure *pg = cls; + struct GNUNET_TIME_AbsoluteNBO nbo + = GNUNET_TIME_absolute_hton (wire_transfer_deadline.abs_time); + char *nbo_str + = 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), @@ -59,9 +60,25 @@ TMH_PG_insert_deposit_confirmation ( 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", + &no_account), + GNUNET_PQ_result_spec_bool ("no_signkey", + &no_signkey), + GNUNET_PQ_result_spec_bool ("conflict", + &conflict), GNUNET_PQ_result_spec_uint64 ("deposit_confirmation_serial", deposit_confirmation_serial_id), GNUNET_PQ_result_spec_end @@ -81,54 +98,49 @@ TMH_PG_insert_deposit_confirmation ( check_connection (pg); PREPARE (pg, "insert_deposit_confirmation", - "WITH md AS" - " (SELECT account_serial, merchant_serial" - " FROM merchant_accounts" - " WHERE h_wire=$7" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))" - ", ed AS" - " (SELECT signkey_serial" - " FROM merchant_exchange_signing_keys" - " WHERE exchange_pub=$9" - " ORDER BY start_date DESC" - " LIMIT 1)" - "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)" - " SELECT " - " order_serial" - " ,$3, $4, $5, $6, $8, $10" - " ,ed.signkey_serial" - " ,md.account_serial" - " FROM merchant_contract_terms" - " JOIN md USING (merchant_serial)" - " FULL OUTER JOIN ed ON TRUE" - " WHERE h_contract_terms=$2" - " RETURNING deposit_confirmation_serial"); + "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);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "insert_deposit_confirmation", params, rs); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + GNUNET_free (nbo_str); + GNUNET_break (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); + if (qs < 0) + 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) { - /* inform taler-merchant-depositcheck about new deadline */ - struct GNUNET_TIME_AbsoluteNBO nbo; - - nbo = GNUNET_TIME_absolute_hton (wire_transfer_deadline.abs_time); - GNUNET_PQ_event_notify (pg->conn, - &es, - &nbo, - sizeof (nbo)); + GNUNET_break (0); + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + if (no_order) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + if (no_account) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + if (no_signkey) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + if (conflict) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } return qs; } diff --git a/src/backenddb/pg_insert_deposit_confirmation.sql b/src/backenddb/pg_insert_deposit_confirmation.sql @@ -20,7 +20,7 @@ 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_fees taler_amount_currency, + 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, @@ -32,7 +32,7 @@ CREATE FUNCTION merchant_do_insert_deposit_confirmation ( OUT out_no_account BOOL, OUT out_no_signkey BOOL, OUT out_conflict BOOL, - OUT out_deposit_confirmation_serial) + OUT out_deposit_confirmation_serial INT8) LANGUAGE plpgsql AS $$ DECLARE @@ -46,10 +46,11 @@ DECLARE BEGIN out_no_instance=TRUE; -out_no_deposit=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 @@ -73,10 +74,10 @@ THEN END IF; out_no_account=FALSE; -SELECT account_serial - INTO my_account_serial +SELECT signkey_serial + INTO my_signkey_serial FROM merchant_exchange_signing_keys - WHERE exchange_pub=in_exchange_pub; + WHERE exchange_pub=in_exchange_pub ORDER BY start_date DESC LIMIT 1; IF NOT FOUND @@ -132,19 +133,22 @@ THEN ) RETURNING deposit_confirmation_serial INTO out_deposit_confirmation_serial; ELSE - IF ( (in_deposit_timestamp != my_record.deposit_timestamp) || - (in_wire_transfer_deadline != my_record.wire_transfer_deadline) || - ((in_wire_fee).val != (my_record.wire_fee).val) || - ((in_wire_frac).val != (my_record.wire_fee).frac) || - (in_wire_transfer_deadline != my_record.wire_transfer_deadline) || - (my_account_serial != my_record.account_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; out_deposit_confirmation_serial = my_record.deposit_confirmation_serial; RETURN; END IF; - IF ( ((in_total_without_fee).val < (my_record.total_without_fee).val) || - ( ((in_total_without_fee).val = (my_record.total_without_fee).val) && + 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. @@ -162,7 +166,7 @@ ELSE END IF; -- Do notify on TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE -NOTIFY XFIXME, - in_notify_arg_str; +PERFORM pg_notify ('XBZ19D98AK2REYNX93F736A56MT14SCY2EEX7XNXQMNCQ01B121R0', + in_notify_arg_str); END $$; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -2735,7 +2735,7 @@ test_insert_deposit (const struct InstanceData *instance, &deposit->amount_with_fee, &deposit->deposit_fee)); TEST_COND_RET_ON_FAIL ( - expected_result == + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == plugin->insert_deposit_confirmation (plugin->cls, instance->instance.id, deposit->timestamp, @@ -3348,7 +3348,7 @@ run_test_deposits (struct TestDeposits_Closure *cls) &cls->signkey, &cls->deposits[0], GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); - /* Test double inserts fail */ + /* Test double inserts are idempotent */ TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, &cls->signkey, &cls->deposits[0],