merchant

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

commit 5779a18b87620c4d62e3841c82879c42e099c697
parent e74802272f11e733b2ddbf405b20a8dbf2b304f8
Author: Florian Dold <dold@taler.net>
Date:   Tue, 25 Aug 2026 23:10:08 +0200

merchant orders: report and repair wired status

Diffstat:
Msrc/backend/taler-merchant-httpd_get-config.c | 2+-
Msrc/backend/taler-merchant-httpd_get-private-orders.c | 4+++-
Asrc/backenddb/sql-schema/merchant-0045.sql | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/sql-schema/meson.build | 1+
Msrc/include/taler/merchant/get-private-orders.h | 7+++++++
Msrc/lib/merchant_api_get-config.c | 4++--
Msrc/lib/merchant_api_get-private-orders.c | 4++++
Msrc/testing/test_merchant_transfer_tracking.sh | 34++++++++++++++++++++++++++++++++++
8 files changed, 138 insertions(+), 4 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-config.c b/src/backend/taler-merchant-httpd_get-config.c @@ -44,7 +44,7 @@ * #MERCHANT_PROTOCOL_CURRENT and #MERCHANT_PROTOCOL_AGE in * merchant_api_get_config.c! */ -#define MERCHANT_PROTOCOL_VERSION "37:0:25" +#define MERCHANT_PROTOCOL_VERSION "38:0:26" /** diff --git a/src/backend/taler-merchant-httpd_get-private-orders.c b/src/backend/taler-merchant-httpd_get-private-orders.c @@ -789,7 +789,9 @@ add_order (void *cls, GNUNET_JSON_pack_bool ("refundable", refundable), GNUNET_JSON_pack_bool ("paid", - paid)))); + paid), + GNUNET_JSON_pack_bool ("wired", + wired)))); break; case POF_CSV: { diff --git a/src/backenddb/sql-schema/merchant-0045.sql b/src/backenddb/sql-schema/merchant-0045.sql @@ -0,0 +1,86 @@ +-- +-- 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-0045.sql +-- @brief repair settlement flags left stale by the reconciliation SQL bug + +BEGIN; + +SELECT _v.register_patch('merchant-0045', NULL, NULL); + +SET search_path TO merchant; + +CREATE PROCEDURE merchant.merchant_0045_init(s TEXT) + LANGUAGE plpgsql + AS $OUTER$ +BEGIN + + EXECUTE format('SET LOCAL search_path TO %I', s); + + -- From July 2025 until April 2026, the reconciliation query missed + -- parentheses around its retry/WTID test. An unrelated deposit without a + -- WTID could therefore keep completed deposit confirmations wire-pending. + -- Reconstruct the flag from the per-deposit settlement evidence. Be + -- conservative about NULL retry state and require at least one deposit. + UPDATE merchant_deposit_confirmations mdc + SET wire_pending=FALSE + WHERE mdc.wire_pending + AND EXISTS + (SELECT 1 + FROM merchant_deposits md + WHERE md.deposit_confirmation_serial= + mdc.deposit_confirmation_serial) + AND NOT EXISTS + (SELECT 1 + FROM merchant_deposits md + WHERE md.deposit_confirmation_serial= + mdc.deposit_confirmation_serial + AND (COALESCE(md.settlement_retry_needed, TRUE) + OR (md.settlement_wtid IS NULL) + OR (COALESCE(md.settlement_last_ec, 0) <> 0))); + + -- A paid contract is wired once all of its deposit confirmations are done. + -- Require a confirmation so externally marked payments are not changed. + -- This migration deliberately does not generate historical notifications + -- or order-settled webhooks. + UPDATE merchant_contract_terms mct + SET wired=TRUE + WHERE mct.paid + AND NOT mct.wired + AND EXISTS + (SELECT 1 + FROM merchant_deposit_confirmations mdc + WHERE mdc.order_serial=mct.order_serial) + AND NOT EXISTS + (SELECT 1 + FROM merchant_deposit_confirmations mdc + WHERE mdc.order_serial=mct.order_serial + AND mdc.wire_pending); + + SET LOCAL search_path TO merchant; + +END +$OUTER$; + +INSERT INTO merchant.instance_fixups + (migration_name + ,version) + VALUES + ('merchant_0045_init' + ,45); +-- Apply new fix-up to existing instances +CALL merchant.fixup_instance_schema (45::INT8); + +COMMIT; diff --git a/src/backenddb/sql-schema/meson.build b/src/backenddb/sql-schema/meson.build @@ -139,6 +139,7 @@ generated_sql = [ ['merchant-0042.sql'], ['merchant-0043.sql'], ['merchant-0044.sql'], + ['merchant-0045.sql'], ] foreach g : generated_sql diff --git a/src/include/taler/merchant/get-private-orders.h b/src/include/taler/merchant/get-private-orders.h @@ -490,6 +490,13 @@ struct TALER_MERCHANT_GetPrivateOrdersOrderEntry bool paid; /** + * True if the exchange reports that it wired the order's funds. + * False if the response came from a backend older than protocol v38. + * @since protocol v38. + */ + bool wired; + + /** * Deadline by which the order must be paid. * Zero if the response came from a backend older than protocol v37. * @since protocol v37. diff --git a/src/lib/merchant_api_get-config.c b/src/lib/merchant_api_get-config.c @@ -34,12 +34,12 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define MERCHANT_PROTOCOL_CURRENT 37 +#define MERCHANT_PROTOCOL_CURRENT 38 /** * How many configs are we backwards-compatible with? */ -#define MERCHANT_PROTOCOL_AGE 13 +#define MERCHANT_PROTOCOL_AGE 14 /** * How many exchanges do we allow at most per merchant? diff --git a/src/lib/merchant_api_get-private-orders.c b/src/lib/merchant_api_get-private-orders.c @@ -208,6 +208,10 @@ parse_orders (const json_t *ia, &ie->refundable), GNUNET_JSON_spec_bool ("paid", &ie->paid), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("wired", + &ie->wired), + NULL), GNUNET_JSON_spec_end () }; diff --git a/src/testing/test_merchant_transfer_tracking.sh b/src/testing/test_merchant_transfer_tracking.sh @@ -228,6 +228,23 @@ check_order_listing 'max_age_s=3600' some check_order_listing 'max_age_s=0' none echo " OK" +echo -n "Checking unwired value in order listing..." +STATUS=$(curl 'http://localhost:9966/instances/test/private/orders?limit=20' \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") +if [ "$STATUS" != "200" ] +then + cat "$LAST_RESPONSE" + exit_fail "Expected 200 ok listing unwired order. got: $STATUS" +fi +LIST_WIRED=$(jq -e -r --arg order_id "$ORDER_ID" \ + '.orders[] | select(.order_id == $order_id) | .wired' < "$LAST_RESPONSE") +if [ "$LIST_WIRED" != "false" ] +then + jq . < "$LAST_RESPONSE" + exit_fail "Order list .wired was not false before settlement" +fi +echo " OK" + # "max_age" was deprecated in protocol v33, but MUST still work. echo -n "Checking order listing with deprecated max_age..." check_order_listing 'max_age=3600000' some @@ -511,6 +528,23 @@ fi echo " OK" +echo -n "Checking wired value and filter in order listing..." +STATUS=$(curl 'http://localhost:9966/instances/test/private/orders?limit=20&wired=YES' \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") +if [ "$STATUS" != "200" ] +then + cat "$LAST_RESPONSE" + exit_fail "Expected 200 ok listing wired order. got: $STATUS" +fi +LIST_WIRED=$(jq -e -r --arg order_id "$ORDER_ID" \ + '.orders[] | select(.order_id == $order_id) | .wired' < "$LAST_RESPONSE") +if [ "$LIST_WIRED" != "true" ] +then + jq . < "$LAST_RESPONSE" + exit_fail "Order list .wired was not true after settlement" +fi +echo " OK" + echo "================== 2nd order ====================== "