diff options
author | Christian Blättler <blatc2@bfh.ch> | 2024-04-10 08:01:28 +0200 |
---|---|---|
committer | Christian Blättler <blatc2@bfh.ch> | 2024-04-10 08:01:28 +0200 |
commit | d42cb68e0ce55310aac0ed0fb83e57ede8e63836 (patch) | |
tree | c26cd5c4704eed3e87a45b52a5f5fac7026d8049 /src/backend | |
parent | 983ad0943b4f0b9ddf7d1b7a14693e4b69395ea6 (diff) | |
parent | d4fd038d116381d76d1fdf8384101f8fa901ffe5 (diff) |
Merge branch 'master' into tokens
# Conflicts:
# src/backend/taler-merchant-httpd_private-post-orders.c
Diffstat (limited to 'src/backend')
20 files changed, 679 insertions, 765 deletions
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c index 6bb3b05b..9245e1fb 100644 --- a/src/backend/taler-merchant-depositcheck.c +++ b/src/backend/taler-merchant-depositcheck.c @@ -391,9 +391,9 @@ run_at (struct GNUNET_TIME_Absolute deadline) GNUNET_TIME_absolute2s (deadline)); return; /* too early */ } + next_deadline = deadline; if (NULL != task) GNUNET_SCHEDULER_cancel (task); - next_deadline = deadline; task = GNUNET_SCHEDULER_add_at (deadline, &select_work, NULL); @@ -513,7 +513,7 @@ deposit_get_cb (void *cls, qs = db_plugin->update_deposit_confirmation_status ( db_plugin->cls, w->deposit_serial, - true, /* FIXME: should we set this to 'false' as we are awaiting KYC? */ + true, GNUNET_TIME_absolute_to_timestamp (future_retry), w->retry_backoff, "Exchange reported 202 Accepted due to KYC/AML block"); @@ -567,8 +567,12 @@ deposit_get_cb (void *cls, GNUNET_assert (NULL != keys); if ( (w_count < CONCURRENCY_LIMIT / 2) || (0 == w_count) ) + { + if (NULL != task) + GNUNET_SCHEDULER_cancel (task); task = GNUNET_SCHEDULER_add_now (&select_work, NULL); + } } @@ -774,6 +778,8 @@ keys_cb ( return; } keys = TALER_EXCHANGE_keys_incref (in_keys); + if (NULL != task) + GNUNET_SCHEDULER_cancel (task); task = GNUNET_SCHEDULER_add_now (&select_work, NULL); } diff --git a/src/backend/taler-merchant-exchange.c b/src/backend/taler-merchant-exchange.c index f77ca3a5..7945cb50 100644 --- a/src/backend/taler-merchant-exchange.c +++ b/src/backend/taler-merchant-exchange.c @@ -238,6 +238,11 @@ static struct GNUNET_DB_EventHandler *eh; static unsigned int active_inquiries; /** + * Set to true if we ever encountered any problem. + */ +static bool found_problem; + +/** * Value to return from main(). 0 on success, non-zero on errors. */ static int global_ret; @@ -394,6 +399,8 @@ update_transaction_status (const struct Inquiry *w, { enum GNUNET_DB_QueryStatus qs; + if (failed) + found_problem = true; qs = db_plugin->update_transfer_status (db_plugin->cls, w->exchange->exchange_url, &w->wtid, @@ -476,6 +483,7 @@ end_inquiry (struct Inquiry *w) (at_limit) ) { at_limit = false; + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&find_work, NULL); } @@ -539,6 +547,11 @@ shutdown_task (void *cls) db_plugin->event_listen_cancel (eh); eh = NULL; } + if (NULL != task) + { + GNUNET_SCHEDULER_cancel (task); + task = NULL; + } TALER_MERCHANTDB_plugin_unload (db_plugin); db_plugin = NULL; cfg = NULL; @@ -707,12 +720,32 @@ check_transfer (void *cls, GNUNET_break (0); return; /* already had a serious issue; odd that we're called more than once as well... */ } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Checking coin with value %s\n", + TALER_amount2s (amount_with_fee)); if ( (GNUNET_OK != TALER_amount_cmp_currency (amount_with_fee, &ttd->coin_value)) || (0 != TALER_amount_cmp (amount_with_fee, - &ttd->coin_value)) || - (GNUNET_OK != + &ttd->coin_value)) ) + { + /* Disagreement between the exchange and us about how much this + coin is worth! */ + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Disagreement about coin value %s\n", + TALER_amount2s (amount_with_fee)); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange gave it a value of %s\n", + TALER_amount2s (&ttd->coin_value)); + ctc->check_transfer_result = GNUNET_SYSERR; + /* Build the `TrackTransferConflictDetails` */ + ctc->ec = TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS; + ctc->failure = true; + /* FIXME: this should be reported to the auditor (once the auditor has an API for this) */ + return; + } + if ( (GNUNET_OK != TALER_amount_cmp_currency (deposit_fee, &ttd->coin_fee)) || (0 != TALER_amount_cmp (deposit_fee, @@ -721,11 +754,17 @@ check_transfer (void *cls, /* Disagreement between the exchange and us about how much this coin is worth! */ GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Expected fee is %s\n", + TALER_amount2s (&ttd->coin_fee)); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Fee claimed by exchange is %s\n", + TALER_amount2s (deposit_fee)); ctc->check_transfer_result = GNUNET_SYSERR; /* Build the `TrackTransferConflictDetails` */ ctc->ec = TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS; ctc->failure = true; - /* TODO: this should be reported to the auditor! */ + /* FIXME: this should be reported to the auditor (once the auditor has an API for this) */ return; } ctc->check_transfer_result = GNUNET_OK; @@ -928,6 +967,12 @@ wire_transfer_cb (void *cls, &w->total)) ) { GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Wire transfer total value was %s\n", + TALER_amount2s (&w->total)); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange claimed total value to be %s\n", + TALER_amount2s (&td->total_amount)); update_transaction_status (w, GNUNET_TIME_UNIT_FOREVER_ABS, TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_CONFLICTING_TRANSFERS, @@ -1203,6 +1248,7 @@ run (void *cls, &transfer_added, NULL); } + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&find_work, NULL); } @@ -1248,6 +1294,9 @@ main (int argc, return EXIT_INVALIDARGUMENT; if (GNUNET_NO == ret) return EXIT_SUCCESS; + if ( (found_problem) && + (0 == global_ret) ) + global_ret = 7; return global_ret; } diff --git a/src/backend/taler-merchant-httpd_config.c b/src/backend/taler-merchant-httpd_config.c index d020985b..3777904f 100644 --- a/src/backend/taler-merchant-httpd_config.c +++ b/src/backend/taler-merchant-httpd_config.c @@ -42,7 +42,7 @@ * #MERCHANT_PROTOCOL_CURRENT and #MERCHANT_PROTOCOL_AGE in * merchant_api_config.c! */ -#define MERCHANT_PROTOCOL_VERSION "11:0:7" +#define MERCHANT_PROTOCOL_VERSION "14:0:10" /** @@ -89,9 +89,23 @@ MH_handler_config (struct TMH_RequestHandler *rh, { json_t *specs = json_object (); json_t *exchanges = json_array (); + struct GNUNET_TIME_Absolute a; + struct GNUNET_TIME_Timestamp km; + char dat[128]; GNUNET_assert (NULL != specs); GNUNET_assert (NULL != exchanges); + a = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS); + /* Round up to next full day to ensure the expiration + time does not become a fingerprint! */ + a = GNUNET_TIME_absolute_round_down (a, + GNUNET_TIME_UNIT_DAYS); + a = GNUNET_TIME_absolute_add (a, + GNUNET_TIME_UNIT_DAYS); + /* => /config response stays at most 48h in caches! */ + km = GNUNET_TIME_absolute_to_timestamp (a); + TALER_MHD_get_date_string (km.abs_time, + dat); TMH_exchange_get_trusted (&add_exchange, exchanges); for (unsigned int i = 0; i<TMH_num_cspecs; i++) @@ -102,7 +116,8 @@ MH_handler_config (struct TMH_RequestHandler *rh, GNUNET_assert (0 == json_object_set_new (specs, cspec->currency, - TALER_CONFIG_currency_specs_to_json ( + TALER_CONFIG_currency_specs_to_json + ( cspec))); } response = TALER_MHD_MAKE_JSON_PACK ( @@ -112,12 +127,21 @@ MH_handler_config (struct TMH_RequestHandler *rh, specs), GNUNET_JSON_pack_array_steal ("exchanges", exchanges), - GNUNET_JSON_pack_string ("implementation", - "urn:net:taler:specs:merchant:c-reference"), + GNUNET_JSON_pack_string ( + "implementation", + "urn:net:taler:specs:taler-merchant:c-reference"), GNUNET_JSON_pack_string ("name", "taler-merchant"), GNUNET_JSON_pack_string ("version", MERCHANT_PROTOCOL_VERSION)); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_EXPIRES, + dat)); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,max-age=21600")); /* 6h */ } return MHD_queue_response (connection, MHD_HTTP_OK, diff --git a/src/backend/taler-merchant-httpd_get-templates-ID.c b/src/backend/taler-merchant-httpd_get-templates-ID.c index fee5ec20..add67b4d 100644 --- a/src/backend/taler-merchant-httpd_get-templates-ID.c +++ b/src/backend/taler-merchant-httpd_get-templates-ID.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2022 Taler Systems SA + (C) 2022-2024 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 @@ -61,10 +61,15 @@ TMH_get_templates_ID ( ret = TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, - GNUNET_JSON_pack_object_steal ("template_contract", - tp.template_contract)); - GNUNET_free (tp.template_description); - GNUNET_free (tp.otp_id); + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("required_currency", + tp.required_currency)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("editable_defaults", + tp.editable_defaults)), + GNUNET_JSON_pack_object_incref ("template_contract", + tp.template_contract)); + TALER_MERCHANTDB_template_details_free (&tp); return ret; } } diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c index f21b2e48..8fb5823e 100644 --- a/src/backend/taler-merchant-httpd_helper.c +++ b/src/backend/taler-merchant-httpd_helper.c @@ -97,7 +97,7 @@ TMH_cmp_wire_account ( bool TMH_accounts_array_valid (const json_t *accounts) { - unsigned int len; + size_t len; if (! json_is_array (accounts)) { @@ -105,7 +105,7 @@ TMH_accounts_array_valid (const json_t *accounts) return false; } len = json_array_size (accounts); - for (unsigned int i = 0; i<len; i++) + for (size_t i = 0; i<len; i++) { json_t *payto_uri = json_array_get (accounts, i); 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 e7baf540..50a793a3 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c @@ -186,7 +186,7 @@ struct AbortContext /** * Number of coins this abort is for. Length of the @e rd array. */ - unsigned int coins_cnt; + size_t coins_cnt; /** * How often have we retried the 'main' transaction? @@ -198,7 +198,7 @@ struct AbortContext * @e coins_cnt, decremented on each transaction that * successfully finished. */ - unsigned int pending; + size_t pending; /** * Number of transactions still pending for the currently selected @@ -206,7 +206,7 @@ struct AbortContext * exchange, decremented on each transaction that successfully * finished. Once it hits zero, we pick the next exchange. */ - unsigned int pending_at_ce; + size_t pending_at_ce; /** * HTTP status code to use for the reply, i.e 200 for "OK". @@ -247,7 +247,7 @@ abort_refunds (struct AbortContext *ac) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Aborting pending /deposit operations\n"); - for (unsigned int i = 0; i<ac->coins_cnt; i++) + for (size_t i = 0; i<ac->coins_cnt; i++) { struct RefundDetails *rdi = &ac->rd[i]; @@ -356,7 +356,7 @@ generate_success_response (struct AbortContext *ac) "could not create JSON array"); return; } - for (unsigned int i = 0; i<ac->coins_cnt; i++) + for (size_t i = 0; i<ac->coins_cnt; i++) { struct RefundDetails *rdi = &ac->rd[i]; json_t *detail; @@ -424,7 +424,7 @@ abort_context_cleanup (void *cls) ac->timeout_task = NULL; } abort_refunds (ac); - for (unsigned int i = 0; i<ac->coins_cnt; i++) + for (size_t i = 0; i<ac->coins_cnt; i++) { struct RefundDetails *rdi = &ac->rd[i]; @@ -522,7 +522,7 @@ process_abort_with_exchange (void *cls, /* Initiate refund operation for all coins of the current exchange (!) */ GNUNET_assert (0 == ac->pending_at_ce); - for (unsigned int i = 0; i<ac->coins_cnt; i++) + for (size_t i = 0; i<ac->coins_cnt; i++) { struct RefundDetails *rdi = &ac->rd[i]; @@ -579,7 +579,7 @@ begin_transaction (struct AbortContext *ac); static void find_next_exchange (struct AbortContext *ac) { - for (unsigned int i = 0; i<ac->coins_cnt; i++) + for (size_t i = 0; i<ac->coins_cnt; i++) { struct RefundDetails *rdi = &ac->rd[i]; @@ -635,7 +635,7 @@ refund_coins (void *cls, (void) deposit_fee; (void) refund_fee; now = GNUNET_TIME_timestamp_get (); - for (unsigned int i = 0; i<ac->coins_cnt; i++) + for (size_t i = 0; i<ac->coins_cnt; i++) { struct RefundDetails *rdi = &ac->rd[i]; enum GNUNET_DB_QueryStatus qs; 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 a1fdabec..14edfd55 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -418,7 +418,7 @@ struct PayContext * Number of coins this payment is made of. Length * of the @e dc array. */ - unsigned int coins_cnt; + size_t coins_cnt; /** * Number of exchanges involved in the payment. Length @@ -477,69 +477,6 @@ struct PayContext /** - * Active KYC operation with an exchange. - */ -struct KycContext -{ - /** - * Kept in a DLL. - */ - struct KycContext *next; - - /** - * Kept in a DLL. - */ - struct KycContext *prev; - - /** - * Looking for the exchange. - */ - struct TMH_EXCHANGES_KeysOperation *fo; - - /** - * Exchange this is about. - */ - char *exchange_url; - - /** - * Merchant instance this is for. - */ - struct TMH_MerchantInstance *mi; - - /** - * Wire method we are checking the status of. - */ - struct TMH_WireMethod *wm; - - /** - * Handle for the GET /deposits operation. - */ - struct TALER_EXCHANGE_DepositGetHandle *dg; - - /** - * Contract we are looking up. - */ - struct TALER_PrivateContractHashP h_contract_terms; - - /** - * Coin we are looking up. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Initial DB timestamp. - */ - struct GNUNET_TIME_Timestamp kyc_timestamp; - - /** - * Initial KYC status. - */ - bool kyc_ok; - -}; - - -/** * Head of active pay context DLL. */ static struct PayContext *pc_head; @@ -549,57 +486,10 @@ static struct PayContext *pc_head; */ static struct PayContext *pc_tail; -/** - * Head of active KYC context DLL. - */ -static struct KycContext *kc_head; - -/** - * Tail of active KYC context DLL. - */ -static struct KycContext *kc_tail; - - -/** - * Free resources used by @a kc. - * - * @param[in] kc object to free - */ -static void -destroy_kc (struct KycContext *kc) -{ - if (NULL != kc->fo) - { - TMH_EXCHANGES_keys4exchange_cancel (kc->fo); - kc->fo = NULL; - } - if (NULL != kc->dg) - { - TALER_EXCHANGE_deposits_get_cancel (kc->dg); - kc->dg = NULL; - } - TMH_instance_decref (kc->mi); - kc->mi = NULL; - GNUNET_free (kc->exchange_url); - GNUNET_CONTAINER_DLL_remove (kc_head, - kc_tail, - kc); - GNUNET_free (kc); -} - void TMH_force_pc_resume () { - struct KycContext *kc; - - while (NULL != (kc = kc_head)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Aborting KYC check at %s\n", - kc->exchange_url); - destroy_kc (kc); - } for (struct PayContext *pc = pc_head; NULL != pc; pc = pc->next) @@ -652,7 +542,6 @@ resume_pay_with_response (struct PayContext *pc, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /pay handling. HTTP status for our reply is %u.\n", response_code); -#if 1 for (unsigned int i = 0; i<pc->num_exchanges; i++) { struct ExchangeGroup *eg = pc->egs[i]; @@ -671,7 +560,6 @@ resume_pay_with_response (struct PayContext *pc, } } GNUNET_assert (0 == pc->pending_at_eg); -#endif if (NULL != pc->timeout_task) { GNUNET_SCHEDULER_cancel (pc->timeout_task); @@ -744,221 +632,6 @@ phase_return_response (struct PayContext *pc) /** - * Function called with detailed wire transfer data. - * - * @param cls a `struct KycContext *` - * @param dr HTTP response data - */ -static void -deposit_get_callback ( - void *cls, - const struct TALER_EXCHANGE_GetDepositResponse *dr) -{ - struct KycContext *kc = cls; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_TIME_Timestamp now; - - kc->dg = NULL; - now = GNUNET_TIME_timestamp_get (); - switch (dr->hr.http_status) - { - case MHD_HTTP_OK: - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - kc->mi->settings.id, - &kc->wm->h_wire, - kc->exchange_url, - 0LL, - NULL, /* no signature */ - NULL, /* no signature */ - now, - true, - TALER_AML_NORMAL); - GNUNET_break (qs > 0); - break; - case MHD_HTTP_ACCEPTED: - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - kc->mi->settings.id, - &kc->wm->h_wire, - kc->exchange_url, - dr->details.accepted.requirement_row, - NULL, /* no signature */ - NULL, /* no signature */ - now, - dr->details.accepted.kyc_ok, - dr->details.accepted.aml_decision); - GNUNET_break (qs > 0); - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "KYC check failed at %s with unexpected status %u\n", - kc->exchange_url, - dr->hr.http_status); - } - destroy_kc (kc); -} - - -/** - * Function called with the result of our exchange lookup. - * - * @param cls the `struct KycContext` - * @param keys NULL if exchange was not found to be acceptable - * @param exchange representation of the exchange - */ -static void -process_kyc_with_exchange ( - void *cls, - struct TALER_EXCHANGE_Keys *keys, - struct TMH_Exchange *exchange) -{ - struct KycContext *kc = cls; - - (void) exchange; - kc->fo = NULL; - if (NULL == keys) - { - destroy_kc (kc); - return; - } - kc->dg = TALER_EXCHANGE_deposits_get ( - TMH_curl_ctx, - kc->exchange_url, - keys, - &kc->mi->merchant_priv, - &kc->wm->h_wire, - &kc->h_contract_terms, - &kc->coin_pub, - GNUNET_TIME_UNIT_ZERO, - &deposit_get_callback, - kc); - if (NULL == kc->dg) - { - GNUNET_break (0); - destroy_kc (kc); - } -} - - -/** - * Function called from ``account_kyc_get_status`` - * with KYC status information for this merchant. - * - * @param cls a `struct KycContext *` - * @param h_wire hash of the wire account - * @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown - * @param payto_uri payto:// URI of the merchant's bank account - * @param exchange_url base URL of the exchange for which this is a status - * @param last_check when did we last get an update on our KYC status from the exchange - * @param kyc_ok true if we satisfied the KYC requirements - * @param aml_decision latest AML decision by the exchange - */ -static void -kyc_cb ( - void *cls, - const struct TALER_MerchantWireHashP *h_wire, - uint64_t exchange_kyc_serial, - const char *payto_uri, - const char *exchange_url, - struct GNUNET_TIME_Timestamp last_check, - bool kyc_ok, - enum TALER_AmlDecisionState aml_decision) -{ - struct KycContext *kc = cls; - - (void) h_wire; - (void) exchange_kyc_serial; - (void) payto_uri; - (void) exchange_url; - kc->kyc_timestamp = last_check; - kc->kyc_ok = kyc_ok; - /* FIXME: act on aml_decision? */ -} - - -/** - * Check for our KYC status at @a exchange_url for the - * payment of @a pc. First checks if we already have a - * positive result from the exchange, and if not checks - * with the exchange. - * - * @param pc payment context to use as starting point - * @param eg exchange group of the exchange we are triggering on - */ -static void -check_kyc (struct PayContext *pc, - const struct ExchangeGroup *eg) -{ - enum GNUNET_DB_QueryStatus qs; - struct KycContext *kc; - - kc = GNUNET_new (struct KycContext); - qs = TMH_db->account_kyc_get_status (TMH_db->cls, - pc->hc->instance->settings.id, - &pc->wm->h_wire, - eg->exchange_url, - &kyc_cb, - kc); - if (qs < 0) - { - GNUNET_break (0); - GNUNET_free (kc); - return; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - if (kc->kyc_ok) - { - GNUNET_free (kc); - return; /* we are done */ - } - if (GNUNET_TIME_relative_cmp ( - GNUNET_TIME_absolute_get_duration ( - kc->kyc_timestamp.abs_time), - <, - KYC_RETRY_FREQUENCY)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Not re-checking KYC status at `%s', as we already recently asked\n", - eg->exchange_url); - GNUNET_free (kc); - return; - } - } - kc->mi = pc->hc->instance; - kc->mi->rc++; - kc->wm = pc->wm; - kc->exchange_url = GNUNET_strdup (eg->exchange_url); - kc->h_contract_terms = pc->h_contract_terms; - /* find one of the coins of the batch */ - for (unsigned int i = 0; i<pc->coins_cnt; i++) - { - struct DepositConfirmation *dc = &pc->dc[i]; - - if (0 != strcmp (eg->exchange_url, - pc->dc[i].exchange_url)) - continue; - kc->coin_pub = dc->cdd.coin_pub; - break; - } - GNUNET_CONTAINER_DLL_insert (kc_head, - kc_tail, - kc); - kc->fo = TMH_EXCHANGES_keys4exchange ( - kc->exchange_url, - false, - &process_kyc_with_exchange, - kc); - if (NULL == kc->fo) - { - GNUNET_break (0); - destroy_kc (kc); - } -} - - -/** * Do database transaction for a completed batch deposit. * * @param eg group that completed @@ -978,7 +651,7 @@ batch_deposit_transaction (const struct ExchangeGroup *eg, GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (pc->amount.currency, &total_without_fees)); - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; struct TALER_Amount amount_without_fees; @@ -1014,7 +687,7 @@ batch_deposit_transaction (const struct ExchangeGroup *eg, if (qs <= 0) return qs; /* Entire batch already known or failure, we're done */ - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1121,7 +794,7 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, } /* Transaction is done, mark affected coins as complete as well. */ - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1133,8 +806,6 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */ pc->pending--; } - check_kyc (pc, - eg); } @@ -1313,7 +984,7 @@ process_pay_with_keys ( /* Initiate /batch-deposit operation for all coins of the current exchange (!) */ group_size = 0; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; const struct TALER_EXCHANGE_DenomPublicKey *denom_details; @@ -1471,9 +1142,9 @@ AGE_FAIL: .refund_deadline = pc->refund_deadline }; enum TALER_ErrorCode ec; - unsigned int off = 0; + size_t off = 0; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1577,9 +1248,9 @@ get_pay_timeout (unsigned int num_coins) { struct GNUNET_TIME_Relative t; - /* FIXME: Do some benchmarking to come up with a better timeout. - * We've increased this value so the wallet integration test passes again - * on my (Florian) machine. + /* FIXME-Performance-Optimization: Do some benchmarking to come up with a + * better timeout. We've increased this value so the wallet integration + * test passes again on my (Florian) machine. */ t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15 * (1 + (num_coins / 5))); @@ -1602,7 +1273,7 @@ phase_batch_deposits (struct PayContext *pc) struct ExchangeGroup *eg = pc->egs[i]; bool have_coins = false; - for (unsigned int j = 0; j<pc->coins_cnt; j++) + for (size_t j = 0; j<pc->coins_cnt; j++) { struct DepositConfirmation *dc = &pc->dc[j]; @@ -1764,7 +1435,7 @@ check_coin_paid (void *cls, { struct PayContext *pc = cls; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1838,7 +1509,7 @@ check_coin_refunded (void *cls, an abort-pay refund (an unusual but possible case), we need to make sure that existing refunds are accounted for. */ - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1924,7 +1595,7 @@ check_payment_sufficient (struct PayContext *pc) GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (pc->amount.currency, &acc_amount)); - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -2161,7 +1832,7 @@ phase_execute_pay_transaction (struct PayContext *pc) GNUNET_break (GNUNET_OK == TALER_amount_set_zero (pc->amount.currency, &pc->total_refunded)); - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) pc->dc[i].found_in_db = false; pc->pending = pc->coins_cnt; @@ -2361,9 +2032,10 @@ phase_execute_pay_transaction (struct PayContext *pc) * @param cls closure with our `struct PayContext *` * @param deposit_serial which deposit operation is this about * @param exchange_url URL of the exchange that issued the coin + * @param h_wire hash of merchant's wire details + * @param deposit_timestamp when was the deposit made * @param amount_with_fee amount the exchange will deposit for this coin * @param deposit_fee fee the exchange will charge for this coin - * @param h_wire hash of merchant's wire details * @param coin_pub public key of the coin */ static void @@ -2372,13 +2044,14 @@ deposit_paid_check ( uint64_t deposit_serial, const char *exchange_url, const struct TALER_MerchantWireHashP *h_wire, + struct GNUNET_TIME_Timestamp deposit_timestamp, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_CoinSpendPublicKeyP *coin_pub) { struct PayContext *pc = cls; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dci = &pc->dc[i]; @@ -2429,7 +2102,7 @@ phase_contract_paid (struct PayContext *pc) "lookup_deposits_by_order")); return; } - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dci = &pc->dc[i]; @@ -2461,7 +2134,7 @@ phase_contract_paid (struct PayContext *pc) pc->order_id); refunds = json_array (); GNUNET_assert (NULL != refunds); - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dci = &pc->dc[i]; struct TALER_MerchantSignatureP merchant_sig; @@ -2655,7 +2328,7 @@ phase_check_contract (struct PayContext *pc) return; } - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; 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 e5595296..134cd2ee 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-refund.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-refund.c @@ -565,7 +565,8 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh, enum GNUNET_GenericReturnValue res; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("h_contract", &prd->h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("h_contract", + &prd->h_contract_terms), GNUNET_JSON_spec_end () }; res = TALER_MHD_parse_json_data (connection, @@ -666,8 +667,9 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh, } { - GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TMH_currency, - &prd->refund_amount)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TMH_currency, + &prd->refund_amount)); qs = TMH_db->lookup_refunds_detailed (TMH_db->cls, hc->instance->settings.id, &prd->h_contract_terms, 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 1c850990..98653997 100644 --- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c +++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c @@ -263,6 +263,11 @@ struct GetOrderRequestContext struct GNUNET_TIME_Timestamp timestamp; /** + * Timestamp of the last payment. + */ + struct GNUNET_TIME_Timestamp last_payment; + + /** * Order summary. Pointer into @e contract_terms. */ const char *summary; @@ -996,15 +1001,16 @@ phase_unpaid_finish (struct GetOrderRequestContext *gorc) * @param pending true if the this refund was not yet processed by the wallet/exchange */ static void -process_refunds_cb (void *cls, - uint64_t refund_serial, - struct GNUNET_TIME_Timestamp timestamp, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const char *exchange_url, - uint64_t rtransaction_id, - const char *reason, - const struct TALER_Amount *refund_amount, - bool pending) +process_refunds_cb ( + void *cls, + uint64_t refund_serial, + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *exchange_url, + uint64_t rtransaction_id, + const char *reason, + const struct TALER_Amount *refund_amount, + bool pending) { struct GetOrderRequestContext *gorc = cls; @@ -1013,18 +1019,19 @@ process_refunds_cb (void *cls, (unsigned long long) rtransaction_id, TALER_amount2s (refund_amount), reason); - GNUNET_assert (0 == - json_array_append_new ( - gorc->refund_details, - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - refund_amount), - GNUNET_JSON_pack_bool ("pending", - pending), - GNUNET_JSON_pack_timestamp ("timestamp", - timestamp), - GNUNET_JSON_pack_string ("reason", - reason)))); + GNUNET_assert ( + 0 == + json_array_append_new ( + gorc->refund_details, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + refund_amount), + GNUNET_JSON_pack_bool ("pending", + pending), + GNUNET_JSON_pack_timestamp ("timestamp", + timestamp), + GNUNET_JSON_pack_string ("reason", + reason)))); /* For refunded coins, we are not charged deposit fees, so subtract those again */ for (struct TransferQuery *tq = gorc->tq_head; @@ -1044,10 +1051,11 @@ process_refunds_cb (void *cls, return; } - GNUNET_assert (0 <= - TALER_amount_subtract (&gorc->deposit_fees_total, - &gorc->deposit_fees_total, - &tq->deposit_fee)); + GNUNET_assert ( + 0 <= + TALER_amount_subtract (&gorc->deposit_fees_total, + &gorc->deposit_fees_total, + &tq->deposit_fee)); } } if (GNUNET_OK != @@ -1084,29 +1092,32 @@ phase_check_refunds (struct GetOrderRequestContext *gorc) GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (gorc->contract_amount.currency, &gorc->refund_amount)); - qs = TMH_db->lookup_refunds_detailed (TMH_db->cls, - hc->instance->settings.id, - &gorc->h_contract_terms, - &process_refunds_cb, - gorc); + qs = TMH_db->lookup_refunds_detailed ( + TMH_db->cls, + hc->instance->settings.id, + &gorc->h_contract_terms, + &process_refunds_cb, + gorc); if (0 > qs) { GNUNET_break (0); phase_end (gorc, - TALER_MHD_reply_with_error (gorc->sc.con, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "detailed refunds")); + TALER_MHD_reply_with_error ( + gorc->sc.con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "detailed refunds")); return; } if (gorc->refund_currency_mismatch) { GNUNET_break (0); phase_end (gorc, - TALER_MHD_reply_with_error (gorc->sc.con, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "refunds in different currency than original order price")); + TALER_MHD_reply_with_error ( + gorc->sc.con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "refunds in different currency than original order price")); return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -1127,19 +1138,22 @@ phase_check_refunds (struct GetOrderRequestContext *gorc) * @param cls a `struct GetOrderRequestContext` * @param deposit_serial identifies the deposit operation * @param exchange_url URL of the exchange that issued @a coin_pub + * @param h_wire hash of the merchant's wire account into which the deposit was made + * @param deposit_timestamp when was the deposit made * @param amount_with_fee amount the exchange will deposit for this coin * @param deposit_fee fee the exchange will charge for this coin - * @param h_wire hash of the merchant's wire account into which the deposit was made * @param coin_pub public key of the deposited coin */ static void -deposit_cb (void *cls, - uint64_t deposit_serial, - const char *exchange_url, - const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_Amount *amount_with_fee, - const struct TALER_Amount *deposit_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub) +deposit_cb ( + void *cls, + uint64_t deposit_serial, + const char *exchange_url, + const struct TALER_MerchantWireHashP *h_wire, + struct GNUNET_TIME_Timestamp deposit_timestamp, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub) { struct GetOrderRequestContext *gorc = cls; struct TransferQuery *tq; @@ -1148,6 +1162,9 @@ deposit_cb (void *cls, "Checking deposit status for coin %s (over %s)\n", TALER_B2S (coin_pub), TALER_amount2s (amount_with_fee)); + gorc->last_payment + = GNUNET_TIME_timestamp_max (gorc->last_payment, + deposit_timestamp); tq = GNUNET_new (struct TransferQuery); tq->gorc = gorc; tq->exchange_url = GNUNET_strdup (exchange_url); @@ -1380,7 +1397,12 @@ phase_reply_result (struct GetOrderRequestContext *gorc) &gorc->claim_token, h_contract); } - + if (GNUNET_TIME_absolute_is_zero (gorc->last_payment.abs_time)) + { + GNUNET_break (GNUNET_YES == + TALER_amount_is_zero (&gorc->contract_amount)); + gorc->last_payment = gorc->timestamp; + } ret = TALER_MHD_REPLY_JSON_PACK ( gorc->sc.con, MHD_HTTP_OK, @@ -1403,6 +1425,8 @@ phase_reply_result (struct GetOrderRequestContext *gorc) gorc->contract_terms), GNUNET_JSON_pack_string ("order_status", "paid"), + GNUNET_JSON_pack_timestamp ("last_payment", + gorc->last_payment), GNUNET_JSON_pack_bool ("refunded", gorc->refunded), GNUNET_JSON_pack_bool ("wired", @@ -1443,9 +1467,10 @@ phase_error (struct GetOrderRequestContext *gorc) MHD_RESULT -TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - struct TMH_HandlerContext *hc) +TMH_private_get_orders_ID ( + const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) { struct GetOrderRequestContext *gorc = hc->ctx; diff --git a/src/backend/taler-merchant-httpd_private-get-orders.c b/src/backend/taler-merchant-httpd_private-get-orders.c index 92a1f389..4c6a104e 100644 --- a/src/backend/taler-merchant-httpd_private-get-orders.c +++ b/src/backend/taler-merchant-httpd_private-get-orders.c @@ -406,6 +406,18 @@ add_order (void *cls, return; } + if (TALER_amount_is_zero (&order_amount) && + (po->of.wired != TALER_EXCHANGE_YNA_ALL) ) + { + /* If we are actually filtering by wire status, + and the order was over an amount of zero, + do not return it as wire status is not + exactly meaningful for orders over zero. */ + json_decref (contract_terms); + GNUNET_free (order_id); + return; + } + if (GNUNET_TIME_absolute_is_future (rd.abs_time) && paid) { @@ -687,44 +699,23 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "wired"); + po->of.delta = -20; + /* deprecated in protocol v12 */ + TALER_MHD_parse_request_snumber (connection, + "delta", + &po->of.delta); + /* since protocol v12 */ + TALER_MHD_parse_request_snumber (connection, + "limit", + &po->of.delta); + if ( (-MAX_DELTA > po->of.delta) || + (po->of.delta > MAX_DELTA) ) { - const char *delta_str; - - delta_str = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "delta"); - if (NULL == delta_str) - { - po->of.delta = -20; - } - else - { - char dummy; - long long ll; - - if (1 != - sscanf (delta_str, - "%lld%c", - &ll, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "delta"); - } - po->of.delta = (int64_t) ll; - if ( (-MAX_DELTA > po->of.delta) || - (po->of.delta > MAX_DELTA) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "delta"); - } - } + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "delta"); } { const char *date_s_str; @@ -769,43 +760,25 @@ TMH_private_get_orders (const struct TMH_RequestHandler *rh, } } } + if (po->of.delta > 0) + po->of.start_row = 0; + else + po->of.start_row = INT64_MAX; + /* deprecated in protocol v12 */ + TALER_MHD_parse_request_number (connection, + "start", + &po->of.start_row); + /* since protocol v12 */ + TALER_MHD_parse_request_number (connection, + "offset", + &po->of.start_row); + if (INT64_MAX < po->of.start_row) { - const char *start_row_str; - - start_row_str = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "start"); - if (NULL == start_row_str) - { - if (po->of.delta > 0) - po->of.start_row = 0; - else - po->of.start_row = INT64_MAX; - } - else - { - char dummy; - unsigned long long ull; - - if (1 != - sscanf (start_row_str, - "%llu%c", - &ull, - &dummy)) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "start"); - po->of.start_row = (uint64_t) ull; - if (INT64_MAX < po->of.start_row) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "start"); - } - } + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "start"); } po->of.session_id = MHD_lookup_connection_value (connection, diff --git a/src/backend/taler-merchant-httpd_private-get-products.c b/src/backend/taler-merchant-httpd_private-get-products.c index bc90c94d..d9fa4e49 100644 --- a/src/backend/taler-merchant-httpd_private-get-products.c +++ b/src/backend/taler-merchant-httpd_private-get-products.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2019, 2020, 2021 Taler Systems SA + (C) 2019, 2020, 2021, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -30,6 +30,7 @@ */ static void add_product (void *cls, + uint64_t product_serial, const char *product_id) { json_t *pa = cls; @@ -38,6 +39,8 @@ add_product (void *cls, json_array_append_new ( pa, GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("product_serial", + product_serial), GNUNET_JSON_pack_string ("product_id", product_id)))); } @@ -50,11 +53,26 @@ TMH_private_get_products (const struct TMH_RequestHandler *rh, { json_t *pa; enum GNUNET_DB_QueryStatus qs; + int64_t limit; + uint64_t offset; + limit = 20; /* default */ + TALER_MHD_parse_request_snumber (connection, + "limit", + &limit); + if (limit < 0) + offset = INT64_MAX; + else + offset = 0; + TALER_MHD_parse_request_number (connection, + "offset", + &offset); pa = json_array (); GNUNET_assert (NULL != pa); qs = TMH_db->lookup_products (TMH_db->cls, hc->instance->settings.id, + offset, + limit, &add_product, pa); if (0 > qs) diff --git a/src/backend/taler-merchant-httpd_private-get-templates-ID.c b/src/backend/taler-merchant-httpd_private-get-templates-ID.c index a5b05f0c..35fdd1d0 100644 --- a/src/backend/taler-merchant-httpd_private-get-templates-ID.c +++ b/src/backend/taler-merchant-httpd_private-get-templates-ID.c @@ -61,15 +61,20 @@ TMH_private_get_templates_ID ( ret = TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("required_currency", + tp.required_currency)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("editable_defaults", + tp.editable_defaults)), GNUNET_JSON_pack_string ("template_description", tp.template_description), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("otp_id", tp.otp_id)), - GNUNET_JSON_pack_object_steal ("template_contract", - tp.template_contract)); - GNUNET_free (tp.template_description); - GNUNET_free (tp.otp_id); + GNUNET_JSON_pack_object_incref ("template_contract", + tp.template_contract)); + TALER_MERCHANTDB_template_details_free (&tp); return ret; } } diff --git a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c index b7c8ab4d..06dbbdf9 100644 --- a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c +++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c @@ -72,9 +72,9 @@ TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh, } else { + GNUNET_break (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - // TODO: What error code to use here? TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, "invalid_token_family_kind"); } @@ -86,12 +86,13 @@ TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh, MHD_HTTP_OK, GNUNET_JSON_pack_string ("name", details.name), GNUNET_JSON_pack_string ("description", details.description), - GNUNET_JSON_pack_object_steal ("description_i18n", details.description_i18n), + GNUNET_JSON_pack_object_steal ("description_i18n", + details.description_i18n), GNUNET_JSON_pack_timestamp ("valid_after", details.valid_after), GNUNET_JSON_pack_timestamp ("valid_before", details.valid_before), GNUNET_JSON_pack_time_rel ("duration", details.duration), GNUNET_JSON_pack_string ("kind", kind) - ); + ); GNUNET_free (details.name); GNUNET_free (details.description); diff --git a/src/backend/taler-merchant-httpd_private-get-transfers.c b/src/backend/taler-merchant-httpd_private-get-transfers.c index c43781dd..3e540297 100644 --- a/src/backend/taler-merchant-httpd_private-get-transfers.c +++ b/src/backend/taler-merchant-httpd_private-get-transfers.c @@ -137,59 +137,16 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh, TALER_EC_GENERIC_PARAMETER_MALFORMED, "after"); } - { - const char *limit_s; - - limit_s = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "limit"); - if (NULL != limit_s) - { - char dummy[2]; - long long l; - - if (1 != - sscanf (limit_s, - "%lld%1s", - &l, - dummy)) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "limit"); - limit = (int64_t) l; - } - } - { - const char *offset_s; - - offset_s = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "offset"); - if (NULL != offset_s) - { - char dummy[2]; - unsigned long long o; - - if (1 != - sscanf (offset_s, - "%llu%1s", - &o, - dummy)) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "offset"); - offset = (uint64_t) o; - } - else - { - if (limit < 0) - offset = INT64_MAX; - else - offset = 0; - } - } + TALER_MHD_parse_request_snumber (connection, + "limit", + &limit); + if (limit < 0) + offset = INT64_MAX; + else + offset = 0; + TALER_MHD_parse_request_number (connection, + "offset", + &offset); if (! (TALER_arg_to_yna (connection, "verified", TALER_EXCHANGE_YNA_ALL, diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c index 68e0a478..e8a6c531 100644 --- a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c +++ b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2022 Taler Systems SA + (C) 2022, 2024 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 @@ -112,6 +112,14 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh, NULL), GNUNET_JSON_spec_json ("template_contract", &tp.template_contract), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("required_currency", + (const char **) &tp.required_currency), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("editable_defaults", + &tp.editable_defaults), + NULL), GNUNET_JSON_spec_end () }; @@ -138,6 +146,56 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh, TALER_EC_GENERIC_PARAMETER_MALFORMED, "template_contract"); } + if ( (NULL != tp.required_currency) && + (GNUNET_OK != + TALER_check_currency (tp.required_currency)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "required_currency"); + } + if ( (NULL != tp.required_currency) && + (NULL != json_object_get (tp.template_contract, + "amount")) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "required_currency and contract::amount specified"); + } + if (NULL != tp.editable_defaults) + { + const char *key; + json_t *val; + + json_object_foreach (tp.editable_defaults, key, val) + { + if (NULL != + json_object_get (tp.template_contract, + key)) + { + char *msg; + MHD_RESULT ret; + + GNUNET_break_op (0); + GNUNET_asprintf (&msg, + "editable_defaults::%s conflicts with template_contract", + key); + GNUNET_JSON_parse_free (spec); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + msg); + GNUNET_free (msg); + return ret; + } + } + } qs = TMH_db->update_template (TMH_db->cls, mi->settings.id, diff --git a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c index c49d80d7..755ed4c9 100644 --- a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c +++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c @@ -136,10 +136,9 @@ TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh, "unexpected serialization problem"); break; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - // TODO: Add error code for token family not found ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, - 0, + TALER_EC_MERCHANT_PATCH_TOKEN_FAMILY_NOT_FOUND, slug); break; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 8a093813..b2070bcc 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -49,6 +49,11 @@ #define MAX_RETRIES 3 /** + * Maximum number of inventory products per order. + */ +#define MAX_PRODUCTS 1024 + +/** * What is the label under which we find/place the merchant's * jurisdiction in the locations list by default? */ @@ -346,6 +351,11 @@ struct OrderContext */ const json_t *extra; + /** + * Minimum age required by the order. + */ + uint32_t minimum_age; + } parse_order; /** @@ -451,6 +461,24 @@ struct OrderContext * Which product (by offset) is out of stock, UINT_MAX if all were in-stock. */ unsigned int out_of_stock_index; + + /** + * Set to a previous claim token *if* @e idempotent + * is also true. + */ + struct TALER_ClaimTokenP token; + + /** + * Set to true if the order was idempotent and there + * was an equivalent one before. + */ + bool idempotent; + + /** + * Set to true if the order is in conflict with a + * previous order with the same order ID. + */ + bool conflict; } execute_order; struct @@ -668,7 +696,6 @@ clean_order (void *cls) oc->parse_request.uuids_length, 0); json_decref (oc->parse_request.order); - /* TODO: Check that all other fields are cleaned up! */ json_decref (oc->serialize_order.contract); GNUNET_free (oc->parse_order.merchant_base_url); GNUNET_free (oc); @@ -695,6 +722,45 @@ execute_transaction (struct OrderContext *oc) GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } + + /* Test if we already have an order with this id */ + { + json_t *contract_terms; + struct TALER_MerchantPostDataHashP orig_post; + + qs = TMH_db->lookup_order (TMH_db->cls, + oc->hc->instance->settings.id, + oc->parse_order.order_id, + &oc->execute_order.token, + &orig_post, + &contract_terms); + /* If yes, check for idempotency */ + if (0 > qs) + { + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); + return qs; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + TMH_db->rollback (TMH_db->cls); + json_decref (contract_terms); + /* Comparing the contract terms is sufficient because all the other + params get added to it at some point. */ + if (0 == GNUNET_memcmp (&orig_post, + &oc->parse_request.h_post_data)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Order creation idempotent\n"); + oc->execute_order.idempotent = true; + return qs; + } + GNUNET_break_op (0); + oc->execute_order.conflict = true; + return qs; + } + } + /* Setup order */ qs = TMH_db->insert_order (TMH_db->cls, oc->hc->instance->settings.id, @@ -791,70 +857,6 @@ execute_order (struct OrderContext *oc) &oc->hc->instance->settings; enum GNUNET_DB_QueryStatus qs; - /* Test if we already have an order with this id */ - /* FIXME: this should be done within the main - transaction! */ - { - struct TALER_ClaimTokenP token; - json_t *contract_terms; - struct TALER_MerchantPostDataHashP orig_post; - - TMH_db->preflight (TMH_db->cls); - qs = TMH_db->lookup_order (TMH_db->cls, - oc->hc->instance->settings.id, - oc->parse_order.order_id, - &token, - &orig_post, - &contract_terms); - /* If yes, check for idempotency */ - if (0 > qs) - { - GNUNET_break (0); - TMH_db->rollback (TMH_db->cls); - reply_with_error (oc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup_order"); - return; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - MHD_RESULT ret; - - json_decref (contract_terms); - /* Comparing the contract terms is sufficient because all the other - params get added to it at some point. */ - if (0 == GNUNET_memcmp (&orig_post, - &oc->parse_request.h_post_data)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Order creation idempotent\n"); - ret = TALER_MHD_REPLY_JSON_PACK ( - oc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ("order_id", - oc->parse_order.order_id), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_varsize ( - "token", - GNUNET_is_zero (&token) - ? NULL - : &token, - sizeof (token)))); - finalize_order (oc, - ret); - return; - } - /* This request is not idempotent */ - GNUNET_break_op (0); - reply_with_error ( - oc, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS, - oc->parse_order.order_id); - return; - } - } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Executing database transaction to create order '%s' for instance '%s'\n", oc->parse_order.order_id, @@ -899,6 +901,37 @@ execute_order (struct OrderContext *oc) return; } + /* DB transaction succeeded, check for idempotent */ + if (oc->execute_order.idempotent) + { + MHD_RESULT ret; + + ret = TALER_MHD_REPLY_JSON_PACK ( + oc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("order_id", + oc->parse_order.order_id), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_varsize ( + "token", + GNUNET_is_zero (&oc->execute_order.token) + ? NULL + : &oc->execute_order.token, + sizeof (oc->execute_order.token)))); + finalize_order (oc, + ret); + return; + } + if (oc->execute_order.conflict) + { + reply_with_error ( + oc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS, + oc->parse_order.order_id); + return; + } + /* DB transaction succeeded, check for out-of-stock */ if (oc->execute_order.out_of_stock_index < UINT_MAX) { @@ -1436,161 +1469,155 @@ serialize_order (struct OrderContext *oc) { const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; - json_t *merchant = NULL; + json_t *merchant; json_t *token_types = json_object (); json_t *choices = json_array (); + merchant = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("name", + settings->name), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("website", + settings->website)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("email", + settings->email)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("logo", + settings->logo))); + GNUNET_assert (NULL != merchant); { - merchant = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("name", - settings->name), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("website", - settings->website)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("email", - settings->email)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("logo", - settings->logo))); - GNUNET_assert (NULL != merchant); - { - json_t *loca; + json_t *loca; - /* Handle merchant address */ - loca = settings->address; - if (NULL != loca) - { - loca = json_deep_copy (loca); - GNUNET_assert (NULL != loca); - GNUNET_assert (0 == - json_object_set_new (merchant, - "address", - loca)); - } - } + /* Handle merchant address */ + loca = settings->address; + if (NULL != loca) { - json_t *juri; - - /* Handle merchant jurisdiction */ - juri = settings->jurisdiction; - if (NULL != juri) - { - juri = json_deep_copy (juri); - GNUNET_assert (NULL != juri); - GNUNET_assert (0 == - json_object_set_new (merchant, - "jurisdiction", - juri)); - } + loca = json_deep_copy (loca); + GNUNET_assert (NULL != loca); + GNUNET_assert (0 == + json_object_set_new (merchant, + "address", + loca)); } } - { - for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) - { - struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i]; - - // TODO: Finish spec to clearly define how token families are stored in - // ContractTerms. - // Here are some thoughts: - // - Multiple keys of the same token family can be referrenced in - // one contract. E.g. exchanging old subscription for new. - // - TokenAuthority should be renamed to TokenFamily for consistency. - // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but - // every token-based in- or ouput needs to have a valid_after date, - // so it's clear with key is referenced. - json_t *jauthority = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("description", - authority->description), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("description_i18n", - authority->description_i18n)), - GNUNET_JSON_pack_data_auto ("h_pub", - &authority->pub->public_key.pub_key_hash), - GNUNET_JSON_pack_data_auto ("pub", - &authority->pub->public_key.pub_key_hash), - GNUNET_JSON_pack_timestamp ("token_expiration", - authority->token_expiration) - ); + json_t *juri; + /* Handle merchant jurisdiction */ + juri = settings->jurisdiction; + if (NULL != juri) + { + juri = json_deep_copy (juri); + GNUNET_assert (NULL != juri); GNUNET_assert (0 == - json_object_set_new (token_types, - authority->label, - jauthority)); + json_object_set_new (merchant, + "jurisdiction", + juri)); } } + for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) { - for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) - { - struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; + struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i]; - json_t *inputs = json_array (); - json_t *outputs = json_array (); + // TODO: Finish spec to clearly define how token families are stored in + // ContractTerms. + // Here are some thoughts: + // - Multiple keys of the same token family can be referrenced in + // one contract. E.g. exchanging old subscription for new. + // - TokenAuthority should be renamed to TokenFamily for consistency. + // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but + // every token-based in- or ouput needs to have a valid_after date, + // so it's clear with key is referenced. + json_t *jauthority = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("description", + authority->description), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + authority->description_i18n)), + GNUNET_JSON_pack_data_auto ("h_pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_data_auto ("pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_timestamp ("token_expiration", + authority->token_expiration) + ); - for (unsigned int j = 0; j<choice->inputs_len; j++) - { - struct TALER_MerchantContractInput *input = &choice->inputs[j]; + GNUNET_assert (0 == + json_object_set_new (token_types, + authority->label, + jauthority)); + } - json_t *jinput = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_int64 ("type", - input->type) - ); + for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) + { + struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; - if (TALER_MCIT_TOKEN == input->type) - { - GNUNET_assert(0 == - json_object_set_new(jinput, - "number", - json_integer ( - input->details.token.count))); - GNUNET_assert(0 == - json_object_set_new(jinput, - "token_family_slug", - json_string ( - input->details.token.token_family_slug))); - } + json_t *inputs = json_array (); + json_t *outputs = json_array (); - GNUNET_assert (0 == json_array_append_new (inputs, jinput)); - } + for (unsigned int j = 0; j<choice->inputs_len; j++) + { + struct TALER_MerchantContractInput *input = &choice->inputs[j]; - for (unsigned int j = 0; j<choice->outputs_len; j++) - { - struct TALER_MerchantContractOutput *output = &choice->outputs[j]; + json_t *jinput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + input->type) + ); - json_t *joutput = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_int64 ("type", - output->type) - ); + if (TALER_MCIT_TOKEN == input->type) + { + GNUNET_assert(0 == + json_object_set_new(jinput, + "number", + json_integer ( + input->details.token.count))); + GNUNET_assert(0 == + json_object_set_new(jinput, + "token_family_slug", + json_string ( + input->details.token.token_family_slug))); + } - if (TALER_MCOT_TOKEN == output->type) - { - GNUNET_assert(0 == - json_object_set_new(joutput, - "number", - json_integer ( - output->details.token.count))); - - GNUNET_assert(0 == - json_object_set_new(joutput, - "token_family_slug", - json_string ( - output->details.token.token_family_slug))); - } + GNUNET_assert (0 == json_array_append_new (inputs, jinput)); + } - GNUNET_assert (0 == json_array_append (outputs, joutput)); - } + for (unsigned int j = 0; j<choice->outputs_len; j++) + { + struct TALER_MerchantContractOutput *output = &choice->outputs[j]; - json_t *jchoice = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_incref ("inputs", - inputs), - GNUNET_JSON_pack_array_incref ("outputs", - outputs) + json_t *joutput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + output->type) ); - GNUNET_assert (0 == json_array_append (choices, jchoice)); + if (TALER_MCOT_TOKEN == output->type) + { + GNUNET_assert(0 == + json_object_set_new(joutput, + "number", + json_integer ( + output->details.token.count))); + + GNUNET_assert(0 == + json_object_set_new(joutput, + "token_family_slug", + json_string ( + output->details.token.token_family_slug))); + } + + GNUNET_assert (0 == json_array_append (outputs, joutput)); } + + json_t *jchoice = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("inputs", + inputs), + GNUNET_JSON_pack_array_incref ("outputs", + outputs) + ); + + GNUNET_assert (0 == json_array_append (choices, jchoice)); } oc->serialize_order.contract = GNUNET_JSON_PACK ( @@ -1613,6 +1640,9 @@ serialize_order (struct OrderContext *oc) GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", oc->parse_order.fulfillment_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_uint64 ("minimum_age", + oc->parse_order.minimum_age)), GNUNET_JSON_pack_array_incref ("products", oc->merge_inventory.products), GNUNET_JSON_pack_data_auto ("h_wire", @@ -1635,8 +1665,8 @@ serialize_order (struct OrderContext *oc) oc->parse_order.delivery_location)), GNUNET_JSON_pack_string ("merchant_base_url", oc->parse_order.merchant_base_url), - GNUNET_JSON_pack_object_incref ("merchant", - merchant), + GNUNET_JSON_pack_object_steal ("merchant", + merchant), GNUNET_JSON_pack_data_auto ("merchant_pub", &oc->hc->instance->merchant_pub), GNUNET_JSON_pack_array_incref ("exchanges", @@ -1883,6 +1913,10 @@ parse_order (struct OrderContext *oc) &oc->parse_order.delivery_date), NULL), GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &oc->parse_order.minimum_age), + NULL), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("auto_refund", &oc->parse_order.auto_refund), NULL), @@ -2600,6 +2634,9 @@ merge_inventory (struct OrderContext *oc) ip->product_id); return; } + oc->parse_order.minimum_age + = GNUNET_MAX (oc->parse_order.minimum_age, + pd.minimum_age); { json_t *p; @@ -2778,9 +2815,22 @@ parse_request (struct OrderContext *oc) /* parse the inventory_products (optionally given) */ if (NULL != ip) { + unsigned int ipl = (unsigned int) json_array_size (ip); + + if ( (json_array_size (ip) != (size_t) ipl) || + (ipl > MAX_PRODUCTS) ) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_ALLOCATION_FAILURE, + "inventory products too long"); + return; + } GNUNET_array_grow (oc->parse_request.inventory_products, oc->parse_request.inventory_products_length, - json_array_size (ip)); + (unsigned int) json_array_size (ip)); for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++) { struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i]; diff --git a/src/backend/taler-merchant-httpd_private-post-templates.c b/src/backend/taler-merchant-httpd_private-post-templates.c index a064769c..7aa72992 100644 --- a/src/backend/taler-merchant-httpd_private-post-templates.c +++ b/src/backend/taler-merchant-httpd_private-post-templates.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2022 Taler Systems SA + (C) 2022-2024 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 @@ -47,6 +47,18 @@ templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *t1, (NULL != t2->otp_id) && (0 == strcmp (t1->otp_id, t2->otp_id))) ) && + ( ( (NULL == t1->required_currency) && + (NULL == t2->required_currency) ) || + ( (NULL != t1->required_currency) && + (NULL != t2->required_currency) && + (0 == strcmp (t1->required_currency, + t2->required_currency))) ) && + ( ( (NULL == t1->editable_defaults) && + (NULL == t2->editable_defaults) ) || + ( (NULL != t1->editable_defaults) && + (NULL != t2->editable_defaults) && + (1 == json_equal (t1->editable_defaults, + t2->editable_defaults))) ) && (1 == json_equal (t1->template_contract, t2->template_contract)) ); } @@ -72,6 +84,14 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh, NULL), GNUNET_JSON_spec_json ("template_contract", &tp.template_contract), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("required_currency", + (const char **) &tp.required_currency), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("editable_defaults", + &tp.editable_defaults), + NULL), GNUNET_JSON_spec_end () }; uint64_t otp_serial = 0; @@ -104,6 +124,57 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh, "template_contract"); } + if ( (NULL != tp.required_currency) && + (GNUNET_OK != + TALER_check_currency (tp.required_currency)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "required_currency"); + } + if ( (NULL != tp.required_currency) && + (NULL != json_object_get (tp.template_contract, + "amount")) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "required_currency and contract::amount specified"); + } + if (NULL != tp.editable_defaults) + { + const char *key; + json_t *val; + + json_object_foreach (tp.editable_defaults, key, val) + { + if (NULL != + json_object_get (tp.template_contract, + key)) + { + char *msg; + MHD_RESULT ret; + + GNUNET_break_op (0); + GNUNET_asprintf (&msg, + "editable_defaults::%s conflicts with template_contract", + key); + GNUNET_JSON_parse_free (spec); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + msg); + GNUNET_free (msg); + return ret; + } + } + } + if (NULL != tp.otp_id) { qs = TMH_db->select_otp_serial (TMH_db->cls, diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.c b/src/backend/taler-merchant-httpd_private-post-token-families.c index b9be59d8..f4472c39 100644 --- a/src/backend/taler-merchant-httpd_private-post-token-families.c +++ b/src/backend/taler-merchant-httpd_private-post-token-families.c @@ -195,10 +195,9 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh, NULL, NULL, 0) - // TODO: Use proper error code : TALER_MHD_reply_with_error (connection, MHD_HTTP_CONFLICT, - 0, + TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT, details.slug); } } /* end switch (qs) */ diff --git a/src/backend/taler-merchant-wirewatch.c b/src/backend/taler-merchant-wirewatch.c index 6242ddbc..17eb7a0a 100644 --- a/src/backend/taler-merchant-wirewatch.c +++ b/src/backend/taler-merchant-wirewatch.c @@ -370,9 +370,8 @@ credit_cb ( w->start_row = serial_id; return GNUNET_OK; } - /* FIXME: consider grouping multiple inserts - into one bigger transaction with just one - notify! */ + /* FIXME-Performance-Optimization: consider grouping multiple inserts + into one bigger transaction with just one notify. */ credit_payto = TALER_payto_normalize (details->credit_account_uri); qs = db_plugin->insert_transfer (db_plugin->cls, w->instance_id, |