merchant

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

commit 65fbe5b8a837614b20858c249c755e2e1892764e
parent be09126110af54308077a43aab2fda927cb93c5d
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 12 Apr 2023 15:11:21 +0200

skeleton logic for taler-merchant-wirewatch

Diffstat:
Msrc/backend/Makefile.am | 1+
Msrc/backend/taler-merchant-wirewatch.c | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 197 insertions(+), 0 deletions(-)

diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -177,6 +177,7 @@ taler_merchant_webhook_CFLAGS = \ taler_merchant_wirewatch_SOURCES = \ taler-merchant-wirewatch.c taler_merchant_wirewatch_LDADD = \ + $(top_builddir)/src/bank/libtalermerchantbank.la \ $(top_builddir)/src/backenddb/libtalermerchantdb.la \ -ltalermhd \ -ltalerjson \ diff --git a/src/backend/taler-merchant-wirewatch.c b/src/backend/taler-merchant-wirewatch.c @@ -22,9 +22,16 @@ #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <pthread.h> +#include "taler_merchant_bank_lib.h" #include "taler_merchantdb_lib.h" #include "taler_merchantdb_plugin.h" +/** + * Timeout for the bank interaction. Rather long as we should do long-polling + * and do not want to wake up too often. + */ +#define BANK_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, \ + 30) /** * The merchant's configuration. @@ -37,11 +44,22 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg; static struct TALER_MERCHANTDB_Plugin *db_plugin; /** + * Login data for the bank. + */ +static struct TALER_MERCHANT_BANK_AuthenticationData ad; + +/** * Next task to run, if any. */ static struct GNUNET_SCHEDULER_Task *task; /** + * Configuration section with authentication data. + * Set to default value, can be overridden via command-line. + */ +static char *section = "taler-merchant-wirewatch"; + +/** * Handle to the context for interacting with the bank. */ static struct GNUNET_CURL_Context *ctx; @@ -57,10 +75,35 @@ static struct GNUNET_CURL_RescheduleContext *rc; static int global_ret; /** + * How many transactions should we fetch at most per batch? + */ +static unsigned int batch_size = 32; + +/** * #GNUNET_YES if we are in test mode and should exit when idle. */ static int test_mode; +/** + * Bank history request. + */ +static struct TALER_MERCHANT_BANK_CreditHistoryHandle *hh; + +/** + * Artificial delay to use between API calls. Used to + * throttle on failures. + */ +static struct GNUNET_TIME_Relative delay; + +/** + * For which instance are we importing bank transfers? + */ +static char *instance_id; + +/** + * Start row for the bank interaction. Exclusive. + */ +static uint64_t start_row; /** * We're being aborted with CTRL-C (or SIGTERM). Shut down. @@ -78,6 +121,11 @@ shutdown_task (void *cls) GNUNET_SCHEDULER_cancel (task); task = NULL; } + if (NULL != hh) + { + TALER_MERCHANT_BANK_credit_history_cancel (hh); + hh = NULL; + } db_plugin->rollback (db_plugin->cls); /* just in case */ TALER_MERCHANTDB_plugin_unload (db_plugin); db_plugin = NULL; @@ -92,6 +140,25 @@ shutdown_task (void *cls) GNUNET_CURL_gnunet_rc_destroy (rc); rc = NULL; } + TALER_MERCHANT_BANK_auth_free (&ad); +} + + +/** + * Parse @a subject from wire transfer into @a wtid and @a exchange_url. + * + * @param subject wire transfer subject to parse + * @param[out] wtid wire transfer ID to extract + * @param[out] exchange_url set to exchange URL + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_subject (const char *subject, + struct TALER_WireTransferIdentifierRawP *wtid, + char **exchange_url) +{ + *exchange_url = NULL; + return GNUNET_SYSERR; } @@ -104,11 +171,111 @@ static void do_work (void *cls); +/** + * Callbacks of this type are used to serve the result of asking + * the bank for the credit transaction history. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the bank's reply is bogus (fails to follow the protocol), + * #MHD_HTTP_NO_CONTENT if there are no more results; on success the + * last callback is always of this status (even if `abs(num_results)` were + * already returned). + * @param ec detailed error code + * @param serial_id monotonically increasing counter corresponding to the transaction + * @param details details about the wire transfer + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration + */ +static enum GNUNET_GenericReturnValue +credit_cb ( + void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t serial_id, + const struct TALER_MERCHANT_BANK_CreditDetails *details) +{ + (void) cls; + switch (http_status) + { + case 0: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid HTTP response from bank\n"); + delay = GNUNET_TIME_STD_BACKOFF (delay); + break; + case MHD_HTTP_OK: + { + enum GNUNET_DB_QueryStatus qs; + char *exchange_url; + struct TALER_WireTransferIdentifierRawP wtid; + + if (GNUNET_OK != + parse_subject (details->wire_subject, + &wtid, + &exchange_url)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping transfer %llu (%s): not from exchange\n", + (unsigned long long) serial_id, + details->wire_subject); + start_row = serial_id; + return GNUNET_OK; + } + // FIXME: should also store serial_id, + // at least "sometimes"... + qs = db_plugin->insert_transfer (db_plugin->cls, + instance_id, + exchange_url, + &wtid, + &details->amount, + details->credit_account_uri, + true /* confirmed */); + GNUNET_free (exchange_url); + if (qs < 0) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + return GNUNET_SYSERR; + } + } + start_row = serial_id; + return GNUNET_OK; + case MHD_HTTP_NO_CONTENT: + delay = GNUNET_TIME_UNIT_ZERO; + break; + default: + delay = GNUNET_TIME_STD_BACKOFF (delay); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unexpected HTTP status code %u(%d) from bank\n", + http_status, + ec); + break; + } + hh = NULL; + task = GNUNET_SCHEDULER_add_delayed (delay, + &do_work, + NULL); + return GNUNET_OK; +} + + static void do_work (void *cls) { (void) cls; task = NULL; + hh = TALER_MERCHANT_BANK_credit_history (ctx, + &ad, + start_row, + batch_size, + BANK_TIMEOUT, + &credit_cb, + NULL); + if (NULL == hh) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + return; + } } @@ -130,6 +297,28 @@ run (void *cls, (void) cfgfile; cfg = c; + if (GNUNET_OK != + TALER_MERCHANT_BANK_auth_parse_cfg (cfg, + section, + &ad)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse authentication data in `%s'\n", + section); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "INSTANCE", + &instance_id)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "INSTANCE"); + TALER_MERCHANT_BANK_auth_free (&ad); + return; + } GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, @@ -157,6 +346,8 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } + // FIXME: extract 'start_row' from database somehow! + // (Note: depends on bank account! => need new table!) GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&do_work, NULL); @@ -175,6 +366,11 @@ main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('s', + "section", + "SECTION", + "configuration section to use for bank authentication data", + &section), GNUNET_GETOPT_option_flag ('t', "test", "run in test mode and exit when idle",