commit 4190d65777940d270ebc9b1f5cd0ff36a61b3d5b
parent 26b591d96d4ab359f02d706174b3273be1141eaf
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 10 Jul 2025 20:59:56 +0200
fix for #9454
Diffstat:
52 files changed, 1926 insertions(+), 1297 deletions(-)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -127,6 +127,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-get-otp-devices.h \
taler-merchant-httpd_private-get-otp-devices-ID.c \
taler-merchant-httpd_private-get-otp-devices-ID.h \
+ taler-merchant-httpd_private-get-incoming.c \
+ taler-merchant-httpd_private-get-incoming.h \
taler-merchant-httpd_private-get-transfers.c \
taler-merchant-httpd_private-get-transfers.h \
taler-merchant-httpd_private-get-templates.c \
@@ -267,6 +269,7 @@ taler_merchant_reconciliation_LDADD = \
-ltalerjson \
-ltalerutil \
-ltalerpq \
+ -lgnunetpq \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2024 Taler Systems SA
+ 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 Affero General Public License as published by the Free Software
@@ -424,7 +424,6 @@ deposit_get_cb (
case MHD_HTTP_OK:
{
enum GNUNET_DB_QueryStatus qs;
- bool cleared = false;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange returned wire transfer over %s for deposited coin %s\n",
@@ -433,25 +432,10 @@ deposit_get_cb (
qs = db_plugin->insert_deposit_to_transfer (
db_plugin->cls,
w->deposit_serial,
- &dr->details.ok,
- &cleared);
- if (qs < 0)
- {
- GNUNET_break (0);
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (! cleared)
- {
- qs = db_plugin->update_deposit_confirmation_status (
- db_plugin->cls,
- w->deposit_serial,
- true, /* this failed, wire_pending remains true */
- GNUNET_TIME_absolute_to_timestamp (future_retry),
- w->retry_backoff,
- "wire transfer unknown");
- }
- if (qs < 0)
+ &w->h_wire,
+ exchange_url,
+ &dr->details.ok);
+ if (qs <= 0)
{
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
@@ -493,9 +477,10 @@ deposit_get_cb (
qs = db_plugin->update_deposit_confirmation_status (
db_plugin->cls,
w->deposit_serial,
- true, /* wire_pending is still true! */
+ true, /* need to try again in the future! */
GNUNET_TIME_absolute_to_timestamp (future_retry),
- w->retry_backoff,
+ MHD_HTTP_ACCEPTED,
+ TALER_EC_NONE,
"Exchange reported 202 Accepted but no KYC block");
if (qs < 0)
{
@@ -517,9 +502,10 @@ deposit_get_cb (
qs = db_plugin->update_deposit_confirmation_status (
db_plugin->cls,
w->deposit_serial,
- true,
+ true /* need to try again in the future */,
GNUNET_TIME_absolute_to_timestamp (future_retry),
- w->retry_backoff,
+ MHD_HTTP_ACCEPTED,
+ TALER_EC_NONE,
"Exchange reported 202 Accepted due to KYC/AML block");
if (qs < 0)
{
@@ -533,32 +519,50 @@ deposit_get_cb (
default:
{
enum GNUNET_DB_QueryStatus qs;
- char *msg;
+ bool retry_needed;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange %s returned tracking failure for deposited coin %s\n",
+ "Exchange %s returned tracking failure for deposited coin %s: %u\n",
exchange_url,
- TALER_B2S (&w->coin_pub));
- GNUNET_asprintf (&msg,
- "Unexpected exchange status %u (#%d, %s)\n",
- dr->hr.http_status,
- (int) dr->hr.ec,
- dr->hr.hint);
+ TALER_B2S (&w->coin_pub),
+ dr->hr.http_status);
+ /* rough classification by HTTP status group */
+ switch (dr->hr.http_status / 100)
+ {
+ case 0:
+ /* timeout */
+ retry_needed = true;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ /* very strange */
+ retry_needed = false;
+ break;
+ case 4:
+ /* likely fatal */
+ retry_needed = false;
+ break;
+ case 5:
+ /* likely transient */
+ retry_needed = true;
+ break;
+ }
qs = db_plugin->update_deposit_confirmation_status (
db_plugin->cls,
w->deposit_serial,
- true, /* this failed, wire_pending remains true */
+ retry_needed,
GNUNET_TIME_absolute_to_timestamp (future_retry),
- w->retry_backoff,
- msg);
- GNUNET_free (msg);
+ (uint32_t) dr->hr.http_status,
+ dr->hr.ec,
+ dr->hr.hint);
if (qs < 0)
{
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
}
- return;
+ break;
}
} /* end switch */
@@ -569,9 +573,14 @@ deposit_get_cb (
GNUNET_free (w->instance_id);
GNUNET_free (w);
GNUNET_assert (NULL != keys);
- if ( (w_count < CONCURRENCY_LIMIT / 2) ||
- (0 == w_count) )
+ if (0 == w_count)
{
+ /* We only SELECT() again after having finished
+ all requests, as otherwise we'll most like
+ just SELECT() those again that are already
+ being requested; alternatively, we could
+ update the retry_time already on SELECT(),
+ but this should be easier on the DB. */
if (NULL != task)
GNUNET_SCHEDULER_cancel (task);
task = GNUNET_SCHEDULER_add_now (&select_work,
@@ -590,7 +599,8 @@ deposit_get_cb (
* @param h_contract_terms hash of the contract terms
* @param merchant_priv private key of the merchant
* @param instance_id row ID of the instance
- * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param h_wire hash of the merchant's wire account into
+ * @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin which the deposit was made
* @param coin_pub public key of the deposited coin
*/
@@ -599,7 +609,7 @@ pending_deposits_cb (
void *cls,
uint64_t deposit_serial,
struct GNUNET_TIME_Absolute wire_deadline,
- struct GNUNET_TIME_Relative retry_backoff,
+ struct GNUNET_TIME_Absolute retry_time,
const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const char *instance_id,
@@ -609,20 +619,30 @@ pending_deposits_cb (
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct ExchangeInteraction *w;
+ struct GNUNET_TIME_Absolute mx
+ = GNUNET_TIME_absolute_max (wire_deadline,
+ retry_time);
+ struct GNUNET_TIME_Relative retry_backoff;
(void) cls;
- if (GNUNET_TIME_absolute_is_future (wire_deadline))
+ if (GNUNET_TIME_absolute_is_future (mx))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Pending deposit has deadline in the future at %s\n",
- GNUNET_TIME_absolute2s (wire_deadline));
- run_at (wire_deadline);
+ "Pending deposit should be checked next at %s\n",
+ GNUNET_TIME_absolute2s (mx));
+ run_at (mx);
return;
}
+ if (GNUNET_TIME_absolute_is_zero (retry_time))
+ retry_backoff = GNUNET_TIME_absolute_get_duration (wire_deadline);
+ else
+ retry_backoff = GNUNET_TIME_absolute_get_difference (wire_deadline,
+ retry_time);
w = GNUNET_new (struct ExchangeInteraction);
w->deposit_serial = deposit_serial;
w->wire_deadline = wire_deadline;
- w->retry_backoff = GNUNET_TIME_STD_BACKOFF (retry_backoff);
+ w->retry_backoff = GNUNET_TIME_randomized_backoff (retry_backoff,
+ GNUNET_TIME_UNIT_DAYS);
w->h_contract_terms = *h_contract_terms;
w->merchant_priv = *merchant_priv;
w->h_wire = *h_wire;
@@ -766,6 +786,14 @@ select_work (void *cls)
static struct GNUNET_OS_Process *
start_worker (const char *base_url)
{
+ char toff[30];
+ long long zo;
+
+ zo = GNUNET_TIME_get_offset ();
+ GNUNET_snprintf (toff,
+ sizeof (toff),
+ "%lld",
+ zo);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Launching worker for exchange `%s' using `%s`\n",
base_url,
@@ -782,6 +810,7 @@ start_worker (const char *base_url)
"taler-merchant-depositcheck",
"-e", base_url,
"-L", "INFO",
+ "-T", toff,
test_mode ? "-t" : NULL,
NULL);
return GNUNET_OS_start_process (
@@ -794,6 +823,7 @@ start_worker (const char *base_url)
"-c", cfg_filename,
"-e", base_url,
"-L", "INFO",
+ "-T", toff,
test_mode ? "-t" : NULL,
NULL);
}
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -50,6 +50,7 @@
#include "taler-merchant-httpd_private-get-accounts-ID.h"
#include "taler-merchant-httpd_private-get-categories.h"
#include "taler-merchant-httpd_private-get-categories-ID.h"
+#include "taler-merchant-httpd_private-get-incoming.h"
#include "taler-merchant-httpd_private-get-instances.h"
#include "taler-merchant-httpd_private-get-instances-ID.h"
#include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
@@ -1554,6 +1555,14 @@ url_handler (void *cls,
.allow_deleted_instance = true,
.handler = &TMH_private_get_transfers
},
+ /* GET /incoming: */
+ {
+ .url_prefix = "/incoming",
+ .permission = "transfers-read",
+ .method = MHD_HTTP_METHOD_GET,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_get_incoming
+ },
/* POST /otp-devices: */
{
.url_prefix = "/otp-devices",
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -933,7 +933,10 @@ batch_deposit_transaction (const struct ExchangeGroup *eg,
&dc->cdd.coin_sig,
&dc->cdd.amount,
&dc->deposit_fee,
- &dc->refund_fee);
+ &dc->refund_fee,
+ GNUNET_TIME_absolute_add (
+ pc->check_contract.contract_terms->wire_deadline.abs_time,
+ GNUNET_TIME_randomize (GNUNET_TIME_UNIT_MINUTES)));
if (qs < 0)
return qs;
GNUNET_break (qs > 0);
diff --git a/src/backend/taler-merchant-httpd_private-get-incoming.c b/src/backend/taler-merchant-httpd_private-get-incoming.c
@@ -0,0 +1,193 @@
+/*
+ This file is part of TALER
+ (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 taler-merchant-httpd_private-get-incoming.c
+ * @brief implement API for obtaining a list of expected incoming wire transfers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_private-get-incoming.h"
+
+
+/**
+ * Function called with information about a wire transfer.
+ * Generate a response (array entry) based on the given arguments.
+ *
+ * @param cls closure with a `json_t *` array to build up the response
+ * @param expected_credit_amount amount expected to be wired to the merchant (minus fees), NULL if unknown
+ * @param wtid wire transfer identifier
+ * @param payto_uri target account that received the wire transfer
+ * @param exchange_url base URL of the exchange that made the wire transfer
+ * @param transfer_serial_id serial number identifying the transfer in the backend
+ * @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_ABS
+ * if it did not yet happen
+ * @param confirmed true if the merchant acknowledged the wire transfer reception
+ * @param validated true if the reconciliation succeeded
+ * @param last_http_status HTTP status of our last request to the exchange for this transfer
+ * @param last_ec last error code we got back (otherwise #TALER_EC_NONE)
+ * @param last_error_detail last detail we got back (or NULL for none)
+ */
+static void
+incoming_cb (void *cls,
+ const struct TALER_Amount *expected_credit_amount,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_FullPayto payto_uri,
+ const char *exchange_url,
+ uint64_t expected_transfer_serial_id,
+ struct GNUNET_TIME_Timestamp execution_time,
+ bool confirmed,
+ bool validated,
+ unsigned int last_http_status,
+ enum TALER_ErrorCode last_ec,
+ const char *last_error_detail)
+{
+ json_t *ja = cls;
+ json_t *r;
+
+ r = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("expected_credit_amount",
+ expected_credit_amount)),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_full_payto ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_string ("exchange_url",
+ exchange_url),
+ GNUNET_JSON_pack_uint64 ("expected_transfer_serial_id",
+ expected_transfer_serial_id),
+ GNUNET_JSON_pack_timestamp ("execution_time",
+ execution_time),
+ GNUNET_JSON_pack_bool ("validated",
+ validated),
+ GNUNET_JSON_pack_bool ("confirmed",
+ confirmed),
+ GNUNET_JSON_pack_uint64 ("last_http_status",
+ last_http_status),
+ GNUNET_JSON_pack_uint64 ("last_ec",
+ last_ec),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("last_error_detail",
+ last_error_detail)));
+ GNUNET_assert (0 ==
+ json_array_append_new (ja,
+ r));
+}
+
+
+/**
+ * Manages a GET /private/incoming call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_incoming (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TALER_FullPayto payto_uri = {
+ .full_payto = NULL
+ };
+ struct GNUNET_TIME_Timestamp before = GNUNET_TIME_UNIT_FOREVER_TS;
+ struct GNUNET_TIME_Timestamp after = GNUNET_TIME_UNIT_ZERO_TS;
+ int64_t limit = -20;
+ uint64_t offset;
+ enum TALER_EXCHANGE_YesNoAll confirmed;
+ enum TALER_EXCHANGE_YesNoAll verified;
+
+ (void) rh;
+ TALER_MHD_parse_request_snumber (connection,
+ "limit",
+ &limit);
+ if (limit < 0)
+ offset = INT64_MAX;
+ else
+ offset = 0;
+ TALER_MHD_parse_request_number (connection,
+ "offset",
+ &offset);
+ TALER_MHD_parse_request_yna (connection,
+ "verified",
+ TALER_EXCHANGE_YNA_ALL,
+ &verified);
+ TALER_MHD_parse_request_yna (connection,
+ "confirmed",
+ TALER_EXCHANGE_YNA_ALL,
+ &confirmed);
+ TALER_MHD_parse_request_timestamp (connection,
+ "before",
+ &before);
+ TALER_MHD_parse_request_timestamp (connection,
+ "after",
+ &after);
+ {
+ const char *esc_payto;
+
+ esc_payto = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "payto_uri");
+ if (NULL != esc_payto)
+ {
+ payto_uri.full_payto
+ = GNUNET_strdup (esc_payto);
+ (void) MHD_http_unescape (payto_uri.full_payto);
+ }
+ }
+ TMH_db->preflight (TMH_db->cls);
+ {
+ json_t *ja;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ja = json_array ();
+ GNUNET_assert (NULL != ja);
+ qs = TMH_db->lookup_expected_transfers (TMH_db->cls,
+ hc->instance->settings.id,
+ payto_uri,
+ before,
+ after,
+ limit,
+ offset,
+ confirmed,
+ verified,
+ &incoming_cb,
+ ja);
+ GNUNET_free (payto_uri.full_payto);
+ if (0 > qs)
+ {
+ /* Simple select queries should not cause serialization issues */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "incoming");
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("incoming",
+ ja));
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-get-incoming.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-incoming.h b/src/backend/taler-merchant-httpd_private-get-incoming.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (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 taler-merchant-httpd_private-get-incoming.h
+ * @brief headers for GET /incoming handler
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_INCOMING_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_INCOMING_H
+#include <microhttpd.h>
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Manages a GET /private/incoming call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_incoming (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-transfers.c b/src/backend/taler-merchant-httpd_private-get-transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2024 Taler Systems SA
+ (C) 2014-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -30,16 +30,13 @@
* Generate a response (array entry) based on the given arguments.
*
* @param cls closure with a `json_t *` array to build up the response
- * @param credit_amount how much was wired to the merchant (minus fees)
+ * @param expected_credit_amount amount expected to be wired to the merchant (minus fees), NULL if unknown
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
* @param exchange_url base URL of the exchange that made the wire transfer
* @param transfer_serial_id serial number identifying the transfer in the backend
* @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_ABS
* if it did not yet happen
- * @param verified YES if we checked the exchange's answer and liked it,
- * NO if we checked the exchange's answer and it is problematic,
- * ALL if we did not yet check
* @param confirmed true if the merchant acknowledged the wire transfer reception
*/
static void
@@ -49,9 +46,8 @@ transfer_cb (void *cls,
struct TALER_FullPayto payto_uri,
const char *exchange_url,
uint64_t transfer_serial_id,
- struct GNUNET_TIME_Timestamp execution_time,
- bool verified,
- bool confirmed)
+ struct GNUNET_TIME_Absolute execution_time,
+ bool expected)
{
json_t *ja = cls;
json_t *r;
@@ -67,16 +63,17 @@ transfer_cb (void *cls,
exchange_url),
GNUNET_JSON_pack_uint64 ("transfer_serial_id",
transfer_serial_id),
+ // FIXME: protocol breaking to remove...
GNUNET_JSON_pack_bool ("verified",
- verified),
+ false),
+ // FIXME: protocol breaking to remove...
GNUNET_JSON_pack_bool ("confirmed",
- confirmed),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp (
- "execution_time",
- GNUNET_TIME_absolute_is_never (execution_time.abs_time)
- ? GNUNET_TIME_UNIT_ZERO_TS /* => field omitted */
- : execution_time)) );
+ true),
+ GNUNET_JSON_pack_bool ("expected",
+ expected),
+ GNUNET_JSON_pack_timestamp (
+ "execution_time",
+ GNUNET_TIME_absolute_to_timestamp (execution_time)));
GNUNET_assert (0 ==
json_array_append_new (ja,
r));
@@ -103,60 +100,9 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh,
struct GNUNET_TIME_Timestamp after = GNUNET_TIME_UNIT_ZERO_TS;
int64_t limit = -20;
uint64_t offset;
- enum TALER_EXCHANGE_YesNoAll verified;
+ enum TALER_EXCHANGE_YesNoAll expected;
(void) rh;
- {
- const char *esc_payto;
-
- esc_payto = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "payto_uri");
- if (NULL != esc_payto)
- {
- payto_uri.full_payto
- = GNUNET_strdup (esc_payto);
- (void) MHD_http_unescape (payto_uri.full_payto);
- }
- }
- {
- const char *before_s;
-
- before_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "before");
- if ( (NULL != before_s) &&
- (GNUNET_OK !=
- GNUNET_STRINGS_fancy_time_to_timestamp (before_s,
- &before)) )
- {
- GNUNET_break_op (0);
- GNUNET_free (payto_uri.full_payto);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "before");
- }
- }
- {
- const char *after_s;
-
- after_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "after");
- if ( (NULL != after_s) &&
- (GNUNET_OK !=
- GNUNET_STRINGS_fancy_time_to_timestamp (after_s,
- &after)) )
- {
- GNUNET_break_op (0);
- GNUNET_free (payto_uri.full_payto);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "after");
- }
- }
TALER_MHD_parse_request_snumber (connection,
"limit",
&limit);
@@ -167,17 +113,28 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh,
TALER_MHD_parse_request_number (connection,
"offset",
&offset);
- if (! (TALER_MHD_arg_to_yna (connection,
- "verified",
+ TALER_MHD_parse_request_yna (connection,
+ "expected",
TALER_EXCHANGE_YNA_ALL,
- &verified)) )
+ &expected);
+ TALER_MHD_parse_request_timestamp (connection,
+ "before",
+ &before);
+ TALER_MHD_parse_request_timestamp (connection,
+ "after",
+ &after);
{
- GNUNET_break_op (0);
- GNUNET_free (payto_uri.full_payto);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "verified");
+ const char *esc_payto;
+
+ esc_payto = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "payto_uri");
+ if (NULL != esc_payto)
+ {
+ payto_uri.full_payto
+ = GNUNET_strdup (esc_payto);
+ (void) MHD_http_unescape (payto_uri.full_payto);
+ }
}
TMH_db->preflight (TMH_db->cls);
{
@@ -193,7 +150,7 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh,
after,
limit,
offset,
- verified,
+ expected,
&transfer_cb,
ja);
GNUNET_free (payto_uri.full_payto);
diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c b/src/backend/taler-merchant-httpd_private-post-transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2023 Taler Systems SA
+ (C) 2014-2023, 2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -32,7 +32,7 @@
/**
* How often do we retry the simple INSERT database transaction?
*/
-#define MAX_RETRIES 3
+#define MAX_RETRIES 5
MHD_RESULT
@@ -91,13 +91,7 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
&wtid,
&amount,
payto_uri,
- true /* confirmed! */);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- qs = TMH_db->set_transfer_status_to_confirmed (TMH_db->cls,
- hc->instance->settings.id,
- exchange_url,
- &wtid,
- &amount);
+ 0 /* no bank serial known! */);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -111,13 +105,13 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
TMH_db->rollback (TMH_db->cls);
continue;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Could not set to confirmed, must differ by amount! */
+ /* Must mean the bank account is unknown! */
TMH_db->rollback (TMH_db->cls);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION,
- NULL);
+ payto_uri.full_payto);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
@@ -151,6 +145,7 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
"post-transfer committed successfully\n");
break;
}
+ break;
}
return TALER_MHD_reply_static (connection,
MHD_HTTP_NO_CONTENT,
diff --git a/src/backend/taler-merchant-reconciliation.c b/src/backend/taler-merchant-reconciliation.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023-2024 Taler Systems SA
+ Copyright (C) 2023-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -154,11 +154,6 @@ struct Inquiry
struct TALER_WireTransferIdentifierRawP wtid;
/**
- * Amount of the wire transfer.
- */
- struct TALER_Amount total;
-
- /**
* Row of the wire transfer in our database.
*/
uint64_t rowid;
@@ -282,28 +277,29 @@ launch_inquiries_at_exchange (struct Exchange *e)
*
* @param w inquiry to update status for
* @param next_attempt when should we retry @a w (if ever)
+ * @param http_status HTTP status of the response
* @param ec error code to use (if any)
- * @param failed failure status (if ultimately failed)
- * @param verified success status (if ultimately successful)
+ * @param last_hint hint delivered with the response (if any, possibly NULL)
+ * @param needs_retry true if we should try the HTTP request again
*/
static void
update_transaction_status (const struct Inquiry *w,
struct GNUNET_TIME_Absolute next_attempt,
+ unsigned int http_status,
enum TALER_ErrorCode ec,
- bool failed,
- bool verified)
+ const char *last_hint,
+ bool needs_retry)
{
enum GNUNET_DB_QueryStatus qs;
- if (failed)
- found_problem = true;
qs = db_plugin->update_transfer_status (db_plugin->cls,
w->exchange->exchange_url,
&w->wtid,
next_attempt,
+ http_status,
ec,
- failed,
- verified);
+ last_hint,
+ needs_retry);
if (qs < 0)
{
GNUNET_break (0);
@@ -725,18 +721,13 @@ wire_transfer_cb (void *cls,
break;
case MHD_HTTP_BAD_REQUEST:
case MHD_HTTP_FORBIDDEN:
- update_transaction_status (w,
- GNUNET_TIME_UNIT_FOREVER_ABS,
- TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_HARD_FAILURE,
- true,
- false);
- end_inquiry (w);
- return;
case MHD_HTTP_NOT_FOUND:
+ found_problem = true;
update_transaction_status (w,
GNUNET_TIME_UNIT_FOREVER_ABS,
- TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_FATAL_NOT_FOUND,
- true,
+ tgr->hr.http_status,
+ tgr->hr.ec,
+ tgr->hr.hint,
false);
end_inquiry (w);
return;
@@ -747,12 +738,14 @@ wire_transfer_cb (void *cls,
update_transaction_status (w,
GNUNET_TIME_relative_to_absolute (
e->transfer_delay),
- TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
- false,
- false);
+ tgr->hr.http_status,
+ tgr->hr.ec,
+ tgr->hr.hint,
+ true);
end_inquiry (w);
return;
default:
+ found_problem = true;
e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected HTTP status %u\n",
@@ -760,9 +753,10 @@ wire_transfer_cb (void *cls,
update_transaction_status (w,
GNUNET_TIME_relative_to_absolute (
e->transfer_delay),
- TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
- false,
- false);
+ tgr->hr.http_status,
+ tgr->hr.ec,
+ tgr->hr.hint,
+ true);
end_inquiry (w);
return;
}
@@ -785,6 +779,11 @@ wire_transfer_cb (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
+ // FIXME: insert_transfer_details has more complex
+ // error possibilities inside, expose them here
+ // and persist them with the transaction status
+ // if they arise (especially no_account, no_exchange, conflict)
+ // -- not sure how no_instance could happen...
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -863,9 +862,10 @@ wire_transfer_cb (void *cls,
? GNUNET_TIME_UNIT_FOREVER_ABS
: GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_UNIT_MINUTES),
+ MHD_HTTP_OK,
ctc.ec,
- ctc.failure,
- false);
+ NULL /* no hint */,
+ ! ctc.failure);
end_inquiry (w);
return;
}
@@ -879,41 +879,33 @@ wire_transfer_cb (void *cls,
GNUNET_break_op (0);
update_transaction_status (w,
GNUNET_TIME_UNIT_FOREVER_ABS,
+ MHD_HTTP_OK,
TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE,
- true,
+ TALER_amount2s (&td->wire_fee),
false);
end_inquiry (w);
return;
}
- if ( (GNUNET_OK !=
- TALER_amount_cmp_currency (&td->total_amount,
- &w->total)) ||
- (0 !=
- TALER_amount_cmp (&td->total_amount,
- &w->total)) )
{
- GNUNET_break_op (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Wire transfer total value was %s\n",
- TALER_amount2s (&w->total));
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange claimed total value to be %s\n",
- TALER_amount2s (&td->total_amount));
- update_transaction_status (w,
- GNUNET_TIME_UNIT_FOREVER_ABS,
- TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_CONFLICTING_TRANSFERS,
- true,
- false);
- end_inquiry (w);
- return;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->finalize_transfer_status (db_plugin->cls,
+ w->exchange->exchange_url,
+ &w->wtid,
+ &td->h_details,
+ &td->total_amount,
+ &td->wire_fee,
+ &td->exchange_pub,
+ &td->exchange_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
}
- /* set transaction to successful */
- update_transaction_status (w,
- GNUNET_TIME_UNIT_FOREVER_ABS,
- TALER_EC_NONE,
- false,
- true);
end_inquiry (w);
}
@@ -947,9 +939,10 @@ exchange_request (void *cls)
update_transaction_status (w,
GNUNET_TIME_relative_to_absolute (
e->transfer_delay),
+ 0 /* failed to begin */,
TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
- false,
- false);
+ "Failed to initiate GET request at exchange",
+ true);
end_inquiry (w);
return;
}
@@ -957,9 +950,10 @@ exchange_request (void *cls)
update_transaction_status (w,
GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_UNIT_MINUTES),
+ 0 /* timeout */,
TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_LIST,
- false,
- false);
+ "Initiated GET with exchange",
+ true);
}
@@ -973,7 +967,6 @@ exchange_request (void *cls)
* @param exchange_url base URL of the exchange that initiated the transfer
* @param payto_uri account of the merchant that received the transfer
* @param wtid wire transfer subject identifying the aggregation
- * @param total total amount that was wired
* @param next_attempt when should we next try to interact with the exchange
*/
static void
@@ -984,7 +977,6 @@ start_inquiry (
const char *exchange_url,
struct TALER_FullPayto payto_uri,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *total,
struct GNUNET_TIME_Absolute next_attempt)
{
struct Exchange *e;
@@ -999,6 +991,8 @@ start_inquiry (
NULL);
return;
}
+ active_inquiries++;
+
e = find_exchange (exchange_url);
for (w = e->w_head; NULL != w; w = w->next)
{
@@ -1012,13 +1006,11 @@ start_inquiry (
}
}
- active_inquiries++;
w = GNUNET_new (struct Inquiry);
w->payto_uri.full_payto = GNUNET_strdup (payto_uri.full_payto);
w->instance_id = GNUNET_strdup (instance_id);
w->rowid = rowid;
w->wtid = *wtid;
- w->total = *total;
GNUNET_CONTAINER_DLL_insert (e->w_head,
e->w_tail,
w);
@@ -1030,9 +1022,10 @@ start_inquiry (
update_transaction_status (w,
GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_UNIT_MINUTES),
+ 0 /* timeout */,
TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_KEYS,
- false,
- false);
+ exchange_url,
+ true);
}
@@ -1204,7 +1197,7 @@ run (void *cls,
{
struct GNUNET_DB_EventHeaderP es = {
.size = htons (sizeof (es)),
- .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED)
+ .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_EXPECTED)
};
eh = db_plugin->event_listen (db_plugin->cls,
diff --git a/src/backend/taler-merchant-wirewatch.c b/src/backend/taler-merchant-wirewatch.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2023, 2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -379,52 +379,7 @@ credit_cb (
&wtid,
&details->amount,
details->credit_account_uri,
- true /* confirmed */);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- struct TALER_Amount total;
- struct TALER_Amount wfee;
- struct TALER_Amount eamount;
- struct GNUNET_TIME_Timestamp timestamp;
- bool have_esig;
- bool verified;
-
- qs = db_plugin->lookup_transfer (db_plugin->cls,
- w->instance_id,
- exchange_url,
- &wtid,
- &total,
- &wfee,
- &eamount,
- ×tamp,
- &have_esig,
- &verified);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Inserting transfer for %s into database failed. Is the credit account %s configured correctly?\n",
- w->instance_id,
- details->credit_account_uri.full_payto);
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (0 !=
- TALER_amount_cmp (&total,
- &details->amount))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Inserting transfer for %s into database failed. An entry exists for a different transfer amount (%s)!\n",
- w->instance_id,
- TALER_amount2s (&total));
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Inserting transfer for %s into database failed. An equivalent entry already exists.\n",
- w->instance_id);
- }
- }
- }
+ serial_id);
GNUNET_free (exchange_url);
if (qs < 0)
{
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
@@ -37,6 +37,7 @@ sql_DATA = \
merchant-0018.sql \
merchant-0019.sql \
merchant-0020.sql \
+ merchant-0021.sql \
drop.sql
BUILT_SOURCES = \
@@ -141,6 +142,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_lookup_deposits.h pg_lookup_deposits.c \
pg_lookup_deposits_by_contract_and_coin.h pg_lookup_deposits_by_contract_and_coin.c \
pg_lookup_deposits_by_order.h pg_lookup_deposits_by_order.c \
+ pg_lookup_expected_transfers.h pg_lookup_expected_transfers.c \
pg_lookup_instance_auth.h pg_lookup_instance_auth.c \
pg_lookup_instances.h pg_lookup_instances.c \
pg_lookup_login_tokens.h pg_lookup_login_tokens.c \
@@ -165,7 +167,6 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_lookup_token_family.h pg_lookup_token_family.c \
pg_lookup_token_family_key.h pg_lookup_token_family_key.c \
pg_lookup_token_family_keys.h pg_lookup_token_family_keys.c \
- pg_lookup_transfer.h pg_lookup_transfer.c \
pg_lookup_transfer_details.h pg_lookup_transfer_details.c \
pg_lookup_transfer_details_by_order.h pg_lookup_transfer_details_by_order.c \
pg_lookup_transfer_summary.h pg_lookup_transfer_summary.c \
@@ -190,7 +191,6 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_select_otp.h pg_select_otp.c \
pg_select_otp_serial.h pg_select_otp_serial.c \
pg_select_wirewatch_accounts.h pg_select_wirewatch_accounts.c \
- pg_set_transfer_status_to_confirmed.h pg_set_transfer_status_to_confirmed.c \
pg_store_wire_fee_by_exchange.h pg_store_wire_fee_by_exchange.c \
pg_unlock_inventory.h pg_unlock_inventory.c \
pg_update_account.h pg_update_account.c \
@@ -205,6 +205,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_update_template.h pg_update_template.c \
pg_update_token_family.h pg_update_token_family.c \
pg_update_transfer_status.h pg_update_transfer_status.c \
+ pg_finalize_transfer_status.h pg_finalize_transfer_status.c \
pg_update_webhook.h pg_update_webhook.c \
pg_update_wirewatch_progress.h pg_update_wirewatch_progress.c \
pg_lookup_statistics_counter_by_bucket.h pg_lookup_statistics_counter_by_bucket.c \
diff --git a/src/backenddb/merchant-0021.sql b/src/backenddb/merchant-0021.sql
@@ -0,0 +1,295 @@
+--
+-- 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 merchant-0021.sql
+-- @brief Tables for statistics
+-- @author Christian Grothoff
+
+
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('merchant-0021', NULL, NULL);
+
+SET search_path TO merchant;
+
+COMMENT ON TABLE merchant_transfers
+ IS 'table represents confirmed incoming wire transfers';
+COMMENT ON COLUMN merchant_transfers.credit_amount
+ IS 'actual value of the confirmed wire transfer';
+
+CREATE TABLE merchant_expected_transfers
+ (expected_credit_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,exchange_url TEXT NOT NULL
+ ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)
+ ,expected_credit_amount taler_amount_currency
+ ,wire_fee taler_amount_currency
+ ,account_serial INT8 NOT NULL
+ REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE
+ ,expected_time INT8 NOT NULL
+ ,retry_time INT8 NOT NULL DEFAULT (0)
+ ,last_http_status INT4 DEFAULT NULL
+ ,last_ec INT4 DEFAULT NULL
+ ,last_detail TEXT DEFAULT NULL
+ ,retry_needed BOOLEAN NOT NULL DEFAULT TRUE
+ ,signkey_serial BIGINT
+ REFERENCES merchant_exchange_signing_keys (signkey_serial)
+ ON DELETE CASCADE
+ ,exchange_sig BYTEA CHECK (LENGTH(exchange_sig)=64) DEFAULT NULL
+ ,h_details BYTEA CHECK (LENGTH(h_details)=64) DEFAULT NULL
+ ,confirmed BOOLEAN NOT NULL DEFAULT FALSE
+ ,UNIQUE (wtid, exchange_url, account_serial)
+ );
+COMMENT ON TABLE merchant_expected_transfers
+ IS 'expected incoming wire transfers';
+COMMENT ON COLUMN merchant_expected_transfers.expected_credit_serial
+ IS 'Unique identifier for this expected wire transfer in this backend';
+COMMENT ON COLUMN merchant_expected_transfers.exchange_url
+ IS 'Base URL of the exchange that originated the wire transfer as extracted from the wire transfer subject';
+COMMENT ON COLUMN merchant_expected_transfers.wtid
+ IS 'Unique wire transfer identifier (or at least, should be unique by protocol) as selected by the exchange and extracted from the wire transfer subject';
+COMMENT ON COLUMN merchant_expected_transfers.expected_credit_amount
+ IS 'expected actual value of the (aggregated) wire transfer, excluding the wire fee; NULL if unknown';
+COMMENT ON COLUMN merchant_expected_transfers.wire_fee
+ IS 'wire fee the exchange claims to have charged us; NULL if unknown';
+COMMENT ON COLUMN merchant_expected_transfers.account_serial
+ IS 'Merchant bank account that should receive this wire transfer; also implies the merchant instance implicated by the wire transfer';
+COMMENT ON COLUMN merchant_expected_transfers.expected_time
+ IS 'Time when we should expect the exchange do do the wire transfer';
+COMMENT ON COLUMN merchant_expected_transfers.retry_time
+ IS 'Time when we should next inquire at the exchange about this wire transfer; used by taler-merchant-reconciliation to limit retries with the exchange in case of failures';
+COMMENT ON COLUMN merchant_expected_transfers.last_http_status
+ IS 'HTTP status of the last request to the exchange, 0 on timeout or if there was no request (200 on success)';
+COMMENT ON COLUMN merchant_expected_transfers.last_ec
+ IS 'Taler error code from the last request to the exchange, 0 on success or if there was no request';
+COMMENT ON COLUMN merchant_expected_transfers.last_detail
+ IS 'Taler error detail from the last request to the exchange, NULL on success or if there was no request';
+COMMENT ON COLUMN merchant_expected_transfers.signkey_serial
+ IS 'Identifies the online signing key of the exchange used to make the exchange_sig';
+COMMENT ON COLUMN merchant_expected_transfers.exchange_sig
+ IS 'Signature over the aggregation response from the exchange, or NULL on error or if we did not yet make that request';
+COMMENT ON COLUMN merchant_expected_transfers.confirmed
+ IS 'true once the merchant confirmed that this transfer was received and a matching transfer exists in the merchant_transfers table; set automatically via INSERT TRIGGER merchant_expected_transfers_insert_trigger';
+COMMENT ON COLUMN merchant_expected_transfers.retry_needed
+ IS 'true if we need to retry the HTTP request to the exchange (never did it, or transient failure)';
+COMMENT ON COLUMN merchant_expected_transfers.h_details
+ IS 'Hash over the aggregation details returned by the exchange, provided here for fast exchange_sig validation';
+
+CREATE INDEX merchant_expected_transfers_by_open
+ ON merchant_expected_transfers
+ (retry_time ASC)
+ WHERE NOT confirmed OR retry_needed;
+COMMENT ON INDEX merchant_expected_transfers_by_open
+ IS 'For select_open_transfers';
+
+-- Migrate data. The backend will just re-do all of the
+-- reconciliation work, so we only preserve confirmed transfers.
+-- However, we must put those also into the new "merchant_expected_transfers"
+-- table already.
+DELETE FROM merchant_transfers
+ WHERE NOT confirmed;
+
+-- This index was replaced by merchant_expected_transfers_by_open.
+DROP INDEX merchant_transfers_by_open;
+
+-- These columns will be in the new merchant_expected_transfers table.
+ALTER TABLE merchant_transfers
+ ADD COLUMN bank_serial_id INT8,
+ ADD COLUMN expected BOOL DEFAULT FALSE,
+ ADD COLUMN execution_time INT8 DEFAULT (0),
+ DROP COLUMN ready_time,
+ DROP COLUMN confirmed,
+ DROP COLUMN failed,
+ DROP COLUMN verified,
+ DROP COLUMN validation_status;
+
+COMMENT ON COLUMN merchant_transfers.expected
+ IS 'True if this wire transfer was expected (has matching entry in merchant_expected_transfers); set automatically via INSERT TRIGGER merchant_transfers_insert_trigger';
+COMMENT ON COLUMN merchant_transfers.bank_serial_id
+ IS 'Row ID of the wire transfer from the automated import; NULL if not available (like when a human manually imported the transfer)';
+COMMENT ON COLUMN merchant_transfers.execution_time
+ IS 'Time when the merchant transfer was added and thus roughly received in our bank account';
+
+-- Note: if the bank_serial_id is NULL (manual import), we always
+-- consider confirmed transfers to be 'UNIQUE'; thus we do
+-- NOT use "NULLS NOT DISTINCT" here.
+
+ALTER TABLE merchant_transfers
+ DROP CONSTRAINT merchant_transfers_wtid_exchange_url_account_serial_key,
+ ADD CONSTRAINT merchant_transfers_unique
+ UNIQUE (wtid, exchange_url, account_serial, bank_serial_id);
+
+
+-- Create triggers to set confirmed/expected status on INSERT.
+CREATE FUNCTION merchant_expected_transfers_insert_trigger()
+RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ UPDATE merchant_transfers
+ SET expected = TRUE
+ WHERE wtid = NEW.wtid
+ AND exchange_url = NEW.exchange_url
+ AND credit_amount = NEW.expected_credit_amount;
+ NEW.confirmed = FOUND;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION merchant_expected_transfers_insert_trigger
+ IS 'Sets "confirmed" to TRUE for the new record if the expected transfer was already confirmed, and updates the already confirmed transfer to "expected"';
+
+-- Whenever an expected transfer is added, check if it was already confirmed
+CREATE TRIGGER merchant_expected_transfers_on_insert
+ BEFORE INSERT
+ ON merchant.merchant_expected_transfers
+ FOR EACH ROW EXECUTE FUNCTION merchant_expected_transfers_insert_trigger();
+
+
+CREATE FUNCTION merchant_transfers_insert_trigger()
+RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ UPDATE merchant_expected_transfers
+ SET confirmed = TRUE
+ WHERE wtid = NEW.wtid
+ AND exchange_url = NEW.exchange_url
+ AND expected_credit_amount = NEW.credit_amount;
+ NEW.expected = FOUND;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION merchant_transfers_insert_trigger
+ IS 'Sets "expected" to TRUE for the new record if the transfer was already expected, and updates the already confirmed transfer to "confirmed"';
+
+-- Whenever a transfer is addeded, check if it was already expected
+CREATE TRIGGER merchant_transfers_on_insert
+ BEFORE INSERT
+ ON merchant.merchant_transfers
+ FOR EACH ROW EXECUTE FUNCTION merchant_transfers_insert_trigger();
+
+
+-- Adjust contract terms table.
+ALTER TABLE merchant_deposits
+ ADD COLUMN settlement_retry_needed BOOL DEFAULT TRUE,
+ ADD COLUMN settlement_retry_time INT8 DEFAULT (0),
+ ADD COLUMN settlement_last_http_status INT4 DEFAULT NULL,
+ ADD COLUMN settlement_last_ec INT4 DEFAULT NULL,
+ ADD COLUMN settlement_last_detail TEXT DEFAULT NULL,
+ ADD COLUMN settlement_wtid BYTEA CHECK (LENGTH(settlement_wtid)=32) DEFAULT NULL,
+ ADD COLUMN settlement_coin_contribution taler_amount_currency DEFAULT NULL,
+ ADD COLUMN settlement_expected_credit_serial INT8 DEFAULT NULL
+ REFERENCES merchant_expected_transfers (expected_credit_serial),
+ ADD COLUMN signkey_serial INT8 DEFAULT NULL
+ REFERENCES merchant_exchange_signing_keys (signkey_serial)
+ ON DELETE CASCADE,
+ ADD COLUMN settlement_exchange_sig BYTEA
+ DEFAULT NULL CHECK (LENGTH(settlement_exchange_sig)=64);
+
+COMMENT ON COLUMN merchant_deposits.settlement_retry_needed
+ IS 'True if we should ask the exchange in the future about the settlement';
+COMMENT ON COLUMN merchant_deposits.settlement_retry_time
+ IS 'When should we next ask the exchange about the settlement wire transfer for this coin, initially set to the wire transfer deadline plus a bit of slack';
+COMMENT ON COLUMN merchant_deposits.settlement_last_http_status
+ IS 'HTTP status of our last inquiry with the exchange for this deposit, NULL if we never inquired, 0 on timeout';
+COMMENT ON COLUMN merchant_deposits.settlement_last_ec
+ IS 'Taler error code for our last inquiry with the exchange for this deposit, NULL if we never inquired, 0 on success';
+COMMENT ON COLUMN merchant_deposits.settlement_last_detail
+ IS 'Taler error detail for our last inquiry with the exchange for this deposit, NULL if we never inquired or on success';
+COMMENT ON COLUMN merchant_deposits.settlement_coin_contribution
+ IS 'Contribution of this coin to the overall wire transfer made by the exchange as claimed by exchange_sig; should match amount_with_fee minus deposit_fee, NULL if we did not get a reply from the exchange';
+COMMENT ON COLUMN merchant_deposits.settlement_expected_credit_serial
+ IS 'Identifies the expected wire transfer from the exchange to the merchant that settled the deposit of coin, NULL if unknown';
+COMMENT ON COLUMN merchant_deposits.signkey_serial
+ IS 'Identifies the online signing key of the exchange used to make the exchange_sig, NULL for none';
+COMMENT ON COLUMN merchant_deposits.settlement_exchange_sig
+ IS 'Exchange signature of purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, NULL if we did not get such an exchange signature';
+
+CREATE INDEX merchant_deposits_by_settlement_open
+ ON merchant_deposits
+ (settlement_retry_time ASC)
+ WHERE settlement_retry_needed;
+COMMENT ON INDEX merchant_deposits_by_settlement_open
+ IS 'For select_open_deposit_settlements';
+
+CREATE INDEX merchant_deposits_by_deposit_confirmation
+ ON merchant_deposits
+ (deposit_confirmation_serial);
+
+
+-- No 1:n mapping necessary, integrated into merchant_deposits table above.
+DROP TABLE merchant_deposit_to_transfer;
+
+-- We need to fully re-do the merchant_transfer_to_coin table,
+-- and data should be re-constructed, so drop and re-build.
+DROP TABLE merchant_transfer_to_coin;
+CREATE TABLE merchant_expected_transfer_to_coin
+ (deposit_serial BIGINT UNIQUE NOT NULL
+ REFERENCES merchant_deposits (deposit_serial) ON DELETE CASCADE
+ ,expected_credit_serial BIGINT NOT NULL
+ REFERENCES merchant_expected_transfers (expected_credit_serial) ON DELETE CASCADE
+ ,offset_in_exchange_list INT8 NOT NULL
+ ,exchange_deposit_value taler_amount_currency NOT NULL
+ ,exchange_deposit_fee taler_amount_currency NOT NULL
+ );
+CREATE INDEX IF NOT EXISTS merchant_transfers_by_credit
+ ON merchant_expected_transfer_to_coin
+ (expected_credit_serial);
+COMMENT ON TABLE merchant_expected_transfer_to_coin
+ IS 'Mapping of (credit) transfers to (deposited) coins';
+COMMENT ON COLUMN merchant_expected_transfer_to_coin.deposit_serial
+ IS 'Identifies the deposited coin that the wire transfer presumably settles';
+COMMENT ON COLUMN merchant_expected_transfer_to_coin.expected_credit_serial
+ IS 'Identifies the expected wire transfer that settles the given deposited coin';
+COMMENT ON COLUMN merchant_expected_transfer_to_coin.offset_in_exchange_list
+ IS 'The exchange settlement data includes an array of the settled coins; this is the index of the coin in that list, useful to reconstruct the correct sequence of coins as needed to check the exchange signature';
+COMMENT ON COLUMN merchant_expected_transfer_to_coin.exchange_deposit_value
+ IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits minus refunds';
+COMMENT ON COLUMN merchant_expected_transfer_to_coin.exchange_deposit_fee
+ IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits';
+
+
+-- We need to fully re-do the merchant_transfer_signatures table,
+-- and data should be re-constructed, so drop and re-build.
+
+DROP TABLE merchant_transfer_signatures;
+CREATE TABLE merchant_transfer_signatures
+ (expected_credit_serial BIGINT PRIMARY KEY
+ REFERENCES merchant_expected_transfers (expected_credit_serial)
+ ON DELETE CASCADE
+ ,signkey_serial BIGINT NOT NULL
+ REFERENCES merchant_exchange_signing_keys (signkey_serial)
+ ON DELETE CASCADE
+ ,wire_fee taler_amount_currency NOT NULL
+ ,credit_amount taler_amount_currency NOT NULL
+ ,execution_time INT8 NOT NULL
+ ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
+ );
+COMMENT ON TABLE merchant_transfer_signatures
+ IS 'table represents the main information returned from the /transfer request to the exchange.';
+COMMENT ON COLUMN merchant_transfer_signatures.expected_credit_serial
+ IS 'expected wire transfer this signature is about';
+COMMENT ON COLUMN merchant_transfer_signatures.signkey_serial
+ IS 'Online signing key by the exchange that was used for the exchange_sig signature';
+COMMENT ON COLUMN merchant_transfer_signatures.wire_fee
+ IS 'wire fee charged by the exchange for this transfer';
+COMMENT ON COLUMN merchant_transfer_signatures.exchange_sig
+ IS 'signature by the exchange of purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT';
+COMMENT ON COLUMN merchant_transfer_signatures.execution_time
+ IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant';
+COMMENT ON COLUMN merchant_transfer_signatures.credit_amount
+ IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the exchange';
+
+
+COMMIT;
diff --git a/src/backenddb/pg_finalize_transfer_status.c b/src/backenddb/pg_finalize_transfer_status.c
@@ -0,0 +1,80 @@
+/*
+ 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_finalize_transfer_status.c
+ * @brief Implementation of the finalize_transfer_status function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_finalize_transfer_status.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_finalize_transfer_status (
+ void *cls,
+ 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 PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_string (exchange_url),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ total_amount),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ wire_fee),
+ GNUNET_PQ_query_param_auto_from_type (h_details),
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (exchange_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ 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");
+ return GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "finalize_transfer_status",
+ params);
+}
diff --git a/src/backenddb/pg_finalize_transfer_status.h b/src/backenddb/pg_finalize_transfer_status.h
@@ -0,0 +1,54 @@
+/*
+ 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_finalize_transfer_status.h
+ * @brief implementation of the finalize_transfer_status function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_FINALIZE_TRANSFER_STATUS_H
+#define PG_FINALIZE_TRANSFER_STATUS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Finalize transfer status.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param h_details hash over all of the aggregated deposits
+ * @param total_amount total amount exchange claimed to have transferred
+ * @param wire_fee wire fee charged by the exchange
+ * @param exchange_pub key used to make @e exchange_sig
+ * @param exchange_sig signature of the exchange over reconciliation data
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_finalize_transfer_status (
+ void *cls,
+ 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);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_deposit.c b/src/backenddb/pg_insert_deposit.c
@@ -36,7 +36,8 @@ TMH_PG_insert_deposit (
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)
+ const struct TALER_Amount *refund_fee,
+ struct GNUNET_TIME_Absolute check_time)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -50,6 +51,7 @@ TMH_PG_insert_deposit (
deposit_fee),
TALER_PQ_query_param_amount_with_currency (pg->conn,
refund_fee),
+ GNUNET_PQ_query_param_absolute_time (&check_time),
GNUNET_PQ_query_param_end
};
@@ -69,7 +71,8 @@ TMH_PG_insert_deposit (
",amount_with_fee"
",deposit_fee"
",refund_fee"
- ") VALUES ($1, $2, $3, $4, $5, $6, $7)");
+ ",settlement_retry_time"
+ ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8)");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_deposit",
params);
diff --git a/src/backenddb/pg_insert_deposit.h b/src/backenddb/pg_insert_deposit.h
@@ -36,6 +36,8 @@
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange charges to refund this coin
+ * @param check_time at what time should we check the deposit status
+ * with the exchange (for settlement)
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -47,6 +49,7 @@ TMH_PG_insert_deposit (
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);
+ const struct TALER_Amount *refund_fee,
+ struct GNUNET_TIME_Absolute check_time);
#endif
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.c b/src/backenddb/pg_insert_deposit_to_transfer.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022, 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
@@ -30,8 +30,9 @@ enum GNUNET_DB_QueryStatus
TMH_PG_insert_deposit_to_transfer (
void *cls,
uint64_t deposit_serial,
- const struct TALER_EXCHANGE_DepositData *dd,
- bool *wpc)
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DepositData *dd)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -39,46 +40,29 @@ TMH_PG_insert_deposit_to_transfer (
TALER_PQ_query_param_amount_with_currency (pg->conn,
&dd->coin_contribution),
GNUNET_PQ_query_param_timestamp (&dd->execution_time),
+ GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig),
GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub),
GNUNET_PQ_query_param_auto_from_type (&dd->wtid),
GNUNET_PQ_query_param_end
};
- bool conflict;
- bool no_exchange_pub;
+ bool dummy;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("out_wire_pending_cleared",
- wpc),
- GNUNET_PQ_result_spec_bool ("out_conflict",
- &conflict),
- GNUNET_PQ_result_spec_bool ("out_no_exchange_pub",
- &no_exchange_pub),
+ GNUNET_PQ_result_spec_bool ("out_dummy",
+ &dummy),
GNUNET_PQ_result_spec_end
};
- enum GNUNET_DB_QueryStatus qs;
- *wpc = false;
PREPARE (pg,
"insert_deposit_to_transfer",
"SELECT"
- " out_wire_pending_cleared"
- " ,out_conflict"
- " ,out_no_exchange_pub"
+ " out_dummy"
" FROM merchant_insert_deposit_to_transfer"
- " ($1,$2,$3,$4,$5,$6);");
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_deposit_to_transfer",
- params,
- rs);
- if (qs <= 0)
- return qs;
- if (no_exchange_pub)
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange public key unknown (bug!?)\n");
- if (*wpc)
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Wire pending flag cleared (good!)\n");
- if (conflict)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- return qs;
+ " ($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/pg_insert_deposit_to_transfer.h b/src/backenddb/pg_insert_deposit_to_transfer.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022, 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
@@ -31,16 +31,18 @@
*
* @param cls closure
* @param deposit_serial serial number of the deposit
+ * @param h_wire hash of the merchant's account that should receive the deposit
+ * @param exchange_url URL of the exchange that is making the deposit
* @param dd deposit transfer data from the exchange to store
- * @param[out] wpc set to true if the wire_pending flag was cleared
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
TMH_PG_insert_deposit_to_transfer (
void *cls,
uint64_t deposit_serial,
- const struct TALER_EXCHANGE_DepositData *dd,
- bool *wpc);
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DepositData *dd);
#endif
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.sql b/src/backenddb/pg_insert_deposit_to_transfer.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2024 Taler Systems SA
+-- 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
@@ -15,31 +15,30 @@
--
-CREATE OR REPLACE FUNCTION merchant_insert_deposit_to_transfer (
+DROP FUNCTION IF EXISTS merchant_insert_deposit_to_transfer;
+CREATE FUNCTION merchant_insert_deposit_to_transfer (
IN in_deposit_serial INT8,
- IN in_amount_with_fee taler_amount_currency,
+ 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_wire_pending_cleared BOOL,
- OUT out_conflict BOOL,
- OUT out_no_exchange_pub BOOL)
+ OUT out_dummy BOOL)
LANGUAGE plpgsql
AS $$
DECLARE
my_signkey_serial INT8;
-DECLARE
- my_confirmed BOOL;
-DECLARE
+ my_account_serial INT8;
my_decose INT8;
-DECLARE
my_order_serial INT8;
-DECLARE
- my_merchant_serial INT8;
-DECLARE
+ my_expected_credit_serial INT8;
+ my_wire_pending_cleared BOOL;
my_order_id TEXT;
BEGIN
+ -- Just to return something (for now).
+ out_dummy=FALSE;
-- Find exchange sign key
SELECT signkey_serial
@@ -51,137 +50,89 @@ SELECT signkey_serial
IF NOT FOUND
THEN
- out_no_exchange_pub=TRUE;
- out_conflict=FALSE;
- out_wire_pending_cleared=FALSE;
+ -- 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;
-out_no_exchange_pub=FALSE;
-
--- Try to insert new wire transfer
-INSERT INTO merchant_deposit_to_transfer
- (deposit_serial
- ,coin_contribution_value
- ,wtid
- ,execution_time
- ,signkey_serial
- ,exchange_sig
- )
- VALUES
- (in_deposit_serial
- ,in_amount_with_fee
- ,in_wtid
- ,in_execution_time
- ,my_signkey_serial
- ,in_exchange_sig
- )
- ON CONFLICT DO NOTHING;
+-- Find deposit confirmation
+SELECT deposit_confirmation_serial
+ INTO my_decose
+ FROM merchant_deposits
+ WHERE deposit_serial=in_deposit_serial;
-IF NOT FOUND
-THEN
- PERFORM FROM merchant_deposit_to_transfer
- WHERE deposit_serial=in_deposit_serial
- AND wtid=in_wtid
- AND signkey_serial=my_signkey_serial
- AND exchange_sig=in_exchange_sig;
-END IF;
+-- 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
- -- Conflicting (!) wire transfer existed in the table already
- out_conflict=TRUE;
- out_wire_pending_cleared=FALSE;
+ -- 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;
-out_conflict=FALSE;
--- Check if we already imported the (confirmed)
--- wire transfer *and* if it is mapped to this deposit.
-PERFORM
- FROM merchant_transfers mt
- JOIN merchant_transfer_to_coin mtc
- USING (credit_serial)
- WHERE mt.wtid=in_wtid
- AND mt.confirmed
- AND mtc.deposit_serial=in_deposit_serial;
+-- 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
- out_wire_pending_cleared=FALSE;
- RETURN;
+ 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;
-
-RAISE NOTICE 'checking affected deposit confirmation for completion';
-
-SELECT deposit_confirmation_serial
- INTO my_decose
- FROM merchant_deposits
+-- 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;
--- we made a change, check about clearing wire_pending
--- for the entire deposit confirmation
-UPDATE merchant_deposit_confirmations
- SET wire_pending=FALSE
- WHERE (deposit_confirmation_serial=my_decose)
- AND NOT EXISTS
- (SELECT 1
- FROM merchant_deposits md
- LEFT JOIN merchant_deposit_to_transfer mdtt
- USING (deposit_serial)
- WHERE md.deposit_confirmation_serial=my_decose
- AND mdtt.signkey_serial IS NULL);
--- credit_serial will be NULL due to LEFT JOIN
--- if we do not have an entry in mdtt for the deposit
--- and thus some entry in md was not yet wired.
+-- MERCHANT_WIRE_TRANSFER_EXPECTED
+NOTIFY XR6849FMRD2AJFY1E2YY0GWA8GN0YT407Z66WHJB0SAKJWF8G2Q60;
-IF NOT FOUND
-THEN
- out_wire_pending_cleared=FALSE;
- RETURN;
-END IF;
-out_wire_pending_cleared=TRUE;
-
-
-RAISE NOTICE 'checking affected contracts for completion';
-
--- Check if all deposit confirmations of the same
--- contract are now wired.
-SELECT deposit_confirmation_serial
- INTO my_order_serial
- FROM merchant_deposit_confirmations
- WHERE deposit_confirmation_serial=my_decose;
--- The above MUST succeed by invariants.
-
--- Check about setting 'wired' for the contract term.
--- Note: the same contract may be paid from
--- multiple exchanges, so we need to check if
--- payments were wired from all of them!
-UPDATE merchant_contract_terms
- SET wired=TRUE
- WHERE (order_serial=my_order_serial)
- AND NOT EXISTS
- (SELECT 1
- FROM merchant_deposit_confirmations mdc
- WHERE mdc.wire_pending
- AND mdc.order_serial=my_order_serial);
-
--- POSSIBLE LOCATION FOR THE WIRE WEBHOOK OF ORDER
---
--- INSERT INTO merchant_pending_webhooks
--- (merchant_serial
--- ,webhook_serial
--- ,url
--- ,http_method
--- ,body)
--- SELECT mw.merchant_serial
--- ,mw.webhook_serial
--- ,mw.url
--- ,mw.http_method
--- ,json_build_object('order_id', my_order_id)::TEXT
--- FROM merchant_webhook mw
--- WHERE mw.event_type = 'order_settled'
--- AND mw.merchant_serial = my_merchant_serial;
END $$;
diff --git a/src/backenddb/pg_insert_transfer.c b/src/backenddb/pg_insert_transfer.c
@@ -34,21 +34,30 @@ TMH_PG_insert_transfer (
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *credit_amount,
struct TALER_FullPayto payto_uri,
- bool confirmed)
+ uint64_t bank_serial_id)
{
struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_auto_from_type (wtid),
TALER_PQ_query_param_amount_with_currency (pg->conn,
credit_amount),
GNUNET_PQ_query_param_string (payto_uri.full_payto),
- GNUNET_PQ_query_param_bool (confirmed),
GNUNET_PQ_query_param_string (instance_id),
+ 0 == bank_serial_id
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&bank_serial_id),
+ GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
check_connection (pg);
+ // FIXME-#10176: if transfer with matching exchange_url, wtid and credit_amount
+ // and account_serial exists already AND where bank_serial_id is NULL
+ // and if our bank_serial_id is NOT NULL, then maybe UPDATE instead?
+ // (user may have switched from manual import to automatic import,
+ // and now we may be duplicating all the records, which would be bad).
PREPARE (pg,
"insert_transfer",
"INSERT INTO merchant_transfers"
@@ -56,16 +65,17 @@ TMH_PG_insert_transfer (
",wtid"
",credit_amount"
",account_serial"
- ",confirmed)"
+ ",bank_serial_id"
+ ",execution_time)"
"SELECT"
- " $1, $2, $3, account_serial, $5"
+ " $1, $2, $3, account_serial, $6, $7"
" FROM merchant_accounts"
" WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')"
" =REGEXP_REPLACE($4,'\\?.*','')"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
- " WHERE merchant_id=$6)"
+ " WHERE merchant_id=$5)"
" ON CONFLICT DO NOTHING;");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_transfer",
diff --git a/src/backenddb/pg_insert_transfer.h b/src/backenddb/pg_insert_transfer.h
@@ -34,8 +34,8 @@
* @param wtid identifier of the wire transfer
* @param credit_amount how much did we receive
* @param payto_uri what is the merchant's bank account that received the transfer
- * @param confirmed whether the transfer was confirmed by the merchant or
- * was merely claimed by the exchange at this point
+ * @param bank_serial_id unique ID for the wire transfer at the bank,
+ * 0 for "NULL" if none is known due to manual import
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -46,7 +46,7 @@ TMH_PG_insert_transfer (
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *credit_amount,
struct TALER_FullPayto payto_uri,
- bool confirmed);
+ uint64_t bank_serial_id);
#endif
diff --git a/src/backenddb/pg_insert_transfer_details.c b/src/backenddb/pg_insert_transfer_details.c
@@ -48,6 +48,7 @@ TMH_PG_insert_transfer_details (
const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (len)];
const struct TALER_PrivateContractHashP *contract_terms[GNUNET_NZL (len)];
enum GNUNET_DB_QueryStatus qs;
+ bool duplicate;
for (unsigned int i = 0; i<len; i++)
{
@@ -117,7 +118,6 @@ TMH_PG_insert_transfer_details (
bool no_instance;
bool no_account;
bool no_exchange;
- bool duplicate;
bool conflict;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_bool ("out_no_instance",
@@ -167,5 +167,7 @@ TMH_PG_insert_transfer_details (
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
break;
}
+ if (duplicate)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
return qs;
}
diff --git a/src/backenddb/pg_insert_transfer_details.sql b/src/backenddb/pg_insert_transfer_details.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2024 Taler Systems SA
+-- 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
@@ -15,7 +15,8 @@
--
-CREATE OR REPLACE FUNCTION merchant_do_insert_transfer_details (
+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,
@@ -39,9 +40,10 @@ AS $$
DECLARE
my_merchant_id INT8;
my_signkey_serial INT8;
- my_credit_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
@@ -75,9 +77,9 @@ END IF;
out_no_instance=FALSE;
-- Determine account that was credited.
-SELECT credit_serial
- INTO my_credit_serial
- FROM merchant_transfers
+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=
@@ -116,14 +118,14 @@ out_no_exchange=FALSE;
-- Add signature first, check for idempotent request
INSERT INTO merchant_transfer_signatures
- (credit_serial
+ (expected_credit_serial
,signkey_serial
,credit_amount
,wire_fee
,execution_time
,exchange_sig)
VALUES
- (my_credit_serial
+ (my_expected_credit_serial
,my_signkey_serial
,in_total_amount
,in_wire_fee
@@ -135,7 +137,7 @@ IF NOT FOUND
THEN
PERFORM 1
FROM merchant_transfer_signatures
- WHERE credit_serial=my_credit_serial
+ 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
@@ -165,15 +167,15 @@ LOOP
ini_coin_pub=ina_coin_pubs[i];
ini_contract_term=ina_contract_terms[i];
- INSERT INTO merchant_transfer_to_coin
+ INSERT INTO merchant_expected_transfer_to_coin
(deposit_serial
- ,credit_serial
+ ,expected_credit_serial
,offset_in_exchange_list
,exchange_deposit_value
,exchange_deposit_fee)
SELECT
dep.deposit_serial
- ,my_credit_serial
+ ,my_expected_credit_serial
,i
,ini_coin_value
,ini_deposit_fee
@@ -194,61 +196,62 @@ LOOP
RAISE NOTICE 'checking affected order for completion';
- -- First, check if deposit confirmation is done.
- UPDATE merchant_deposit_confirmations
- SET wire_pending=FALSE
- WHERE (deposit_confirmation_serial=my_affected_orders.deposit_confirmation_serial)
- AND NOT EXISTS
- (SELECT 1
- FROM merchant_deposits md
- LEFT JOIN merchant_deposit_to_transfer mdtt
- USING (deposit_serial)
- WHERE md.deposit_confirmation_serial=my_affected_orders.deposit_confirmation_serial
- AND mdtt.wtid IS NULL);
- -- wtid will be NULL due to LEFT JOIN
- -- if we do not have an entry in mdtt for the deposit
- -- and thus some entry in md was not yet wired.
+ my_decose=my_affected_orders.deposit_confirmation_serial;
- IF FOUND
+ 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
- -- Also update contract terms, if all (other) associated
- -- deposit_confirmations are also done.
+ -- must be all done, clear flag
+ UPDATE merchant_deposit_confirmations
+ SET wire_pending=FALSE
+ WHERE (deposit_confirmation_serial=my_decose);
- UPDATE merchant_contract_terms
- SET wired=TRUE
- WHERE (order_serial=my_affected_orders.order_serial)
- AND NOT EXISTS
- (SELECT 1
- FROM merchant_deposit_confirmations mdc
- WHERE mdc.wire_pending
- AND mdc.order_serial=my_affected_orders.order_serial);
+ IF FOUND
+ THEN
+ -- Also update contract terms, if all (other) associated
+ -- deposit_confirmations are also done.
- -- 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;
+ 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
- -- Insert pending webhook if it exists
- INSERT INTO merchant_pending_webhooks
- (merchant_serial
- ,webhook_serial
- ,url
- ,http_method
- ,body)
- SELECT mw.merchant_serial
- ,mw.webhook_serial
- ,mw.url
- ,mw.http_method
- ,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;
+ UPDATE merchant_contract_terms
+ SET wired=TRUE
+ WHERE (order_serial=my_affected_orders.order_serial);
- END IF;
+ -- 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;
+
+ -- Insert pending webhook if it exists
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial
+ ,webhook_serial
+ ,url
+ ,http_method
+ ,body)
+ SELECT mw.merchant_serial
+ ,mw.webhook_serial
+ ,mw.url
+ ,mw.http_method
+ ,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;
+ 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;
diff --git a/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c
@@ -306,7 +306,7 @@ TMH_PG_lookup_deposits_by_contract_and_coin (
" JOIN merchant_deposits dep"
" USING (deposit_confirmation_serial)"
" JOIN merchant_exchange_signing_keys msig"
- " USING (signkey_serial)"
+ " ON (mcon.signkey_serial=msig.signkey_serial)"
" JOIN merchant_accounts acc"
" USING (account_serial)"
" WHERE h_contract_terms=$2"
diff --git a/src/backenddb/pg_lookup_expected_transfers.c b/src/backenddb/pg_lookup_expected_transfers.c
@@ -0,0 +1,272 @@
+/*
+ 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_lookup_expected_transfers.c
+ * @brief Implementation of the lookup_expected_transfers function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_expected_transfers.h"
+#include "pg_helper.h"
+#include <microhttpd.h> /* for HTTP status codes */
+
+/**
+ * Closure for #lookup_expected_transfers_cb().
+ */
+struct LookupExpectedTransfersContext
+{
+ /**
+ * Function to call on results.
+ */
+ TALER_MERCHANTDB_IncomingCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Postgres context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Transaction status (set).
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupExpectedTransfersContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_expected_transfers_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupExpectedTransfersContext *ltc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount expected_credit_amount;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_FullPayto payto_uri;
+ char *exchange_url;
+ uint64_t expected_transfer_serial_id;
+ struct GNUNET_TIME_Timestamp execution_time;
+ bool confirmed;
+ bool validated;
+ char *last_detail = NULL;
+ uint32_t last_http_status = 0;
+ uint32_t last_ec = TALER_EC_NONE;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount_with_currency ("expected_credit_amount",
+ &expected_credit_amount),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri.full_payto),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_PQ_result_spec_uint64 ("expected_credit_serial",
+ &expected_transfer_serial_id),
+ GNUNET_PQ_result_spec_timestamp ("execution_time",
+ &execution_time),
+ GNUNET_PQ_result_spec_bool ("confirmed",
+ &confirmed),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("last_http_status",
+ &last_http_status),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("last_ec",
+ &last_ec),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("last_detail",
+ &last_detail),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ validated = ( (MHD_HTTP_OK == last_http_status) &&
+ (TALER_EC_NONE == last_ec) );
+ ltc->cb (ltc->cb_cls,
+ &expected_credit_amount,
+ &wtid,
+ payto_uri,
+ exchange_url,
+ expected_transfer_serial_id,
+ execution_time,
+ confirmed,
+ validated,
+ last_http_status,
+ last_ec,
+ last_detail);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ ltc->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_expected_transfers (
+ void *cls,
+ 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)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ bool by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
+ (! GNUNET_TIME_absolute_is_zero (after.abs_time)) );
+ struct LookupExpectedTransfersContext ltc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .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),
+ GNUNET_PQ_query_param_uint64 (&plimit),
+ 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 (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? */
+ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == verified),
+
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ 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 "
+ " (mt.confirmed = $9) )"
+ " AND ( $10 OR "
+ " ($11 = (200=mt.last_http_status) AND"
+ " (0=mt.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 "
+ " (mt.expected = $9) )"
+ " AND ( $10 OR "
+ " ($11 = (200=mt.last_http_status) AND"
+ " (0=mt.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");
+ 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);
+ if (0 >= qs)
+ return qs;
+ return ltc.qs;
+}
diff --git a/src/backenddb/pg_lookup_expected_transfers.h b/src/backenddb/pg_lookup_expected_transfers.h
@@ -0,0 +1,61 @@
+/*
+ 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_lookup_expected_transfers.h
+ * @brief implementation of the lookup_expected_transfers function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_EXPECTED_TRANSFERS_H
+#define PG_LOOKUP_EXPECTED_TRANSFERS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup expected incoming transfers.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param payto_uri account that we are interested in transfers to
+ * @param before timestamp for the earliest transfer we care about
+ * @param after timestamp for the last transfer we care about
+ * @param limit number of entries to return, negative for descending in execution time,
+ * positive for ascending in execution time
+ * @param offset expected_transfer_serial number of the transfer we want to offset from
+ * @param confirmed filter by confirmation status
+ * @param verified filter by verification status
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_expected_transfers (
+ void *cls,
+ 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);
+
+
+#endif
diff --git a/src/backenddb/pg_lookup_pending_deposits.c b/src/backenddb/pg_lookup_pending_deposits.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2023, 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
@@ -73,7 +73,7 @@ lookup_deposits_cb (void *cls,
{
uint64_t deposit_serial;
struct GNUNET_TIME_Absolute wire_deadline;
- struct GNUNET_TIME_Relative retry_backoff;
+ struct GNUNET_TIME_Absolute retry_time;
struct TALER_PrivateContractHashP h_contract_terms;
struct TALER_MerchantPrivateKeyP merchant_priv;
char *instance_id;
@@ -92,8 +92,8 @@ lookup_deposits_cb (void *cls,
&instance_id),
GNUNET_PQ_result_spec_absolute_time ("wire_transfer_deadline",
&wire_deadline),
- GNUNET_PQ_result_spec_relative_time ("retry_backoff",
- &retry_backoff),
+ GNUNET_PQ_result_spec_absolute_time ("retry_time",
+ &retry_time),
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
&h_wire),
TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
@@ -117,7 +117,7 @@ lookup_deposits_cb (void *cls,
ldc->cb (ldc->cb_cls,
deposit_serial,
wire_deadline,
- retry_backoff,
+ retry_time,
&h_contract_terms,
&merchant_priv,
instance_id,
@@ -165,29 +165,29 @@ TMH_PG_lookup_pending_deposits (
",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"
- ",mdc.retry_backoff"
- " FROM merchant_deposit_confirmations mdc"
+ " FROM merchant_deposits md"
+ " JOIN merchant_deposit_confirmations mdc"
+ " USING (deposit_confirmation_serial)"
" JOIN merchant_contract_terms mct"
- " USING (order_serial)"
+ " ON (mct.order_serial=mdc.order_serial)"
" JOIN merchant_accounts ma"
- " USING (account_serial)"
+ " ON (mdc.account_serial=ma.account_serial)"
" LEFT JOIN merchant_kyc kyc"
- " ON (ma.account_serial=kyc.account_serial)"
+ " 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)"
- " JOIN merchant_deposits md"
- " USING (deposit_confirmation_serial)"
- " WHERE mdc.wire_pending"
- " AND (mdc.exchange_url=$1)"
- " AND ($4 OR (mdc.wire_transfer_deadline < $2))"
+ " 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 mdc.wire_transfer_deadline ASC"
+ " ORDER BY md.settlement_retry_time ASC"
" LIMIT $3");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_pending_deposits",
diff --git a/src/backenddb/pg_lookup_transfer.c b/src/backenddb/pg_lookup_transfer.c
@@ -1,125 +0,0 @@
-/*
- 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 backenddb/pg_lookup_transfer.c
- * @brief Implementation of the lookup_transfer function for Postgres
- * @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 "pg_lookup_transfer.h"
-#include "pg_helper.h"
-
-enum GNUNET_DB_QueryStatus
-TMH_PG_lookup_transfer (void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct TALER_Amount *total_amount,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *exchange_amount,
- struct GNUNET_TIME_Timestamp *execution_time,
- bool *have_exchange_sig,
- bool *verified)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_url),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_end
- };
- uint8_t verified8;
- /** Amount we got actually credited, _excludes_ the wire fee */
- bool no_sig;
- struct TALER_Amount credit_amount;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_amount_with_currency ("credit_amount",
- &credit_amount),
- GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_result_spec_amount_with_currency ("wire_fee",
- wire_fee),
- &no_sig),
- GNUNET_PQ_result_spec_allow_null (
- TALER_PQ_result_spec_amount_with_currency ("exchange_amount",
- exchange_amount),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("execution_time",
- execution_time),
- NULL),
- GNUNET_PQ_result_spec_auto_from_type ("verified",
- &verified8),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- check_connection (pg);
- *execution_time = GNUNET_TIME_UNIT_ZERO_TS;
-
- PREPARE (pg,
- "lookup_transfer",
- "SELECT"
- " mt.credit_amount AS credit_amount"
- ",mts.credit_amount AS exchange_amount"
- ",wire_fee"
- ",execution_time"
- ",verified"
- " FROM merchant_transfers mt"
- " JOIN merchant_accounts USING (account_serial)"
- " JOIN merchant_instances USING (merchant_serial)"
- " LEFT JOIN merchant_transfer_signatures mts USING (credit_serial)"
- " WHERE wtid=$2"
- " AND exchange_url=$1"
- " AND merchant_id=$3;");
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_transfer",
- params,
- rs);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Lookup transfer returned %d\n",
- qs);
- if (qs > 0)
- {
- *have_exchange_sig = ! no_sig;
- *verified = (0 != verified8);
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (&credit_amount,
- wire_fee))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if ( (! no_sig) &&
- (0 >
- TALER_amount_add (total_amount,
- &credit_amount,
- wire_fee)) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- else
- {
- *verified = false;
- *have_exchange_sig = false;
- }
- return qs;
-}
diff --git a/src/backenddb/pg_lookup_transfer.h b/src/backenddb/pg_lookup_transfer.h
@@ -1,57 +0,0 @@
-/*
- 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 backenddb/pg_lookup_transfer.h
- * @brief implementation of the lookup_transfer function for Postgres
- * @author Iván Ávalos
- */
-#ifndef PG_LOOKUP_TRANSFER_H
-#define PG_LOOKUP_TRANSFER_H
-
-#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
-#include "taler_merchantdb_plugin.h"
-
-/**
- * Lookup transfer status.
- *
- * @param cls closure
- * @param instance_id at which instance should we resolve the transfer
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @param[out] total_amount amount that was debited from our
- * aggregate balance at the exchange (in total, sum of
- * the wire transfer amount and the @a wire_fee)
- * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true)
- * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true)
- * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true)
- * @param[out] have_exchange_sig do we have a response from the exchange about this transfer
- * @param[out] verified did we confirm the transfer was OK
- * @return transaction status
- */
-enum GNUNET_DB_QueryStatus
-TMH_PG_lookup_transfer (void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct TALER_Amount *total_amount,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *exchange_amount,
- struct GNUNET_TIME_Timestamp *execution_time,
- bool *have_exchange_sig,
- bool *verified);
-
-#endif
diff --git a/src/backenddb/pg_lookup_transfer_details.c b/src/backenddb/pg_lookup_transfer_details.c
@@ -102,10 +102,12 @@ lookup_transfer_details_cb (void *cls,
ltdc->qs = num_results;
}
+
enum GNUNET_DB_QueryStatus
TMH_PG_lookup_transfer_details (void *cls,
const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_WireTransferIdentifierRawP *
+ wtid,
TALER_MERCHANTDB_TransferDetailsCallback cb,
void *cb_cls)
{
@@ -131,17 +133,17 @@ TMH_PG_lookup_transfer_details (void *cls,
",dep.coin_pub"
",mtcoin.exchange_deposit_value"
",mtcoin.exchange_deposit_fee"
- " FROM merchant_transfer_to_coin mtcoin"
+ " 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_transfers mtr"
- " USING (credit_serial)"
- " WHERE mtr.wtid=$2"
- " AND mtr.exchange_url=$1");
+ " 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,
diff --git a/src/backenddb/pg_lookup_transfer_details_by_order.c b/src/backenddb/pg_lookup_transfer_details_by_order.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2023, 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
@@ -76,7 +76,7 @@ lookup_transfer_details_by_order_cb (void *cls,
struct GNUNET_TIME_Timestamp execution_time;
struct TALER_Amount deposit_value;
struct TALER_Amount deposit_fee;
- uint8_t transfer_confirmed;
+ bool confirmed;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("deposit_serial",
&deposit_serial),
@@ -84,14 +84,14 @@ lookup_transfer_details_by_order_cb (void *cls,
&execution_time),
GNUNET_PQ_result_spec_string ("exchange_url",
&exchange_url),
+ GNUNET_PQ_result_spec_bool ("confirmed",
+ &confirmed),
GNUNET_PQ_result_spec_auto_from_type ("wtid",
&wtid),
TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_value",
&deposit_value),
TALER_PQ_result_spec_amount_with_currency ("exchange_deposit_fee",
&deposit_fee),
- GNUNET_PQ_result_spec_auto_from_type ("transfer_confirmed",
- &transfer_confirmed),
GNUNET_PQ_result_spec_end
};
@@ -110,7 +110,7 @@ lookup_transfer_details_by_order_cb (void *cls,
execution_time,
&deposit_value,
&deposit_fee,
- (0 != transfer_confirmed));
+ confirmed);
GNUNET_PQ_cleanup_result (rs); /* technically useless here */
}
ltdo->qs = num_results;
@@ -118,10 +118,11 @@ lookup_transfer_details_by_order_cb (void *cls,
enum GNUNET_DB_QueryStatus
-TMH_PG_lookup_transfer_details_by_order (void *cls,
- uint64_t order_serial,
- TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
- void *cb_cls)
+TMH_PG_lookup_transfer_details_by_order (
+ void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
+ void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupTransferDetailsByOrderContext ltdo = {
@@ -141,20 +142,20 @@ TMH_PG_lookup_transfer_details_by_order (void *cls,
"SELECT"
" md.deposit_serial"
",mcon.exchange_url"
- ",mt.wtid"
+ ",met.wtid"
",mtc.exchange_deposit_value"
",mtc.exchange_deposit_fee"
",mcon.deposit_timestamp"
- ",mt.confirmed AS transfer_confirmed"
- " FROM merchant_transfer_to_coin mtc"
+ ",met.confirmed"
+ " 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_transfers mt"
- " USING (credit_serial)"
+ " JOIN merchant_expected_transfers met"
+ " USING (expected_credit_serial)"
" JOIN merchant_accounts acc"
- " ON (acc.account_serial = mt.account_serial)"
+ " 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)"
diff --git a/src/backenddb/pg_lookup_transfer_summary.c b/src/backenddb/pg_lookup_transfer_summary.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2023, 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
@@ -104,7 +104,8 @@ lookup_transfer_summary_cb (void *cls,
enum GNUNET_DB_QueryStatus
TMH_PG_lookup_transfer_summary (void *cls,
const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_WireTransferIdentifierRawP *
+ wtid,
TALER_MERCHANTDB_TransferSummaryCallback cb,
void *cb_cls)
{
@@ -128,17 +129,17 @@ TMH_PG_lookup_transfer_summary (void *cls,
" mct.order_id"
",mtc.exchange_deposit_value"
",mtc.exchange_deposit_fee"
- " FROM merchant_transfers mtr"
- " JOIN merchant_transfer_to_coin mtc"
- " USING (credit_serial)"
+ " 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 mtr.wtid=$2"
- " AND mtr.exchange_url=$1");
+ " WHERE met.wtid=$2"
+ " AND met.exchange_url=$1");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
diff --git a/src/backenddb/pg_lookup_transfers.c b/src/backenddb/pg_lookup_transfers.c
@@ -76,9 +76,8 @@ lookup_transfers_cb (void *cls,
struct TALER_FullPayto payto_uri;
char *exchange_url;
uint64_t transfer_serial_id;
- struct GNUNET_TIME_Timestamp execution_time = GNUNET_TIME_UNIT_FOREVER_TS;
- bool verified;
- bool confirmed;
+ struct GNUNET_TIME_Absolute execution_time;
+ bool expected;
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_result_spec_amount_with_currency ("credit_amount",
&credit_amount),
@@ -90,14 +89,10 @@ lookup_transfers_cb (void *cls,
&exchange_url),
GNUNET_PQ_result_spec_uint64 ("credit_serial",
&transfer_serial_id),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("execution_time",
- &execution_time),
- NULL),
- GNUNET_PQ_result_spec_bool ("verified",
- &verified),
- GNUNET_PQ_result_spec_bool ("confirmed",
- &confirmed),
+ GNUNET_PQ_result_spec_absolute_time ("execution_time",
+ &execution_time),
+ GNUNET_PQ_result_spec_bool ("expected",
+ &expected),
GNUNET_PQ_result_spec_end
};
@@ -117,8 +112,7 @@ lookup_transfers_cb (void *cls,
exchange_url,
transfer_serial_id,
execution_time,
- verified,
- confirmed);
+ expected);
GNUNET_PQ_cleanup_result (rs);
}
ltc->qs = num_results;
@@ -133,7 +127,7 @@ TMH_PG_lookup_transfers (void *cls,
struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll verified,
+ enum TALER_EXCHANGE_YesNoAll expected,
TALER_MERCHANTDB_TransferCallback cb,
void *cb_cls)
{
@@ -156,8 +150,9 @@ TMH_PG_lookup_transfers (void *cls,
? 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 (TALER_EXCHANGE_YNA_ALL == verified), /* filter by verified? */
- GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == verified),
+ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == expected), /* filter by expected? */
+ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == expected),
+
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
@@ -167,65 +162,57 @@ TMH_PG_lookup_transfers (void *cls,
"lookup_transfers_asc",
"SELECT"
" mt.credit_amount"
- ",wtid"
+ ",mt.wtid"
",mac.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",mts.execution_time"
- ",verified"
- ",confirmed"
+ ",mt.exchange_url"
+ ",mt.credit_serial"
+ ",mt.execution_time"
+ ",mt.expected"
" FROM merchant_transfers mt"
" JOIN merchant_accounts mac"
" USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures mts"
- " USING (credit_serial)"
" WHERE ( $7 OR "
- " (mts.execution_time IS NOT NULL AND"
- " mts.execution_time < $2 AND"
- " mts.execution_time >= $3) )"
- " AND ( $8 OR "
- " (verified = $9) )"
+ " (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 (credit_serial > $4)"
- " ORDER BY credit_serial ASC"
+ " AND (mt.credit_serial > $4)"
+ " ORDER BY mt.credit_serial ASC"
" LIMIT $5");
PREPARE (pg,
"lookup_transfers_desc",
"SELECT"
" mt.credit_amount"
- ",wtid"
+ ",mt.wtid"
",mac.payto_uri"
- ",exchange_url"
- ",credit_serial"
- ",mts.execution_time"
- ",verified"
- ",confirmed"
+ ",mt.exchange_url"
+ ",mt.credit_serial"
+ ",mt.execution_time"
+ ",mt.expected"
" FROM merchant_transfers mt"
" JOIN merchant_accounts mac"
" USING (account_serial)"
- " LEFT JOIN merchant_transfer_signatures mts"
- " USING (credit_serial)"
" WHERE ( $7 OR "
- " (mts.execution_time IS NOT NULL AND"
- " mts.execution_time < $2 AND"
- " mts.execution_time >= $3) )"
- " AND ( $8 OR "
- " (verified = $9) )"
+ " (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 (credit_serial < $4)"
- " ORDER BY credit_serial DESC"
+ " AND (mt.credit_serial < $4)"
+ " ORDER BY mt.credit_serial DESC"
" LIMIT $5");
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
diff --git a/src/backenddb/pg_lookup_transfers.h b/src/backenddb/pg_lookup_transfers.h
@@ -40,7 +40,7 @@
* @param limit number of entries to return, negative for descending in execution time,
* positive for ascending in execution time
* @param offset transfer_serial number of the transfer we want to offset from
- * @param verified filter transfers by verification status
+ * @param expected filter for transfers that were expected
* @param cb function to call with detailed transfer data
* @param cb_cls closure for @a cb
* @return transaction status
@@ -53,7 +53,7 @@ TMH_PG_lookup_transfers (void *cls,
struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll verified,
+ enum TALER_EXCHANGE_YesNoAll expected,
TALER_MERCHANTDB_TransferCallback cb,
void *cb_cls);
diff --git a/src/backenddb/pg_select_open_transfers.c b/src/backenddb/pg_select_open_transfers.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022, 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
@@ -75,10 +75,9 @@ open_transfers_cb (void *cls,
char *exchange_url;
struct TALER_FullPayto payto_uri;
struct TALER_WireTransferIdentifierRawP wtid;
- struct TALER_Amount total;
- struct GNUNET_TIME_Absolute next_attempt;
+ struct GNUNET_TIME_Absolute retry_time;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("credit_serial",
+ GNUNET_PQ_result_spec_uint64 ("expected_credit_serial",
&rowid),
GNUNET_PQ_result_spec_string ("instance_id",
&instance_id),
@@ -88,10 +87,8 @@ open_transfers_cb (void *cls,
&payto_uri.full_payto),
GNUNET_PQ_result_spec_auto_from_type ("wtid",
&wtid),
- TALER_PQ_result_spec_amount_with_currency ("credit_amount",
- &total),
- GNUNET_PQ_result_spec_absolute_time ("next_attempt",
- &next_attempt),
+ GNUNET_PQ_result_spec_absolute_time ("retry_time",
+ &retry_time),
GNUNET_PQ_result_spec_end
};
@@ -110,8 +107,7 @@ open_transfers_cb (void *cls,
exchange_url,
payto_uri,
&wtid,
- &total,
- next_attempt);
+ retry_time);
GNUNET_PQ_cleanup_result (rs);
}
}
@@ -138,23 +134,20 @@ TMH_PG_select_open_transfers (void *cls,
PREPARE (pg,
"select_open_transfers",
"SELECT"
- " credit_serial"
- ",merchant_id AS instance_id"
- ",exchange_url"
- ",payto_uri"
- ",wtid"
- ",credit_amount"
- ",ready_time AS next_attempt"
- " FROM merchant_transfers"
- " JOIN merchant_accounts"
+ " 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"
- " USING (merchant_serial)"
- " WHERE confirmed AND"
- " NOT (failed OR verified)"
- " ORDER BY ready_time ASC"
+ " JOIN merchant_instances mi"
+ " ON (ma.merchant_serial=mi.merchant_serial)"
+ " WHERE retry_needed"
+ " ORDER BY retry_time ASC"
" LIMIT $1;");
-
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"select_open_transfers",
diff --git a/src/backenddb/pg_set_transfer_status_to_confirmed.c b/src/backenddb/pg_set_transfer_status_to_confirmed.c
@@ -1,66 +0,0 @@
-/*
- 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 backenddb/pg_set_transfer_status_to_confirmed.c
- * @brief Implementation of the set_transfer_status_to_confirmed function for Postgres
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <taler/taler_error_codes.h>
-#include <taler/taler_dbevents.h>
-#include <taler/taler_pq_lib.h>
-#include "pg_set_transfer_status_to_confirmed.h"
-#include "pg_helper.h"
-
-
-enum GNUNET_DB_QueryStatus
-TMH_PG_set_transfer_status_to_confirmed (
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *amount)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_string (exchange_url),
- TALER_PQ_query_param_amount_with_currency (pg->conn,
- amount),
- GNUNET_PQ_query_param_end
- };
-
- check_connection (pg);
- PREPARE (pg,
- "set_transfer_status_to_confirmed",
- "UPDATE merchant_transfers SET"
- " confirmed=TRUE"
- " WHERE wtid=$2"
- " AND credit_amount=cast($4 AS taler_amount_currency)"
- " AND exchange_url=$3"
- " AND account_serial IN"
- " (SELECT account_serial"
- " FROM merchant_accounts"
- " WHERE merchant_serial ="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1));");
- return GNUNET_PQ_eval_prepared_non_select (
- pg->conn,
- "set_transfer_status_to_confirmed",
- params);
-}
diff --git a/src/backenddb/pg_set_transfer_status_to_confirmed.h b/src/backenddb/pg_set_transfer_status_to_confirmed.h
@@ -1,48 +0,0 @@
-/*
- 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_set_transfer_status_to_confirmed.h
- * @brief implementation of the set_transfer_status_to_confirmed function for Postgres
- * @author Christian Grothoff
- */
-#ifndef PG_SET_TRANSFER_STATUS_TO_CONFIRMED_H
-#define PG_SET_TRANSFER_STATUS_TO_CONFIRMED_H
-
-#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
-#include "taler_merchantdb_plugin.h"
-
-
-/**
- * Set transfer status to confirmed.
- *
- * @param cls closure
- * @param instance_id merchant instance with the update
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @param amount confirmed amount of the wire transfer
- * @return transaction status
- */
-enum GNUNET_DB_QueryStatus
-TMH_PG_set_transfer_status_to_confirmed (
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *amount);
-
-
-#endif
diff --git a/src/backenddb/pg_update_deposit_confirmation_status.c b/src/backenddb/pg_update_deposit_confirmation_status.c
@@ -30,35 +30,36 @@ enum GNUNET_DB_QueryStatus
TMH_PG_update_deposit_confirmation_status (
void *cls,
uint64_t deposit_serial,
- bool wire_pending,
- struct GNUNET_TIME_Timestamp future_retry,
- struct GNUNET_TIME_Relative retry_backoff,
- const char *emsg)
+ bool retry_needed,
+ struct GNUNET_TIME_Timestamp retry_time,
+ uint32_t last_http_status,
+ enum TALER_ErrorCode last_ec,
+ const char *last_detail)
{
struct PostgresClosure *pg = cls;
+ uint32_t ec32 = (uint32_t) last_ec;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&deposit_serial),
- GNUNET_PQ_query_param_timestamp (&future_retry),
- NULL == emsg
+ GNUNET_PQ_query_param_bool (retry_needed),
+ GNUNET_PQ_query_param_timestamp (&retry_time),
+ GNUNET_PQ_query_param_uint32 (&last_http_status),
+ GNUNET_PQ_query_param_uint32 (&ec32),
+ NULL == last_detail
? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (emsg),
- GNUNET_PQ_query_param_relative_time (&retry_backoff),
- GNUNET_PQ_query_param_bool (wire_pending),
+ : GNUNET_PQ_query_param_string (last_detail),
GNUNET_PQ_query_param_end
};
check_connection (pg);
PREPARE (pg,
"update_deposit_confirmation_status",
- "UPDATE merchant_deposit_confirmations SET"
- " wire_transfer_deadline=$2"
- ",exchange_failure=$3"
- ",retry_backoff=$4"
- ",wire_pending=$5 AND wire_pending"
- " WHERE deposit_confirmation_serial="
- " (SELECT deposit_confirmation_serial"
- " FROM merchant_deposits"
- " WHERE deposit_serial=$1);");
+ "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",
diff --git a/src/backenddb/pg_update_deposit_confirmation_status.h b/src/backenddb/pg_update_deposit_confirmation_status.h
@@ -32,20 +32,22 @@
*
* @param cls closure
* @param deposit_serial deposit to update status for
- * @param wire_pending did the exchange say that the wire is still pending?
- * @param future_retry when should we ask the exchange again
- * @param retry_backoff current value for the retry backoff
- * @param emsg error message to record
+ * @param retry_needed true if the HTTP request should be retried
+ * @param retry_time when should we ask the exchange again
+ * @param last_http_status HTTP status code of the last reply
+ * @param last_ec Taler error code of the last reply
+ * @param last_hint hint from error message to record, possibly NULL
* @return database result code
*/
enum GNUNET_DB_QueryStatus
TMH_PG_update_deposit_confirmation_status (
void *cls,
uint64_t deposit_serial,
- bool wire_pending,
- struct GNUNET_TIME_Timestamp future_retry,
- struct GNUNET_TIME_Relative retry_backoff,
- const char *emsg);
+ bool retry_needed,
+ struct GNUNET_TIME_Timestamp retry_time,
+ uint32_t last_http_status,
+ enum TALER_ErrorCode last_ec,
+ const char *last_hint);
#endif
diff --git a/src/backenddb/pg_update_transfer_status.c b/src/backenddb/pg_update_transfer_status.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022, 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
@@ -32,18 +32,23 @@ TMH_PG_update_transfer_status (
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct GNUNET_TIME_Absolute next_attempt,
+ unsigned int http_status,
enum TALER_ErrorCode ec,
- bool failed,
- bool verified)
+ const char *detail,
+ bool needs_retry)
{
struct PostgresClosure *pg = cls;
+ uint32_t hs32 = (uint32_t) http_status;
uint32_t ec32 = (uint32_t) ec;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_string (exchange_url),
+ GNUNET_PQ_query_param_uint32 (&hs32),
GNUNET_PQ_query_param_uint32 (&ec32),
- GNUNET_PQ_query_param_bool (failed),
- GNUNET_PQ_query_param_bool (verified),
+ NULL == detail
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (detail),
+ GNUNET_PQ_query_param_bool (needs_retry),
GNUNET_PQ_query_param_absolute_time (&next_attempt),
GNUNET_PQ_query_param_end
};
@@ -51,11 +56,12 @@ TMH_PG_update_transfer_status (
check_connection (pg);
PREPARE (pg,
"update_transfer_status",
- "UPDATE merchant_transfers SET"
- " validation_status=$3"
- ",failed=$4"
- ",verified=$5"
- ",ready_time=$6"
+ "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 (
diff --git a/src/backenddb/pg_update_transfer_status.h b/src/backenddb/pg_update_transfer_status.h
@@ -33,9 +33,10 @@
* @param exchange_url the exchange that made the transfer
* @param wtid wire transfer subject
* @param next_attempt when should we try again (if ever)
+ * @param http_status last HTTP status code from the server, 0 for timeout
* @param ec current error state of checking the transfer
- * @param failed true if validation has failed for good
- * @param verified true if validation has succeeded for good
+ * @param hint last hint from the server, possibly NULL
+ * @param needs_retry true if we should retry the request
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -44,8 +45,9 @@ TMH_PG_update_transfer_status (
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct GNUNET_TIME_Absolute next_attempt,
+ unsigned int http_status,
enum TALER_ErrorCode ec,
- bool failed,
- bool verified);
+ const char *hint,
+ bool needs_retry);
#endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014--2024 Taler Systems SA
+ (C) 2014--2025 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
@@ -63,6 +63,7 @@
#include "pg_lookup_instance_auth.h"
#include "pg_lookup_otp_devices.h"
#include "pg_update_transfer_status.h"
+#include "pg_finalize_transfer_status.h"
#include "pg_insert_instance.h"
#include "pg_account_kyc_set_status.h"
#include "pg_account_kyc_get_status.h"
@@ -120,13 +121,13 @@
#include "pg_delete_transfer.h"
#include "pg_check_transfer_exists.h"
#include "pg_lookup_account.h"
-#include "pg_lookup_wire_fee.h"
+#include "pg_lookup_expected_transfers.h"
#include "pg_lookup_deposits_by_contract_and_coin.h"
-#include "pg_lookup_transfer.h"
#include "pg_lookup_transfer_summary.h"
#include "pg_lookup_transfer_details.h"
#include "pg_lookup_webhooks.h"
#include "pg_lookup_webhook.h"
+#include "pg_lookup_wire_fee.h"
#include "pg_delete_webhook.h"
#include "pg_insert_webhook.h"
#include "pg_update_webhook.h"
@@ -136,7 +137,6 @@
#include "pg_update_pending_webhook.h"
#include "pg_lookup_pending_webhooks.h"
#include "pg_update_deposit_confirmation_status.h"
-#include "pg_set_transfer_status_to_confirmed.h"
#include "pg_insert_exchange_keys.h"
#include "pg_select_exchange_keys.h"
#include "pg_insert_deposit_to_transfer.h"
@@ -434,6 +434,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_inactivate_account;
plugin->update_transfer_status
= &TMH_PG_update_transfer_status;
+ plugin->finalize_transfer_status
+ = &TMH_PG_finalize_transfer_status;
plugin->lookup_products
= &TMH_PG_lookup_products;
plugin->lookup_all_products
@@ -530,10 +532,6 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_lookup_wire_fee;
plugin->lookup_deposits_by_contract_and_coin
= &TMH_PG_lookup_deposits_by_contract_and_coin;
- plugin->lookup_transfer
- = &TMH_PG_lookup_transfer;
- plugin->set_transfer_status_to_confirmed
- = &TMH_PG_set_transfer_status_to_confirmed;
plugin->lookup_transfer_summary
= &TMH_PG_lookup_transfer_summary;
plugin->lookup_transfer_details
@@ -620,6 +618,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_insert_exchange_account;
plugin->insert_token_family
= &TMH_PG_insert_token_family;
+ plugin->lookup_expected_transfers
+ = &TMH_PG_lookup_expected_transfers;
plugin->lookup_token_family
= &TMH_PG_lookup_token_family;
plugin->lookup_token_families
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -1383,8 +1383,10 @@ static int
test_products (void)
{
struct TestProducts_Closure test_cls;
+ int test_result;
+
pre_test_products (&test_cls);
- int test_result = run_test_products (&test_cls);
+ test_result = run_test_products (&test_cls);
post_test_products (&test_cls);
return test_result;
}
@@ -1602,9 +1604,11 @@ lookup_orders_cb (void *cls,
struct GNUNET_TIME_Timestamp timestamp)
{
struct TestLookupOrders_Closure *cmp = cls;
+ unsigned int i;
+
if (NULL == cmp)
return;
- unsigned int i = cmp->results_length;
+ i = cmp->results_length;
cmp->results_length += 1;
if (cmp->orders_to_cmp_length > i)
{
@@ -2435,8 +2439,10 @@ static int
test_orders (void)
{
struct TestOrders_Closure test_cls;
+ int test_result;
+
pre_test_orders (&test_cls);
- int test_result = run_test_orders (&test_cls);
+ test_result = run_test_orders (&test_cls);
post_test_orders (&test_cls);
return test_result;
}
@@ -2710,7 +2716,8 @@ test_insert_deposit (const struct InstanceData *instance,
&deposit->coin_sig,
&deposit->amount_with_fee,
&deposit->deposit_fee,
- &deposit->refund_fee),
+ &deposit->refund_fee,
+ GNUNET_TIME_absolute_get ()),
"Insert deposit failed\n");
return 0;
}
@@ -2880,7 +2887,7 @@ lookup_deposits_contract_coin_cb (
if (NULL == cmp)
return;
- cmp->results_length += 1;
+ cmp->results_length++;
for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i)
{
if ((GNUNET_TIME_timestamp_cmp (cmp->deposits_to_cmp[i].timestamp,
@@ -2913,7 +2920,7 @@ lookup_deposits_contract_coin_cb (
(0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].exchange_sig,
exchange_sig)))
{
- cmp->results_matching[i] += 1;
+ cmp->results_matching[i]++;
}
}
}
@@ -3369,8 +3376,10 @@ static int
test_deposits (void)
{
struct TestDeposits_Closure test_cls;
+ int test_result;
+
pre_test_deposits (&test_cls);
- int test_result = run_test_deposits (&test_cls);
+ test_result = run_test_deposits (&test_cls);
post_test_deposits (&test_cls);
return test_result;
}
@@ -3543,69 +3552,6 @@ make_transfer (const struct ExchangeSignkeyData *signkey,
/**
- * Tests looking up a transfer from the database.
- *
- * @param exchange_url url to the exchange of the transfer.
- * @param wtid id of the transfer.
- * @param total_expected the total amount of the transfer.
- * @param fee_expected the fee on the transfer.
- * @param time_expected when the transfer was made.
- * @param verified_expected whether the transfer was verified.
- * @return 1 on success, 0 otherwise.
- */
-static int
-test_lookup_transfer (
- const struct InstanceData *instance,
- const struct TransferData *transfer)
-{
- struct TALER_Amount total_with_fee;
- struct TALER_Amount total;
- struct TALER_Amount fee;
- struct TALER_Amount exchange_amount;
- struct GNUNET_TIME_Timestamp time;
- bool esig;
- bool verified;
-
- if (1 != plugin->lookup_transfer (plugin->cls,
- instance->instance.id,
- transfer->exchange_url,
- &transfer->wtid,
- &total,
- &fee,
- &exchange_amount,
- &time,
- &esig,
- &verified))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup transfer failed\n");
- return 1;
- }
- GNUNET_assert (0 <= TALER_amount_add (&total_with_fee,
- &transfer->data.total_amount,
- &transfer->data.wire_fee));
- if ((GNUNET_OK != TALER_amount_cmp_currency (&total_with_fee,
- &total)) ||
- (0 != TALER_amount_cmp (&total_with_fee,
- &total)) ||
- (GNUNET_OK != TALER_amount_cmp_currency (&transfer->data.wire_fee,
- &fee)) ||
- (0 != TALER_amount_cmp (&transfer->data.wire_fee,
- &fee)) ||
- (GNUNET_TIME_timestamp_cmp (transfer->data.execution_time,
- !=,
- time)) ||
- (transfer->verified != verified))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup transfer failed: mismatched data\n");
- return 1;
- }
- return 0;
-}
-
-
-/**
* Closure for testing 'lookup_transfer_summary'
*/
struct TestLookupTransferSummary_Closure
@@ -3867,7 +3813,8 @@ struct TestLookupTransferDetailsByOrder_Closure
* @param execution_time when the transfer found occurred.
* @param deposit_value amount of the deposit for the transfer found.
* @param deposit_fee amount of the fee for the deposit of the transfer.
- * @param transfer_confirmed whether the transfer was confirmed.
+ * @param transfer_confirmed did the merchant confirm that a wire transfer with
+ * @a wtid over the total amount happened?
*/
static void
lookup_transfer_details_order_cb (
@@ -3880,13 +3827,14 @@ lookup_transfer_details_order_cb (
bool transfer_confirmed)
{
struct TestLookupTransferDetailsByOrder_Closure *cmp = cls;
+
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; i < cmp->transfers_to_cmp_length; ++i)
{
/* Right now lookup_transfer_details_by_order leaves execution_time
- uninitialized and transfer_confirmed always false. */
+ uninitialized */
if ((0 == GNUNET_memcmp (&cmp->transfers_to_cmp[i].wtid,
wtid)) &&
(0 == strcmp (cmp->transfers_to_cmp[i].exchange_url,
@@ -3904,8 +3852,7 @@ lookup_transfer_details_order_cb (
deposit_fee)) &&
(0 ==
TALER_amount_cmp (&cmp->transfers_to_cmp[i].deposit_fee,
- deposit_fee)) /* &&
- (cmp->transfers_to_cmp[i].confirmed == transfer_confirmed)*/)
+ deposit_fee)) )
cmp->results_matching[i] += 1;
}
}
@@ -4078,8 +4025,6 @@ struct TestLookupTransfers_Closure
* @param transfer_serial_id serial number identifying the transfer in the backend
* @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_TS
* if it did not yet happen
- * @param verified true if we checked the exchange's answer and liked it,
- * false there is a problem (verification failed or did not yet happen)
* @param confirmed true if the merchant confirmed this wire transfer
* false if it is so far only claimed to have been made by the exchange
*/
@@ -4090,8 +4035,7 @@ lookup_transfers_cb (void *cls,
struct TALER_FullPayto payto_uri,
const char *exchange_url,
uint64_t transfer_serial_id,
- struct GNUNET_TIME_Timestamp execution_time,
- bool verified,
+ struct GNUNET_TIME_Absolute execution_time,
bool confirmed)
{
struct TestLookupTransfers_Closure *cmp = cls;
@@ -4099,21 +4043,17 @@ lookup_transfers_cb (void *cls,
return;
for (unsigned int i = 0; cmp->transfers_to_cmp_length > i; ++i)
{
- if ((GNUNET_OK ==
- TALER_amount_cmp_currency (
- &cmp->transfers_to_cmp[i].data.total_amount,
- credit_amount)) &&
- (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
- credit_amount)) &&
- (GNUNET_TIME_timestamp_cmp (
- cmp->transfers_to_cmp[i].data.execution_time,
- ==,
- execution_time)))
+ if ( (GNUNET_OK ==
+ TALER_amount_cmp_currency (
+ &cmp->transfers_to_cmp[i].data.total_amount,
+ credit_amount)) &&
+ (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
+ credit_amount)) )
{
- cmp->results_matching[i] += 1;
+ cmp->results_matching[i]++;
}
}
- cmp->results_length += 1;
+ cmp->results_length++;
}
@@ -4138,7 +4078,6 @@ test_lookup_transfers (const struct InstanceData *instance,
struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll filter_verified,
unsigned int transfers_length,
const struct TransferData *transfers)
{
@@ -4159,7 +4098,7 @@ test_lookup_transfers (const struct InstanceData *instance,
after,
limit,
offset,
- filter_verified,
+ TALER_EXCHANGE_YNA_ALL,
&lookup_transfers_cb,
&cmp))
{
@@ -4244,17 +4183,15 @@ test_insert_deposit_to_transfer (const struct InstanceData *instance,
uint64_t deposit_serial = get_deposit_serial (instance,
order,
deposit);
- bool cleared;
TEST_COND_RET_ON_FAIL (expected_result ==
- plugin->insert_deposit_to_transfer (plugin->cls,
- deposit_serial,
- &deposit_data,
- &cleared),
+ plugin->insert_deposit_to_transfer (
+ plugin->cls,
+ deposit_serial,
+ &deposit->h_wire,
+ deposit->exchange_url,
+ &deposit_data),
"insert deposit to transfer failed\n");
- TEST_COND_RET_ON_FAIL (expect_cleared ==
- cleared,
- "cleared status wrong");
return 0;
}
@@ -4490,21 +4427,7 @@ run_test_transfers (struct TestTransfers_Closure *cls)
cls->order.id,
NULL,
false,
- true));
- TEST_RET_ON_FAIL (test_lookup_transfer (&cls->instance,
- &cls->transfers[0]));
- TEST_COND_RET_ON_FAIL (
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->set_transfer_status_to_confirmed (
- plugin->cls,
- cls->instance.instance.id,
- cls->deposit.exchange_url,
- &cls->transfers[0].wtid,
- &cls->deposit.amount_with_fee),
- "Set transfer status to confirmed failed\n");
- cls->transfers[0].confirmed = true;
- TEST_RET_ON_FAIL (test_lookup_transfer (&cls->instance,
- &cls->transfers[0]));
+ false));
TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
&cls->signkey,
&cls->order,
@@ -4532,7 +4455,6 @@ run_test_transfers (struct TestTransfers_Closure *cls)
GNUNET_TIME_UNIT_ZERO_TS,
8,
0,
- TALER_EXCHANGE_YNA_ALL,
1,
&cls->transfers[0]));
return 0;
@@ -4548,9 +4470,10 @@ static int
test_transfers (void)
{
struct TestTransfers_Closure test_cls;
+ int test_result;
pre_test_transfers (&test_cls);
- int test_result = run_test_transfers (&test_cls);
+ test_result = run_test_transfers (&test_cls);
post_test_transfers (&test_cls);
return test_result;
}
@@ -5317,8 +5240,10 @@ static int
test_refunds (void)
{
struct TestRefunds_Closure test_cls;
+ int test_result;
+
pre_test_refunds (&test_cls);
- int test_result = run_test_refunds (&test_cls);
+ test_result = run_test_refunds (&test_cls);
post_test_refunds (&test_cls);
return test_result;
}
@@ -5881,6 +5806,8 @@ static int
test_lookup_template (const struct InstanceData *instance,
const struct TemplateData *template)
{
+ const struct TALER_MERCHANTDB_TemplateDetails *to_cmp
+ = &template->template;
struct TALER_MERCHANTDB_TemplateDetails lookup_result;
if (0 > plugin->lookup_template (plugin->cls,
@@ -5893,7 +5820,6 @@ test_lookup_template (const struct InstanceData *instance,
TALER_MERCHANTDB_template_details_free (&lookup_result);
return 1;
}
- const struct TALER_MERCHANTDB_TemplateDetails *to_cmp = &template->template;
if (0 != check_templates_equal (&lookup_result,
to_cmp))
{
@@ -6326,7 +6252,9 @@ static int
test_lookup_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook)
{
+ const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
struct TALER_MERCHANTDB_WebhookDetails lookup_result;
+
if (0 > plugin->lookup_webhook (plugin->cls,
instance->instance.id,
webhook->id,
@@ -6337,7 +6265,6 @@ test_lookup_webhook (const struct InstanceData *instance,
TALER_MERCHANTDB_webhook_details_free (&lookup_result);
return 1;
}
- const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
if (0 != check_webhooks_equal (&lookup_result,
to_cmp))
{
@@ -6727,8 +6654,10 @@ static int
test_webhooks (void)
{
struct TestWebhooks_Closure test_cls;
+ int test_result;
+
pre_test_webhooks (&test_cls);
- int test_result = run_test_webhooks (&test_cls);
+ test_result = run_test_webhooks (&test_cls);
post_test_webhooks (&test_cls);
return test_result;
}
@@ -7221,6 +7150,9 @@ post_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
static int
run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
{
+ uint64_t webhook_pending_serial0;
+ uint64_t webhook_pending_serial1;
+
/* Test that insert without an instance fails */
TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
&cls->pwebhooks[0],
@@ -7256,10 +7188,10 @@ run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
2,
cls->pwebhooks));
- uint64_t webhook_pending_serial0 = get_pending_serial (&cls->instance,
- &cls->pwebhooks[0]);
- uint64_t webhook_pending_serial1 = get_pending_serial (&cls->instance,
- &cls->pwebhooks[1]);
+ webhook_pending_serial0 = get_pending_serial (&cls->instance,
+ &cls->pwebhooks[0]);
+ webhook_pending_serial1 = get_pending_serial (&cls->instance,
+ &cls->pwebhooks[1]);
/* Test webhook deletion */
TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
@@ -7285,8 +7217,10 @@ static int
test_pending_webhooks (void)
{
struct TestPendingWebhooks_Closure test_cls;
+ int test_result;
+
pre_test_pending_webhooks (&test_cls);
- int test_result = run_test_pending_webhooks (&test_cls);
+ test_result = run_test_pending_webhooks (&test_cls);
post_test_pending_webhooks (&test_cls);
return test_result;
}
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2024 Taler Systems SA
+ Copyright (C) 2014-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -4396,22 +4396,15 @@ struct TALER_MERCHANT_TransferData
uint64_t credit_serial;
/**
- * Time of the wire transfer, according to the exchange.
- * 0 for not provided by the exchange.
+ * Time of the wire transfer, based on when we received
+ * a confirmation for the wire transfer.
*/
struct GNUNET_TIME_Timestamp execution_time;
/**
- * Did we check the exchange's answer and are happy about it? False if we
- * did not check or are unhappy with the answer.
+ * True if this wire transfer was expected.
*/
- bool verified;
-
- /**
- * Did we confirm the wire transfer happened (via
- * #TALER_MERCHANT_transfers_post())?
- */
- bool confirmed;
+ bool expected;
};
@@ -4467,16 +4460,7 @@ typedef void
/**
* Request backend to return list of all wire transfers that
- * we received (or that the exchange claims we should have received).
- *
- * Note that when filtering by timestamp (using “before” and/or “after”), we
- * use the time reported by the exchange and thus will ONLY return results for
- * which we already have a response from the exchange. This should be
- * virtually all transfers, however it is conceivable that for some transfer
- * the exchange responded with a temporary error (i.e. HTTP status 500+) and
- * then we do not yet have an execution time to filter by. Thus, IF timestamp
- * filters are given, transfers for which we have no response from the
- * exchange yet are automatically excluded.
+ * we received.
*
* @param ctx execution context
* @param backend_url base URL of the backend
@@ -4487,7 +4471,7 @@ typedef void
* #GNUNET_TIME_UNIT_ZERO_ABS to not filter by @a after
* @param limit return at most this number of results; negative to descend in execution time
* @param offset start at this "credit serial" number (exclusive)
- * @param verified filter results by verification status
+ * @param expected filter results by expectation status
* @param cb the callback to call when a reply for this request is available
* @param cb_cls closure for @a cb
* @return a handle for this request
@@ -4501,7 +4485,7 @@ TALER_MERCHANT_transfers_get (
const struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll verified,
+ enum TALER_EXCHANGE_YesNoAll expected,
TALER_MERCHANT_GetTransfersCallback cb,
void *cb_cls);
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -913,10 +913,9 @@ typedef void
* @param cls closure
* @param rowid row of the transfer in the merchant database
* @param instance_id instance that received the transfer
- * @param exchange_url base URL of the exchange that initiated the transfer
+ * @param exchange_url URL of the exchange that is making the deposit
* @param payto_uri account of the merchant that received the transfer
* @param wtid wire transfer subject identifying the aggregation
- * @param total total amount that was wired
* @param next_attempt when should we next try to interact with the exchange
*/
typedef void
@@ -927,7 +926,6 @@ typedef void
const char *exchange_url,
struct TALER_FullPayto payto_uri,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *total,
struct GNUNET_TIME_Absolute next_attempt);
@@ -937,11 +935,12 @@ typedef void
* @param cls NULL
* @param deposit_serial identifies the deposit operation
* @param wire_deadline when is the wire due
- * @param retry_backoff current value of the retry backoff
+ * @param retry_time when to next try the exchange again
* @param h_contract_terms hash of the contract terms
* @param merchant_priv private key of the merchant
* @param instance_id name of the instance
- * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param h_wire hash of the merchant's wire account into
+ * @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin which the deposit was made
* @param coin_pub public key of the deposited coin
*/
@@ -950,7 +949,7 @@ typedef void
void *cls,
uint64_t deposit_serial,
struct GNUNET_TIME_Absolute wire_deadline,
- struct GNUNET_TIME_Relative retry_backoff,
+ struct GNUNET_TIME_Absolute retry_time,
const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const char *instance_id,
@@ -1000,32 +999,62 @@ typedef void
* Function called with information about a wire transfer.
*
* @param cls closure with a `json_t *` array to build up the response
- * @param credit_amount how much was wired to the merchant (minus fees)
+ * @param expected_credit_amount how we expect to see wired to the merchant (minus fees), NULL if unknown
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
* @param exchange_url base URL of the exchange that made the wire transfer
- * @param transfer_serial_id serial number identifying the transfer in the backend
+ * @param expected_transfer_serial_id serial number identifying the expected transfer in the backend
* @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_ABS
* if it did not yet happen
- * @param verified true if we checked the exchange's answer and liked it,
- * false there is a problem (verification failed or did not yet happen)
* @param confirmed true if the merchant confirmed this wire transfer
* false if it is so far only claimed to have been made by the exchange
*/
typedef void
(*TALER_MERCHANTDB_TransferCallback)(
void *cls,
- const struct TALER_Amount *credit_amount,
+ const struct TALER_Amount *expected_credit_amount,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_FullPayto payto_uri,
const char *exchange_url,
- uint64_t transfer_serial_id,
- struct GNUNET_TIME_Timestamp execution_time,
- bool verified,
+ uint64_t expected_transfer_serial_id,
+ struct GNUNET_TIME_Absolute execution_time,
bool confirmed);
/**
+ * Function called with information about expected incoming wire transfers.
+ *
+ * @param cls closure with a `json_t *` array to build up the response
+ * @param expected_credit_amount how we expect to see wired to the merchant (minus fees), NULL if unknown
+ * @param wtid wire transfer identifier
+ * @param payto_uri target account that received the wire transfer
+ * @param exchange_url base URL of the exchange that made the wire transfer
+ * @param expected_transfer_serial_id serial number identifying the expected transfer in the backend
+ * @param execution_time when did the exchange claim to have made the transfer
+ * @param confirmed true if the merchant confirmed this wire transfer
+ * false if it is so far only claimed to have been made by the exchange
+ * @param validated true if the reconciliation succeeded
+ * @param last_http_status HTTP status of our last request to the exchange for this transfer
+ * @param last_ec last error code we got back (otherwise #TALER_EC_NONE)
+ * @param last_error_detail last detail we got back (or NULL for none)
+ */
+typedef void
+(*TALER_MERCHANTDB_IncomingCallback)(
+ void *cls,
+ const struct TALER_Amount *expected_credit_amount,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_FullPayto payto_uri,
+ const char *exchange_url,
+ uint64_t expected_transfer_serial_id,
+ struct GNUNET_TIME_Timestamp execution_time,
+ bool confirmed,
+ bool validated,
+ unsigned int last_http_status,
+ enum TALER_ErrorCode last_ec,
+ const char *last_error_detail);
+
+
+/**
* If the given account is feasible, add it to the array
* of accounts we return.
*
@@ -2586,6 +2615,8 @@ struct TALER_MERCHANTDB_Plugin
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunds of coin
+ * @param check_time at what time should we check the deposit status
+ * with the exchange (for settlement)
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -2597,7 +2628,8 @@ struct TALER_MERCHANTDB_Plugin
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);
+ const struct TALER_Amount *refund_fee,
+ struct GNUNET_TIME_Absolute check_time);
/**
@@ -2757,9 +2789,10 @@ struct TALER_MERCHANTDB_Plugin
* @param exchange_url the exchange that made the transfer
* @param wtid wire transfer subject
* @param next_attempt when should we try again (if ever)
+ * @param http_status last HTTP status code from the server, 0 for timeout
* @param ec current error state of checking the transfer
- * @param failed true if validation has failed for good
- * @param verified true if validation has succeeded for good
+ * @param detail last error detail from the server, possibly NULL
+ * @param needs_retry true if we should retry the request
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -2768,9 +2801,36 @@ struct TALER_MERCHANTDB_Plugin
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct GNUNET_TIME_Absolute next_attempt,
+ unsigned int http_status,
enum TALER_ErrorCode ec,
- bool failed,
- bool verified);
+ const char *detail,
+ bool needs_retry);
+
+
+ /**
+ * Finalize transfer status with success.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param h_details hash over all of the aggregated deposits
+ * @param total_amount total amount exchange claimed to have transferred
+ * @param wire_fee wire fee charged by the exchange
+ * @param exchange_pub key used to make @e exchange_sig
+ * @param exchange_sig signature of the exchange over reconciliation data
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*finalize_transfer_status)(
+ void *cls,
+ 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);
+
/**
* Retrieve wire transfer details of wire details
@@ -2796,15 +2856,17 @@ struct TALER_MERCHANTDB_Plugin
*
* @param cls closure
* @param deposit_serial serial number of the deposit
+ * @param h_wire hash of the merchant's account that should receive the deposit
+ * @param exchange_url URL of the exchange that is making the deposit
* @param dd deposit transfer data from the exchange to store
- * @param[out] wpc set to true if the wire_pending flag was cleared
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*insert_deposit_to_transfer)(void *cls,
uint64_t deposit_serial,
- const struct TALER_EXCHANGE_DepositData *dd,
- bool *wpc);
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DepositData *dd);
/**
@@ -3006,8 +3068,7 @@ struct TALER_MERCHANTDB_Plugin
* @param wtid identifier of the wire transfer
* @param credit_amount how much did we receive
* @param payto_uri what is the merchant's bank account that received the transfer
- * @param confirmed whether the transfer was confirmed by the merchant or
- * was merely claimed by the exchange at this point
+ * @param bank_serial_id bank serial transfer ID, 0 for none (use NULL in DB!)
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -3018,7 +3079,7 @@ struct TALER_MERCHANTDB_Plugin
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *credit_amount,
struct TALER_FullPayto payto_uri,
- bool confirmed);
+ uint64_t bank_serial_id);
/**
@@ -3141,56 +3202,6 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Lookup transfer status.
- *
- * @param cls closure
- * @param instance_id the instance to look up details at
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @param[out] total_amount amount that was debited from our
- * aggregate balance at the exchange (in total, sum of
- * the wire transfer amount and the @a wire_fee)
- * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true)
- * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true)
- * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true)
- * @param[out] have_exchange_sig do we have a response from the exchange about this transfer
- * @param[out] verified did we confirm the transfer was OK
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*lookup_transfer)(
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct TALER_Amount *total_amount,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *exchange_amount,
- struct GNUNET_TIME_Timestamp *execution_time,
- bool *have_exchange_sig,
- bool *verified);
-
-
- /**
- * Set transfer status to confirmed.
- *
- * @param cls closure
- * @param instance_id instance to lookup payments for
- * @param exchange_url the exchange that made the transfer
- * @param wtid wire transfer subject
- * @param amount confirmed amount of the wire transfer
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*set_transfer_status_to_confirmed)(
- void *cls,
- const char *instance_id,
- const char *exchange_url,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *amount);
-
-
- /**
* Lookup transfer summary (used if we already verified the details).
*
* @param cls closure
@@ -3239,7 +3250,7 @@ struct TALER_MERCHANTDB_Plugin
* @param limit number of entries to return, negative for descending in execution time,
* positive for ascending in execution time
* @param offset transfer_serial number of the transfer we want to offset from
- * @param verified filter transfers by verification status
+ * @param expected filter for transfers that were expected
* @param cb function to call with detailed transfer data
* @param cb_cls closure for @a cb
* @return transaction status
@@ -3253,12 +3264,44 @@ struct TALER_MERCHANTDB_Plugin
struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll yna,
+ enum TALER_EXCHANGE_YesNoAll expected,
TALER_MERCHANTDB_TransferCallback cb,
void *cb_cls);
/**
+ * Lookup expected incoming transfers.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param payto_uri account that we are interested in transfers to
+ * @param before timestamp for the earliest transfer we care about
+ * @param after timestamp for the last transfer we care about
+ * @param limit number of entries to return, negative for descending in execution time,
+ * positive for ascending in execution time
+ * @param offset expected_transfer_serial number of the transfer we want to offset from
+ * @param confirmed filter by confirmation status
+ * @param verified filter by verification status
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_expected_transfers)(
+ void *cls,
+ 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);
+
+
+ /**
* Store information about wire fees charged by an exchange,
* including signature (so we have proof).
*
@@ -4018,20 +4061,23 @@ struct TALER_MERCHANTDB_Plugin
*
* @param cls closure
* @param deposit_serial deposit to update status for
- * @param wire_pending should we keep checking for the wire status with the exchange?
- * @param future_retry when should we ask the exchange again
- * @param retry_backoff current value for the retry backoff
- * @param emsg error message to record
+ * @param retry_needed true if the HTTP request should be retried
+ * @param retry_time when should we ask the exchange again
+ * @param last_http_status HTTP status code of the last reply
+ * @param last_ec Taler error code of the last reply
+ * @param last_detail detail from error message to record, possibly NULL
* @return database result code
*/
enum GNUNET_DB_QueryStatus
(*update_deposit_confirmation_status)(
void *cls,
uint64_t deposit_serial,
- bool wire_pending,
- struct GNUNET_TIME_Timestamp future_retry,
- struct GNUNET_TIME_Relative retry_backoff,
- const char *emsg);
+ bool retry_needed,
+ struct GNUNET_TIME_Timestamp retry_time,
+ uint32_t last_http_status,
+ enum TALER_ErrorCode last_ec,
+ const char *last_detail);
+
/**
* Lookup amount statistics for instance and slug by bucket.
diff --git a/src/lib/merchant_api_get_transfers.c b/src/lib/merchant_api_get_transfers.c
@@ -136,18 +136,10 @@ handle_transfers_get_finished (void *cls,
&td->exchange_url),
GNUNET_JSON_spec_uint64 ("transfer_serial_id",
&td->credit_serial),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("execution_time",
- &td->execution_time),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("verified",
- &td->verified),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("confirmed",
- &td->confirmed),
- NULL),
+ GNUNET_JSON_spec_timestamp ("execution_time",
+ &td->execution_time),
+ GNUNET_JSON_spec_bool ("expected",
+ &td->expected),
GNUNET_JSON_spec_end ()
};
@@ -224,13 +216,13 @@ TALER_MERCHANT_transfers_get (
const struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll verified,
+ enum TALER_EXCHANGE_YesNoAll expected,
TALER_MERCHANT_GetTransfersCallback cb,
void *cb_cls)
{
struct TALER_MERCHANT_GetTransfersHandle *gth;
CURL *eh;
- const char *verified_s = NULL;
+ const char *expected_s = NULL;
char limit_s[30];
char offset_s[30];
char before_s[30];
@@ -240,7 +232,7 @@ TALER_MERCHANT_transfers_get (
gth->ctx = ctx;
gth->cb = cb;
gth->cb_cls = cb_cls;
- verified_s = TALER_yna_to_string (verified);
+ expected_s = TALER_yna_to_string (expected);
GNUNET_snprintf (limit_s,
sizeof (limit_s),
"%lld",
@@ -249,8 +241,6 @@ TALER_MERCHANT_transfers_get (
sizeof (offset_s),
"%lld",
(unsigned long long) offset);
-
-
GNUNET_snprintf (before_s,
sizeof (before_s),
"%llu",
@@ -266,9 +256,9 @@ TALER_MERCHANT_transfers_get (
"private/transfers",
"payto_uri",
enc_payto,
- "verified",
- (TALER_EXCHANGE_YNA_ALL != verified)
- ? verified_s
+ "expected",
+ (TALER_EXCHANGE_YNA_ALL != expected)
+ ? expected_s
: NULL,
"limit",
0 != limit
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
@@ -494,6 +494,8 @@ run (void *cls,
MHD_HTTP_NO_CONTENT,
"deposit-simple",
NULL),
+ TALER_TESTING_cmd_depositcheck ("run taler-merchant-depositcheck-1",
+ config_file),
TALER_TESTING_cmd_run_tme ("run taler-merchant-reconciliation-1",
config_file),
TALER_TESTING_cmd_merchant_post_transfer2 ("post-transfer-bad",
diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh
@@ -482,8 +482,15 @@ echo "OK"
NOW=$(date +%s)
echo -n "Pay first order ${PAY_URL} ..."
-taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log
-taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y 2> wallet-pay1.err > wallet-pay1.log
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log
NOW2=$(date +%s)
echo " OK (took $(( NOW2 - NOW )) secs )"
@@ -513,12 +520,20 @@ WIRE_DEADLINE=$(jq -r .contract_terms.wire_transfer_deadline.t_s < "$LAST_RESPON
NOW=$(date +%s)
-TO_SLEEP=$(( WIRE_DEADLINE - NOW ))
+TO_SLEEP=$((1200 + WIRE_DEADLINE - NOW ))
echo "Waiting $TO_SLEEP secs for wire transfer"
echo -n "Perform wire transfers ..."
-taler-exchange-aggregator -y -c "$CONF" -T "${TO_SLEEP}"000000 -t -L INFO &> aggregator.log
-taler-exchange-transfer -c "$CONF" -t -L INFO &> transfer.log
+taler-exchange-aggregator \
+ -y \
+ -c "$CONF" \
+ -T "${TO_SLEEP}"000000 \
+ -t \
+ -L INFO &> aggregator.log
+taler-exchange-transfer \
+ -c "$CONF" \
+ -t \
+ -L INFO &> transfer.log
echo " DONE"
echo -n "Give time to Nexus to route the payment to Sandbox..."
# FIXME-MS: trigger immediate update at nexus
@@ -560,21 +575,6 @@ fi
echo "OK"
-echo -n "Notifying merchant of correct wire transfer (conflicting with old data)..."
-
-STATUS=$(curl 'http://localhost:9966/private/transfers' \
- -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \
- -m 3 \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
-
-if [ "$STATUS" != "409" ]
-then
- jq . < "$LAST_RESPONSE"
- exit_fail "Expected response conflict, after providing conflicting transfer data. got: $STATUS"
-fi
-
-echo " OK"
-
echo -n "Deleting bogus wire transfer ..."
TID=$(curl -s http://localhost:9966/private/transfers | jq -r .transfers[0].transfer_serial_id)
@@ -599,7 +599,7 @@ fi
echo " OK"
-echo -n "Notifying merchant of correct wire transfer (now working)..."
+echo -n "Notifying merchant of correct wire transfer..."
STATUS=$(curl 'http://localhost:9966/private/transfers' \
-d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \
@@ -614,26 +614,22 @@ fi
echo " OK"
-echo -n "Testing idempotence ..."
+echo -n "Running taler-merchant-depositcheck ..."
set -e
-
-
-# Test idempotence: do it again!
-
-STATUS=$(curl 'http://localhost:9966/private/transfers' \
- -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
-
-if [ "$STATUS" != "204" ]
-then
- jq . < "$LAST_RESPONSE"
- exit_fail "Expected response No Content, after providing transfer data. got: $STATUS"
-fi
-
+taler-merchant-depositcheck \
+ -L INFO \
+ -c "$CONF" \
+ -T "${TO_SLEEP}"000000 \
+ -t &> taler-merchant-depositcheck.log
echo " OK"
-echo -n "Testing taler-merchant-reconciliation ..."
+
+echo -n "Running taler-merchant-reconciliation ..."
set -e
-taler-merchant-reconciliation -L INFO -c "$CONF" -t &> taler-merchant-reconciliation.log
+taler-merchant-reconciliation \
+ -L INFO \
+ -c "$CONF" \
+ -T "${TO_SLEEP}"000000 \
+ -t &> taler-merchant-reconciliation.log
echo " OK"
diff --git a/src/testing/test_merchant_transfer_tracking.sh b/src/testing/test_merchant_transfer_tracking.sh
@@ -86,7 +86,10 @@ echo -n "."
# NOTE: once libeufin can do long-polling, we should
# be able to reduce the delay here and run wirewatch
# always in the background via setup
-taler-exchange-wirewatch -L "INFO" -c "$CONF" -t &> taler-exchange-wirewatch.out
+taler-exchange-wirewatch \
+ -L "INFO" \
+ -c "$CONF" \
+ -t &> taler-exchange-wirewatch0.out
echo -n "."
taler-wallet-cli \
@@ -236,7 +239,7 @@ WIRE_DEADLINE=$(jq -r .contract_terms.wire_transfer_deadline.t_s < "$LAST_RESPON
NOW=$(date +%s)
-TO_SLEEP=$(( WIRE_DEADLINE - NOW ))
+TO_SLEEP=$((3600 + WIRE_DEADLINE - NOW ))
echo "waiting $TO_SLEEP secs for wire transfer"
echo -n "Perform wire transfers ..."
@@ -277,7 +280,9 @@ echo -n "Notifying merchant of correct wire transfer, but on wrong instance..."
STATUS=$(curl 'http://localhost:9966/private/transfers' \
-d "{\"credit_amount\":\"$CREDIT_AMOUNT\",\"wtid\":\"$WTID\",\"payto_uri\":\"$TOR_PAYTO\",\"exchange_url\":\"$WURL\"}" \
-m 3 \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
@@ -290,7 +295,9 @@ echo " OK"
echo -n "Fetching wire transfers of ADMIN instance ..."
STATUS=$(curl 'http://localhost:9966/private/transfers' \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
@@ -309,13 +316,18 @@ fi
echo "OK"
echo -n "Fetching running taler-merchant-reconciliation on bogus transfer ..."
-taler-merchant-reconciliation -c "$CONF" -L INFO -t &> taler-merchant-reconciliation-bad.log
+taler-merchant-reconciliation \
+ -c "$CONF" \
+ -L INFO \
+ -t &> taler-merchant-reconciliation0.log
echo "OK"
echo -n "Fetching wire transfers of 'test' instance ..."
STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
@@ -356,26 +368,36 @@ echo " OK"
echo -n "Notifying merchant of correct wire transfer in the correct instance..."
#this time in the correct instance so the order will be marked as wired...
-STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \
- -m 3 \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
+echo -n "Running taler-merchant-wirewatch to check transfer ..."
+taler-merchant-wirewatch \
+ -c $CONF \
+ -t \
+ -L INFO &> taler-merchant-wirewatch1.log
+echo " DONE"
-if [ "$STATUS" != "204" ]
-then
- jq . < "$LAST_RESPONSE"
- exit_fail "Expected response 204 no content, after providing transfer data. got: $STATUS"
-fi
-echo " OK"
+echo -n "Post-check for exchange deposit ..."
+taler-merchant-depositcheck \
+ -c $CONF \
+ -t \
+ -e "http://localhost:8081/" \
+ -T ${TO_SLEEP}000000 \
+ -L INFO &> depositcheck1a.log
+echo " DONE"
echo -n "Fetching running taler-merchant-reconciliation on good transfer ..."
-taler-merchant-reconciliation -c $CONF -L INFO -t &> taler-merchant-reconciliation-bad.log
+taler-merchant-reconciliation \
+ -c $CONF \
+ -L INFO \
+ -T ${TO_SLEEP}000000 \
+ -t &> taler-merchant-reconciliation1.log
echo "OK"
echo -n "Fetching wire transfers of TEST instance ..."
STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
@@ -481,20 +503,37 @@ WIRE_DEADLINE=$(jq -r .contract_terms.wire_transfer_deadline.t_s < "$LAST_RESPON
NOW=$(date +%s)
-TO_SLEEP=$(( WIRE_DEADLINE - NOW ))
+TO_SLEEP=$((3600 + WIRE_DEADLINE - NOW ))
echo "waiting $TO_SLEEP secs for wire transfer"
echo -n "Pre-check for exchange deposit ..."
-taler-merchant-depositcheck -c $CONF -t -L INFO &> depositcheck2a.log
+taler-merchant-depositcheck \
+ -c $CONF \
+ -t \
+ -e "http://localhost:8081/" \
+ -L INFO &> depositcheck1b.log
echo " DONE"
echo -n "Perform wire transfers ..."
-taler-exchange-aggregator -y -c $CONF -T ${TO_SLEEP}000000 -t -L INFO &> aggregator2.log
-taler-exchange-transfer -c $CONF -t -L INFO &> transfer2.log
+taler-exchange-aggregator \
+ -y \
+ -c $CONF \
+ -T ${TO_SLEEP}000000 \
+ -t \
+ -L INFO &> aggregator2.log
+taler-exchange-transfer \
+ -c $CONF \
+ -t \
+ -L INFO &> transfer2.log
echo " DONE"
echo -n "Post-check for exchange deposit ..."
-taler-merchant-depositcheck -c $CONF -t -T ${TO_SLEEP}000000 -L INFO &> depositcheck2b.log
+taler-merchant-depositcheck \
+ -c $CONF \
+ -t \
+ -e "http://localhost:8081/" \
+ -T ${TO_SLEEP}000000 \
+ -L INFO &> depositcheck1c.log
echo " DONE"
@@ -523,29 +562,27 @@ then
fi
echo " OK"
-echo -n "Notifying merchant of correct wire transfer in the correct instance..."
-#this time in the correct instance so the order will be marked as wired...
-
-STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -d '{"credit_amount":"'"$CREDIT_AMOUNT"'","wtid":"'"$WTID"'","payto_uri":"'"$TARGET_PAYTO"'","exchange_url":"'"$WURL"'"}' \
- -m 3 \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
-
-if [ "$STATUS" != "204" ]
-then
- jq . < "$LAST_RESPONSE"
- exit_fail "Expected response 204 no content, after providing transfer data. got: $STATUS"
-fi
-echo " OK"
+echo -n "Running taler-merchant-wirewatch to check transfer ..."
+taler-merchant-wirewatch \
+ -c $CONF \
+ -t \
+ -L INFO &> taler-merchant-wirewatch2.log
+echo " DONE"
echo -n "Fetching running taler-merchant-reconciliation on good transfer ..."
-taler-merchant-reconciliation -c $CONF -L INFO -t &> taler-merchant-reconciliation2.log
+taler-merchant-reconciliation \
+ -c $CONF \
+ -L INFO \
+ -T ${TO_SLEEP}000000 \
+ -t &> taler-merchant-reconciliation2.log
echo "OK"
echo -n "Fetching wire transfers of TEST instance ..."
STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
@@ -648,26 +685,44 @@ WIRE_DEADLINE=$(jq -r .contract_terms.wire_transfer_deadline.t_s < "$LAST_RESPON
NOW=$(date +%s)
-TO_SLEEP=$(( WIRE_DEADLINE - NOW ))
+TO_SLEEP=$((1200 + WIRE_DEADLINE - NOW ))
echo "waiting $TO_SLEEP secs for wire transfer"
-echo -n "Perform wire transfers ..."
-taler-exchange-aggregator -y -c $CONF -T ${TO_SLEEP}000000 -t -L INFO &> aggregator3.log
-taler-exchange-transfer -c $CONF -t -L INFO &> transfer3.log
+echo -n "Perform wire transfers for 3rd order..."
+taler-exchange-aggregator \
+ -y \
+ -c $CONF \
+ -T ${TO_SLEEP}000000 \
+ -t \
+ -L INFO &> aggregator3.log
+taler-exchange-transfer \
+ -c $CONF \
+ -t \
+ -L INFO &> transfer3.log
echo " DONE"
echo -n "Running taler-merchant-wirewatch to check transfer ..."
-taler-merchant-wirewatch -c $CONF -t -L INFO &> taler-merchant-wirewatch.log
+taler-merchant-wirewatch \
+ -c $CONF \
+ -t \
+ -L INFO &> taler-merchant-wirewatch3.log
echo " DONE"
echo -n "Post-wirewatch check for exchange deposit ..."
-taler-merchant-depositcheck -c $CONF -t -T ${TO_SLEEP}000000 -L INFO &> depositcheck2b.log
+taler-merchant-depositcheck \
+ -c $CONF \
+ -t \
+ -e "http://localhost:8081/" \
+ -T ${TO_SLEEP}000000 \
+ -L INFO &> depositcheck1d.log
echo " DONE"
echo -n "Fetching wire transfers of TEST instance ..."
STATUS=$(curl 'http://localhost:9966/instances/test/private/transfers' \
- -w "%{http_code}" -s -o "$LAST_RESPONSE")
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
then
@@ -686,7 +741,11 @@ fi
echo "OK"
echo -n "Fetching running taler-merchant-reconciliation on good transfer ..."
-taler-merchant-reconciliation -c $CONF -L INFO -t &> taler-merchant-reconciliation2.log
+taler-merchant-reconciliation \
+ -c $CONF \
+ -L INFO \
+ -T ${TO_SLEEP}000000 \
+ -t &> taler-merchant-reconciliation3.log
echo "OK"
echo -n "Checking order status ..."
diff --git a/src/testing/test_merchant_wirewatch.sh b/src/testing/test_merchant_wirewatch.sh
@@ -298,7 +298,7 @@ fi
WIRE_DEADLINE=$(jq -r .contract_terms.wire_transfer_deadline.t_s < "$LAST_RESPONSE")
NOW=$(date +%s)
-TO_SLEEP="$(( 1 + WIRE_DEADLINE - NOW ))"
+TO_SLEEP="$(( 3600 + WIRE_DEADLINE - NOW ))"
echo -n "Perform wire transfers (with ${TO_SLEEP}s timeshift) ..."
taler-exchange-aggregator \
-y \
@@ -331,6 +331,23 @@ taler-merchant-wirewatch \
-L INFO &> merchant-wirewatch.log
echo " OK"
+echo -n "Obtaining deposit data from exchange..."
+taler-merchant-depositcheck \
+ -c "$CONF" \
+ -e "http://localhost:8081/" \
+ -T "${TO_SLEEP}000000" \
+ -t \
+ -L INFO &> merchant-depositcheck.log
+echo " OK"
+
+echo -n "Obtaining reconciliation data from exchange..."
+taler-merchant-reconciliation \
+ -c "$CONF" \
+ -T "${TO_SLEEP}000000" \
+ -t \
+ -L INFO &> merchant-reconciliation.log
+echo " OK"
+
echo -n "Fetching wire transfers of ADMIN instance ..."
STATUS=$(curl 'http://localhost:9966/private/transfers' \
-w "%{http_code}" \
@@ -347,7 +364,7 @@ then
fi
echo " OK"
-echo -n "Integrating wire transfer data with exchange..."
+echo -n "Reconciling wire transfer data with exchange..."
taler-merchant-reconciliation \
-c "$CONF" \
-t \
diff --git a/src/testing/testing_api_cmd_depositcheck.c b/src/testing/testing_api_cmd_depositcheck.c
@@ -70,7 +70,8 @@ depositcheck_run (void *cls,
"taler-merchant-depositcheck",
"-c", ws->config_filename,
"-t", /* exit when done */
- "-L", "DEBUG",
+ "-T", "1200s",
+ "-L", "INFO",
NULL);
if (NULL == ws->depositcheck_proc)
{
diff --git a/src/testing/testing_api_cmd_tme.c b/src/testing/testing_api_cmd_tme.c
@@ -69,7 +69,8 @@ tme_run (void *cls,
"taler-merchant-reconciliation",
"-c", ws->config_filename,
"-t", /* exit when done */
- "-L", "DEBUG",
+ "-T", "1200s",
+ "-L", "INFO",
NULL);
if (NULL == ws->merchant_reconciliation_proc)
{