diff options
author | Christian Grothoff <christian@grothoff.org> | 2023-05-01 00:09:55 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2023-05-01 00:09:55 +0200 |
commit | 583c01c6224dd317f9665e623b02a32b74fdf74a (patch) | |
tree | 0b12e40c9dafde2b235c3c405a1ee032ff0e70d4 | |
parent | a7fe1d1b77ce1d7959522262f85788807d56316b (diff) | |
download | merchant-583c01c6224dd317f9665e623b02a32b74fdf74a.tar.gz merchant-583c01c6224dd317f9665e623b02a32b74fdf74a.tar.bz2 merchant-583c01c6224dd317f9665e623b02a32b74fdf74a.zip |
first rough cut at merchant update for #7810 (still with known bugs)
33 files changed, 1495 insertions, 682 deletions
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c index f0324c47..90875836 100644 --- a/src/backend/taler-merchant-httpd_exchanges.c +++ b/src/backend/taler-merchant-httpd_exchanges.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2021 Taler Systems SA + (C) 2014-2023 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 @@ -24,6 +24,10 @@ #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd.h" +/** + * How often do we retry DB transactions with soft errors? + */ +#define MAX_RETRIES 3 /** * Delay after which we'll re-fetch key information from the exchange. @@ -130,11 +134,6 @@ struct FeesByWireMethod char *wire_method; /** - * Full payto URI of the exchange. - */ - char *payto_uri; - - /** * Applicable fees, NULL if unknown/error. */ struct TALER_EXCHANGE_WireAggregateFees *af; @@ -285,16 +284,11 @@ json_t *TMH_trusted_exchanges; * this callback is called. Thus, once 'pending' turns 'false', * it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle, * in order to get the "good" keys. - * @param hr http response details - * @param keys information about the various keys used - * by the exchange - * @param compat version compatibility data + * @param kr response details */ static void keys_mgmt_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_Keys *keys, - enum TALER_EXCHANGE_VersionCompatibility compat); + const struct TALER_EXCHANGE_KeysResponse *kr); /** @@ -347,134 +341,189 @@ retry_exchange (void *cls) * * @param exchange connection to the exchange * @param master_pub public key of the exchange - * @param wire_method name of the wire method (i.e. "iban") - * @param payto_uri full payto URI of the exchange - * @param fees fee structure for this method + * @param num_methods number of wire methods supported + * @param fbm wire fees by method * @return #TALER_EC_NONE on success */ static enum TALER_ErrorCode process_wire_fees (struct Exchange *exchange, const struct TALER_MasterPublicKeyP *master_pub, - const char *wire_method, - const char *payto_uri, - const struct TALER_EXCHANGE_WireAggregateFees *fees) + unsigned int num_methods, + const struct TALER_EXCHANGE_WireFeesByMethod *fbm) { - struct FeesByWireMethod *f; - struct TALER_EXCHANGE_WireAggregateFees *endp; - struct TALER_EXCHANGE_WireAggregateFees *af; - - for (f = exchange->wire_fees_head; NULL != f; f = f->next) - if (0 == strcasecmp (wire_method, - f->wire_method)) - break; - if (NULL == f) + for (unsigned int i = 0; i<num_methods; i++) { - f = GNUNET_new (struct FeesByWireMethod); - f->wire_method = GNUNET_strdup (wire_method); - f->payto_uri = GNUNET_strdup (payto_uri); - GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head, - exchange->wire_fees_tail, - f); - } - endp = f->af; - while ( (NULL != endp) && - (NULL != endp->next) ) - endp = endp->next; - while ( (NULL != endp) && - (NULL != fees) && - (GNUNET_TIME_timestamp_cmp (fees->start_date, - <, - endp->end_date)) ) - fees = fees->next; - if ( (NULL != endp) && - (NULL != fees) && - (GNUNET_TIME_timestamp_cmp (fees->start_date, - !=, - endp->end_date)) ) - { - /* Hole in the fee structure, not allowed! */ - GNUNET_break_op (0); - return TALER_EC_MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE; - } - while (NULL != fees) - { - struct GNUNET_HashCode h_wire_method; - enum GNUNET_DB_QueryStatus qs; + const char *wire_method = fbm[i].method; + const struct TALER_EXCHANGE_WireAggregateFees *fees = fbm[i].fees_head; + struct FeesByWireMethod *f; + struct TALER_EXCHANGE_WireAggregateFees *endp; + struct TALER_EXCHANGE_WireAggregateFees *af; - af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees); - *af = *fees; - GNUNET_CRYPTO_hash (wire_method, - strlen (wire_method) + 1, - &h_wire_method); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Storing wire fee for `%s' and method `%s' at %s in DB; the fee is %s\n", - TALER_B2S (master_pub), - wire_method, - GNUNET_TIME_timestamp2s (af->start_date), - TALER_amount2s (&af->fees.wire)); - TMH_db->preflight (TMH_db->cls); - if (GNUNET_OK != - TMH_db->start (TMH_db->cls, - "store wire fee")) + for (f = exchange->wire_fees_head; NULL != f; f = f->next) + if (0 == strcasecmp (wire_method, + f->wire_method)) + break; + if (NULL == f) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to start database transaction to store exchange wire fees (will try to continue anyway)!\n"); - GNUNET_free (af); - fees = fees->next; - continue; + f = GNUNET_new (struct FeesByWireMethod); + f->wire_method = GNUNET_strdup (wire_method); + GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head, + exchange->wire_fees_tail, + f); } - qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls, - master_pub, - &h_wire_method, - &af->fees, - af->start_date, - af->end_date, - &af->master_sig); - if (0 > qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n"); - GNUNET_free (af); + endp = f->af; + while ( (NULL != endp) && + (NULL != endp->next) ) + endp = endp->next; + while ( (NULL != endp) && + (NULL != fees) && + (GNUNET_TIME_timestamp_cmp (fees->start_date, + <, + endp->end_date)) ) fees = fees->next; - TMH_db->rollback (TMH_db->cls); - continue; - } - if (0 == qs) + if ( (NULL != endp) && + (NULL != fees) && + (GNUNET_TIME_timestamp_cmp (fees->start_date, + !=, + endp->end_date)) ) { - /* Entry was already in DB, fine, continue as if we had succeeded */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Fees already in DB, rolling back transaction attempt!\n"); - TMH_db->rollback (TMH_db->cls); + /* Hole in the fee structure, not allowed! */ + GNUNET_break_op (0); + return TALER_EC_MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE; } - if (0 < qs) + while (NULL != fees) { - /* Inserted into DB, make sure transaction completes */ - qs = TMH_db->commit (TMH_db->cls); + struct GNUNET_HashCode h_wire_method; + enum GNUNET_DB_QueryStatus qs; + + af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees); + *af = *fees; + GNUNET_CRYPTO_hash (wire_method, + strlen (wire_method) + 1, + &h_wire_method); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Storing wire fee for `%s' and method `%s' at %s in DB; the fee is %s\n", + TALER_B2S (master_pub), + wire_method, + GNUNET_TIME_timestamp2s (af->start_date), + TALER_amount2s (&af->fees.wire)); + TMH_db->preflight (TMH_db->cls); + if (GNUNET_OK != + TMH_db->start (TMH_db->cls, + "store wire fee")) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start database transaction to store exchange wire fees (will try to continue anyway)!\n"); + GNUNET_free (af); + fees = fees->next; + continue; + } + qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls, + master_pub, + &h_wire_method, + &af->fees, + af->start_date, + af->end_date, + &af->master_sig); if (0 > qs) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n"); GNUNET_free (af); fees = fees->next; + TMH_db->rollback (TMH_db->cls); continue; } - } - af->next = NULL; - if (NULL == endp) - f->af = af; - else - endp->next = af; - endp = af; - fees = fees->next; - } + if (0 == qs) + { + /* Entry was already in DB, fine, continue as if we had succeeded */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Fees already in DB, rolling back transaction attempt!\n"); + TMH_db->rollback (TMH_db->cls); + } + if (0 < qs) + { + /* Inserted into DB, make sure transaction completes */ + qs = TMH_db->commit (TMH_db->cls); + if (0 > qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n"); + GNUNET_free (af); + fees = fees->next; + continue; + } + } + af->next = NULL; + if (NULL == endp) + f->af = af; + else + endp->next = af; + endp = af; + fees = fees->next; + } /* all fees for this method */ + } /* for all methods (i) */ return TALER_EC_NONE; } /** - * Function called with information about the wire accounts - * of the exchange. Stores the wire fees with the - * exchange for laster use. + * Add account restriction @a a to array of @a restrictions. + * + * @param[in,out] restrictions JSON array to build + * @param r restriction to add to @a restrictions + * @return #GNUNET_SYSERR if @a r is malformed + */ +static enum GNUNET_GenericReturnValue +add_restriction (json_t *restrictions, + const struct TALER_EXCHANGE_AccountRestriction *r) +{ + json_t *jr; + + jr = NULL; + switch (r->type) + { + case TALER_EXCHANGE_AR_INVALID: + GNUNET_break_op (0); + return GNUNET_SYSERR; + case TALER_EXCHANGE_AR_DENY: + jr = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "deny") + ); + break; + case TALER_EXCHANGE_AR_REGEX: + jr = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "type", + "regex"), + GNUNET_JSON_pack_string ( + "regex", + r->details.regex.posix_egrep), + GNUNET_JSON_pack_string ( + "human_hint", + r->details.regex.human_hint), + GNUNET_JSON_pack_object_incref ( + "human_hint_i18n", + (json_t *) r->details.regex.human_hint_i18n) + ); + break; + } + if (NULL == jr) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_assert (0 == + json_array_append_new (restrictions, + jr)); + return GNUNET_OK; + +} + + +/** + * Function called with information about the wire accounts of the exchange. * * @param exchange the exchange * @param master_pub public key of the exchange @@ -488,28 +537,89 @@ process_wire_accounts (struct Exchange *exchange, unsigned int accounts_len, const struct TALER_EXCHANGE_WireAccount *accounts) { - for (unsigned int i = 0; i<accounts_len; i++) + for (unsigned int r = 0; r<MAX_RETRIES; r++) { - enum TALER_ErrorCode ec; - char *method; + enum GNUNET_DB_QueryStatus qs; - method = TALER_payto_get_method (accounts[i].payto_uri); - if (NULL == method) + if (GNUNET_OK != + TMH_db->start (TMH_db->cls, + "update_exchange_accounts")) { - /* malformed payto:// URI returned by exchange */ - GNUNET_break_op (0); - return TALER_EC_GENERIC_PAYTO_URI_MALFORMED; + TMH_db->rollback (TMH_db->cls); + GNUNET_break (0); + return TALER_EC_GENERIC_DB_START_FAILED; } - ec = process_wire_fees (exchange, - master_pub, - method, - accounts[i].payto_uri, - accounts[i].fees); - GNUNET_free (method); - if (TALER_EC_NONE != ec) - return ec; + qs = TMH_db->delete_exchange_accounts (TMH_db->cls, + master_pub); + if (qs < 0) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + continue; + GNUNET_break (0); + return TALER_EC_GENERIC_DB_STORE_FAILED; + } + for (unsigned int i = 0; i<accounts_len; i++) + { + const struct TALER_EXCHANGE_WireAccount *account = &accounts[i]; + json_t *debit_restrictions; + json_t *credit_restrictions; + + debit_restrictions = json_array (); + GNUNET_assert (NULL != debit_restrictions); + credit_restrictions = json_array (); + GNUNET_assert (NULL != credit_restrictions); + for (unsigned int j = 0; j<account->debit_restrictions_length; j++) + { + if (GNUNET_OK != + add_restriction (debit_restrictions, + &account->debit_restrictions[j])) + { + TMH_db->rollback (TMH_db->cls); + GNUNET_break (0); + return TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED; + } + } + for (unsigned int j = 0; j<account->credit_restrictions_length; j++) + { + if (GNUNET_OK != + add_restriction (credit_restrictions, + &account->credit_restrictions[j])) + { + TMH_db->rollback (TMH_db->cls); + GNUNET_break (0); + return TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED; + } + } + qs = TMH_db->insert_exchange_account (TMH_db->cls, + master_pub, + account->payto_uri, + account->conversion_url, + debit_restrictions, + credit_restrictions, + &account->master_sig); + if (qs < 0) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto outer; + GNUNET_break (0); + return TALER_EC_GENERIC_DB_STORE_FAILED; + } + } + qs = TMH_db->commit (TMH_db->cls); + if (qs < 0) + { + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + continue; + GNUNET_break (0); + return TALER_EC_GENERIC_DB_COMMIT_FAILED; + } + return TALER_EC_NONE; +outer:; } - return TALER_EC_NONE; + return TALER_EC_GENERIC_DB_SOFT_FAILURE; } @@ -643,8 +753,6 @@ process_find_operations (struct Exchange *exchange) fo->fc (fo->fc_cls, &hr, exchange->conn, - (NULL != fbw) ? fbw->payto_uri : NULL, - (NULL != fbw) ? &fbw->af->fees.wire : NULL, exchange->trusted); } TMH_EXCHANGES_find_exchange_cancel (fo); @@ -668,15 +776,11 @@ wire_task_cb (void *cls); * that is #TALER_EXCHANGE_get_keys() will succeed. * * @param cls closure, a `struct Exchange` - * @param hr HTTP response details - * @param accounts_len length of the @a accounts array - * @param accounts list of wire accounts of the exchange, NULL on error + * @param wr response details */ static void handle_wire_data (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - unsigned int accounts_len, - const struct TALER_EXCHANGE_WireAccount *accounts) + const struct TALER_EXCHANGE_WireResponse *wr) { struct Exchange *exchange = cls; const struct TALER_EXCHANGE_Keys *keys; @@ -685,7 +789,7 @@ handle_wire_data (void *cls, exchange->wire_request = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received /wire response\n"); - if (MHD_HTTP_OK != hr->http_status) + if (MHD_HTTP_OK != wr->hr.http_status) { struct TMH_EXCHANGES_FindOperation *fo; @@ -693,16 +797,14 @@ handle_wire_data (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to obtain /wire details from `%s': %u/%d\n", exchange->url, - hr->http_status, - hr->ec); + wr->hr.http_status, + wr->hr.ec); while (NULL != (fo = exchange->fo_head)) { fo->fc (fo->fc_cls, - hr, + &wr->hr, exchange->conn, - NULL, - NULL, - GNUNET_NO); + false); TMH_EXCHANGES_find_exchange_cancel (fo); } return; @@ -711,8 +813,13 @@ handle_wire_data (void *cls, GNUNET_assert (NULL != keys); ecx = process_wire_accounts (exchange, &keys->master_pub, - accounts_len, - accounts); + wr->details.ok.accounts_len, + wr->details.ok.accounts); + if (TALER_EC_NONE == ecx) + ecx = process_wire_fees (exchange, + &keys->master_pub, + wr->details.ok.fees_len, + wr->details.ok.fees); if (TALER_EC_NONE != ecx) { /* Report hard failure to all callbacks! */ @@ -720,7 +827,7 @@ handle_wire_data (void *cls, struct TALER_EXCHANGE_HttpResponse hrx = { .ec = ecx, .http_status = 0, - .reply = hr->reply + .reply = wr->hr.reply }; GNUNET_break_op (0); @@ -730,9 +837,7 @@ handle_wire_data (void *cls, fo->fc (fo->fc_cls, &hrx, NULL, - NULL, - NULL, - GNUNET_NO); + false); TMH_EXCHANGES_find_exchange_cancel (fo); } return; @@ -754,7 +859,7 @@ handle_wire_data (void *cls, GNUNET_STRINGS_relative_time_to_string ( exchange->wire_retry_delay, - GNUNET_YES)); + true)); exchange->wire_task = GNUNET_SCHEDULER_add_delayed (exchange->wire_retry_delay, &wire_task_cb, @@ -818,7 +923,6 @@ free_exchange_entry (struct Exchange *exchange) GNUNET_free (af); } GNUNET_free (f->wire_method); - GNUNET_free (f->payto_uri); GNUNET_free (f); } if (NULL != exchange->wire_request) @@ -854,12 +958,10 @@ free_exchange_entry (struct Exchange *exchange) * * @param exchange exchange that failed * @param hr details about the HTTP reply - * @param compat version compatibility data */ static void fail_and_retry (struct Exchange *exchange, - const struct TALER_EXCHANGE_HttpResponse *hr, - enum TALER_EXCHANGE_VersionCompatibility compat) + const struct TALER_EXCHANGE_HttpResponse *hr) { struct TMH_EXCHANGES_FindOperation *fo; @@ -879,23 +981,9 @@ fail_and_retry (struct Exchange *exchange, fo->fc (fo->fc_cls, hr, NULL, - NULL, - NULL, - GNUNET_NO); + false); TMH_EXCHANGES_find_exchange_cancel (fo); } - if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat) - { - /* Log hard error: we likely need admin help! */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Exchange `%s' runs an incompatible more recent version of the Taler protocol. Will not retry. This client may need to be updated.\n", - exchange->url); - /* Theoretically, the exchange could downgrade, - but let's not be too aggressive about retries - on this one. */ - exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS, - exchange->retry_delay); - } if ( (NULL == exchange->fo_head) && (TALER_EC_GENERIC_CONFIGURATION_INVALID == hr->ec) ) { @@ -921,40 +1009,22 @@ fail_and_retry (struct Exchange *exchange, } -/** - * Function called with information about who is auditing - * a particular exchange and what key the exchange is using. - * - * @param cls closure, will be `struct Exchange` so that - * when this function gets called, it will change the flag 'pending' - * to 'false'. Note: 'keys' is automatically saved inside the exchange's - * handle, which is contained inside 'struct Exchange', when - * this callback is called. Thus, once 'pending' turns 'false', - * it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle, - * in order to get the "good" keys. - * @param hr http response details - * @param keys information about the various keys used - * by the exchange - * @param compat version compatibility data - */ static void keys_mgmt_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_Keys *keys, - enum TALER_EXCHANGE_VersionCompatibility compat) + const struct TALER_EXCHANGE_KeysResponse *kr) { struct Exchange *exchange = cls; struct GNUNET_TIME_Timestamp expire; struct GNUNET_TIME_Relative delay; + const struct TALER_EXCHANGE_Keys *keys; - if ( (MHD_HTTP_OK != hr->http_status) || - (NULL == keys) ) + if (MHD_HTTP_OK != kr->hr.http_status) { fail_and_retry (exchange, - hr, - compat); + &kr->hr); return; } + keys = kr->details.ok.keys; if ( (exchange->trusted) && (0 != GNUNET_memcmp (&exchange->master_pub, &keys->master_pub)) ) @@ -982,19 +1052,6 @@ keys_mgmt_cb (void *cls, exchange->trusted = true; /* same exchange, different URL => trust applies */ } } - if (0 != (TALER_EXCHANGE_VC_NEWER & compat)) - { - /* Warn user exactly once about need to upgrade */ - static int once; - - if (0 == once) - { - once = 1; - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Exchange `%s' runs a more recent version of the Taler protocol. You may want to update this client.\n", - exchange->url); - } - } /* store exchange online signing keys in our DB */ for (unsigned int i = 0; i<keys->num_sign_keys; i++) @@ -1015,8 +1072,7 @@ keys_mgmt_cb (void *cls, { GNUNET_break (0); fail_and_retry (exchange, - hr, - compat); + &kr->hr); return; } } @@ -1089,16 +1145,35 @@ return_result (void *cls) } +/** + * Lookup exchange by @a exchange_url. + * + * @param exchange_url base URL to match against + * @return NULL if exchange is not yet known + */ +static struct Exchange * +lookup_exchange (const char *exchange_url) +{ + for (struct Exchange *exchange = exchange_head; + NULL != exchange; + exchange = exchange->next) + if (0 == strcmp (exchange->url, + exchange_url)) + return exchange; + return NULL; +} + + struct TMH_EXCHANGES_FindOperation * TMH_EXCHANGES_find_exchange (const char *chosen_exchange, - const char *wire_method, - int force_reload, + bool force_reload, TMH_EXCHANGES_FindContinuation fc, void *fc_cls) { struct Exchange *exchange; struct TMH_EXCHANGES_FindOperation *fo; struct GNUNET_TIME_Timestamp now; + const char *wire_method = NULL; if (NULL == merchant_curl_ctx) { @@ -1109,22 +1184,7 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange, "Trying to find chosen exchange `%s'\n", chosen_exchange); /* Check if the exchange is known */ - for (exchange = exchange_head; NULL != exchange; exchange = exchange->next) - { - /* test it by checking URL */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Comparing chosen exchange url '%s' with known url '%s'.\n", - chosen_exchange, - exchange->url); - if (0 == strcmp (exchange->url, - chosen_exchange)) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "The exchange `%s' is already known (good)\n", - chosen_exchange); - break; - } - } + exchange = lookup_exchange (chosen_exchange); if (NULL == exchange) { /* This is a new exchange */ @@ -1326,6 +1386,31 @@ accept_exchanges (void *cls, enum GNUNET_GenericReturnValue +TMH_EXCHANGES_lookup_wire_fee (const char *exchange_url, + const char *wire_method, + struct TALER_Amount *wire_fee) +{ + struct Exchange *exchange; + const struct FeesByWireMethod *fbm; + const struct TALER_EXCHANGE_WireAggregateFees *af; + + exchange = lookup_exchange (exchange_url); + if (NULL == exchange) + return GNUNET_SYSERR; + if (! exchange->have_wire) + return GNUNET_SYSERR; + fbm = get_wire_fees (exchange, + GNUNET_TIME_timestamp_get (), + wire_method); + if (NULL == fbm) + return GNUNET_NO; + af = fbm->af; + *wire_fee = af->fees.wire; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg) { merchant_curl_ctx diff --git a/src/backend/taler-merchant-httpd_exchanges.h b/src/backend/taler-merchant-httpd_exchanges.h index df68b9a5..2f4e69fe 100644 --- a/src/backend/taler-merchant-httpd_exchanges.h +++ b/src/backend/taler-merchant-httpd_exchanges.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2020 Taler Systems SA + (C) 2014-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 @@ -61,16 +61,12 @@ TMH_EXCHANGES_done (void); * @param cls closure * @param hr HTTP response details * @param eh handle to the exchange context - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ typedef void (*TMH_EXCHANGES_FindContinuation)(void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted); @@ -86,24 +82,36 @@ struct TMH_EXCHANGES_FindOperation; * NULL for the exchange. * * @param chosen_exchange URL of the exchange we would like to talk to - * @param wire_method the wire method we will use with @a chosen_exchange, NULL for none - * @param force_reload try to force reloading /keys from the exchange ASAP; note - * that IF the forced reload fails, it is possible @a fc won't be called at all - * until a /keys download succeeds; only use #GNUNET_YES if a new /keys request - * is mandatory. If the force reload request is not allowed due to our rate limiting, - * then @a fc will be called immediately with the existing /keys data + * @param force_reload set to true to download /wire again even if we already have + * an answer (used if the answer might be stale). * @param fc function to call with the handles for the exchange * @param fc_cls closure for @a fc */ struct TMH_EXCHANGES_FindOperation * TMH_EXCHANGES_find_exchange (const char *chosen_exchange, - const char *wire_method, - int force_reload, + bool force_reload, TMH_EXCHANGES_FindContinuation fc, void *fc_cls); /** + * Lookup current wire fee by @a exchange_url and + * @a wire_method. + * + * @param exchange_url base URL of the exchange + * @param wire_method wire method to lookup fee by + * @param[out] wire_fee set to the wire fee + * @return #GNUNET_OK on success + * #GNUNET_NO if @a wire_method is not supported + * #GNUNET_SYSERR if @a exchange_url did not yet respond properly to our /wire request + */ +enum GNUNET_GenericReturnValue +TMH_EXCHANGES_lookup_wire_fee (const char *exchange_url, + const char *wire_method, + struct TALER_Amount *wire_fee); + + +/** * Abort pending find operation. * * @param fo handle to operation to abort diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c index 981f5937..b93cdb12 100644 --- a/src/backend/taler-merchant-httpd_helper.c +++ b/src/backend/taler-merchant-httpd_helper.c @@ -755,3 +755,99 @@ TMH_trigger_webhook (const char *instance, return qs; return t.rv; } + + +/** + * Closure for #add_matching_account(). + */ +struct ExchangeMatchContext +{ + /** + * Wire method to match, NULL for all. + */ + const char *wire_method; + + /** + * Array of accounts to return. + */ + json_t *accounts; +}; + + +/** + * If the given account is feasible, add it to the array + * of accounts we return. + * + * @param cls a `struct PostReserveContext` + * @param payto_uri URI of the account + * @param conversion_url URL of a conversion service + * @param debit_restrictions restrictions for debits from account + * @param credit_restrictions restrictions for credits to account + * @param master_sig signature affirming the account + */ +static void +add_matching_account (void *cls, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig) +{ + struct ExchangeMatchContext *rc = cls; + char *method; + + method = TALER_payto_get_method (payto_uri); + if ( (NULL == rc->wire_method) || + (0 == strcmp (method, + rc->wire_method)) ) + { + json_t *acc; + + acc = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("payto_uri", + payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), + GNUNET_JSON_pack_array_incref ("credit_restrictions", + (json_t *) credit_restrictions) + ); + GNUNET_assert (0 == + json_array_append_new (rc->accounts, + acc)); + } + GNUNET_free (method); +} + + +/** + * Return JSON array with all of the exchange accounts + * that support the given @a wire_method. + * + * @param master_pub master public key to match exchange by + * @param wire_method NULL for any + * @return JSON array with information about all matching accounts + */ +json_t * +TMH_exchange_accounts_by_method ( + const struct TALER_MasterPublicKeyP *master_pub, + const char *wire_method) +{ + struct ExchangeMatchContext emc = { + .wire_method = wire_method, + .accounts = json_array () + }; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_assert (NULL != emc.accounts); + qs = TMH_db->select_accounts_by_exchange (TMH_db->cls, + master_pub, + &add_matching_account, + &emc); + if (qs < 0) + { + json_decref (emc.accounts); + return NULL; + } + return emc.accounts; +} diff --git a/src/backend/taler-merchant-httpd_helper.h b/src/backend/taler-merchant-httpd_helper.h index 930186d3..3b64c04b 100644 --- a/src/backend/taler-merchant-httpd_helper.h +++ b/src/backend/taler-merchant-httpd_helper.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021 Taler Systems SA + Copyright (C) 2021-2023 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 @@ -187,4 +187,18 @@ TMH_trigger_webhook (const char *instance, const json_t *args); +/** + * Return JSON array with all of the exchange accounts + * that support the given @a wire_method. + * + * @param master_pub master public key to match exchange by + * @param wire_method NULL for any + * @return JSON array with information about all matching accounts + */ +json_t * +TMH_exchange_accounts_by_method ( + const struct TALER_MasterPublicKeyP *master_pub, + const char *wire_method); + + #endif diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c index d0fcfbc0..9ab10939 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c @@ -468,30 +468,23 @@ find_next_exchange (struct AbortContext *ac); * passed back to the wallet). * * @param cls closure - * @param hr HTTP response data - * @param sign_key exchange key used to sign @a obj, or NULL - * @param signature the actual signature, or NULL on error + * @param rr response data */ static void refund_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_ExchangePublicKeyP *sign_key, - const struct TALER_ExchangeSignatureP *signature) + const struct TALER_EXCHANGE_RefundResponse *rr) { struct RefundDetails *rd = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; struct AbortContext *ac = rd->ac; - (void) sign_key; - (void) signature; rd->rh = NULL; rd->http_status = hr->http_status; rd->exchange_reply = json_incref ((json_t*) hr->reply); if (MHD_HTTP_OK == hr->http_status) { - GNUNET_assert (NULL != sign_key); - GNUNET_assert (NULL != signature); - rd->exchange_pub = *sign_key; - rd->exchange_sig = *signature; + rd->exchange_pub = rr->details.ok.exchange_pub; + rd->exchange_sig = rr->details.ok.exchange_sig; } ac->pending_at_ce--; if (0 == ac->pending_at_ce) @@ -504,10 +497,7 @@ refund_cb (void *cls, * * @param cls the `struct AbortContext` * @param hr HTTP response details - * @param payto_uri payto://-URI of the exchange * @param exchange_handle NULL if exchange was not found to be acceptable - * @param wire_fee current applicable fee for dealing with @a exchange_handle, - * NULL if not available * @param exchange_trusted true if this exchange is * trusted by config */ @@ -515,13 +505,10 @@ static void process_abort_with_exchange (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *exchange_handle, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct AbortContext *ac = cls; - (void) payto_uri; (void) exchange_trusted; ac->fo = NULL; GNUNET_assert (GNUNET_YES == ac->suspended); @@ -613,8 +600,7 @@ find_next_exchange (struct AbortContext *ac) { ac->current_exchange = rdi->exchange_url; ac->fo = TMH_EXCHANGES_find_exchange (ac->current_exchange, - NULL, - GNUNET_NO, + false, &process_abort_with_exchange, ac); if (NULL == ac->fo) diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c index df9bd21e..55e345e6 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -805,9 +805,6 @@ deposit_get_callback ( * @param cls the `struct KycContext` * @param hr HTTP response details * @param exchange_handle NULL if exchange was not found to be acceptable - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable fee for dealing with @a exchange_handle, - * NULL if not available * @param exchange_trusted true if this exchange is * trusted by config */ @@ -816,8 +813,6 @@ process_kyc_with_exchange ( void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *exchange_handle, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct KycContext *kc = cls; @@ -948,8 +943,7 @@ check_kyc (struct PayContext *pc, kc_tail, kc); kc->fo = TMH_EXCHANGES_find_exchange (kc->exchange_url, - NULL, - GNUNET_NO, + false, &process_kyc_with_exchange, kc); if (NULL == kc->fo) @@ -1013,11 +1007,11 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, it is possible to over-pay if two wallets literally make a concurrent payment, as the earlier check for 'paid' is not in the same transaction scope as this 'insert' operation. */ - GNUNET_assert (j < dr->details.success.num_signatures); + GNUNET_assert (j < dr->details.ok.num_signatures); qs = TMH_db->insert_deposit ( TMH_db->cls, pc->hc->instance->settings.id, - dr->details.success.deposit_timestamp, + dr->details.ok.deposit_timestamp, &pc->h_contract_terms, &dc->cdd.coin_pub, dc->exchange_url, @@ -1026,8 +1020,8 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, &dc->refund_fee, &dc->wire_fee, &pc->wm->h_wire, - &dr->details.success.exchange_sigs[j++], - dr->details.success.exchange_pub); + &dr->details.ok.exchange_sigs[j++], + dr->details.ok.exchange_pub); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { TMH_db->rollback (TMH_db->cls); @@ -1181,9 +1175,6 @@ batch_deposit_cb ( * @param cls the `struct ExchangeGroup` * @param hr HTTP response details * @param exchange_handle NULL if exchange was not found to be acceptable - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable fee for dealing with @a exchange_handle, - * NULL if not available * @param exchange_trusted true if this exchange is * trusted by config */ @@ -1192,8 +1183,6 @@ process_pay_with_exchange ( void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *exchange_handle, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct ExchangeGroup *eg = cls; @@ -1202,7 +1191,6 @@ process_pay_with_exchange ( const struct TALER_EXCHANGE_Keys *keys; unsigned int group_size; - (void) payto_uri; eg->fo = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Processing payment with exchange %s\n", @@ -1275,8 +1263,7 @@ process_pay_with_exchange ( Maybe the wallet has seen /keys that we missed. */ eg->tried_force_keys = true; eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, - pc->wm->wire_method, - GNUNET_YES, + true, &process_pay_with_exchange, eg); if (NULL != eg->fo) @@ -1316,8 +1303,7 @@ process_pay_with_exchange ( Maybe the wallet has seen auditors that we missed. */ eg->tried_force_keys = true; eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, - pc->wm->wire_method, - GNUNET_YES, + true, &process_pay_with_exchange, eg); if (NULL != eg->fo) @@ -1439,6 +1425,7 @@ AGE_FAIL: for (unsigned int i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; + enum GNUNET_GenericReturnValue ret; if (dc->found_in_db) continue; @@ -1446,7 +1433,27 @@ AGE_FAIL: eg->exchange_url)) continue; cdds[i] = dc->cdd; - dc->wire_fee = *wire_fee; + ret = TMH_EXCHANGES_lookup_wire_fee (dc->exchange_url, + pc->wm->wire_method, + &dc->wire_fee); + if (GNUNET_OK != ret) + { + enum TALER_ErrorCode ec; + + ec = (GNUNET_NO == ret) + ? TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED + : TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED; + pc->pending_at_eg--; + GNUNET_break_op (0); + resume_pay_with_response ( + pc, + TALER_ErrorCode_get_http_status_safe (ec), + TALER_MHD_MAKE_JSON_PACK ( + TALER_JSON_pack_ec (ec), + GNUNET_JSON_pack_string ("wire_method", + pc->wm->wire_method))); + return; + } } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Initiating batch deposit with %u coins\n", @@ -1509,8 +1516,7 @@ start_batch_deposits (struct PayContext *pc) if (! have_coins) continue; /* no coins left to deposit at this exchange */ eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, - pc->wm->wire_method, - GNUNET_NO, + false, &process_pay_with_exchange, eg); if (NULL == eg->fo) diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-refund.c b/src/backend/taler-merchant-httpd_post-orders-ID-refund.c index 40c89712..766c8814 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-refund.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-refund.c @@ -386,17 +386,14 @@ notify_refund_obtained (struct PostRefundData *prd) * refund request to an exchange. * * @param cls a `struct CoinRefund` - * @param hr HTTP response data - * @param exchange_pub exchange key used to sign refund confirmation - * @param exchange_sig exchange's signature over refund + * @param rr response data */ static void refund_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_ExchangePublicKeyP *exchange_pub, - const struct TALER_ExchangeSignatureP *exchange_sig) + const struct TALER_EXCHANGE_RefundResponse *rr) { struct CoinRefund *cr = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; cr->rh = NULL; cr->exchange_status = hr->http_status; @@ -413,12 +410,12 @@ refund_cb (void *cls, { enum GNUNET_DB_QueryStatus qs; - cr->exchange_pub = *exchange_pub; - cr->exchange_sig = *exchange_sig; + cr->exchange_pub = rr->details.ok.exchange_pub; + cr->exchange_sig = rr->details.ok.exchange_sig; qs = TMH_db->insert_refund_proof (TMH_db->cls, cr->refund_serial, - exchange_sig, - exchange_pub); + &rr->details.ok.exchange_sig, + &rr->details.ok.exchange_pub); if (0 >= qs) { /* generally, this is relatively harmless for the merchant, but let's at @@ -443,23 +440,17 @@ refund_cb (void *cls, * @param cls a `struct CoinRefund *` * @param hr HTTP response details * @param eh handle to the exchange context - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void exchange_found_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct CoinRefund *cr = cls; struct PostRefundData *prd = cr->prd; - (void) payto_uri; - (void) wire_fee; (void) exchange_trusted; cr->fo = NULL; if (NULL == hr) @@ -725,8 +716,7 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh, { /* We need to talk to the exchange */ cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url, - NULL, - GNUNET_NO, + false, &exchange_found_cb, cr); } diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c index 24bbba35..fb560de6 100644 --- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c +++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c @@ -309,15 +309,14 @@ TMH_force_tip_pickup_resume () * planchet operation, resume HTTP processing. * * @param cls closure with a `struct PlanchetOperation *` - * @param hr HTTP response data - * @param blind_sig blind signature over the coin, NULL on error + * @param w2r response data */ static void withdraw_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_BlindedDenominationSignature *blind_sig) + const struct TALER_EXCHANGE_Withdraw2Response *w2r) { struct PlanchetOperation *po = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &w2r->hr; struct PickupContext *pc = po->pc; enum GNUNET_DB_QueryStatus qs; @@ -325,7 +324,7 @@ withdraw_cb (void *cls, pc->po_tail, po); TMH_db->preflight (TMH_db->cls); - if (NULL == blind_sig) + if (MHD_HTTP_OK != hr->http_status) { GNUNET_free (po); stop_operations (pc); @@ -344,7 +343,7 @@ withdraw_cb (void *cls, qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls, &pc->pickup_id, po->offset, - blind_sig); + &w2r->details.ok.blind_sig); GNUNET_free (po); if (qs < 0) { @@ -380,16 +379,12 @@ withdraw_cb (void *cls, * @param cls closure, with our `struct PlanchetOperation *` * @param hr HTTP response details * @param eh handle to the exchange context - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void do_withdraw (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct PlanchetOperation *po = cls; @@ -463,8 +458,7 @@ try_withdraw (struct PickupContext *pc, po->pd = *planchet; po->offset = offset; po->fo = TMH_EXCHANGES_find_exchange (exchange_url, - NULL, - GNUNET_NO, + false, &do_withdraw, po); GNUNET_assert (NULL != po->fo); @@ -507,16 +501,12 @@ do_timeout (void *cls) * @param cls closure, with our `struct PickupContext *` * @param hr HTTP response details * @param eh handle to the exchange context - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void compute_total_requested (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct PickupContext *pc = cls; @@ -796,8 +786,7 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh, &do_timeout, pc); pc->fo = TMH_EXCHANGES_find_exchange (exchange_url, - NULL, - GNUNET_NO, + false, &compute_total_requested, pc); GNUNET_free (exchange_url); diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c index e7ab0468..773415f8 100644 --- a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c +++ b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c @@ -494,7 +494,7 @@ exchange_check_cb (void *cls, { enum GNUNET_DB_QueryStatus qs; - if (TALER_AML_NORMAL != ks->details.success.aml_status) + if (TALER_AML_NORMAL != ks->details.ok.aml_status) { GNUNET_assert ( 0 == @@ -503,7 +503,7 @@ exchange_check_cb (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ( "aml_status", - ks->details.success.aml_status), + ks->details.ok.aml_status), GNUNET_JSON_pack_string ("exchange_url", ekr->exchange_url), GNUNET_JSON_pack_string ("payto_uri", @@ -514,11 +514,11 @@ exchange_check_cb (void *cls, &ekr->h_wire, ekr->exchange_url, ekr->exchange_kyc_serial, - &ks->details.success.exchange_sig, - &ks->details.success.exchange_pub, - ks->details.success.timestamp, + &ks->details.ok.exchange_sig, + &ks->details.ok.exchange_pub, + ks->details.ok.timestamp, true, /* KYC OK */ - ks->details.success.aml_status); + ks->details.ok.aml_status); if (qs < 0) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -651,24 +651,18 @@ exchange_check_cb (void *cls, * @param cls closure with our `struct ExchangeKycRequest *` * @param hr HTTP response details * @param eh handle to the exchange context - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void kyc_with_exchange (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct ExchangeKycRequest *ekr = cls; struct KycContext *kc = ekr->kc; struct TALER_PaytoHashP h_payto; - (void) payto_uri; - (void) wire_fee; (void) exchange_trusted; ekr->fo = NULL; if (MHD_HTTP_OK != hr->http_status) @@ -755,8 +749,7 @@ kyc_status_cb (void *cls, ekr->last_check = last_check; ekr->kc = kc; ekr->fo = TMH_EXCHANGES_find_exchange (exchange_url, - NULL, - GNUNET_NO, + false, &kyc_with_exchange, ekr); } diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c index 87ebc44e..cff03c7e 100644 --- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c +++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c @@ -468,7 +468,7 @@ deposit_get_cb (void *cls, qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls, tq->deposit_serial, - &dr->details.success); + &dr->details.ok); if (qs < 0) { gorc_report (gorc, @@ -487,7 +487,7 @@ deposit_get_cb (void *cls, if (0 > TALER_amount_add (&gorc->deposits_total, &gorc->deposits_total, - &dr->details.success.coin_contribution)) + &dr->details.ok.coin_contribution)) { gorc_report (gorc, TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE, @@ -592,16 +592,12 @@ deposit_get_cb (void *cls, * @param cls closure with a `struct GetOrderRequestContext *` * @param hr HTTP response details * @param eh handle to the exchange context - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void exchange_found_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct TransferQuery *tq = cls; @@ -697,8 +693,7 @@ deposit_cb (void *cls, tq->amount_with_fee = *amount_with_fee; tq->deposit_fee = *deposit_fee; tq->fo = TMH_EXCHANGES_find_exchange (exchange_url, - NULL, - GNUNET_NO, + false, &exchange_found_cb, tq); if (NULL == tq->fo) diff --git a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c b/src/backend/taler-merchant-httpd_private-get-reserves-ID.c index 5545b02f..80d52399 100644 --- a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c +++ b/src/backend/taler-merchant-httpd_private-get-reserves-ID.c @@ -25,6 +25,7 @@ #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_mhd.h" #include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_private-get-reserves-ID.h" @@ -64,7 +65,6 @@ struct GetReserveContext * picked up yet * @param active true if the reserve is still active (we have the private key) * @param exchange_url URL of the exchange, NULL if not active - * @param payto_uri payto:// URI to fill the reserve, NULL if not active or already paid * @param tips_length length of the @a tips array * @param tips information about the tips created by this reserve */ @@ -77,13 +77,14 @@ handle_reserve_details (void *cls, const struct TALER_Amount *picked_up_amount, const struct TALER_Amount *committed_amount, bool active, + const struct TALER_MasterPublicKeyP *master_pub, const char *exchange_url, - const char *payto_uri, unsigned int tips_length, const struct TALER_MERCHANTDB_TipDetails *tips) { struct GetReserveContext *ctx = cls; json_t *tips_json; + json_t *accounts; if (NULL != tips) { @@ -107,6 +108,8 @@ handle_reserve_details (void *cls, { tips_json = NULL; } + accounts = TMH_exchange_accounts_by_method (master_pub, + NULL); ctx->res = TALER_MHD_REPLY_JSON_PACK ( ctx->connection, MHD_HTTP_OK, @@ -131,8 +134,8 @@ handle_reserve_details (void *cls, GNUNET_JSON_pack_string ("exchange_url", exchange_url)), GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("payto_uri", - payto_uri))); + GNUNET_JSON_pack_array_steal ("accounts", + accounts))); } diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.c b/src/backend/taler-merchant-httpd_private-post-reserves.c index 82fc865f..a6008f32 100644 --- a/src/backend/taler-merchant-httpd_private-post-reserves.c +++ b/src/backend/taler-merchant-httpd_private-post-reserves.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2021, 2022 Taler Systems SA + (C) 2021-2023 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 @@ -16,7 +16,6 @@ License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** * @file taler-merchant-httpd_private-post-reserves.c * @brief implementing POST /reserves request handling @@ -25,6 +24,7 @@ #include "platform.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_private-post-reserves.h" +#include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_reserves.h" #include <taler/taler_json_lib.h> @@ -74,9 +74,14 @@ struct PostReserveContext const char *exchange_url; /** - * URI of the exchange where the payment needs to be made to. + * Wire method the client wants to use for the payment. + */ + const char *wire_method; + + /** + * Array of accounts that could be used. */ - char *payto_uri; + json_t *accounts; /** * Handle for contacting the exchange. @@ -89,6 +94,12 @@ struct PostReserveContext struct GNUNET_SCHEDULER_Task *timeout_task; /** + * Master public key of the exchange matching + * @e exchange_url. + */ + struct TALER_MasterPublicKeyP master_pub; + + /** * Initial balance of the reserve. */ struct TALER_Amount initial_balance; @@ -187,7 +198,7 @@ reserve_context_cleanup (void *cls) rc->timeout_task = NULL; } GNUNET_assert (GNUNET_YES != rc->suspended); - GNUNET_free (rc->payto_uri); + json_decref (rc->accounts); GNUNET_free (rc); } @@ -198,17 +209,13 @@ reserve_context_cleanup (void *cls) * * @param cls closure with our `struct PostReserveContext *` * @param hr HTTP response details - * @param payto_uri URI of the exchange for the wire transfer, NULL on errors * @param eh handle to the exchange context - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void handle_exchange (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct PostReserveContext *rc = cls; @@ -222,6 +229,7 @@ handle_exchange (void *cls, } rc->suspended = GNUNET_NO; MHD_resume_connection (rc->connection); + TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc); @@ -229,42 +237,47 @@ handle_exchange (void *cls, { rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT; rc->http_status = MHD_HTTP_GATEWAY_TIMEOUT; - TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ return; } if (NULL == eh) { rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE; rc->http_status = MHD_HTTP_BAD_GATEWAY; - TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ - return; - } - keys = TALER_EXCHANGE_get_keys (eh); - if (NULL == keys) - { - rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE; - rc->http_status = MHD_HTTP_BAD_GATEWAY; - TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ return; } if (MHD_HTTP_OK != hr->http_status) { rc->ec = hr->ec; rc->http_status = hr->http_status; - TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ return; } - if (NULL == payto_uri) + keys = TALER_EXCHANGE_get_keys (eh); + if (NULL == keys) { - rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD; - rc->http_status = MHD_HTTP_CONFLICT; - TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ + rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE; + rc->http_status = MHD_HTTP_BAD_GATEWAY; return; } + rc->master_pub = keys->master_pub; + { + rc->accounts = TMH_exchange_accounts_by_method ( + &keys->master_pub, + rc->wire_method); + if (NULL == rc->accounts) + { + rc->ec = TALER_EC_GENERIC_DB_FETCH_FAILED; + rc->http_status = MHD_HTTP_CONFLICT; + return; + } + if (0 == json_array_size (rc->accounts)) + { + rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD; + rc->http_status = MHD_HTTP_CONFLICT; + return; + } + } rc->reserve_expiration = GNUNET_TIME_relative_to_timestamp (keys->reserve_closing_delay); - rc->payto_uri = GNUNET_strdup (payto_uri); - TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ } @@ -308,11 +321,12 @@ TMH_private_post_reserves (const struct TMH_RequestHandler *rh, GNUNET_assert (NULL != mi); if (NULL == rc) { - const char *wire_method; rc = GNUNET_new (struct PostReserveContext); rc->connection = connection; rc->hc = hc; + rc->accounts = json_array (); + GNUNET_assert (NULL != rc->accounts); hc->ctx = rc; hc->cc = &reserve_context_cleanup; @@ -322,7 +336,7 @@ TMH_private_post_reserves (const struct TMH_RequestHandler *rh, GNUNET_JSON_spec_string ("exchange_url", &rc->exchange_url), GNUNET_JSON_spec_string ("wire_method", - &wire_method), + &rc->wire_method), TALER_JSON_spec_amount ("initial_balance", TMH_currency, &rc->initial_balance), @@ -337,8 +351,7 @@ TMH_private_post_reserves (const struct TMH_RequestHandler *rh, : MHD_NO; } rc->fo = TMH_EXCHANGES_find_exchange (rc->exchange_url, - wire_method, - GNUNET_NO, + false, &handle_exchange, rc); rc->timeout_task @@ -354,15 +367,14 @@ TMH_private_post_reserves (const struct TMH_RequestHandler *rh, } if (GNUNET_SYSERR == rc->suspended) return MHD_NO; /* we are in shutdown */ - - GNUNET_assert (GNUNET_NO == rc->suspended); - if (NULL == rc->payto_uri) + if (TALER_EC_NONE != rc->ec) { return TALER_MHD_reply_with_error (connection, rc->http_status, rc->ec, NULL); } + GNUNET_assert (GNUNET_NO == rc->suspended); { struct TALER_ReservePublicKeyP reserve_pub; struct TALER_ReservePrivateKeyP reserve_priv; @@ -375,8 +387,8 @@ TMH_private_post_reserves (const struct TMH_RequestHandler *rh, mi->settings.id, &reserve_priv, &reserve_pub, + &rc->master_pub, rc->exchange_url, - rc->payto_uri, &rc->initial_balance, rc->reserve_expiration); GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); @@ -394,8 +406,8 @@ TMH_private_post_reserves (const struct TMH_RequestHandler *rh, MHD_HTTP_OK, GNUNET_JSON_pack_data_auto ("reserve_pub", &reserve_pub), - GNUNET_JSON_pack_string ("payto_uri", - rc->payto_uri)); + GNUNET_JSON_pack_array_steal ("accounts", + rc->accounts)); } } diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c b/src/backend/taler-merchant-httpd_private-post-transfers.c index aa21c747..a57c5e3b 100644 --- a/src/backend/taler-merchant-httpd_private-post-transfers.c +++ b/src/backend/taler-merchant-httpd_private-post-transfers.c @@ -457,15 +457,14 @@ check_wire_fee (struct PostTransfersContext *ptc, * of the coin transactions that were combined into the wire transfer. * * @param cls closure - * @param hr HTTP response details - * @param td transfer data + * @param tgr response details */ static void wire_transfer_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_TransferData *td) + const struct TALER_EXCHANGE_TransfersGetResponse *tgr) { struct PostTransfersContext *ptc = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &tgr->hr; const char *instance_id = ptc->hc->instance->settings.id; enum GNUNET_DB_QueryStatus qs; @@ -503,7 +502,7 @@ wire_transfer_cb (void *cls, ptc->exchange_url, ptc->payto_uri, &ptc->wtid, - td); + &tgr->details.ok.td); if (0 > qs) { /* Always report on DB error as well to enable diagnostics */ @@ -528,7 +527,7 @@ wire_transfer_cb (void *cls, return; } if (0 != - TALER_amount_cmp (&td->total_amount, + TALER_amount_cmp (&tgr->details.ok.td.total_amount, &ptc->amount)) { resume_transfer_with_error ( @@ -553,21 +552,16 @@ wire_transfer_cb (void *cls, * @param cls the `struct PostTransfersContext` * @param hr HTTP response details * @param eh NULL if exchange was not found to be acceptable - * @param payto_uri payto://-URI of the exchange - * @param wire_fee NULL (we did not specify a wire method) * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config */ static void process_transfer_with_exchange (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct PostTransfersContext *ptc = cls; - (void) payto_uri; (void) exchange_trusted; ptc->fo = NULL; if (NULL == hr) @@ -899,8 +893,7 @@ download (struct PostTransfersContext *ptc) ptc_tail, ptc); ptc->fo = TMH_EXCHANGES_find_exchange (ptc->exchange_url, - NULL, - GNUNET_NO, + false, &process_transfer_with_exchange, ptc); ptc->timeout_task diff --git a/src/backend/taler-merchant-httpd_reserves.c b/src/backend/taler-merchant-httpd_reserves.c index 50af145f..fec18440 100644 --- a/src/backend/taler-merchant-httpd_reserves.c +++ b/src/backend/taler-merchant-httpd_reserves.c @@ -232,16 +232,12 @@ reserve_cb (void *cls, * @param cls closure * @param hr HTTP response details * @param eh handle to the exchange context - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void find_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, - const char *payto_uri, - const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct Reserve *r = cls; @@ -277,8 +273,7 @@ try_now (void *cls) r->tt = NULL; r->fo = TMH_EXCHANGES_find_exchange (r->exchange_url, - NULL, - GNUNET_NO, + false, &find_cb, r); if (NULL == r->fo) diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index c72ddd6f..5a3611e7 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -59,6 +59,9 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_select_open_transfers.h pg_select_open_transfers.c \ pg_lookup_instances.h pg_lookup_instances.c \ pg_lookup_transfers.h pg_lookup_transfers.c \ + pg_delete_exchange_accounts.h pg_delete_exchange_accounts.c \ + pg_select_accounts_by_exchange.h pg_select_accounts_by_exchange.c \ + pg_insert_exchange_account.h pg_insert_exchange_account.c \ plugin_merchantdb_postgres.c pg_helper.h libtaler_plugin_merchantdb_postgres_la_LIBADD = \ $(LTLIBINTL) diff --git a/src/backenddb/merchant-0005.sql b/src/backenddb/merchant-0005.sql index 8124341b..5c01e55b 100644 --- a/src/backenddb/merchant-0005.sql +++ b/src/backenddb/merchant-0005.sql @@ -27,6 +27,12 @@ ALTER TABLE merchant_instances COMMENT ON COLUMN merchant_instances.user_type IS 'what type of user is this (individual or business)'; +-- Column makes no sense for multi-account exchanges. Instead, we should +-- lookup the various accounts of the exchange (by the master_pub) and return +-- all of them (with constraints). +ALTER TABLE merchant_tip_reserve_keys + DROP COLUMN payto_uri, + ADD COLUMN master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32); ALTER TABLE merchant_transfers ADD COLUMN failed BOOLEAN NOT NULL DEFAULT FALSE, @@ -55,5 +61,29 @@ COMMENT ON COLUMN merchant_accounts.credit_facade_credentials COMMENT ON COLUMN merchant_accounts.last_bank_serial IS 'Serial number of the bank of the last transaction we successfully imported'; + +CREATE TABLE IF NOT EXISTS merchant_exchange_accounts + (mea_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY + ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32) + ,payto_uri VARCHAR NOT NULL + ,conversion_url VARCHAR + ,debit_restrictions VARCHAR NOT NULL + ,credit_restrictions VARCHAR NOT NULL + ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) + ); +COMMENT ON TABLE merchant_exchange_accounts + IS 'Here we store which bank accounts the exchange uses and with which constraints'; +COMMENT ON COLUMN merchant_exchange_accounts.master_pub + IS 'Master public key of the exchange with these accounts'; +COMMENT ON COLUMN merchant_exchange_accounts.payto_uri + IS 'RFC 8905 URI of the exchange bank account'; +COMMENT ON COLUMN merchant_exchange_accounts.conversion_url + IS 'NULL if this account does not require currency conversion'; +COMMENT ON COLUMN merchant_exchange_accounts.debit_restrictions + IS 'JSON array with account restrictions'; +COMMENT ON COLUMN merchant_exchange_accounts.credit_restrictions + IS 'JSON array with account restrictions'; + + -- Complete transaction COMMIT; diff --git a/src/backenddb/pg_delete_exchange_accounts.c b/src/backenddb/pg_delete_exchange_accounts.c new file mode 100644 index 00000000..7d8a5e48 --- /dev/null +++ b/src/backenddb/pg_delete_exchange_accounts.c @@ -0,0 +1,48 @@ +/* + 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_delete_exchange_accounts.c + * @brief Implementation of the delete_exchange_accounts 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_delete_exchange_accounts.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_delete_exchange_accounts ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (master_pub), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + PREPARE (pg, + "delete_exchange_accounts", + "DELETE FROM merchant_exchange_accounts" + " WHERE master_pub=$1;"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_exchange_accounts", + params); +} diff --git a/src/backenddb/pg_delete_exchange_accounts.h b/src/backenddb/pg_delete_exchange_accounts.h new file mode 100644 index 00000000..da9013d3 --- /dev/null +++ b/src/backenddb/pg_delete_exchange_accounts.h @@ -0,0 +1,42 @@ +/* + 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_delete_exchange_accounts.h + * @brief implementation of the delete_exchange_accounts function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_DELETE_EXCHANGE_ACCOUNTS_H +#define PG_DELETE_EXCHANGE_ACCOUNTS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Delete information about wire accounts of an exchange. (Used when we got new account data.) + * + * @param cls closure + * @param master_pub public key of the exchange + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_delete_exchange_accounts ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub); + + +#endif diff --git a/src/backenddb/pg_insert_exchange_account.c b/src/backenddb/pg_insert_exchange_account.c new file mode 100644 index 00000000..4495ccc0 --- /dev/null +++ b/src/backenddb/pg_insert_exchange_account.c @@ -0,0 +1,66 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_insert_exchange_account.c + * @brief Implementation of the insert_exchange_account 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_insert_exchange_account.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_exchange_account ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (master_pub), + GNUNET_PQ_query_param_string (payto_uri), + NULL == conversion_url + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (conversion_url), + TALER_PQ_query_param_json (debit_restrictions), + TALER_PQ_query_param_json (credit_restrictions), + GNUNET_PQ_query_param_auto_from_type (master_sig), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + PREPARE (pg, + "insert_exchange_account", + "INSERT INTO merchant_exchange_accounts" + "(master_pub" + ",payto_uri" + ",conversion_url" + ",debit_restrictions" + ",credit_restrictions" + ",master_sig)" + " VALUES ($1, $2, $3, $4, $5, $6);"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_exchange_account", + params); +} diff --git a/src/backenddb/pg_insert_exchange_account.h b/src/backenddb/pg_insert_exchange_account.h new file mode 100644 index 00000000..acfcdba2 --- /dev/null +++ b/src/backenddb/pg_insert_exchange_account.h @@ -0,0 +1,51 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_insert_exchange_account.h + * @brief implementation of the insert_exchange_account function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_INSERT_EXCHANGE_ACCOUNT_H +#define PG_INSERT_EXCHANGE_ACCOUNT_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Insert information about a wire account of an exchange. + * + * @param cls closure + * @param master_pub public key of the exchange + * @param payto_uri URI of the bank account + * @param conversion_url conversion service, NULL if there is no conversion required + * @param debit_restrictions JSON array of debit restrictions on the account + * @param credit_restrictions JSON array of debit restrictions on the account + * @param master_sig signature affirming the account of the exchange + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_exchange_account ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig); + + +#endif diff --git a/src/backenddb/pg_select_accounts_by_exchange.c b/src/backenddb/pg_select_accounts_by_exchange.c new file mode 100644 index 00000000..c7637031 --- /dev/null +++ b/src/backenddb/pg_select_accounts_by_exchange.c @@ -0,0 +1,146 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_select_accounts_by_exchange.c + * @brief Implementation of the select_accounts_by_exchange 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_select_accounts_by_exchange.h" +#include "pg_helper.h" + + +/** + * Closure for #parse_accounts. + */ +struct SelectAccountContext +{ + /** + * Function to call on each result. + */ + TALER_MERCHANTDB_ExchangeAccountCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Set to true on failure. + */ + bool failed; +}; + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results about accounts. + * + * @param cls of type `struct SelectAccountContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +parse_accounts (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct SelectAccountContext *ctx = cls; + + for (unsigned int i = 0; i < num_results; i++) + { + char *payto_uri; + char *conversion_url = NULL; + json_t *debit_restrictions; + json_t *credit_restrictions; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("master_sig", + &master_sig), + GNUNET_PQ_result_spec_string ("payto_uri", + &payto_uri), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("conversion_url", + &conversion_url), + NULL), + TALER_PQ_result_spec_json ("debit_restrictions", + &debit_restrictions), + TALER_PQ_result_spec_json ("credit_restrictions", + &credit_restrictions), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->failed = true; + return; + } + ctx->cb (ctx->cb_cls, + payto_uri, + conversion_url, + debit_restrictions, + credit_restrictions, + &master_sig); + GNUNET_PQ_cleanup_result (rs); + } +} + + +enum GNUNET_DB_QueryStatus +TMH_PG_select_accounts_by_exchange ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + TALER_MERCHANTDB_ExchangeAccountCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct SelectAccountContext ctx = { + .cb = cb, + .cb_cls = cb_cls + }; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (master_pub), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + PREPARE (pg, + "select_exchange_accounts", + "SELECT" + " payto_uri" + ",conversion_url" + ",debit_restrictions" + ",credit_restrictions" + ",master_sig" + " FROM merchant_exchange_accounts" + " WHERE master_pub=$1;"); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "select_exchange_accounts", + params, + &parse_accounts, + &ctx); + if (ctx.failed) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} diff --git a/src/backenddb/pg_select_accounts_by_exchange.h b/src/backenddb/pg_select_accounts_by_exchange.h new file mode 100644 index 00000000..e22c1601 --- /dev/null +++ b/src/backenddb/pg_select_accounts_by_exchange.h @@ -0,0 +1,45 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_select_accounts_by_exchange.h + * @brief implementation of the select_accounts_by_exchange function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_SELECT_ACCOUNTS_BY_EXCHANGE_H +#define PG_SELECT_ACCOUNTS_BY_EXCHANGE_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Return information about wire accounts of an exchange. + * + * @param cls closure + * @param master_pub public key of the exchange + * @param cb function to call on each account + * @param cb_cls closure for @a cb + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_select_accounts_by_exchange ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + TALER_MERCHANTDB_ExchangeAccountCallback cb, + void *cb_cls); + +#endif diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 6040c9af..baaad568 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -37,6 +37,9 @@ #include "pg_update_wirewatch_progress.h" #include "pg_select_wirewatch_accounts.h" #include "pg_select_open_transfers.h" +#include "pg_delete_exchange_accounts.h" +#include "pg_select_accounts_by_exchange.h" +#include "pg_insert_exchange_account.h" /** @@ -4553,8 +4556,8 @@ postgres_store_wire_fee_by_exchange ( * @param instance_id which instance is the reserve tied to * @param reserve_priv which reserve is topped up or created * @param reserve_pub which reserve is topped up or created + * @param master_pub master public key of the exchange * @param exchange_url what URL is the exchange reachable at where the reserve is located - * @param payto_uri URI to use to fund the reserve * @param initial_balance how much money will be added to the reserve * @param expiration when does the reserve expire? * @return transaction status, usually @@ -4565,8 +4568,8 @@ postgres_insert_reserve (void *cls, const char *instance_id, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_MasterPublicKeyP *master_pub, const char *exchange_url, - const char *payto_uri, const struct TALER_Amount *initial_balance, struct GNUNET_TIME_Timestamp expiration) { @@ -4618,7 +4621,7 @@ RETRY: GNUNET_PQ_query_param_auto_from_type (reserve_pub), GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_string (exchange_url), - GNUNET_PQ_query_param_string (payto_uri), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), GNUNET_PQ_query_param_end }; @@ -5084,9 +5087,9 @@ postgres_lookup_reserve (void *cls, struct TALER_Amount exchange_initial_balance; struct TALER_Amount pickup_amount; struct TALER_Amount committed_amount; - uint8_t active; + struct TALER_MasterPublicKeyP master_pub; + bool active; char *exchange_url = NULL; - char *payto_uri = NULL; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_timestamp ("creation_time", &creation_time), @@ -5100,16 +5103,14 @@ postgres_lookup_reserve (void *cls, &pickup_amount), TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed", &committed_amount), - GNUNET_PQ_result_spec_auto_from_type ("active", - &active), + GNUNET_PQ_result_spec_auto_from_type ("master_pub", + &master_pub), + GNUNET_PQ_result_spec_bool ("active", + &active), GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_string ("exchange_url", &exchange_url), NULL), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("payto_uri", - &payto_uri), - NULL), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; @@ -5130,9 +5131,9 @@ postgres_lookup_reserve (void *cls, &exchange_initial_balance, &pickup_amount, &committed_amount, - (0 != active), + active, + &master_pub, exchange_url, - payto_uri, 0, NULL); GNUNET_PQ_cleanup_result (rs); @@ -5155,9 +5156,9 @@ postgres_lookup_reserve (void *cls, &exchange_initial_balance, &pickup_amount, &committed_amount, - 0 != active, + active, + &master_pub, exchange_url, - payto_uri, ltc.tips_length, ltc.tips); } @@ -9064,7 +9065,7 @@ postgres_connect (void *cls) "(reserve_serial" ",reserve_priv" ",exchange_url" - ",payto_uri" + ",master_pub" ")" "SELECT reserve_serial, $3, $4, $5" " FROM merchant_tip_reserves" @@ -9123,7 +9124,7 @@ postgres_connect (void *cls) ",tips_picked_up_frac" ",reserve_priv IS NOT NULL AS active" ",exchange_url" - ",payto_uri" + ",master_pub" " FROM merchant_tip_reserves" " FULL OUTER JOIN merchant_tip_reserve_keys USING (reserve_serial)" " WHERE reserve_pub = $2" @@ -9804,6 +9805,12 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->delete_pending_webhook = &postgres_delete_pending_webhook; plugin->insert_pending_webhook = &postgres_insert_pending_webhook; plugin->update_pending_webhook = &postgres_update_pending_webhook; + plugin->delete_exchange_accounts + = &TMH_PG_delete_exchange_accounts; + plugin->select_accounts_by_exchange + = &TMH_PG_select_accounts_by_exchange; + plugin->insert_exchange_account + = &TMH_PG_insert_exchange_account; return plugin; } diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index 30f65a0f..1135cb38 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -3268,22 +3268,58 @@ TALER_MERCHANT_transfers_get_cancel ( struct TALER_MERCHANT_PostReservesHandle; -// FIXME: change signature! +/** + * Response to a POST /reserves request. + */ +struct TALER_MERCHANT_PostReservesResponse +{ + /** + * HTTP response details. + */ + struct TALER_MERCHANT_HttpResponse hr; + + /** + * Details depending on HTTP status. + */ + union + { + /** + * Response on #MHD_HTTP_OK. + */ + struct + { + /** + * Public key of the created reserve. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Accounts to credit to for filling the reserve. + * Array of accounts of the exchange. + */ + const struct TALER_EXCHANGE_WireAccount *accounts; + + /** + * Length of @e accounts array. + */ + unsigned int accounts_len; + + } ok; + } details; +}; + + /** * Callbacks of this type are used to work the result of submitting a * POST /reserves request to a merchant * * @param cls closure - * @param hr HTTP response details - * @param reserve_pub public key of the created reserve, NULL on error - * @param payto_uri where to make the payment to for filling the reserve, NULL on error + * @param prr response details */ typedef void (*TALER_MERCHANT_PostReservesCallback) ( void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *payto_uri); + const struct TALER_MERCHANT_PostReservesResponse *prr); /** @@ -3455,29 +3491,82 @@ struct TALER_MERCHANT_TipDetails }; -// FIXME: change signature! +/** + * Response to a GET /reserves/$ID request. + */ +struct TALER_MERCHANT_ReserveGetResponse +{ + /** + * HTTP response. + */ + struct TALER_MERCHANT_HttpResponse hr; + + /** + * Details depending on HTTP status. + */ + union + { + + /** + * Details on #MHD_HTTP_OK. + */ + struct + { + + /** + * reserve summary for the reserve + */ + struct TALER_MERCHANT_ReserveSummary rs; + + /** + * URL of the exchange hosting the reserve, NULL if not @a active + */ + const char *exchange_url; + + /** + * Accounts to credit to for filling the reserve. + * Array of accounts of the exchange. Empty if + * already filled. + */ + const struct TALER_EXCHANGE_WireAccount *accounts; + + /** + * Length of @e accounts array. + */ + unsigned int accounts_len; + + /** + * Array with details about the tips granted. + */ + const struct TALER_MERCHANT_TipDetails *tips; + + /** + * Length of the @e tips array + */ + unsigned int tips_length; + + /** + * Is this reserve active (false if it was deleted but not purged) + */ + bool active; + + } ok; + + } details; + +}; + + /** * Callback to process a GET /reserve/$RESERVE_PUB request * * @param cls closure - * @param hr HTTP response details - * @param rs reserve summary for the reserve, NULL on error - * @param active is this reserve active (false if it was deleted but not purged) - * @param exchange_url URL of the exchange hosting the reserve, NULL if not @a active - * @param payto_uri URI to fill the reserve, NULL if not @a active or already filled - * @param tips_length length of the @a reserves array - * @param tips array with details about the tips granted, NULL on error + * @param rgr response details */ typedef void (*TALER_MERCHANT_ReserveGetCallback) ( void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const struct TALER_MERCHANT_ReserveSummary *rs, - bool active, - const char *exchange_url, - const char *payto_uri, - unsigned int tips_length, - const struct TALER_MERCHANT_TipDetails tips[]); + const struct TALER_MERCHANT_ReserveGetResponse *rgr); /** diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 0d191878..30609bae 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -802,6 +802,27 @@ typedef void /** + * If the given account is feasible, add it to the array + * of accounts we return. + * + * @param cls closure + * @param payto_uri URI of the account + * @param conversion_url URL of a conversion service + * @param debit_restrictions restrictions for debits from account + * @param credit_restrictions restrictions for credits to account + * @param master_sig signature affirming the account + */ +typedef void +(*TALER_MERCHANTDB_ExchangeAccountCallback) ( + void *cls, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig); + + +/** * Callback with reserve details. * * @param cls closure @@ -904,8 +925,8 @@ typedef void * @param committed_amount total of tips that the merchant committed to, but that were not * picked up yet * @param active true if the reserve is still active (we have the private key) + * @param master_pub master public key of the exchange * @param exchange_url base URL of the exchange hosting the reserve, NULL if not @a active - * @param payto_uri URI to use to fund the reserve, NULL if not @a active * @param tips_length length of the @a tips array * @param tips information about the tips created by this reserve */ @@ -919,8 +940,8 @@ typedef void const struct TALER_Amount *picked_up_amount, const struct TALER_Amount *committed_amount, bool active, + const struct TALER_MasterPublicKeyP *master_pub, const char *exchange_url, - const char *payto_uri, unsigned int tips_length, const struct TALER_MERCHANTDB_TipDetails *tips); @@ -1122,7 +1143,6 @@ struct TALER_MERCHANTDB_Plugin * Roll back the current transaction of a database connection. * * @param cls the `struct PostgresClosure` with the plugin-specific state - * @return #GNUNET_OK on success */ void (*rollback) (void *cls); @@ -2391,23 +2411,76 @@ struct TALER_MERCHANTDB_Plugin * including signature (so we have proof). * * @param cls closure - * @param exchange_pub public key of the exchange + * @param master_pub master public key of the exchange * @param h_wire_method hash of wire method * @param fees wire fees charged * @param start_date start of fee being used * @param end_date end of fee being used - * @param exchange_sig signature of exchange over fee structure + * @param master_sig signature of exchange over fee structure * @return transaction status code */ enum GNUNET_DB_QueryStatus (*store_wire_fee_by_exchange)( void *cls, - const struct TALER_MasterPublicKeyP *exchange_pub, + const struct TALER_MasterPublicKeyP *master_pub, const struct GNUNET_HashCode *h_wire_method, const struct TALER_WireFeeSet *fees, struct GNUNET_TIME_Timestamp start_date, struct GNUNET_TIME_Timestamp end_date, - const struct TALER_MasterSignatureP *exchange_sig); + const struct TALER_MasterSignatureP *master_sig); + + + /** + * Delete information about wire accounts of an exchange. (Used when we got new account data.) + * + * @param cls closure + * @param master_pub public key of the exchange + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*delete_exchange_accounts)( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub); + + + /** + * Return information about wire accounts of an exchange. + * + * @param cls closure + * @param master_pub public key of the exchange + * @param cb function to call on each account + * @param cb_cls closure for @a cb + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*select_accounts_by_exchange)( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + TALER_MERCHANTDB_ExchangeAccountCallback cb, + void *cb_cls); + + + /** + * Insert information about a wire account of an exchange. + * + * @param cls closure + * @param master_pub public key of the exchange + * @param payto_uri URI of the bank account + * @param conversion_url conversion service, NULL if there is no conversion required + * @param debit_restrictions JSON array of debit restrictions on the account + * @param credit_restrictions JSON array of debit restrictions on the account + * @param master_sig signature affirming the account of the exchange + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*insert_exchange_account)( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig); /** @@ -2421,8 +2494,8 @@ struct TALER_MERCHANTDB_Plugin * @param instance_id which instance is the reserve tied to * @param reserve_priv which reserve is topped up or created * @param reserve_pub which reserve is topped up or created + * @param master_pub master public key of the exchange * @param exchange_url what URL is the exchange reachable at where the reserve is located - * @param payto_uri URI to fund the reserve * @param initial_balance how much money will be added to the reserve * @param expiration when does the reserve expire? * @return transaction status, usually @@ -2433,8 +2506,8 @@ struct TALER_MERCHANTDB_Plugin const char *instance_id, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_MasterPublicKeyP *master_pub, const char *exchange_url, - const char *payto_uri, const struct TALER_Amount *initial_balance, struct GNUNET_TIME_Timestamp expiration); diff --git a/src/lib/merchant_api_get_reserve.c b/src/lib/merchant_api_get_reserve.c index 6c970db8..3df1dc75 100644 --- a/src/lib/merchant_api_get_reserve.c +++ b/src/lib/merchant_api_get_reserve.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -81,49 +81,59 @@ handle_reserve_get_finished (void *cls, { struct TALER_MERCHANT_ReserveGetHandle *rgh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_ReserveGetResponse rgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; - bool active; rgh->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { - struct TALER_MERCHANT_ReserveSummary rs; - const json_t *tips; - const char *exchange_url = NULL; - const char *payto_uri = NULL; + struct TALER_MERCHANT_ReserveSummary *rs + = &rgr.details.ok.rs; + const json_t *tips = NULL; + const json_t *accounts = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("creation_time", - &rs.creation_time), + &rs->creation_time), GNUNET_JSON_spec_timestamp ("expiration_time", - &rs.expiration_time), + &rs->expiration_time), GNUNET_JSON_spec_bool ("active", - &active), + &rgr.details.ok.active), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("exchange_url", - &exchange_url), + GNUNET_JSON_spec_array_const ("tips", + &tips), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("payto_uri", - &payto_uri), + GNUNET_JSON_spec_array_const ("accounts", + &accounts), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ( + "exchange_url", + &rgr.details.ok.exchange_url), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("accounts", + &accounts), NULL), TALER_JSON_spec_amount_any ("merchant_initial_amount", - &rs.merchant_initial_amount), + &rs->merchant_initial_amount), TALER_JSON_spec_amount_any ("exchange_initial_amount", - &rs.exchange_initial_amount), + &rs->exchange_initial_amount), TALER_JSON_spec_amount_any ("pickup_amount", - &rs.pickup_amount), + &rs->pickup_amount), TALER_JSON_spec_amount_any ("committed_amount", - &rs.committed_amount), + &rs->committed_amount), GNUNET_JSON_spec_end () }; + struct TALER_EXCHANGE_WireAccount *was = NULL; + struct TALER_MERCHANT_TipDetails *tds = NULL; if (GNUNET_OK != GNUNET_JSON_parse (json, @@ -131,45 +141,43 @@ handle_reserve_get_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rgr.hr.http_status = 0; + rgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - tips = json_object_get (json, - "tips"); - if ((NULL == tips) || - json_is_null (tips)) - { - rgh->cb (rgh->cb_cls, - &hr, - &rs, - false, - NULL, - NULL, - 0, - NULL); - TALER_MERCHANT_reserve_get_cancel (rgh); - return; - } - if (! json_is_array (tips)) + if (NULL != accounts) { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } + unsigned int accounts_len = json_array_size (accounts); + + was = GNUNET_new_array (accounts_len, + struct TALER_EXCHANGE_WireAccount); + if (GNUNET_OK != + TALER_EXCHANGE_parse_accounts (NULL, + accounts, + was, + accounts_len)) + { + GNUNET_break_op (0); + rgr.hr.http_status = 0; + rgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + else + { + rgr.details.ok.accounts = was; + rgr.details.ok.accounts_len = accounts_len; + } + } /* end 'have accounts' */ + + if (NULL != tips) { - size_t tds_length; + size_t tds_length = json_array_size (tips); + bool ok = true; json_t *tip; - struct TALER_MERCHANT_TipDetails *tds; unsigned int i; - bool ok; - tds_length = json_array_size (tips); tds = GNUNET_new_array (tds_length, struct TALER_MERCHANT_TipDetails); - ok = true; json_array_foreach (tips, i, tip) { struct TALER_MERCHANT_TipDetails *td = &tds[i]; struct GNUNET_JSON_Specification ispec[] = { @@ -192,60 +200,56 @@ handle_reserve_get_finished (void *cls, break; } } - if (! ok) { GNUNET_break_op (0); - GNUNET_free (tds); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; + rgr.hr.http_status = 0; + rgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + } + else + { + rgr.details.ok.tips = tds; + rgr.details.ok.tips_length = tds_length; } - rgh->cb (rgh->cb_cls, - &hr, - &rs, - active, - exchange_url, - payto_uri, - tds_length, - tds); - GNUNET_free (tds); - TALER_MERCHANT_reserve_get_cancel (rgh); - return; + } /* end 'have tips' */ + + rgh->cb (rgh->cb_cls, + &rgr); + if (NULL != accounts) + { + TALER_EXCHANGE_free_accounts (was, + json_array_size (accounts)); + GNUNET_free (was); } + GNUNET_free (tds); + TALER_MERCHANT_reserve_get_cancel (rgh); + return; } case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rgr.hr.ec = TALER_JSON_get_error_code (json); + rgr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rgr.hr.ec = TALER_JSON_get_error_code (json); + rgr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &rgr.hr); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); - response_code = 0; + (int) rgr.hr.ec); break; } rgh->cb (rgh->cb_cls, - &hr, - NULL, - false, - NULL, - NULL, - 0, - NULL); + &rgr); TALER_MERCHANT_reserve_get_cancel (rgh); } diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c index 3923db21..c22e9ba5 100644 --- a/src/lib/merchant_api_post_order_pay.c +++ b/src/lib/merchant_api_post_order_pay.c @@ -190,27 +190,25 @@ check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph, * validate the conflict error. * * @param cls a `struct TALER_MERCHANT_OrderPayHandle` - * @param ehr reply from the exchange - * @param keys the key structure - * @param compat protocol compatibility indication + * @param kr reply from the exchange */ static void cert_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *ehr, - const struct TALER_EXCHANGE_Keys *keys, - enum TALER_EXCHANGE_VersionCompatibility compat) + const struct TALER_EXCHANGE_KeysResponse *kr) { struct TALER_MERCHANT_OrderPayHandle *oph = cls; + const struct TALER_EXCHANGE_HttpResponse *ehr = &kr->hr; - if (TALER_EXCHANGE_VC_INCOMPATIBLE & compat) + if ( (MHD_HTTP_OK != ehr->http_status) || + (NULL == kr->details.ok.keys) ) { struct TALER_MERCHANT_PayResponse pr = { .hr.http_status = MHD_HTTP_CONFLICT, - .hr.exchange_http_status = 0, - .hr.ec = TALER_EC_WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE, + .hr.exchange_http_status = ehr->http_status, + .hr.ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, .hr.reply = oph->full_reply, .hr.exchange_reply = ehr->reply, - .hr.hint = "could not check error: incompatible exchange version" + .hr.hint = "failed to download /keys from the exchange" }; oph->pay_cb (oph->pay_cb_cls, @@ -218,16 +216,15 @@ cert_cb (void *cls, TALER_MERCHANT_order_pay_cancel (oph); return; } - if ( (MHD_HTTP_OK != ehr->http_status) || - (NULL == keys) ) + if (TALER_EXCHANGE_VC_INCOMPATIBLE & kr->details.ok.compat) { struct TALER_MERCHANT_PayResponse pr = { .hr.http_status = MHD_HTTP_CONFLICT, - .hr.exchange_http_status = ehr->http_status, - .hr.ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + .hr.exchange_http_status = 0, + .hr.ec = TALER_EC_WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE, .hr.reply = oph->full_reply, .hr.exchange_reply = ehr->reply, - .hr.hint = "failed to download /keys from the exchange" + .hr.hint = "could not check error: incompatible exchange version" }; oph->pay_cb (oph->pay_cb_cls, @@ -238,7 +235,7 @@ cert_cb (void *cls, if (GNUNET_OK != check_conflict (oph, - keys)) + kr->details.ok.keys)) { struct TALER_MERCHANT_PayResponse pr = { .hr.http_status = 0, diff --git a/src/lib/merchant_api_post_reserves.c b/src/lib/merchant_api_post_reserves.c index 65239dfb..a2822c26 100644 --- a/src/lib/merchant_api_post_reserves.c +++ b/src/lib/merchant_api_post_reserves.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015, 2016, 2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -86,26 +86,27 @@ handle_post_reserves_finished (void *cls, { struct TALER_MERCHANT_PostReservesHandle *prh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_PostReservesResponse prr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; prh->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + prr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { - const char *payto_uri; - struct TALER_ReservePublicKeyP reserve_pub; + const json_t *accounts; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &reserve_pub), - GNUNET_JSON_spec_string ("payto_uri", - &payto_uri), + GNUNET_JSON_spec_fixed_auto ( + "reserve_pub", + &prr.details.ok.reserve_pub), + GNUNET_JSON_spec_array_const ( + "accounts", + &accounts), GNUNET_JSON_spec_end () }; @@ -115,16 +116,31 @@ handle_post_reserves_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + prr.hr.http_status = 0; + prr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - else + { + unsigned int accounts_len = json_array_size (accounts); + struct TALER_EXCHANGE_WireAccount was[GNUNET_NZL (accounts_len)]; + + prr.details.ok.accounts = was; + prr.details.ok.accounts_len = accounts_len; + if (GNUNET_OK != + TALER_EXCHANGE_parse_accounts (NULL, + accounts, + was, + accounts_len)) + { + GNUNET_break_op (0); + prr.hr.http_status = 0; + prr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } prh->cb (prh->cb_cls, - &hr, - &reserve_pub, - payto_uri); + &prr); + TALER_EXCHANGE_free_accounts (was, + accounts_len); GNUNET_JSON_parse_free (spec); TALER_MERCHANT_reserves_post_cancel (prh); return; @@ -133,8 +149,8 @@ handle_post_reserves_finished (void *cls, case MHD_HTTP_ACCEPTED: break; case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + prr.hr.ec = TALER_JSON_get_error_code (json); + prr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_NOT_FOUND: @@ -142,31 +158,29 @@ handle_post_reserves_finished (void *cls, happen, we should pass the JSON reply to the application */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Did not find any data\n"); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + prr.hr.ec = TALER_JSON_get_error_code (json); + prr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + prr.hr.ec = TALER_JSON_get_error_code (json); + prr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &prr.hr); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) prr.hr.ec); break; } prh->cb (prh->cb_cls, - &hr, - NULL, - NULL); + &prr); TALER_MERCHANT_reserves_post_cancel (prh); } diff --git a/src/lib/merchant_api_tip_pickup.c b/src/lib/merchant_api_tip_pickup.c index 593efa43..ba1256b1 100644 --- a/src/lib/merchant_api_tip_pickup.c +++ b/src/lib/merchant_api_tip_pickup.c @@ -312,7 +312,7 @@ csr_cb (void *cls, { struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[pd->off]; - pcd->exchange_vals = csrr->details.success.alg_values; + pcd->exchange_vals = csrr->details.ok.alg_values; } if (0 != tp->csr_active) return; diff --git a/src/merchant-tools/taler-merchant-setup-reserve.c b/src/merchant-tools/taler-merchant-setup-reserve.c index 1ed50530..46888171 100644 --- a/src/merchant-tools/taler-merchant-setup-reserve.c +++ b/src/merchant-tools/taler-merchant-setup-reserve.c @@ -156,46 +156,81 @@ do_request (void *cls); * POST /reserves request to a merchant * * @param cls closure - * @param hr HTTP response details - * @param reserve_pub public key of the created reserve, NULL on error - * @param payto_uri where to make the payment to for filling the reserve, NULL on error + * @param prr response details */ static void result_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *payto_uri) + const struct TALER_MERCHANT_PostReservesResponse *prr) { (void) cls; prh = NULL; - switch (hr->http_status) + switch (prr->hr.http_status) { case MHD_HTTP_OK: { - char res_str[sizeof (*reserve_pub) * 2 + 1]; + char res_str[sizeof (prr->details.ok.reserve_pub) * 2 + 1]; - GNUNET_STRINGS_data_to_string (reserve_pub, - sizeof (*reserve_pub), + GNUNET_STRINGS_data_to_string (&prr->details.ok.reserve_pub, + sizeof (prr->details.ok.reserve_pub), res_str, sizeof (res_str)); - if (NULL != strchr (payto_uri, '?')) - fprintf (stdout, - "%s&message=%s\n", - payto_uri, - res_str); - else - fprintf (stdout, - "%s?message=%s\n", - payto_uri, - res_str); + for (unsigned int i = 0; i<prr->details.ok.accounts_len; i++) + { + const struct TALER_EXCHANGE_WireAccount *wa + = &prr->details.ok.accounts[i]; + const char *payto_uri = wa->payto_uri; + bool skip = false; + + for (unsigned int j = 0; j<wa->credit_restrictions_length; j++) + if (TALER_EXCHANGE_AR_DENY == + wa->credit_restrictions[j].type) + skip = true; + if (skip) + continue; + if (NULL != strchr (payto_uri, '?')) + fprintf (stdout, + "%s&message=%s\n", + payto_uri, + res_str); + else + fprintf (stdout, + "%s?message=%s\n", + payto_uri, + res_str); + if (NULL != wa->conversion_url) + fprintf (stdout, + "\tConversion needed: %s\n", + wa->conversion_url); + for (unsigned int j = 0; j<wa->credit_restrictions_length; j++) + { + const struct TALER_EXCHANGE_AccountRestriction *cr + = &wa->credit_restrictions[j]; + + switch (cr->type) + { + case TALER_EXCHANGE_AR_INVALID: + GNUNET_assert (0); + break; + case TALER_EXCHANGE_AR_DENY: + GNUNET_assert (0); + break; + case TALER_EXCHANGE_AR_REGEX: + fprintf (stdout, + "\tCredit restriction: %s (%s)\n", + cr->details.regex.human_hint, + cr->details.regex.posix_egrep); + break; + } + } + } } break; case MHD_HTTP_CONFLICT: fprintf (stderr, "Conflict trying to setup reserve: %u/%d\nHint: %s\n", - hr->http_status, - (int) hr->ec, - hr->hint); + prr->hr.http_status, + (int) prr->hr.ec, + prr->hr.hint); global_ret = 1; break; case MHD_HTTP_INTERNAL_SERVER_ERROR: @@ -212,16 +247,16 @@ result_cb (void *cls, } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Merchant failed too often (%u/%d), giving up\n", - hr->http_status, - hr->ec); + prr->hr.http_status, + prr->hr.ec); global_ret = 1; break; default: fprintf (stderr, "Unexpected backend failure: %u/%d\nHint: %s\n", - hr->http_status, - (int) hr->ec, - hr->hint); + prr->hr.http_status, + (int) prr->hr.ec, + prr->hr.hint); global_ret = 1; break; } diff --git a/src/testing/testing_api_cmd_get_reserve.c b/src/testing/testing_api_cmd_get_reserve.c index ef7f67e0..db6f2562 100644 --- a/src/testing/testing_api_cmd_get_reserve.c +++ b/src/testing/testing_api_cmd_get_reserve.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-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 @@ -75,15 +75,10 @@ struct GetReserveState static void get_reserve_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const struct TALER_MERCHANT_ReserveSummary *rs, - bool active, - const char *exchange_url, - const char *payto_uri, - unsigned int tips_length, - const struct TALER_MERCHANT_TipDetails tips[]) + const struct TALER_MERCHANT_ReserveGetResponse *rgr) { struct GetReserveState *grs = cls; + const struct TALER_MERCHANT_HttpResponse *hr = &rgr->hr; const struct TALER_TESTING_Command *reserve_cmd; reserve_cmd = TALER_TESTING_interpreter_lookup_command ( @@ -105,6 +100,7 @@ get_reserve_cb (void *cls, { case MHD_HTTP_OK: { + const struct TALER_MERCHANT_ReserveSummary *rs = &rgr->details.ok.rs; const struct TALER_Amount *initial_amount; if (GNUNET_OK != TALER_TESTING_get_trait_amount (reserve_cmd, @@ -122,71 +118,76 @@ get_reserve_cb (void *cls, return; } } - if (tips_length != grs->tips_length) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Number of tips authorized does not match\n"); - TALER_TESTING_interpreter_fail (grs->is); - return; - } - for (unsigned int i = 0; i < tips_length; ++i) - { - const struct TALER_TESTING_Command *tip_cmd; + unsigned int tips_length = rgr->details.ok.tips_length; + const struct TALER_MERCHANT_TipDetails *tips = rgr->details.ok.tips; - tip_cmd = TALER_TESTING_interpreter_lookup_command (grs->is, - grs->tips[i]); + if (tips_length != grs->tips_length) { - const struct TALER_TipIdentifierP *tip_id; - - if (GNUNET_OK != - TALER_TESTING_get_trait_tip_id (tip_cmd, - &tip_id)) - TALER_TESTING_interpreter_fail (grs->is); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Number of tips authorized does not match\n"); + TALER_TESTING_interpreter_fail (grs->is); + return; + } + for (unsigned int i = 0; i < tips_length; ++i) + { + const struct TALER_TESTING_Command *tip_cmd; - if (0 != GNUNET_memcmp (&tips[i].tip_id, - tip_id)) + tip_cmd = TALER_TESTING_interpreter_lookup_command (grs->is, + grs->tips[i]); { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Reserve tip id does not match\n"); - TALER_TESTING_interpreter_fail (grs->is); - return; + const struct TALER_TipIdentifierP *tip_id; + + if (GNUNET_OK != + TALER_TESTING_get_trait_tip_id (tip_cmd, + &tip_id)) + TALER_TESTING_interpreter_fail (grs->is); + + if (0 != GNUNET_memcmp (&tips[i].tip_id, + tip_id)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Reserve tip id does not match\n"); + TALER_TESTING_interpreter_fail (grs->is); + return; + } } - } - { - const struct TALER_Amount *total_amount; - - if (GNUNET_OK != - TALER_TESTING_get_trait_amount (tip_cmd, - &total_amount)) - TALER_TESTING_interpreter_fail (grs->is); - - if ((GNUNET_OK != - TALER_amount_cmp_currency (&tips[i].amount, - total_amount)) || - (0 != TALER_amount_cmp (&tips[i].amount, - total_amount))) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Reserve tip amount does not match\n"); - TALER_TESTING_interpreter_fail (grs->is); - return; + const struct TALER_Amount *total_amount; + + if (GNUNET_OK != + TALER_TESTING_get_trait_amount (tip_cmd, + &total_amount)) + TALER_TESTING_interpreter_fail (grs->is); + + if ((GNUNET_OK != + TALER_amount_cmp_currency (&tips[i].amount, + total_amount)) || + (0 != TALER_amount_cmp (&tips[i].amount, + total_amount))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Reserve tip amount does not match\n"); + TALER_TESTING_interpreter_fail (grs->is); + return; + } } - } - { - const char **reason; - - if (GNUNET_OK != - TALER_TESTING_get_trait_reason (tip_cmd, - &reason)) - TALER_TESTING_interpreter_fail (grs->is); - - if (0 != strcmp (tips[i].reason, - *reason)) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Reserve tip reason does not match\n"); - TALER_TESTING_interpreter_fail (grs->is); - return; + const char **reason; + + if (GNUNET_OK != + TALER_TESTING_get_trait_reason (tip_cmd, + &reason)) + TALER_TESTING_interpreter_fail (grs->is); + + if (0 != strcmp (tips[i].reason, + *reason)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Reserve tip reason does not match\n"); + TALER_TESTING_interpreter_fail (grs->is); + return; + } } } } diff --git a/src/testing/testing_api_cmd_post_reserves.c b/src/testing/testing_api_cmd_post_reserves.c index b2167534..fe3de9ed 100644 --- a/src/testing/testing_api_cmd_post_reserves.c +++ b/src/testing/testing_api_cmd_post_reserves.c @@ -79,32 +79,29 @@ struct PostReservesState * POST /reserves request to a merchant * * @param cls closure - * @param hr HTTP response details - * @param reserve_pub public key of the created reserve, NULL on error - * @param payto_uri where to make the payment to for filling the reserve, NULL on error + * @param prr response details */ static void post_reserves_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *payto_uri) + const struct TALER_MERCHANT_PostReservesResponse *prr) { struct PostReservesState *prs = cls; prs->prh = NULL; - if (prs->http_status != hr->http_status) + if (prs->http_status != prr->hr.http_status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u (%d) to command %s\n", - hr->http_status, - (int) hr->ec, + prr->hr.http_status, + (int) prr->hr.ec, TALER_TESTING_interpreter_get_current_label (prs->is)); TALER_TESTING_interpreter_fail (prs->is); return; } - switch (hr->http_status) + switch (prr->hr.http_status) { case MHD_HTTP_OK: + prs->reserve_pub = prr->details.ok.reserve_pub; break; case MHD_HTTP_ACCEPTED: break; @@ -116,9 +113,9 @@ post_reserves_cb (void *cls, GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unhandled HTTP status %u for POST /reserves.\n", - hr->http_status); + prr->hr.http_status); + break; } - prs->reserve_pub = *reserve_pub; TALER_TESTING_interpreter_next (prs->is); } diff --git a/src/testing/testing_api_cmd_post_transfers.c b/src/testing/testing_api_cmd_post_transfers.c index 9a01d2d5..b73c6b15 100644 --- a/src/testing/testing_api_cmd_post_transfers.c +++ b/src/testing/testing_api_cmd_post_transfers.c @@ -404,10 +404,10 @@ debit_cb ( TALER_TESTING_interpreter_fail (pts->is); return; } - for (unsigned int i = 0; i<reply->details.success.details_length; i++) + for (unsigned int i = 0; i<reply->details.ok.details_length; i++) { const struct TALER_BANK_DebitDetails *details - = &reply->details.success.details[i]; + = &reply->details.ok.details[i]; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Bank reports transfer of %s to %s\n", |