diff options
69 files changed, 2194 insertions, 1507 deletions
diff --git a/configure.ac b/configure.ac index 659d7349..ccc71965 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ # This configure file is in the public domain AC_PREREQ([2.69]) -AC_INIT([taler-merchant],[0.10.0],[taler-bug@gnunet.org]) +AC_INIT([taler-merchant],[0.10.1],[taler-bug@gnunet.org]) AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c]) AC_CONFIG_HEADERS([taler_merchant_config.h]) # support for non-recursive builds @@ -268,12 +268,12 @@ AS_CASE([$with_exchange], CPPFLAGS="-I$with_exchange/include $CPPFLAGS $POSTGRESQL_CPPFLAGS"]) AC_CHECK_HEADERS([taler/taler_mhd_lib.h], - [AC_CHECK_LIB([talermhd], [TALER_MHD_parse_request_arg_amount], libtalermhd=1)]) + [AC_CHECK_LIB([talermhd], [TALER_MHD_parse_request_arg_snumber], libtalermhd=1)]) AM_CONDITIONAL(HAVE_TALERMHD, test x$libtalermhd = x1) AS_IF([test $libtalermhd != 1], [AC_MSG_ERROR([[ *** -*** You need libtalermhd >= 0.9.4a to build this program. +*** You need libtalermhd >= 0.10.1 (API v2) to build this program. *** This library is part of the GNU Taler exchange, available at *** https://taler.net *** ]])]) diff --git a/contrib/ci/jobs/0-codespell/job.sh b/contrib/ci/jobs/0-codespell/job.sh index 7b486bc7..28f43239 100755 --- a/contrib/ci/jobs/0-codespell/job.sh +++ b/contrib/ci/jobs/0-codespell/job.sh @@ -7,6 +7,7 @@ skip=$(cat <<EOF ABOUT-NLS */afl-tests/* **/auditor/*.sql +*/debian/tmp/** *.bbl *.bib *build-aux* diff --git a/contrib/wallet-core b/contrib/wallet-core -Subproject 35212ac57bfe5625fcf574b0fd2d9baf395dc4c +Subproject 240d647da85de6b575d15c37efec04757541e3d diff --git a/debian/changelog b/debian/changelog index 36344f8b..4944eb80 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +taler-merchant (0.10.1) unstable; urgency=low + + * Implement cache control headers for /config + * Do not return orders over amount of 0 as unpaid + * Handle refunds in wire transfer reconciliation + * Implement protocol v12 and v13 + * Simplify KYC logic in payment processing + + -- Christian Grothoff <grothoff@gnu.org> Tue, 9 Apr 2024 09:50:12 +0200 + taler-merchant (0.10.0) unstable; urgency=low * Implement public GET API for templates (#8608). diff --git a/debian/rules b/debian/rules index 9e170335..eba1c7cd 100755 --- a/debian/rules +++ b/debian/rules @@ -38,6 +38,11 @@ override_dh_installsystemd: # Need to specify units manually, since we have multiple # and dh_installsystemd by default only looks for "<package>.service". dh_installsystemd -ptaler-merchant --name=taler-merchant-httpd --no-start --no-enable + dh_installsystemd -ptaler-merchant --name=taler-merchant-exchange --no-start --no-enable + dh_installsystemd -ptaler-merchant --name=taler-merchant-depositcheck --no-start --no-enable + dh_installsystemd -ptaler-merchant --name=taler-merchant-webhook --no-start --no-enable + dh_installsystemd -ptaler-merchant --name=taler-merchant-wirewatch --no-start --no-enable + dh_installsystemd -ptaler-merchant --name=taler-merchant --no-start --no-enable # final invocation to generate daemon reload dh_installsystemd diff --git a/debian/taler-merchant.taler-merchant-depositcheck.service b/debian/taler-merchant.taler-merchant-depositcheck.service index a7741dfa..bc5b84c0 100644 --- a/debian/taler-merchant.taler-merchant-depositcheck.service +++ b/debian/taler-merchant.taler-merchant-depositcheck.service @@ -14,3 +14,4 @@ PrivateTmp=yes PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s +Slice=taler-merchant.slice diff --git a/debian/taler-merchant.taler-merchant-exchange.service b/debian/taler-merchant.taler-merchant-exchange.service index c93422ae..4d368c3b 100644 --- a/debian/taler-merchant.taler-merchant-exchange.service +++ b/debian/taler-merchant.taler-merchant-exchange.service @@ -14,3 +14,4 @@ PrivateTmp=yes PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s +Slice=taler-merchant.slice diff --git a/debian/taler-merchant.taler-merchant-httpd.service b/debian/taler-merchant.taler-merchant-httpd.service index 164645a8..e97bb6f6 100644 --- a/debian/taler-merchant.taler-merchant-httpd.service +++ b/debian/taler-merchant.taler-merchant-httpd.service @@ -10,6 +10,7 @@ RestartSec=1s RestartPreventExitStatus=9 RuntimeMaxSec=3600s ExecStart=/usr/bin/taler-merchant-httpd -c /etc/taler/taler.conf -L INFO +Slice=taler-merchant.slice [Install] WantedBy=multi-user.target diff --git a/debian/taler-merchant.taler-merchant-webhook.service b/debian/taler-merchant.taler-merchant-webhook.service index 3071e8e6..e71bb5c8 100644 --- a/debian/taler-merchant.taler-merchant-webhook.service +++ b/debian/taler-merchant.taler-merchant-webhook.service @@ -14,3 +14,4 @@ PrivateTmp=yes PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s +Slice=taler-merchant.slice diff --git a/debian/taler-merchant.taler-merchant-wirewatch.service b/debian/taler-merchant.taler-merchant-wirewatch.service index bb13eccd..8b61d68e 100644 --- a/debian/taler-merchant.taler-merchant-wirewatch.service +++ b/debian/taler-merchant.taler-merchant-wirewatch.service @@ -14,3 +14,5 @@ PrivateTmp=yes PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s +Slice=taler-merchant.slice + diff --git a/debian/taler-merchant.taler-merchant.slice b/debian/taler-merchant.taler-merchant.slice new file mode 100644 index 00000000..6717bf7b --- /dev/null +++ b/debian/taler-merchant.taler-merchant.slice @@ -0,0 +1,7 @@ +[Unit] +Description=Slice for GNU taler merchant processes +Before=slices.target + +[Slice] +# Add settings that should affect all GNU Taler merchant +# components here. 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, diff --git a/src/backenddb/merchant-0005.sql b/src/backenddb/merchant-0005.sql index b7bf4c91..a356f6da 100644 --- a/src/backenddb/merchant-0005.sql +++ b/src/backenddb/merchant-0005.sql @@ -1,6 +1,6 @@ -- -- This file is part of TALER --- Copyright (C) 2023 Taler Systems SA +-- Copyright (C) 2024 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 @@ -18,10 +18,19 @@ BEGIN; -- Check patch versioning is in place. --- SELECT _v.register_patch('merchant-0005', NULL, NULL); +SELECT _v.register_patch('merchant-0005', NULL, NULL); SET search_path TO merchant; +ALTER TABLE merchant_template + ADD COLUMN required_currency VARCHAR(12) DEFAULT NULL, + ADD COLUMN editable_defaults TEXT DEFAULT NULL; + +COMMENT ON COLUMN merchant_template.required_currency + IS 'currency that the amount to be paid entered by the user must be in; if not given and the amount is not fixed in the template contract, the user may edit the currency'; +COMMENT ON COLUMN merchant_template.editable_defaults + IS 'JSON object with fields matching the template contract, just with default values that are editable by the user'; + -- Complete transaction COMMIT; diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c index 4ba70e4b..5894525c 100644 --- a/src/backenddb/merchantdb_helper.c +++ b/src/backenddb/merchantdb_helper.c @@ -43,6 +43,8 @@ TALER_MERCHANTDB_template_details_free ( { GNUNET_free (tp->template_description); GNUNET_free (tp->otp_id); + GNUNET_free (tp->required_currency); + json_decref (tp->editable_defaults); json_decref (tp->template_contract); } @@ -69,6 +71,7 @@ TALER_MERCHANTDB_pending_webhook_details_free ( GNUNET_free (pwb->body); } + void TALER_MERCHANTDB_token_family_details_free ( struct TALER_MERCHANTDB_TokenFamilyDetails *tf) diff --git a/src/backenddb/pg_insert_deposit_confirmation.c b/src/backenddb/pg_insert_deposit_confirmation.c index 6ad0f5fb..f23bf252 100644 --- a/src/backenddb/pg_insert_deposit_confirmation.c +++ b/src/backenddb/pg_insert_deposit_confirmation.c @@ -70,10 +70,14 @@ TMH_PG_insert_deposit_confirmation ( /* no preflight check here, run in transaction by caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Storing deposit confirmation for instance `%s' h_contract_terms `%s', total_without_fees: %s\n", + "Storing deposit confirmation for instance `%s' h_contract_terms `%s', total_without_fees: %s and wire transfer deadline in %s\n", instance_id, GNUNET_h2s (&h_contract_terms->hash), - TALER_amount2s (total_without_fees)); + TALER_amount2s (total_without_fees), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + wire_transfer_deadline.abs_time), + true)); check_connection (pg); PREPARE (pg, "insert_deposit_confirmation", diff --git a/src/backenddb/pg_insert_template.c b/src/backenddb/pg_insert_template.c index 5fc76a1d..67cae495 100644 --- a/src/backenddb/pg_insert_template.c +++ b/src/backenddb/pg_insert_template.c @@ -42,6 +42,12 @@ TMH_PG_insert_template (void *cls, ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_uint64 (&otp_serial_id), TALER_PQ_query_param_json (td->template_contract), + (NULL == td->editable_defaults) + ? GNUNET_PQ_query_param_null () + : TALER_PQ_query_param_json (td->editable_defaults), + (NULL == td->required_currency) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (td->required_currency), GNUNET_PQ_query_param_end }; @@ -54,13 +60,14 @@ TMH_PG_insert_template (void *cls, ",template_description" ",otp_device_id" ",template_contract" + ",editable_defaults" + ",required_currency" ")" " SELECT merchant_serial," - " $2, $3, $4, $5" + " $2, $3, $4, $5, $6, $7" " FROM merchant_instances" " WHERE merchant_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_template", params); } - diff --git a/src/backenddb/pg_insert_transfer_details.sql b/src/backenddb/pg_insert_transfer_details.sql index 1650d157..0dd11b1b 100644 --- a/src/backenddb/pg_insert_transfer_details.sql +++ b/src/backenddb/pg_insert_transfer_details.sql @@ -135,7 +135,7 @@ THEN FROM merchant_transfer_signatures WHERE credit_serial=my_credit_serial AND signkey_serial=my_signkey_serial - AND credit_amount=in_credit_amount + AND credit_amount=in_total_amount AND wire_fee=in_wire_fee AND execution_time=in_execution_time AND exchange_sig=in_exchange_sig; diff --git a/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c index 9bf46d0c..089543ea 100644 --- a/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c +++ b/src/backenddb/pg_lookup_deposits_by_contract_and_coin.c @@ -46,6 +46,11 @@ struct LookupDepositsByCnCContext struct PostgresClosure *pg; /** + * Total amount refunded on this coin and contract. + */ + struct TALER_Amount refund_total; + + /** * Transaction result. */ enum GNUNET_DB_QueryStatus qs; @@ -61,6 +66,54 @@ struct LookupDepositsByCnCContext * @param num_results the number of results in @a result */ static void +lookup_refunds_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupDepositsByCnCContext *ldcc = cls; + + for (unsigned int i = 0; i<num_results; i++) + { + struct TALER_Amount refund_amount; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_amount_with_currency ("refund_amount", + &refund_amount), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Coin had refund of %s\n", + TALER_amount2s (&refund_amount)); + if (0 == i) + ldcc->refund_total = refund_amount; + else + GNUNET_assert (0 <= + TALER_amount_add (&ldcc->refund_total, + &ldcc->refund_total, + &refund_amount)); + GNUNET_PQ_cleanup_result (rs); /* technically useless here */ + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls of type `struct LookupDepositsByCnCContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void lookup_deposits_by_contract_and_coin_cb (void *cls, PGresult *result, unsigned int num_results) @@ -112,6 +165,56 @@ lookup_deposits_by_contract_and_coin_cb (void *cls, ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Coin original deposit value is %s\n", + TALER_amount2s (&amount_with_fee)); + if (TALER_amount_is_valid (&ldcc->refund_total)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Coin had total refunds of %s\n", + TALER_amount2s (&ldcc->refund_total)); + if (1 == + TALER_amount_cmp (&ldcc->refund_total, + &amount_with_fee)) + { + /* Refunds exceeded total deposit? not OK! */ + GNUNET_break (0); + ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + if (0 == + TALER_amount_cmp (&ldcc->refund_total, + &amount_with_fee)) + { + /* refund_total == amount_with_fee; + in this case, the total contributed to the + wire transfer is zero (as are fees) */ + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (ldcc->refund_total.currency, + &amount_with_fee)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (ldcc->refund_total.currency, + &deposit_fee)); + + } + else + { + /* Compute deposit value by subtracting refunds */ + GNUNET_assert (0 < + TALER_amount_subtract (&amount_with_fee, + &amount_with_fee, + &ldcc->refund_total)); + if (-1 == + TALER_amount_cmp (&amount_with_fee, + &deposit_fee)) + { + /* amount_with_fee < deposit_fee, so after refunds less than + the deposit fee remains; reduce deposit fee to + the remaining value of the coin */ + deposit_fee = amount_with_fee; + } + } + } ldcc->cb (ldcc->cb_cls, exchange_url, &amount_with_fee, @@ -128,13 +231,15 @@ lookup_deposits_by_contract_and_coin_cb (void *cls, ldcc->qs = num_results; } + enum GNUNET_DB_QueryStatus -TMH_PG_lookup_deposits_by_contract_and_coin (void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - TALER_MERCHANTDB_CoinDepositCallback cb, - void *cb_cls) +TMH_PG_lookup_deposits_by_contract_and_coin ( + void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -151,6 +256,37 @@ TMH_PG_lookup_deposits_by_contract_and_coin (void *cls, enum GNUNET_DB_QueryStatus qs; check_connection (pg); + /* no preflight check here, run in transaction by caller! */ + TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", + GNUNET_h2s (&h_contract_terms->hash), + instance_id); + check_connection (pg); + PREPARE (pg, + "lookup_refunds_by_coin_and_contract", + "SELECT" + " refund_amount" + " FROM merchant_refunds" + /* Join to filter by refunds that actually + did work, not only those we approved */ + " JOIN merchant_refund_proofs" + " USING (refund_serial)" + " WHERE coin_pub=$3" + " AND order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))"); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_refunds_by_coin_and_contract", + params, + &lookup_refunds_cb, + &ldcc); + if (0 > qs) + return qs; + PREPARE (pg, "lookup_deposits_by_contract_and_coin", "SELECT" diff --git a/src/backenddb/pg_lookup_deposits_by_order.c b/src/backenddb/pg_lookup_deposits_by_order.c index fdaf1dfc..fb7637f0 100644 --- a/src/backenddb/pg_lookup_deposits_by_order.c +++ b/src/backenddb/pg_lookup_deposits_by_order.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2023, 2024 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 @@ -74,6 +74,7 @@ lookup_deposits_by_order_cb (void *cls, char *exchange_url; struct TALER_MerchantWireHashP h_wire; struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_TIME_Timestamp deposit_timestamp; struct TALER_Amount amount_with_fee; struct TALER_Amount deposit_fee; struct GNUNET_PQ_ResultSpec rs[] = { @@ -81,6 +82,8 @@ lookup_deposits_by_order_cb (void *cls, &deposit_serial), GNUNET_PQ_result_spec_string ("exchange_url", &exchange_url), + GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", + &deposit_timestamp), GNUNET_PQ_result_spec_auto_from_type ("h_wire", &h_wire), TALER_PQ_result_spec_amount_with_currency ("amount_with_fee", @@ -105,6 +108,7 @@ lookup_deposits_by_order_cb (void *cls, deposit_serial, exchange_url, &h_wire, + deposit_timestamp, &amount_with_fee, &deposit_fee, &coin_pub); @@ -139,6 +143,7 @@ TMH_PG_lookup_deposits_by_order (void *cls, " dep.deposit_serial" ",mcon.exchange_url" ",acc.h_wire" + ",mcon.deposit_timestamp" ",dep.amount_with_fee" ",dep.deposit_fee" ",dep.coin_pub" diff --git a/src/backenddb/pg_lookup_pending_deposits.c b/src/backenddb/pg_lookup_pending_deposits.c index c48fc24d..ab8981a5 100644 --- a/src/backenddb/pg_lookup_pending_deposits.c +++ b/src/backenddb/pg_lookup_pending_deposits.c @@ -175,7 +175,7 @@ TMH_PG_lookup_pending_deposits ( " USING (order_serial)" " JOIN merchant_accounts ma" " USING (account_serial)" - " JOIN merchant_kyc kyc" + " LEFT JOIN merchant_kyc kyc" " ON (ma.account_serial=kyc.account_serial)" " JOIN merchant_instances mi" " ON (mct.merchant_serial=mi.merchant_serial)" @@ -186,8 +186,8 @@ TMH_PG_lookup_pending_deposits ( " WHERE mdc.wire_pending" " AND (mdc.exchange_url=$1)" " AND ($4 OR (mdc.wire_transfer_deadline < $2))" - " AND kyc.kyc_ok" - " AND (0=kyc.aml_decision)" + " AND ( (kyc.kyc_ok IS NULL) OR kyc.kyc_ok)" + " AND ( (kyc.aml_decision IS NULL) OR (0=kyc.aml_decision) )" " ORDER BY mdc.wire_transfer_deadline ASC" " LIMIT $3"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, diff --git a/src/backenddb/pg_lookup_products.c b/src/backenddb/pg_lookup_products.c index d16aeb8d..fa2887a8 100644 --- a/src/backenddb/pg_lookup_products.c +++ b/src/backenddb/pg_lookup_products.c @@ -65,9 +65,12 @@ lookup_products_cb (void *cls, for (unsigned int i = 0; i < num_results; i++) { char *product_id; + uint64_t product_serial; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("product_id", &product_id), + GNUNET_PQ_result_spec_uint64 ("product_serial", + &product_serial), GNUNET_PQ_result_spec_end }; @@ -81,6 +84,7 @@ lookup_products_cb (void *cls, return; } plc->cb (plc->cb_cls, + product_serial, product_id); GNUNET_PQ_cleanup_result (rs); } @@ -90,10 +94,13 @@ lookup_products_cb (void *cls, enum GNUNET_DB_QueryStatus TMH_PG_lookup_products (void *cls, const char *instance_id, + uint64_t offset, + int64_t limit, TALER_MERCHANTDB_ProductsCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; + uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit); struct LookupProductsContext plc = { .cb = cb, .cb_cls = cb_cls, @@ -102,24 +109,45 @@ TMH_PG_lookup_products (void *cls, }; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_uint64 (&offset), + GNUNET_PQ_query_param_uint64 (&plimit), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); PREPARE (pg, - "lookup_products", + "lookup_products_asc", "SELECT" - " product_id" + " product_id" + " ,product_serial" " FROM merchant_inventory" " JOIN merchant_instances" " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1"); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_products", - params, - &lookup_products_cb, - &plc); + " WHERE merchant_instances.merchant_id=$1" + " AND product_serial > $2" + " ORDER BY product_serial ASC" + " LIMIT $3"); + PREPARE (pg, + "lookup_products_desc", + "SELECT" + " product_id" + " ,product_serial" + " FROM merchant_inventory" + " JOIN merchant_instances" + " USING (merchant_serial)" + " WHERE merchant_instances.merchant_id=$1" + " AND product_serial < $2" + " ORDER BY product_serial DESC" + " LIMIT $3"); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + (limit > 0) + ? "lookup_products_asc" + : "lookup_products_desc", + params, + &lookup_products_cb, + &plc); /* If there was an error inside lookup_products_cb, return a hard error. */ if (plc.extract_failed) return GNUNET_DB_STATUS_HARD_ERROR; diff --git a/src/backenddb/pg_lookup_products.h b/src/backenddb/pg_lookup_products.h index 398b5eac..d96328c8 100644 --- a/src/backenddb/pg_lookup_products.h +++ b/src/backenddb/pg_lookup_products.h @@ -30,6 +30,9 @@ * * @param cls closure * @param instance_id instance to lookup products for + * @param offset transfer_serial number of the transfer we want to offset from + * @param limit number of entries to return, negative for descending, + * positive for ascending * @param cb function to call on all products found * @param cb_cls closure for @a cb * @return database result code @@ -37,6 +40,8 @@ enum GNUNET_DB_QueryStatus TMH_PG_lookup_products (void *cls, const char *instance_id, + uint64_t offset, + int64_t limit, TALER_MERCHANTDB_ProductsCallback cb, void *cb_cls); diff --git a/src/backenddb/pg_lookup_refunds.h b/src/backenddb/pg_lookup_refunds.h index 2b047019..20f44e9d 100644 --- a/src/backenddb/pg_lookup_refunds.h +++ b/src/backenddb/pg_lookup_refunds.h @@ -36,11 +36,11 @@ * @return transaction status */ enum GNUNET_DB_QueryStatus -TMH_PG_lookup_refunds (void *cls, - const char *instance_id, - const struct - TALER_PrivateContractHashP *h_contract_terms, - TALER_MERCHANTDB_RefundCallback rc, - void *rc_cls); +TMH_PG_lookup_refunds ( + void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + TALER_MERCHANTDB_RefundCallback rc, + void *rc_cls); #endif diff --git a/src/backenddb/pg_lookup_refunds_detailed.h b/src/backenddb/pg_lookup_refunds_detailed.h index c2531446..665f06cf 100644 --- a/src/backenddb/pg_lookup_refunds_detailed.h +++ b/src/backenddb/pg_lookup_refunds_detailed.h @@ -36,10 +36,11 @@ * @return transaction status */ enum GNUNET_DB_QueryStatus -TMH_PG_lookup_refunds_detailed (void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - TALER_MERCHANTDB_RefundDetailCallback rc, - void *rc_cls); +TMH_PG_lookup_refunds_detailed ( + void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + TALER_MERCHANTDB_RefundDetailCallback rc, + void *rc_cls); #endif diff --git a/src/backenddb/pg_lookup_template.c b/src/backenddb/pg_lookup_template.c index a0326bc8..6e9d3681 100644 --- a/src/backenddb/pg_lookup_template.c +++ b/src/backenddb/pg_lookup_template.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2024 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 @@ -56,6 +56,8 @@ TMH_PG_lookup_template (void *cls, " mt.template_description" ",mod.otp_id" ",mt.template_contract" + ",mt.required_currency" + ",mt.editable_defaults" " FROM merchant_template mt" " JOIN merchant_instances mi" " ON (mi.merchant_serial = mt.merchant_serial)" @@ -83,16 +85,25 @@ TMH_PG_lookup_template (void *cls, GNUNET_PQ_result_spec_string ("otp_id", &td->otp_id), NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("required_currency", + &td->required_currency), + NULL), TALER_PQ_result_spec_json ("template_contract", &td->template_contract), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("editable_defaults", + &td->editable_defaults), + NULL), GNUNET_PQ_result_spec_end }; - td->otp_id = NULL; + memset (td, + 0, + sizeof (*td)); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_template", params, rs); } } - diff --git a/src/backenddb/pg_lookup_templates.c b/src/backenddb/pg_lookup_templates.c index 59240994..7938d575 100644 --- a/src/backenddb/pg_lookup_templates.c +++ b/src/backenddb/pg_lookup_templates.c @@ -126,10 +126,8 @@ TMH_PG_lookup_templates (void *cls, params, &lookup_templates_cb, &tlc); - /* If there was an error inside lookup_template_cb, return a hard error. */ + /* If there was an error inside lookup_templates_cb, return a hard error. */ if (tlc.extract_failed) return GNUNET_DB_STATUS_HARD_ERROR; return qs; } - - diff --git a/src/backenddb/pg_update_template.c b/src/backenddb/pg_update_template.c index 9f7f37c3..c0c35df3 100644 --- a/src/backenddb/pg_update_template.c +++ b/src/backenddb/pg_update_template.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2024 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 @@ -26,17 +26,6 @@ #include "pg_helper.h" -/** - * Update details about a particular template. - * - * @param cls closure - * @param instance_id instance to update template for - * @param template_id template to update - * @param td update to the template details on success, can be NULL - * (in that case we only want to check if the template exists) - * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template - * does not yet exist. - */ enum GNUNET_DB_QueryStatus TMH_PG_update_template (void *cls, const char *instance_id, @@ -52,6 +41,12 @@ TMH_PG_update_template (void *cls, ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_string (td->otp_id), TALER_PQ_query_param_json (td->template_contract), + (NULL == td->editable_defaults) + ? GNUNET_PQ_query_param_null () + : TALER_PQ_query_param_json (td->editable_defaults), + (NULL == td->required_currency) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (td->required_currency), GNUNET_PQ_query_param_end }; @@ -73,6 +68,8 @@ TMH_PG_update_template (void *cls, " COALESCE((SELECT otp_serial" " FROM otp), NULL)" ",template_contract=$5" + ",editable_defaults=$6" + ",required_currency=$7" " WHERE merchant_serial=" " (SELECT merchant_serial" " FROM mid)" @@ -81,5 +78,3 @@ TMH_PG_update_template (void *cls, "update_template", params); } - - diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index 53902b3d..fbb662f8 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2023 Taler Systems SA + (C) 2014-2024 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 @@ -42,28 +42,28 @@ static struct TALER_MERCHANTDB_Plugin *plugin; * @param test 0 on success, non-zero on failure */ #define TEST_WITH_FAIL_CLAUSE(test, on_fail) \ - if ((test)) \ - { \ - GNUNET_break (0); \ - on_fail \ - } + if ((test)) \ + { \ + GNUNET_break (0); \ + on_fail \ + } #define TEST_COND_RET_ON_FAIL(cond, msg) \ - if (! (cond)) \ - { \ - GNUNET_break (0); \ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ - msg); \ - return 1; \ - } + if (! (cond)) \ + { \ + GNUNET_break (0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ + msg); \ + return 1; \ + } /** * @param __test 0 on success, non-zero on failure */ #define TEST_RET_ON_FAIL(__test) \ - TEST_WITH_FAIL_CLAUSE (__test, \ - return 1; \ - ) + TEST_WITH_FAIL_CLAUSE (__test, \ + return 1; \ + ) /* ********** Instances ********** */ @@ -906,13 +906,17 @@ struct TestLookupProducts_Closure * Function called after calling @e test_lookup_products * * @param cls a pointer to the lookup closure. + * @param product_serial DB row ID * @param product_id the identifier of the product found. */ static void lookup_products_cb (void *cls, + uint64_t product_serial, const char *product_id) { struct TestLookupProducts_Closure *cmp = cls; + + GNUNET_assert (product_serial > 0); if (NULL == cmp) return; cmp->results_length += 1; @@ -948,6 +952,8 @@ test_lookup_products (const struct InstanceData *instance, memset (results_matching, 0, sizeof (unsigned int) * products_length); if (0 > plugin->lookup_products (plugin->cls, instance->instance.id, + 0, + 20, &lookup_products_cb, &cls)) { @@ -1130,7 +1136,8 @@ run_test_products (struct TestProducts_Closure *cls) stock_dec.product.total_stock = 40; TEST_RET_ON_FAIL (test_update_product (&cls->instance, &stock_dec, - GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); + GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)) + ; } { struct ProductData lost_dec = cls->products[0]; @@ -1138,7 +1145,8 @@ run_test_products (struct TestProducts_Closure *cls) lost_dec.product.total_lost = 1; TEST_RET_ON_FAIL (test_update_product (&cls->instance, &lost_dec, - GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); + GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)) + ; } TEST_RET_ON_FAIL (test_lookup_product (&cls->instance, &cls->products[0])); @@ -2189,14 +2197,16 @@ run_test_orders (struct TestOrders_Closure *cls) cls->orders)); /* Test marking orders as wired */ TEST_RET_ON_FAIL (test_mark_order_wired (serial, - GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)) + ; TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, cls->orders[0].id, NULL, true, true)); TEST_RET_ON_FAIL (test_mark_order_wired (1007, - GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); + GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)) + ; /* If an order has been claimed and we aren't past the pay deadline, we can't delete it. */ TEST_RET_ON_FAIL (test_delete_order (&cls->instance, @@ -2472,7 +2482,8 @@ test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey, TEST_COND_RET_ON_FAIL (expected_result == plugin->insert_exchange_signkey (plugin->cls, &signkey->master_pub, - &signkey->exchange_pub, + &signkey->exchange_pub + , signkey->start_date, signkey->expire_date, signkey->end_date, @@ -2800,8 +2811,8 @@ test_lookup_deposits_contract_and_coin ( * @param cls pointer to the test lookup closure. * @param deposit_serial row number of the deposit in the database. * @param exchange_url URL to the exchange - * @param amount_with_fee amount of the deposit with fees. * @param h_wire hash of the wire transfer details. + * @param deposit_timestamp when was the deposit made * @param amount_with_fee amount of the deposit with fees. * @param deposit_fee fee charged for the deposit. * @param coin_pub public key of the coin deposited. @@ -2811,11 +2822,13 @@ lookup_deposits_order_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 TestLookupDeposits_Closure *cmp = cls; + if (NULL == cmp) return; cmp->results_length += 1; @@ -2865,11 +2878,11 @@ test_lookup_deposits_by_order (uint64_t order_serial, memset (results_matching, 0, sizeof (unsigned int) * deposits_length); - if (deposits_length != plugin->lookup_deposits_by_order (plugin->cls, - order_serial, - & - lookup_deposits_order_cb, - &cmp)) + if (deposits_length != + plugin->lookup_deposits_by_order (plugin->cls, + order_serial, + &lookup_deposits_order_cb, + &cmp)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup deposits by order failed\n"); @@ -2917,8 +2930,8 @@ struct LookupDepositSerial_Closure * @param cls pointer to the test lookup closure. * @param deposit_serial row number of the deposit in the database. * @param exchange_url URL to the exchange - * @param amount_with_fee amount of the deposit with fees. * @param h_wire hash of the wire transfer details. + * @param deposit_timestamp when was the deposit made. * @param amount_with_fee amount of the deposit with fees. * @param deposit_fee fee charged for the deposit. * @param coin_pub public key of the coin deposited. @@ -2928,11 +2941,14 @@ get_deposit_serial_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 LookupDepositSerial_Closure *lookup_cls = cls; + + (void) deposit_timestamp; if (NULL == lookup_cls) return; if ((0 == strcmp (lookup_cls->deposit->exchange_url, @@ -4084,7 +4100,8 @@ test_insert_transfer_details ( TEST_COND_RET_ON_FAIL (expected_result == plugin->insert_transfer_details (plugin->cls, instance->instance.id, - transfer->exchange_url, + transfer->exchange_url + , account->payto_uri, &transfer->wtid, &transfer->data), @@ -4452,7 +4469,8 @@ test_lookup_refunds (const struct InstanceData *instance, if (refunds_length != cmp.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Lookup refunds failed: incorrect number of results returned\n"); + "Lookup refunds failed: incorrect number of results returned\n") + ; return 1; } for (unsigned int i = 0; refunds_length > i; ++i) @@ -4660,7 +4678,8 @@ test_lookup_refunds_detailed ( if (refunds_length != cmp.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Lookup refunds detailed failed: incorrect number of results\n"); + "Lookup refunds detailed failed: incorrect number of results\n") + ; return 1; } for (unsigned int i = 0; refunds_length > i; ++i) @@ -4974,7 +4993,8 @@ run_test_refunds (struct TestRefunds_Closure *cls) TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == plugin->refund_coin (plugin->cls, cls->instance.instance.id, - &cls->deposits[0].h_contract_terms, + &cls->deposits[0].h_contract_terms + , cls->refunds[0].timestamp, cls->refunds[0].coin_pub, cls->refunds[0].reason), @@ -4986,7 +5006,8 @@ run_test_refunds (struct TestRefunds_Closure *cls) TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == plugin->refund_coin (plugin->cls, cls->instance.instance.id, - &cls->deposits[0].h_contract_terms, + &cls->deposits[0].h_contract_terms + , cls->refunds[0].timestamp, cls->refunds[0].coin_pub, cls->refunds[0].reason), @@ -5010,14 +5031,16 @@ run_test_refunds (struct TestRefunds_Closure *cls) refund_serial, &cls->refund_proof. exchange_sig, - &cls->signkey.exchange_pub), + &cls->signkey.exchange_pub + ), "Insert refund proof failed\n"); TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == plugin->insert_refund_proof (plugin->cls, refund_serial, &cls->refund_proof. exchange_sig, - &cls->signkey.exchange_pub), + &cls->signkey.exchange_pub + ), "Insert refund proof failed\n"); /* Test that we can't give too much in refunds */ GNUNET_assert (GNUNET_OK == @@ -5712,15 +5735,16 @@ lookup_templates_cb (void *cls, const char *template_description) { struct TestLookupTemplates_Closure *cmp = cls; + if (NULL == cmp) return; cmp->results_length += 1; for (unsigned int i = 0; cmp->templates_to_cmp_length > i; ++i) { - if ((0 == strcmp (cmp->templates_to_cmp[i].id, - template_id)) && - (0 == strcmp (cmp->templates_to_cmp[i].template.template_description, - template_description)) ) + if ( (0 == strcmp (cmp->templates_to_cmp[i].id, + template_id)) && + (0 == strcmp (cmp->templates_to_cmp[i].template.template_description, + template_description)) ) cmp->results_matching[i] += 1; } } @@ -5746,7 +5770,10 @@ test_lookup_templates (const struct InstanceData *instance, .results_matching = results_matching, .results_length = 0 }; - memset (results_matching, 0, sizeof (unsigned int) * templates_length); + + memset (results_matching, + 0, + sizeof (unsigned int) * templates_length); if (0 > plugin->lookup_templates (plugin->cls, instance->instance.id, &lookup_templates_cb, @@ -5909,7 +5936,6 @@ run_test_templates (struct TestTemplates_Closure *cls) "otp_id", &td)); } - GNUNET_assert (0 == json_array_append_new ( cls->templates[0].template.template_contract, @@ -5917,7 +5943,6 @@ run_test_templates (struct TestTemplates_Closure *cls) TEST_RET_ON_FAIL (test_update_template (&cls->instance, &cls->templates[0], GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); - TEST_RET_ON_FAIL (test_lookup_template (&cls->instance, &cls->templates[0])); TEST_RET_ON_FAIL (test_update_template (&cls->instance, @@ -5955,8 +5980,13 @@ static int test_templates (void) { struct TestTemplates_Closure test_cls; + int test_result; + + memset (&test_cls, + 0, + sizeof (test_cls)); pre_test_templates (&test_cls); - int test_result = run_test_templates (&test_cls); + test_result = run_test_templates (&test_cls); post_test_templates (&test_cls); return test_result; } @@ -6555,7 +6585,8 @@ test_insert_pending_webhook (const struct InstanceData *instance, http_method, pwebhook->pwebhook. header, - pwebhook->pwebhook.body), + pwebhook->pwebhook.body + ), "Insert pending webhook failed\n"); return 0; } @@ -7007,7 +7038,8 @@ run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) &cls->pwebhooks[1])); TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance, &cls->pwebhooks[1], - GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); // ??? + GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); + // ??? TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance, 2, cls->pwebhooks)); diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index 2ed51a29..e8c890b1 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -34,7 +34,7 @@ /** * Library version (in hex) for compatibility tests. */ -#define TALER_MERCHANT_SERVICE_VERSION 0x00090403 +#define TALER_MERCHANT_SERVICE_VERSION 0x00100000 /** @@ -949,7 +949,7 @@ TALER_MERCHANT_instance_delete_cancel ( * @param arg request to cancel. */ #define TALER_MERCHANT_instance_purge_cancel(arg) \ - TALER_MERCHANT_instance_delete_cancel (arg) + TALER_MERCHANT_instance_delete_cancel (arg) /* *************** Accounts **************** */ @@ -1386,6 +1386,10 @@ struct TALER_MERCHANT_InventoryEntry */ const char *product_id; + /** + * Serial ID of the product. + */ + uint64_t product_serial; }; @@ -1659,6 +1663,50 @@ TALER_MERCHANT_products_post ( /** + * Make a POST /products request to add a product to the + * inventory. + * + * @param ctx the context + * @param backend_url HTTP base URL for the backend + * @param product_id identifier to use for the product + * @param description description of the product + * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions + * @param unit unit in which the product is measured (liters, kilograms, packages, etc.) + * @param price the price for one @a unit of the product, zero is used to imply that + * this product is not sold separately or that the price is not fixed and + * must be supplied by the front-end. If non-zero, price must include + * applicable taxes. + * @param image base64-encoded product image + * @param taxes list of taxes paid by the merchant + * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books) + * @param address where the product is in stock + * @param next_restock when the next restocking is expected to happen, 0 for unknown, + * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'. + * @param minimum_age minimum age the buyer must have + * @param cb function to call with the backend's result + * @param cb_cls closure for @a cb + * @return the request handle; NULL upon error + */ +struct TALER_MERCHANT_ProductsPostHandle * +TALER_MERCHANT_products_post2 ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *product_id, + const char *description, + const json_t *description_i18n, + const char *unit, + const struct TALER_Amount *price, + const char *image, + const json_t *taxes, + int64_t total_stock, + const json_t *address, + struct GNUNET_TIME_Timestamp next_restock, + uint32_t minimum_age, + TALER_MERCHANT_ProductsPostCallback cb, + void *cb_cls); + + +/** * Cancel POST /products operation. * * @param pph operation to cancel @@ -2616,6 +2664,12 @@ struct TALER_MERCHANT_OrderStatusResponse */ bool wired; + /** + * Time of the last payment made on this order. + * Only available if the server supports protocol + * **v14** or higher, otherwise zero. + */ + struct GNUNET_TIME_Timestamp last_payment; } paid; /** diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h index 85613fa6..b1de5292 100644 --- a/src/include/taler_merchant_testing_lib.h +++ b/src/include/taler_merchant_testing_lib.h @@ -315,9 +315,10 @@ TALER_TESTING_cmd_merchant_delete_instance (const char *label, * @param image base64-encoded product image * @param taxes list of taxes paid by the merchant * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books) + * @param minimum_age minimum age required for buying this product * @param address where the product is in stock * @param next_restock when the next restocking is expected to happen, 0 for unknown, - * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'. + * #GNUNET_TIME_UNIT_FOREVER_TS for 'never'. * @param http_status expected HTTP response code. * @return the command. */ @@ -333,6 +334,7 @@ TALER_TESTING_cmd_merchant_post_products2 ( const char *image, json_t *taxes, int64_t total_stock, + uint32_t minimum_age, json_t *address, struct GNUNET_TIME_Timestamp next_restock, unsigned int http_status); @@ -866,6 +868,27 @@ TALER_TESTING_cmd_merchant_get_order3 ( /** + * Define a GET /private/orders/$ORDER_ID CMD. + * + * @param label the command label + * @param merchant_url base URL of the merchant which will + * serve the request. + * @param order_reference reference to a command that created an order. + * @param osc expected order status + * @param expected_min_age expected minimum age for the contract + * @param expected_http_status expected HTTP response code for the request. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_get_order4 ( + const char *label, + const char *merchant_url, + const char *order_reference, + enum TALER_MERCHANT_OrderStatusCode osc, + uint32_t expected_min_age, + unsigned int expected_http_status); + + +/** * Start a long poll for GET /private/orders/$ORDER_ID. */ struct TALER_TESTING_Command diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 07eff056..44fdc0ab 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -251,10 +251,12 @@ typedef void * Typically called by `lookup_products`. * * @param cls a `json_t *` JSON array to build + * @param product_serial row ID of the product * @param product_id ID of the product */ typedef void (*TALER_MERCHANTDB_ProductsCallback)(void *cls, + uint64_t product_serial, const char *product_id); @@ -377,6 +379,20 @@ struct TALER_MERCHANTDB_TemplateDetails * ID of the OTP device linked to the template, or NULL. */ char *otp_id; + + /** + * Currency the payment must be in, NULL to allow any + * supported currency. + */ + char *required_currency; + + /** + * Editable default values for fields not specified + * in the @e template_contract. NULL if the user + * cannot edit anything. + */ + json_t *editable_defaults; + }; @@ -918,9 +934,10 @@ typedef void * @param cls closure * @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 */ typedef void @@ -929,6 +946,7 @@ typedef void 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); @@ -1586,6 +1604,9 @@ struct TALER_MERCHANTDB_Plugin * * @param cls closure * @param instance_id instance to lookup products for + * @param offset transfer_serial number of the transfer we want to offset from + * @param limit number of entries to return, negative for descending, + * positive for ascending * @param cb function to call on all products found * @param cb_cls closure for @a cb * @return database result code @@ -1593,6 +1614,8 @@ struct TALER_MERCHANTDB_Plugin enum GNUNET_DB_QueryStatus (*lookup_products)(void *cls, const char *instance_id, + uint64_t offset, + int64_t limit, TALER_MERCHANTDB_ProductsCallback cb, void *cb_cls); diff --git a/src/lib/merchant_api_get_accounts.c b/src/lib/merchant_api_get_accounts.c index 95238827..c08cd92d 100644 --- a/src/lib/merchant_api_get_accounts.c +++ b/src/lib/merchant_api_get_accounts.c @@ -30,6 +30,10 @@ #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> +/** + * Maximum number of accounts permitted. + */ +#define MAX_ACCOUNTS 1024 /** * Handle for a GET /accounts operation. @@ -77,35 +81,44 @@ parse_accounts (const json_t *ia, struct TALER_MERCHANT_AccountsGetResponse *tgr, struct TALER_MERCHANT_AccountsGetHandle *tgh) { - unsigned int tmpl_len = json_array_size (ia); - struct TALER_MERCHANT_AccountEntry tmpl[GNUNET_NZL (tmpl_len)]; - size_t index; - json_t *value; - - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_AccountEntry *ie = &tmpl[index]; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_payto_uri ("payto_uri", - &ie->payto_uri), - GNUNET_JSON_spec_fixed_auto ("h_wire", - &ie->h_wire), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; + unsigned int tmpl_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) tmpl_len) || + (tmpl_len > MAX_ACCOUNTS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_AccountEntry tmpl[GNUNET_NZL (tmpl_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_AccountEntry *ie = &tmpl[index]; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_payto_uri ("payto_uri", + &ie->payto_uri), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &ie->h_wire), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } } + tgr->details.ok.accounts_length = tmpl_len; + tgr->details.ok.accounts = tmpl; + tgh->cb (tgh->cb_cls, + tgr); + tgh->cb = NULL; /* just to be sure */ } - tgr->details.ok.accounts_length = tmpl_len; - tgr->details.ok.accounts = tmpl; - tgh->cb (tgh->cb_cls, - tgr); - tgh->cb = NULL; /* just to be sure */ return GNUNET_OK; } @@ -120,8 +133,8 @@ parse_accounts (const json_t *ia, */ static void handle_get_accounts_finished (void *cls, - long response_code, - const void *response) + long response_code, + const void *response) { struct TALER_MERCHANT_AccountsGetHandle *tgh = cls; const json_t *json = response; @@ -156,8 +169,8 @@ handle_get_accounts_finished (void *cls, } if (GNUNET_OK == parse_accounts (accounts, - &tgr, - tgh)) + &tgr, + tgh)) { TALER_MERCHANT_accounts_get_cancel (tgh); return; diff --git a/src/lib/merchant_api_get_config.c b/src/lib/merchant_api_get_config.c index 3f1471e3..b4b700bd 100644 --- a/src/lib/merchant_api_get_config.c +++ b/src/lib/merchant_api_get_config.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2023 Taler Systems SA + Copyright (C) 2014-2024 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 @@ -34,13 +34,22 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define MERCHANT_PROTOCOL_CURRENT 11 +#define MERCHANT_PROTOCOL_CURRENT 14 /** * How many configs are we backwards-compatible with? */ -#define MERCHANT_PROTOCOL_AGE 6 +#define MERCHANT_PROTOCOL_AGE 2 +/** + * How many exchanges do we allow at most per merchant? + */ +#define MAX_EXCHANGES 1024 + +/** + * How many currency specs do we allow at most per merchant? + */ +#define MAX_CURRENCIES 1024 /** * @brief A handle for /config operations @@ -108,6 +117,7 @@ handle_config_finished (void *cls, const json_t *exchanges = NULL; struct TALER_MERCHANT_ExchangeConfigInfo *eci = NULL; unsigned int num_eci = 0; + unsigned int nspec; struct TALER_JSON_ProtocolVersion pv; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_object_const ("currencies", @@ -136,26 +146,42 @@ handle_config_finished (void *cls, GNUNET_break_op (0); cr.hr.http_status = 0; cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } - else + cr.details.ok.compat = TALER_MERCHANT_VC_MATCH; + if (MERCHANT_PROTOCOL_CURRENT < pv.current) { - cr.details.ok.compat = TALER_MERCHANT_VC_MATCH; - if (MERCHANT_PROTOCOL_CURRENT < pv.current) - { - cr.details.ok.compat |= TALER_MERCHANT_VC_NEWER; - if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age) - cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE; - } - if (MERCHANT_PROTOCOL_CURRENT > pv.current) - { - cr.details.ok.compat |= TALER_MERCHANT_VC_OLDER; - if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current) - cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE; - } + cr.details.ok.compat |= TALER_MERCHANT_VC_NEWER; + if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age) + cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE; + } + if (MERCHANT_PROTOCOL_CURRENT > pv.current) + { + cr.details.ok.compat |= TALER_MERCHANT_VC_OLDER; + if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current) + cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE; + } + + nspec = (unsigned int) json_object_size (jcs); + if ( (nspec > MAX_CURRENCIES) || + (json_object_size (jcs) != (size_t) nspec) ) + { + GNUNET_break_op (0); + cr.hr.http_status = 0; + cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } if (NULL != exchanges) { - num_eci = json_object_size (exchanges); + num_eci = (unsigned int) json_object_size (exchanges); + if ( (num_eci > MAX_EXCHANGES) || + (json_object_size (exchanges) != (size_t) num_eci) ) + { + GNUNET_break_op (0); + cr.hr.http_status = 0; + cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } eci = GNUNET_new_array (num_eci, struct TALER_MERCHANT_ExchangeConfigInfo); for (unsigned int i = 0; i<num_eci; i++) @@ -187,7 +213,6 @@ handle_config_finished (void *cls, } } { - unsigned int nspec = json_object_size (jcs); struct TALER_CurrencySpecification *cspecs; unsigned int off = 0; json_t *obj; @@ -197,7 +222,7 @@ handle_config_finished (void *cls, struct TALER_CurrencySpecification); cr.details.ok.num_cspecs = nspec; cr.details.ok.cspecs = cspecs; - cr.details.ok.num_exchanges = num_eci; + cr.details.ok.num_exchanges = (unsigned int) num_eci; cr.details.ok.exchanges = eci; json_object_foreach ((json_t *) jcs, curr, obj) { @@ -218,6 +243,8 @@ handle_config_finished (void *cls, cr.hr.http_status = 0; cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; GNUNET_free (eci); + TALER_CONFIG_free_currencies (off - 1, + cspecs); break; } } @@ -238,10 +265,10 @@ handle_config_finished (void *cls, "Unexpected response code %u/%d\n", (unsigned int) response_code, (int) cr.hr.ec); - vgh->cb (vgh->cb_cls, - &cr); break; } + vgh->cb (vgh->cb_cls, + &cr); TALER_MERCHANT_config_get_cancel (vgh); } diff --git a/src/lib/merchant_api_get_instances.c b/src/lib/merchant_api_get_instances.c index c0553941..b2f99853 100644 --- a/src/lib/merchant_api_get_instances.c +++ b/src/lib/merchant_api_get_instances.c @@ -32,6 +32,11 @@ /** + * Maximum number of instances permitted. + */ +#define MAX_INSTANCES 1024 + +/** * Handle for a GET /instances operation. */ struct TALER_MERCHANT_InstancesGetHandle @@ -77,62 +82,71 @@ parse_instances (const json_t *json, const json_t *ia, struct TALER_MERCHANT_InstancesGetHandle *igh) { - unsigned int iis_len = json_array_size (ia); - struct TALER_MERCHANT_InstanceInformation iis[GNUNET_NZL (iis_len)]; - size_t index; - json_t *value; - struct TALER_MERCHANT_InstancesGetResponse igr = { - .hr.http_status = MHD_HTTP_OK, - .hr.reply = json, - .details.ok.iis_length = iis_len, - .details.ok.iis = iis - }; + unsigned int iis_len = (unsigned int) json_array_size (ia); - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_InstanceInformation *ii = &iis[index]; - const char *uts; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("name", - &ii->name), - GNUNET_JSON_spec_string ("user_type", - &uts), - GNUNET_JSON_spec_string ("id", - &ii->id), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &ii->merchant_pub), - GNUNET_JSON_spec_array_const ("payment_targets", - &ii->payment_targets), - GNUNET_JSON_spec_end () + if ( (json_array_size (ia) != (size_t) iis_len) || + (iis_len > MAX_INSTANCES) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_InstanceInformation iis[GNUNET_NZL (iis_len)]; + size_t index; + json_t *value; + struct TALER_MERCHANT_InstancesGetResponse igr = { + .hr.http_status = MHD_HTTP_OK, + .hr.reply = json, + .details.ok.iis_length = iis_len, + .details.ok.iis = iis }; - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_KYCLOGIC_kyc_user_type_from_string (uts, - &ii->ut)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - for (unsigned int i = 0; i<json_array_size (ii->payment_targets); i++) - { - if (! json_is_string (json_array_get (ii->payment_targets, - i))) + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_InstanceInformation *ii = &iis[index]; + const char *uts; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &ii->name), + GNUNET_JSON_spec_string ("user_type", + &uts), + GNUNET_JSON_spec_string ("id", + &ii->id), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &ii->merchant_pub), + GNUNET_JSON_spec_array_const ("payment_targets", + &ii->payment_targets), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; } - } - } /* for all instances */ - igh->cb (igh->cb_cls, - &igr); - igh->cb = NULL; /* just to be sure */ + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_user_type_from_string (uts, + &ii->ut)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (size_t i = 0; i<json_array_size (ii->payment_targets); i++) + { + if (! json_is_string (json_array_get (ii->payment_targets, + i))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + } /* for all instances */ + igh->cb (igh->cb_cls, + &igr); + igh->cb = NULL; /* just to be sure */ + } return GNUNET_OK; } diff --git a/src/lib/merchant_api_get_kyc.c b/src/lib/merchant_api_get_kyc.c index a9aabbe0..d2a819ea 100644 --- a/src/lib/merchant_api_get_kyc.c +++ b/src/lib/merchant_api_get_kyc.c @@ -32,6 +32,11 @@ /** + * Maximum length of the KYC arrays supported. + */ +#define MAX_KYC 1024 + +/** * Handle for a GET /kyc operation. */ struct TALER_MERCHANT_KycGetHandle @@ -79,72 +84,88 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, const json_t *pends, const json_t *touts) { - unsigned int num_pends = json_array_size (pends); - unsigned int num_touts = json_array_size (touts); - struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[GNUNET_NZL ( - num_pends)]; - struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[GNUNET_NZL ( - num_touts)]; - - memset (pending_kycs, - 0, - sizeof (pending_kycs)); - for (unsigned int i = 0; i<num_pends; i++) + unsigned int num_pends = (unsigned int) json_array_size (pends); + unsigned int num_touts = (unsigned int) json_array_size (touts); + + if ( (json_array_size (pends) != (size_t) num_pends) || + (num_pends > MAX_KYC) ) { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_web_url ("kyc_url", - &pending_kycs[i].kyc_url), - NULL), - TALER_JSON_spec_aml_decision ("aml_status", - &pending_kycs[i].aml_status), - TALER_JSON_spec_web_url ("exchange_url", - &pending_kycs[i].exchange_url), - TALER_JSON_spec_payto_uri ("payto_uri", - &pending_kycs[i].payto_uri), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (pends, - i), - spec, - NULL, NULL)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } + GNUNET_break (0); + return GNUNET_SYSERR; + } + if ( (json_array_size (touts) != (size_t) num_touts) || + (num_touts > MAX_KYC) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; } - for (unsigned int i = 0; i<num_touts; i++) + { - uint32_t hs; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_web_url ("exchange_url", - &timeout_kycs[i].exchange_url), - TALER_JSON_spec_ec ("exchange_code", - &timeout_kycs[i].exchange_code), - GNUNET_JSON_spec_uint32 ("exchange_http_status", - &hs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (touts, - i), - spec, - NULL, NULL)) + struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[ + GNUNET_NZL (num_pends)]; + struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[ + GNUNET_NZL (num_touts)]; + + memset (pending_kycs, + 0, + sizeof (pending_kycs)); + for (unsigned int i = 0; i<num_pends; i++) { - GNUNET_break (0); - return GNUNET_SYSERR; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_web_url ("kyc_url", + &pending_kycs[i].kyc_url), + NULL), + TALER_JSON_spec_aml_decision ("aml_status", + &pending_kycs[i].aml_status), + TALER_JSON_spec_web_url ("exchange_url", + &pending_kycs[i].exchange_url), + TALER_JSON_spec_payto_uri ("payto_uri", + &pending_kycs[i].payto_uri), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (pends, + i), + spec, + NULL, NULL)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } } - timeout_kycs[i].exchange_http_status = (unsigned int) hs; + for (unsigned int i = 0; i<num_touts; i++) + { + uint32_t hs; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_web_url ("exchange_url", + &timeout_kycs[i].exchange_url), + TALER_JSON_spec_ec ("exchange_code", + &timeout_kycs[i].exchange_code), + GNUNET_JSON_spec_uint32 ("exchange_http_status", + &hs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (touts, + i), + spec, + NULL, NULL)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + timeout_kycs[i].exchange_http_status = (unsigned int) hs; + } + kr->details.kyc_status.pending_kycs = pending_kycs; + kr->details.kyc_status.timeout_kycs = timeout_kycs; + kr->details.kyc_status.pending_kycs_length = num_pends; + kr->details.kyc_status.timeout_kycs_length = num_touts; + kyc->cb (kyc->cb_cls, + kr); } - kr->details.kyc_status.pending_kycs = pending_kycs; - kr->details.kyc_status.timeout_kycs = timeout_kycs; - kr->details.kyc_status.pending_kycs_length = num_pends; - kr->details.kyc_status.timeout_kycs_length = num_touts; - kyc->cb (kyc->cb_cls, - kr); return GNUNET_OK; } diff --git a/src/lib/merchant_api_get_orders.c b/src/lib/merchant_api_get_orders.c index af2b46d9..459409fd 100644 --- a/src/lib/merchant_api_get_orders.c +++ b/src/lib/merchant_api_get_orders.c @@ -30,6 +30,10 @@ #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> +/** + * Maximum number of orders we return. + */ +#define MAX_ORDERS 1024 /** * Handle for a GET /orders operation. @@ -77,45 +81,54 @@ parse_orders (const json_t *ia, struct TALER_MERCHANT_OrdersGetResponse *ogr, struct TALER_MERCHANT_OrdersGetHandle *ogh) { - unsigned int oes_len = json_array_size (ia); - struct TALER_MERCHANT_OrderEntry oes[GNUNET_NZL (oes_len)]; - size_t index; - json_t *value; + unsigned int oes_len = (unsigned int) json_array_size (ia); - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_OrderEntry *ie = &oes[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("order_id", - &ie->order_id), - GNUNET_JSON_spec_timestamp ("timestamp", - &ie->timestamp), - GNUNET_JSON_spec_uint64 ("row_id", - &ie->order_serial), - TALER_JSON_spec_amount_any ("amount", - &ie->amount), - GNUNET_JSON_spec_string ("summary", - &ie->summary), - GNUNET_JSON_spec_bool ("refundable", - &ie->refundable), - GNUNET_JSON_spec_bool ("paid", - &ie->paid), - GNUNET_JSON_spec_end () - }; + if ( (json_array_size (ia) != (size_t) oes_len) || + (oes_len > MAX_ORDERS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_OrderEntry oes[GNUNET_NZL (oes_len)]; + size_t index; + json_t *value; - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_OrderEntry *ie = &oes[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("order_id", + &ie->order_id), + GNUNET_JSON_spec_timestamp ("timestamp", + &ie->timestamp), + GNUNET_JSON_spec_uint64 ("row_id", + &ie->order_serial), + TALER_JSON_spec_amount_any ("amount", + &ie->amount), + GNUNET_JSON_spec_string ("summary", + &ie->summary), + GNUNET_JSON_spec_bool ("refundable", + &ie->refundable), + GNUNET_JSON_spec_bool ("paid", + &ie->paid), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } } + ogr->details.ok.orders_length = oes_len; + ogr->details.ok.orders = oes; + ogh->cb (ogh->cb_cls, + ogr); + ogh->cb = NULL; /* just to be sure */ } - ogr->details.ok.orders_length = oes_len; - ogr->details.ok.orders = oes; - ogh->cb (ogh->cb_cls, - ogr); - ogh->cb = NULL; /* just to be sure */ return GNUNET_OK; } @@ -275,6 +288,12 @@ TALER_MERCHANT_orders_get3 ( / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; GNUNET_assert (NULL != backend_url); + if ( (delta > MAX_ORDERS) || + (delta < -MAX_ORDERS) ) + { + GNUNET_break (0); + return NULL; + } if (0 == delta) { GNUNET_break (0); diff --git a/src/lib/merchant_api_get_otp_devices.c b/src/lib/merchant_api_get_otp_devices.c index 3e48486a..4737944c 100644 --- a/src/lib/merchant_api_get_otp_devices.c +++ b/src/lib/merchant_api_get_otp_devices.c @@ -30,6 +30,11 @@ #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> +/** + * Maximum number of OTP devices we return. + */ +#define MAX_OTP 1024 + /** * Handle for a GET /otp-devices operation. @@ -77,35 +82,44 @@ parse_otp_devices (const json_t *ia, struct TALER_MERCHANT_OtpDevicesGetResponse *tgr, struct TALER_MERCHANT_OtpDevicesGetHandle *tgh) { - unsigned int tmpl_len = json_array_size (ia); - struct TALER_MERCHANT_OtpDeviceEntry tmpl[GNUNET_NZL (tmpl_len)]; - size_t index; - json_t *value; - - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_OtpDeviceEntry *ie = &tmpl[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("otp_device_id", - &ie->otp_device_id), - GNUNET_JSON_spec_string ("device_description", - &ie->device_description), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) + unsigned int otp_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) otp_len) || + (otp_len > MAX_OTP) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_OtpDeviceEntry otp[GNUNET_NZL (otp_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_OtpDeviceEntry *ie = &otp[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("otp_device_id", + &ie->otp_device_id), + GNUNET_JSON_spec_string ("device_description", + &ie->device_description), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; } + } + tgr->details.ok.otp_devices_length = otp_len; + tgr->details.ok.otp_devices = otp; + tgh->cb (tgh->cb_cls, + tgr); + tgh->cb = NULL; /* just to be sure */ } - tgr->details.ok.otp_devices_length = tmpl_len; - tgr->details.ok.otp_devices = tmpl; - tgh->cb (tgh->cb_cls, - tgr); - tgh->cb = NULL; /* just to be sure */ return GNUNET_OK; } @@ -156,8 +170,8 @@ handle_get_otp_devices_finished (void *cls, } if (GNUNET_OK == parse_otp_devices (otp_devices, - &tgr, - tgh)) + &tgr, + tgh)) { TALER_MERCHANT_otp_devices_get_cancel (tgh); return; diff --git a/src/lib/merchant_api_get_products.c b/src/lib/merchant_api_get_products.c index 01115094..c33e24c9 100644 --- a/src/lib/merchant_api_get_products.c +++ b/src/lib/merchant_api_get_products.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2023 Taler Systems SA + Copyright (C) 2014-2024 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 @@ -32,6 +32,12 @@ /** + * Maximum number of products we return. + */ +#define MAX_PRODUCTS 1024 + + +/** * Handle for a GET /products operation. */ struct TALER_MERCHANT_ProductsGetHandle @@ -78,46 +84,57 @@ parse_products (const json_t *json, struct TALER_MERCHANT_ProductsGetHandle *pgh) { unsigned int ies_len = json_array_size (ia); - struct TALER_MERCHANT_InventoryEntry ies[GNUNET_NZL (ies_len)]; - size_t index; - json_t *value; - enum GNUNET_GenericReturnValue ret; - - ret = GNUNET_OK; - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_InventoryEntry *ie = &ies[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("product_id", - &ie->product_id), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - continue; - } - if (GNUNET_SYSERR == ret) - break; + + if ( (json_array_size (ia) != (size_t) ies_len) || + (ies_len > MAX_PRODUCTS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; } - if (GNUNET_OK == ret) { - struct TALER_MERCHANT_GetProductsResponse gpr = { - .hr.http_status = MHD_HTTP_OK, - .hr.reply = json, - .details.ok.products_length = ies_len, - .details.ok.products = ies - }; - - pgh->cb (pgh->cb_cls, - &gpr); - pgh->cb = NULL; /* just to be sure */ + struct TALER_MERCHANT_InventoryEntry ies[GNUNET_NZL (ies_len)]; + size_t index; + json_t *value; + enum GNUNET_GenericReturnValue ret; + + ret = GNUNET_OK; + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_InventoryEntry *ie = &ies[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("product_id", + &ie->product_id), + GNUNET_JSON_spec_uint64 ("product_serial", + &ie->product_serial), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + continue; + } + if (GNUNET_SYSERR == ret) + break; + } + if (GNUNET_OK == ret) + { + struct TALER_MERCHANT_GetProductsResponse gpr = { + .hr.http_status = MHD_HTTP_OK, + .hr.reply = json, + .details.ok.products_length = ies_len, + .details.ok.products = ies + }; + + pgh->cb (pgh->cb_cls, + &gpr); + pgh->cb = NULL; /* just to be sure */ + } + return ret; } - return ret; } diff --git a/src/lib/merchant_api_get_templates.c b/src/lib/merchant_api_get_templates.c index 98f2e304..f1f973b5 100644 --- a/src/lib/merchant_api_get_templates.c +++ b/src/lib/merchant_api_get_templates.c @@ -32,6 +32,12 @@ /** + * Maximum number of templates we return. + */ +#define MAX_TEMPLATES 1024 + + +/** * Handle for a GET /templates operation. */ struct TALER_MERCHANT_TemplatesGetHandle @@ -77,33 +83,42 @@ parse_templates (const json_t *ia, struct TALER_MERCHANT_TemplatesGetResponse *tgr, struct TALER_MERCHANT_TemplatesGetHandle *tgh) { - unsigned int tmpl_len = json_array_size (ia); - struct TALER_MERCHANT_TemplateEntry tmpl[GNUNET_NZL (tmpl_len)]; - size_t index; - json_t *value; - - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_TemplateEntry *ie = &tmpl[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("template_id", - &ie->template_id), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; + unsigned int tmpl_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) tmpl_len) || + (tmpl_len > MAX_TEMPLATES) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_TemplateEntry tmpl[GNUNET_NZL (tmpl_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_TemplateEntry *ie = &tmpl[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("template_id", + &ie->template_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } } + tgr->details.ok.templates_length = tmpl_len; + tgr->details.ok.templates = tmpl; + tgh->cb (tgh->cb_cls, + tgr); + tgh->cb = NULL; /* just to be sure */ } - tgr->details.ok.templates_length = tmpl_len; - tgr->details.ok.templates = tmpl; - tgh->cb (tgh->cb_cls, - tgr); - tgh->cb = NULL; /* just to be sure */ return GNUNET_OK; } diff --git a/src/lib/merchant_api_get_transfers.c b/src/lib/merchant_api_get_transfers.c index 66a11edb..6116a54f 100644 --- a/src/lib/merchant_api_get_transfers.c +++ b/src/lib/merchant_api_get_transfers.c @@ -116,7 +116,7 @@ handle_transfers_get_finished (void *cls, size_t tds_length; struct TALER_MERCHANT_TransferData *tds; json_t *transfer; - unsigned int i; + size_t i; bool ok; tds_length = json_array_size (transfers); diff --git a/src/lib/merchant_api_get_webhooks.c b/src/lib/merchant_api_get_webhooks.c index 521230e6..e702baac 100644 --- a/src/lib/merchant_api_get_webhooks.c +++ b/src/lib/merchant_api_get_webhooks.c @@ -32,6 +32,11 @@ /** + * Maximum number of webhooks we return. + */ +#define MAX_WEBHOOKS 1024 + +/** * Handle for a GET /webhooks operation. */ struct TALER_MERCHANT_WebhooksGetHandle @@ -77,33 +82,42 @@ parse_webhooks (const json_t *ia, struct TALER_MERCHANT_WebhooksGetResponse *wgr, struct TALER_MERCHANT_WebhooksGetHandle *wgh) { - unsigned int whook_len = json_array_size (ia); - struct TALER_MERCHANT_WebhookEntry whook[GNUNET_NZL (whook_len)]; - size_t index; - json_t *value; - - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_WebhookEntry *ie = &whook[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("webhook_id", - &ie->webhook_id), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; + unsigned int whook_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) whook_len) || + (whook_len > MAX_WEBHOOKS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_WebhookEntry whook[GNUNET_NZL (whook_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_WebhookEntry *ie = &whook[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("webhook_id", + &ie->webhook_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } } + wgr->details.ok.webhooks_length = whook_len; + wgr->details.ok.webhooks = whook; + wgh->cb (wgh->cb_cls, + wgr); + wgh->cb = NULL; /* just to be sure */ } - wgr->details.ok.webhooks_length = whook_len; - wgr->details.ok.webhooks = whook; - wgh->cb (wgh->cb_cls, - wgr); - wgh->cb = NULL; /* just to be sure */ return GNUNET_OK; } diff --git a/src/lib/merchant_api_merchant_get_order.c b/src/lib/merchant_api_merchant_get_order.c index afef8853..3bd4003b 100644 --- a/src/lib/merchant_api_merchant_get_order.c +++ b/src/lib/merchant_api_merchant_get_order.c @@ -34,6 +34,17 @@ /** + * Maximum number of refund details we return. + */ +#define MAX_REFUND_DETAILS 1024 + +/** + * Maximum number of wire details we return. + */ +#define MAX_WIRE_DETAILS 1024 + + +/** * @brief A GET /private/orders/$ORDER handle */ struct TALER_MERCHANT_OrderMerchantGetHandle @@ -191,6 +202,11 @@ handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, &wire_details), GNUNET_JSON_spec_array_const ("refund_details", &refund_details), + /* Only available since **v14** */ + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("last_payment", + &osr->details.ok.details.paid.last_payment), + NULL), GNUNET_JSON_spec_end () }; @@ -210,79 +226,102 @@ handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32; { - unsigned int wts_len = json_array_size (wire_details); - unsigned int ref_len = json_array_size (refund_details); - struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)]; - struct TALER_MERCHANT_RefundOrderDetail ref[GNUNET_NZL (ref_len)]; + unsigned int wts_len = (unsigned int) json_array_size (wire_details); + unsigned int ref_len = (unsigned int) json_array_size (refund_details); - for (unsigned int i = 0; i<wts_len; i++) + if ( (json_array_size (wire_details) != (size_t) wts_len) || + (wts_len > MAX_WIRE_DETAILS) ) + { + GNUNET_break (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + omgh->cb (omgh->cb_cls, + osr); + return; + } + if ( (json_array_size (refund_details) != (size_t) ref_len) || + (ref_len > MAX_REFUND_DETAILS) ) + { + GNUNET_break (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + omgh->cb (omgh->cb_cls, + osr); + return; + } { - struct TALER_MERCHANT_WireTransfer *wt = &wts[i]; - const json_t *w = json_array_get (wire_details, - i); - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_web_url ("exchange_url", - &wt->exchange_url), - GNUNET_JSON_spec_fixed_auto ("wtid", - &wt->wtid), - GNUNET_JSON_spec_timestamp ("execution_time", - &wt->execution_time), - TALER_JSON_spec_amount_any ("amount", - &wt->total_amount), - GNUNET_JSON_spec_bool ("confirmed", - &wt->confirmed), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (w, - ispec, - NULL, NULL)) + struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)]; + struct TALER_MERCHANT_RefundOrderDetail ref[GNUNET_NZL (ref_len)]; + + for (unsigned int i = 0; i<wts_len; i++) { - GNUNET_break_op (0); - osr->hr.http_status = 0; - osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - omgh->cb (omgh->cb_cls, - osr); - return; + struct TALER_MERCHANT_WireTransfer *wt = &wts[i]; + const json_t *w = json_array_get (wire_details, + i); + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_web_url ("exchange_url", + &wt->exchange_url), + GNUNET_JSON_spec_fixed_auto ("wtid", + &wt->wtid), + GNUNET_JSON_spec_timestamp ("execution_time", + &wt->execution_time), + TALER_JSON_spec_amount_any ("amount", + &wt->total_amount), + GNUNET_JSON_spec_bool ("confirmed", + &wt->confirmed), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (w, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + omgh->cb (omgh->cb_cls, + osr); + return; + } } - } - for (unsigned int i = 0; i<ref_len; i++) - { - struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i]; - const json_t *w = json_array_get (refund_details, - i); - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_amount_any ("amount", - &ro->refund_amount), - GNUNET_JSON_spec_string ("reason", - &ro->reason), - GNUNET_JSON_spec_timestamp ("timestamp", - &ro->refund_time), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (w, - ispec, - NULL, NULL)) + for (unsigned int i = 0; i<ref_len; i++) { - GNUNET_break_op (0); - osr->hr.http_status = 0; - osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - omgh->cb (omgh->cb_cls, - osr); - return; + struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i]; + const json_t *w = json_array_get (refund_details, + i); + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_amount_any ("amount", + &ro->refund_amount), + GNUNET_JSON_spec_string ("reason", + &ro->reason), + GNUNET_JSON_spec_timestamp ("timestamp", + &ro->refund_time), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (w, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + omgh->cb (omgh->cb_cls, + osr); + return; + } } - } - osr->details.ok.details.paid.wts = wts; - osr->details.ok.details.paid.wts_len = wts_len; - osr->details.ok.details.paid.refunds = ref; - osr->details.ok.details.paid.refunds_len = ref_len; - omgh->cb (omgh->cb_cls, - osr); + osr->details.ok.details.paid.wts = wts; + osr->details.ok.details.paid.wts_len = wts_len; + osr->details.ok.details.paid.refunds = ref; + osr->details.ok.details.paid.refunds_len = ref_len; + omgh->cb (omgh->cb_cls, + osr); + } } } diff --git a/src/lib/merchant_api_post_order_abort.c b/src/lib/merchant_api_post_order_abort.c index 82cca481..270ceb7e 100644 --- a/src/lib/merchant_api_post_order_abort.c +++ b/src/lib/merchant_api_post_order_abort.c @@ -39,6 +39,12 @@ /** + * Maximum number of refunds we return. + */ +#define MAX_REFUNDS 1024 + + +/** * @brief An abort Handle */ struct TALER_MERCHANT_OrderAbortHandle @@ -127,7 +133,14 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah, GNUNET_break_op (0); return GNUNET_SYSERR; } - num_refunds = json_array_size (refunds); + num_refunds = (unsigned int) json_array_size (refunds); + if ( (json_array_size (refunds) != (size_t) num_refunds) || + (num_refunds > MAX_REFUNDS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)]; diff --git a/src/lib/merchant_api_post_products.c b/src/lib/merchant_api_post_products.c index 2a5b9a9b..0f09f397 100644 --- a/src/lib/merchant_api_post_products.c +++ b/src/lib/merchant_api_post_products.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020-2021 Taler Systems SA + Copyright (C) 2020-2024 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 @@ -159,7 +159,7 @@ handle_post_products_finished (void *cls, struct TALER_MERCHANT_ProductsPostHandle * -TALER_MERCHANT_products_post ( +TALER_MERCHANT_products_post2 ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const char *product_id, @@ -172,6 +172,7 @@ TALER_MERCHANT_products_post ( int64_t total_stock, const json_t *address, struct GNUNET_TIME_Timestamp next_restock, + uint32_t minimum_age, TALER_MERCHANT_ProductsPostCallback cb, void *cb_cls) { @@ -183,20 +184,26 @@ TALER_MERCHANT_products_post ( product_id), GNUNET_JSON_pack_string ("description", description), - GNUNET_JSON_pack_object_incref ("description_i18n", - (json_t *) description_i18n), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + (json_t *) description_i18n)), GNUNET_JSON_pack_string ("unit", unit), TALER_JSON_pack_amount ("price", price), GNUNET_JSON_pack_string ("image", image), - GNUNET_JSON_pack_array_incref ("taxes", - (json_t *) taxes), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("taxes", + (json_t *) taxes)), GNUNET_JSON_pack_uint64 ("total_stock", total_stock), - GNUNET_JSON_pack_object_incref ("address", - (json_t *) address), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_uint64 ("minimum_age", + minimum_age)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("address", + (json_t *) address)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_timestamp ("next_restock", next_restock))); @@ -235,6 +242,41 @@ TALER_MERCHANT_products_post ( } +struct TALER_MERCHANT_ProductsPostHandle * +TALER_MERCHANT_products_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *product_id, + const char *description, + const json_t *description_i18n, + const char *unit, + const struct TALER_Amount *price, + const char *image, + const json_t *taxes, + int64_t total_stock, + const json_t *address, + struct GNUNET_TIME_Timestamp next_restock, + TALER_MERCHANT_ProductsPostCallback cb, + void *cb_cls) +{ + return TALER_MERCHANT_products_post2 (ctx, + backend_url, + product_id, + description, + description_i18n, + unit, + price, + image, + taxes, + total_stock, + address, + next_restock, + 0, + cb, + cb_cls); +} + + void TALER_MERCHANT_products_post_cancel ( struct TALER_MERCHANT_ProductsPostHandle *pph) diff --git a/src/lib/merchant_api_wallet_post_order_refund.c b/src/lib/merchant_api_wallet_post_order_refund.c index 405231ef..e72982f3 100644 --- a/src/lib/merchant_api_wallet_post_order_refund.c +++ b/src/lib/merchant_api_wallet_post_order_refund.c @@ -32,6 +32,10 @@ #include <taler/taler_signatures.h> #include <taler/taler_curl_lib.h> +/** + * Maximum number of refunds we return. + */ +#define MAX_REFUNDS 1024 /** * Handle for a (public) POST /orders/ID/refund operation. @@ -123,6 +127,14 @@ handle_refund_finished (void *cls, break; } refund_len = json_array_size (refunds); + if ( (json_array_size (refunds) != (size_t) refund_len) || + (refund_len > MAX_REFUNDS) ) + { + GNUNET_break (0); + wrr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + wrr.hr.http_status = 0; + break; + } { struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (refund_len)]; diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c index 1bb9f472..6ef40b45 100644 --- a/src/testing/test_kyc_api.c +++ b/src/testing/test_kyc_api.c @@ -138,211 +138,250 @@ run (void *cls, /** * Move money to the exchange's bank account. */ - cmd_transfer_to_exchange ("create-reserve-1", - "EUR:10.02"), + cmd_transfer_to_exchange ( + "create-reserve-1", + "EUR:10.02"), /** * Make a reserve exist, according to the previous transfer. */ - TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1", - CONFIG_FILE), - TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2", - "EUR:10.02", - payer_payto, - exchange_payto, - "create-reserve-1"), - TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", - "create-reserve-1", - "EUR:5", - 0, - MHD_HTTP_OK), - TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", - "create-reserve-1", - "EUR:5", - 0, - MHD_HTTP_OK), - TALER_TESTING_cmd_merchant_get_orders ("get-orders-empty", - merchant_url, - MHD_HTTP_OK, - NULL), + TALER_TESTING_cmd_exec_wirewatch ( + "wirewatch-1", + CONFIG_FILE), + TALER_TESTING_cmd_check_bank_admin_transfer ( + "check_bank_transfer-2", + "EUR:10.02", + payer_payto, + exchange_payto, + "create-reserve-1"), + TALER_TESTING_cmd_withdraw_amount ( + "withdraw-coin-1", + "create-reserve-1", + "EUR:5", + 0, + MHD_HTTP_OK), + TALER_TESTING_cmd_withdraw_amount ( + "withdraw-coin-2", + "create-reserve-1", + "EUR:5", + 0, + MHD_HTTP_OK), + TALER_TESTING_cmd_merchant_get_orders ( + "get-orders-empty", + merchant_url, + MHD_HTTP_OK, + NULL), /** * Check the reserve is depleted. */ - TALER_TESTING_cmd_status ("withdraw-status-1", - "create-reserve-1", - "EUR:0", - MHD_HTTP_OK), - TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-1", - cred.cfg, - merchant_url, - MHD_HTTP_OK, - "1", /* order ID */ - GNUNET_TIME_UNIT_ZERO_TS, - GNUNET_TIME_UNIT_FOREVER_TS, - true, - "EUR:5.0", - "x-taler-bank", - "", - "", - NULL), - TALER_TESTING_cmd_merchant_claim_order ("reclaim-1", - merchant_url, - MHD_HTTP_OK, - "create-proposal-1", - NULL), - TALER_TESTING_cmd_merchant_pay_order ("deposit-simple", - merchant_url, - MHD_HTTP_OK, - "create-proposal-1", - "withdraw-coin-1", - "EUR:5", - "EUR:4.99", - "session-0"), - TALER_TESTING_cmd_merchant_post_orders_paid ("verify-order-1-paid", - merchant_url, - "deposit-simple", - "session-1", - MHD_HTTP_OK), - TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-1"), - CMD_EXEC_AGGREGATOR ("run-aggregator"), + TALER_TESTING_cmd_status ( + "withdraw-status-1", + "create-reserve-1", + "EUR:0", + MHD_HTTP_OK), + TALER_TESTING_cmd_merchant_post_orders2 ( + "create-proposal-1", + cred.cfg, + merchant_url, + MHD_HTTP_OK, + "1", /* order ID */ + GNUNET_TIME_UNIT_ZERO_TS, + GNUNET_TIME_UNIT_FOREVER_TS, + true, + "EUR:5.0", + "x-taler-bank", + "", + "", + NULL), + TALER_TESTING_cmd_merchant_claim_order ( + "reclaim-1", + merchant_url, + MHD_HTTP_OK, + "create-proposal-1", + NULL), + TALER_TESTING_cmd_merchant_pay_order ( + "deposit-simple", + merchant_url, + MHD_HTTP_OK, + "create-proposal-1", + "withdraw-coin-1", + "EUR:5", + "EUR:4.99", + "session-0"), + TALER_TESTING_cmd_merchant_post_orders_paid ( + "verify-order-1-paid", + merchant_url, + "deposit-simple", + "session-1", + MHD_HTTP_OK), + TALER_TESTING_cmd_check_bank_empty ( + "check_bank_empty-1"), + CMD_EXEC_AGGREGATOR ( + "run-aggregator"), /* KYC: hence nothing happened at the bank yet: */ - TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"), + TALER_TESTING_cmd_check_bank_empty ( + "check_bank_empty-2"), /* KYC: we don't even know the legitimization UUID yet */ - TALER_TESTING_cmd_merchant_kyc_get ("kyc-pending-early", - merchant_url, - NULL, - NULL, - EXCHANGE_URL, - MHD_HTTP_NO_CONTENT, - TALER_AML_NORMAL), + TALER_TESTING_cmd_merchant_kyc_get ( + "kyc-pending-early", + merchant_url, + NULL, + NULL, + EXCHANGE_URL, + MHD_HTTP_NO_CONTENT, + TALER_AML_NORMAL), /* now we get the legi UUID by running taler-merchant-depositcheck */ - TALER_TESTING_cmd_depositcheck ("deposit-check", - CONFIG_FILE), + TALER_TESTING_cmd_depositcheck ( + "deposit-check", + CONFIG_FILE), /* Now we should get a status of pending */ - TALER_TESTING_cmd_merchant_kyc_get ("kyc-pending", - merchant_url, - NULL, - NULL, - EXCHANGE_URL, - MHD_HTTP_ACCEPTED, - TALER_AML_NORMAL), - TALER_TESTING_cmd_proof_kyc_oauth2 ("kyc-do", - "kyc-pending", - "kyc-provider-test-oauth2", - "pass", - MHD_HTTP_SEE_OTHER), + TALER_TESTING_cmd_merchant_kyc_get ( + "kyc-pending", + merchant_url, + NULL, + NULL, + EXCHANGE_URL, + MHD_HTTP_ACCEPTED, + TALER_AML_NORMAL), + TALER_TESTING_cmd_proof_kyc_oauth2 ( + "kyc-do", + "kyc-pending", + "kyc-provider-test-oauth2", + "pass", + MHD_HTTP_SEE_OTHER), CMD_EXEC_AGGREGATOR ("run-aggregator"), - TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-498c", - EXCHANGE_URL, - "EUR:4.98", - exchange_payto, - merchant_payto), - TALER_TESTING_cmd_merchant_post_transfer ("post-transfer-1", - &cred.ba, - merchant_payto, - merchant_url, - "EUR:4.98", - MHD_HTTP_NO_CONTENT, - "deposit-simple", - NULL), - TALER_TESTING_cmd_run_tme ("run taler-merchant-exchange-1", - CONFIG_FILE), - TALER_TESTING_cmd_merchant_get_transfers ("get-transfers-1", - merchant_url, - merchant_payto, - MHD_HTTP_OK, - "post-transfer-1", - NULL), - TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-3"), + TALER_TESTING_cmd_check_bank_transfer ( + "check_bank_transfer-498c", + EXCHANGE_URL, + "EUR:4.98", + exchange_payto, + merchant_payto), + TALER_TESTING_cmd_merchant_post_transfer ( + "post-transfer-1", + &cred.ba, + merchant_payto, + merchant_url, + "EUR:4.98", + MHD_HTTP_NO_CONTENT, + "deposit-simple", + NULL), + TALER_TESTING_cmd_run_tme ( + "run taler-merchant-exchange-1", + CONFIG_FILE), + TALER_TESTING_cmd_merchant_get_transfers ( + "get-transfers-1", + merchant_url, + merchant_payto, + MHD_HTTP_OK, + "post-transfer-1", + NULL), + TALER_TESTING_cmd_check_bank_empty ( + "check_bank_empty-3"), TALER_TESTING_cmd_end () }; struct TALER_TESTING_Command aml[] = { - TALER_TESTING_cmd_set_officer ("aml-officer", - NULL, - "Ernest&Young", - true, - false), - cmd_transfer_to_exchange ("create-reserve-big", - "EUR:100.02"), - TALER_TESTING_cmd_exec_wirewatch ("wirewatch-big", - CONFIG_FILE), - TALER_TESTING_cmd_take_aml_decision ("freeze", - "aml-officer", - "post-transfer-1", - "EUR:1", - "suspicious", - TALER_AML_FROZEN, - NULL, - MHD_HTTP_NO_CONTENT), - TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-big", - "EUR:100.02", - payer_payto, - exchange_payto, - "create-reserve-big"), - TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aml", - "create-reserve-big", - "EUR:5", - 0, - MHD_HTTP_OK), - TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-aml", - cred.cfg, - merchant_url, - MHD_HTTP_OK, - "10-aml", /* order ID */ - GNUNET_TIME_UNIT_ZERO_TS, - GNUNET_TIME_UNIT_FOREVER_TS, - true, - "EUR:5.0", - "x-taler-bank", - "", - "", - NULL), - TALER_TESTING_cmd_merchant_claim_order ("reclaim-aml", - merchant_url, - MHD_HTTP_OK, - "create-proposal-aml", - NULL), - TALER_TESTING_cmd_merchant_pay_order ("deposit-simple", - merchant_url, - MHD_HTTP_OK, - "create-proposal-aml", - "withdraw-coin-aml", - "EUR:5", - "EUR:4.99", - "session-aml"), - TALER_TESTING_cmd_merchant_post_orders_paid ("verify-order-aml-paid", - merchant_url, - "deposit-simple", - "session-aml", - MHD_HTTP_OK), - TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-aml-1"), + TALER_TESTING_cmd_set_officer ( + "aml-officer", + NULL, + "Ernest&Young", + true, + false), + cmd_transfer_to_exchange ( + "create-reserve-big", + "EUR:100.02"), + TALER_TESTING_cmd_exec_wirewatch ( + "wirewatch-big", + CONFIG_FILE), + TALER_TESTING_cmd_take_aml_decision ( + "freeze", + "aml-officer", + "post-transfer-1", + "EUR:1", + "suspicious", + TALER_AML_FROZEN, + NULL, + MHD_HTTP_NO_CONTENT), + TALER_TESTING_cmd_check_bank_admin_transfer ( + "check_bank_transfer-big", + "EUR:100.02", + payer_payto, + exchange_payto, + "create-reserve-big"), + TALER_TESTING_cmd_withdraw_amount ( + "withdraw-coin-aml", + "create-reserve-big", + "EUR:5", + 0, + MHD_HTTP_OK), + TALER_TESTING_cmd_merchant_post_orders2 ( + "create-proposal-aml", + cred.cfg, + merchant_url, + MHD_HTTP_OK, + "10-aml", /* order ID */ + GNUNET_TIME_UNIT_ZERO_TS, + GNUNET_TIME_UNIT_FOREVER_TS, + true, + "EUR:5.0", + "x-taler-bank", + "", + "", + NULL), + TALER_TESTING_cmd_merchant_claim_order ( + "reclaim-aml", + merchant_url, + MHD_HTTP_OK, + "create-proposal-aml", + NULL), + TALER_TESTING_cmd_merchant_pay_order ( + "deposit-simple", + merchant_url, + MHD_HTTP_OK, + "create-proposal-aml", + "withdraw-coin-aml", + "EUR:5", + "EUR:4.99", + "session-aml"), + TALER_TESTING_cmd_merchant_post_orders_paid ( + "verify-order-aml-paid", + merchant_url, + "deposit-simple", + "session-aml", + MHD_HTTP_OK), + TALER_TESTING_cmd_check_bank_empty ( + "check_bank_empty-aml-1"), CMD_EXEC_AGGREGATOR ("run-aggregator-aml-frozen"), /* AML-frozen: hence nothing happened at the bank yet: */ - TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-aml-2"), + TALER_TESTING_cmd_check_bank_empty ( + "check_bank_empty-aml-2"), /* Now we should get a status of frozen */ - TALER_TESTING_cmd_merchant_kyc_get ("aml-frozen", - merchant_url, - NULL, /* no instance ID */ - NULL, /* no wire ref */ - EXCHANGE_URL, - MHD_HTTP_ACCEPTED, - TALER_AML_FROZEN), - TALER_TESTING_cmd_sleep ("sleep to de-collide AML timestamps", - 1), - TALER_TESTING_cmd_take_aml_decision ("unfreeze", - "aml-officer", - "post-transfer-1", - "EUR:100", - "fine", - TALER_AML_NORMAL, - NULL, - MHD_HTTP_NO_CONTENT), - TALER_TESTING_cmd_merchant_kyc_get ("aml-unfrozen", - merchant_url, - NULL, /* no instance ID */ - NULL, /* no wire ref */ - EXCHANGE_URL, - MHD_HTTP_NO_CONTENT, - TALER_AML_NORMAL), + TALER_TESTING_cmd_merchant_kyc_get ( + "aml-frozen", + merchant_url, + NULL, /* no instance ID */ + NULL, /* no wire ref */ + EXCHANGE_URL, + MHD_HTTP_ACCEPTED, + TALER_AML_FROZEN), + TALER_TESTING_cmd_sleep ( + "sleep to de-collide AML timestamps", + 1), + TALER_TESTING_cmd_take_aml_decision ( + "unfreeze", + "aml-officer", + "post-transfer-1", + "EUR:100", + "fine", + TALER_AML_NORMAL, + NULL, + MHD_HTTP_NO_CONTENT), + TALER_TESTING_cmd_merchant_kyc_get ( + "aml-unfrozen", + merchant_url, + NULL, /* no instance ID */ + NULL, /* no wire ref */ + EXCHANGE_URL, + MHD_HTTP_NO_CONTENT, + TALER_AML_NORMAL), CMD_EXEC_AGGREGATOR ("run-aggregator-aml-normal"), TALER_TESTING_cmd_check_bank_transfer ( "check_bank_transfer-498c-post-unfreeze", @@ -350,47 +389,55 @@ run (void *cls, "EUR:4.98", exchange_payto, merchant_payto), - TALER_TESTING_cmd_merchant_post_transfer ("post-transfer-aml", - &cred.ba, - merchant_payto, - merchant_url, - "EUR:4.98", - MHD_HTTP_NO_CONTENT, - "deposit-simple", - NULL), - TALER_TESTING_cmd_run_tme ("run taler-merchant-exchange-2-aml", - CONFIG_FILE), - TALER_TESTING_cmd_merchant_get_transfers ("get-transfers-aml", - merchant_url, - merchant_payto, - MHD_HTTP_OK, - "post-transfer-1", - "post-transfer-aml", - NULL), + TALER_TESTING_cmd_merchant_post_transfer ( + "post-transfer-aml", + &cred.ba, + merchant_payto, + merchant_url, + "EUR:4.98", + MHD_HTTP_NO_CONTENT, + "deposit-simple", + NULL), + TALER_TESTING_cmd_run_tme ( + "run taler-merchant-exchange-2-aml", + CONFIG_FILE), + TALER_TESTING_cmd_merchant_get_transfers ( + "get-transfers-aml", + merchant_url, + merchant_payto, + MHD_HTTP_OK, + "post-transfer-1", + "post-transfer-aml", + NULL), TALER_TESTING_cmd_end () }; /* end of aml batch */ struct TALER_TESTING_Command commands[] = { /* general setup */ - TALER_TESTING_cmd_run_fakebank ("run-fakebank", - cred.cfg, - "exchange-account-exchange"), - TALER_TESTING_cmd_system_start ("start-taler", - CONFIG_FILE, - "-ema", - "-u", "exchange-account-exchange", - NULL), - TALER_TESTING_cmd_get_exchange ("get-exchange", - cred.cfg, - NULL, - true, - true), - TALER_TESTING_cmd_oauth ("start-oauth-service", - 6666), - TALER_TESTING_cmd_merchant_post_instances ("instance-create-default-setup", - merchant_url, - "default", - MHD_HTTP_NO_CONTENT), + TALER_TESTING_cmd_run_fakebank ( + "run-fakebank", + cred.cfg, + "exchange-account-exchange"), + TALER_TESTING_cmd_system_start ( + "start-taler", + CONFIG_FILE, + "-ema", + "-u", "exchange-account-exchange", + NULL), + TALER_TESTING_cmd_get_exchange ( + "get-exchange", + cred.cfg, + NULL, + true, + true), + TALER_TESTING_cmd_oauth ( + "start-oauth-service", + 6666), + TALER_TESTING_cmd_merchant_post_instances ( + "instance-create-default-setup", + merchant_url, + "default", + MHD_HTTP_NO_CONTENT), TALER_TESTING_cmd_merchant_post_account ( "instance-create-default-account", merchant_url, @@ -437,4 +484,4 @@ main (int argc, } -/* end of test_merchant_api.c */ +/* end of test_kyc_api.c */ diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c index e9b492b3..ed07bce6 100644 --- a/src/testing/test_merchant_api.c +++ b/src/testing/test_merchant_api.c @@ -599,6 +599,20 @@ run (void *cls, "a product", "EUR:1", MHD_HTTP_NO_CONTENT), + TALER_TESTING_cmd_merchant_post_products2 ("post-products-p4", + merchant_url, + "product-4age", + "an age-restricted product", + NULL, + "unit", + "EUR:1", + "", /* image */ + NULL, + 4, + 16 /* minimum age */, + NULL, + GNUNET_TIME_UNIT_FOREVER_TS, + MHD_HTTP_NO_CONTENT), TALER_TESTING_cmd_merchant_patch_product ("patch-products-p3", merchant_url, "product-3", @@ -673,6 +687,25 @@ run (void *cls, "product-3/3", "lock-product-p3", NULL), + TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p4-age", + cred.cfg, + merchant_url, + MHD_HTTP_OK, + "order-p4-age", + GNUNET_TIME_UNIT_ZERO_TS, + GNUNET_TIME_UNIT_FOREVER_TS, + false, + "EUR:5.0", + "x-taler-bank", + "product-4age", + "", /* locks */ + NULL), + TALER_TESTING_cmd_merchant_get_order4 ("get-order-merchant-p4-age", + merchant_url, + "create-proposal-p4-age", + TALER_MERCHANT_OSC_CLAIMED, + 16, + MHD_HTTP_OK), TALER_TESTING_cmd_merchant_delete_order ("delete-order-paid", merchant_url, "1", diff --git a/src/testing/testing_api_cmd_merchant_get_order.c b/src/testing/testing_api_cmd_merchant_get_order.c index 1b235c93..6301c9f6 100644 --- a/src/testing/testing_api_cmd_merchant_get_order.c +++ b/src/testing/testing_api_cmd_merchant_get_order.c @@ -106,6 +106,11 @@ struct MerchantGetOrderState const char *repurchase_order_ref; /** + * Expected minimum age. + */ + unsigned int expected_min_age; + + /** * True if we should pass the 'allow_refunded_for_repurchase' flag. */ bool allow_refunded_for_repurchase; @@ -176,7 +181,9 @@ merchant_get_order_cb ( if (gos->osc != osr->details.ok.status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Order paid does not match\n"); + "Order paid does not match: %d vs %d\n", + gos->osc, + osr->details.ok.status); TALER_TESTING_interpreter_fail (gos->is); return; } @@ -187,6 +194,17 @@ merchant_get_order_cb ( const struct TALER_TESTING_Command *order_cmd; struct TALER_Amount refunded_total; + if ( (0 != gos->expected_min_age) && + (gos->expected_min_age != + json_integer_value ( + json_object_get ( + osr->details.ok.details.paid.contract_terms, + "minimum_age"))) ) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (gos->is); + return; + } order_cmd = TALER_TESTING_interpreter_lookup_command ( gos->is, gos->order_reference); @@ -422,6 +440,17 @@ merchant_get_order_cb ( break; case TALER_MERCHANT_OSC_CLAIMED: /* FIXME: Check contract terms... */ + if ( (0 != gos->expected_min_age) && + (gos->expected_min_age != + json_integer_value ( + json_object_get ( + osr->details.ok.details.claimed.contract_terms, + "minimum_age"))) ) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (gos->is); + return; + } break; case TALER_MERCHANT_OSC_UNPAID: { @@ -754,6 +783,36 @@ TALER_TESTING_cmd_merchant_get_order3 ( } +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_get_order4 ( + const char *label, + const char *merchant_url, + const char *order_reference, + enum TALER_MERCHANT_OrderStatusCode osc, + uint32_t expected_min_age, + unsigned int expected_http_status) +{ + struct MerchantGetOrderState *gos; + + gos = GNUNET_new (struct MerchantGetOrderState); + gos->merchant_url = merchant_url; + gos->order_reference = order_reference; + gos->osc = osc; + gos->expected_min_age = expected_min_age; + gos->http_status = expected_http_status; + { + struct TALER_TESTING_Command cmd = { + .cls = gos, + .label = label, + .run = &merchant_get_order_run, + .cleanup = &merchant_get_order_cleanup + }; + + return cmd; + } +} + + struct MerchantPollOrderConcludeState { /** diff --git a/src/testing/testing_api_cmd_post_orders.c b/src/testing/testing_api_cmd_post_orders.c index 5688d174..d5cfdddc 100644 --- a/src/testing/testing_api_cmd_post_orders.c +++ b/src/testing/testing_api_cmd_post_orders.c @@ -536,19 +536,20 @@ orders_run2 (void *cls, locks_length, uuid); } - ps->po = TALER_MERCHANT_orders_post2 (TALER_TESTING_interpreter_get_context ( - is), - ps->merchant_url, - order, - GNUNET_TIME_UNIT_ZERO, - ps->payment_target, - products_length, - products, - locks_length, - locks, - ps->make_claim_token, - &order_cb, - ps); + ps->po = TALER_MERCHANT_orders_post2 ( + TALER_TESTING_interpreter_get_context ( + is), + ps->merchant_url, + order, + GNUNET_TIME_UNIT_ZERO, + ps->payment_target, + products_length, + products, + locks_length, + locks, + ps->make_claim_token, + &order_cb, + ps); GNUNET_free (products_string); GNUNET_free (locks_string); GNUNET_array_grow (products, diff --git a/src/testing/testing_api_cmd_post_products.c b/src/testing/testing_api_cmd_post_products.c index e98ea3c5..4ffafddc 100644 --- a/src/testing/testing_api_cmd_post_products.c +++ b/src/testing/testing_api_cmd_post_products.c @@ -95,6 +95,11 @@ struct PostProductsState json_t *address; /** + * Minimum age requirement to use for the product. + */ + unsigned int minimum_age; + + /** * when the next restocking is expected to happen, 0 for unknown, */ struct GNUNET_TIME_Timestamp next_restock; @@ -168,7 +173,7 @@ post_products_run (void *cls, struct PostProductsState *pis = cls; pis->is = is; - pis->iph = TALER_MERCHANT_products_post ( + pis->iph = TALER_MERCHANT_products_post2 ( TALER_TESTING_interpreter_get_context (is), pis->merchant_url, pis->product_id, @@ -181,6 +186,7 @@ post_products_run (void *cls, pis->total_stock, pis->address, pis->next_restock, + pis->minimum_age, &post_products_cb, pis); GNUNET_assert (NULL != pis->iph); @@ -197,7 +203,7 @@ post_products_run (void *cls, * @param index index number of the object to extract. * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue post_products_traits (void *cls, const void **ret, const char *trait, @@ -265,6 +271,7 @@ TALER_TESTING_cmd_merchant_post_products2 ( const char *image, json_t *taxes, int64_t total_stock, + uint32_t minimum_age, json_t *address, struct GNUNET_TIME_Timestamp next_restock, unsigned int http_status) @@ -288,6 +295,7 @@ TALER_TESTING_cmd_merchant_post_products2 ( pis->image = GNUNET_strdup (image); pis->taxes = taxes; /* ownership taken */ pis->total_stock = total_stock; + pis->minimum_age = minimum_age; pis->address = address; /* ownership taken */ pis->next_restock = next_restock; { @@ -305,12 +313,13 @@ TALER_TESTING_cmd_merchant_post_products2 ( struct TALER_TESTING_Command -TALER_TESTING_cmd_merchant_post_products (const char *label, - const char *merchant_url, - const char *product_id, - const char *description, - const char *price, - unsigned int http_status) +TALER_TESTING_cmd_merchant_post_products ( + const char *label, + const char *merchant_url, + const char *product_id, + const char *description, + const char *price, + unsigned int http_status) { return TALER_TESTING_cmd_merchant_post_products2 ( label, @@ -322,7 +331,8 @@ TALER_TESTING_cmd_merchant_post_products (const char *label, price, "", json_array (), - 4, + 4, /* total stock */ + 0, /* minimum age */ json_pack ("{s:s}", "street", "my street"), GNUNET_TIME_UNIT_ZERO_TS, http_status); |