aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac6
-rwxr-xr-xcontrib/ci/jobs/0-codespell/job.sh1
m---------contrib/wallet-core0
-rw-r--r--debian/changelog10
-rwxr-xr-xdebian/rules5
-rw-r--r--debian/taler-merchant.taler-merchant-depositcheck.service1
-rw-r--r--debian/taler-merchant.taler-merchant-exchange.service1
-rw-r--r--debian/taler-merchant.taler-merchant-httpd.service1
-rw-r--r--debian/taler-merchant.taler-merchant-webhook.service1
-rw-r--r--debian/taler-merchant.taler-merchant-wirewatch.service2
-rw-r--r--debian/taler-merchant.taler-merchant.slice7
-rw-r--r--src/backend/taler-merchant-depositcheck.c10
-rw-r--r--src/backend/taler-merchant-exchange.c55
-rw-r--r--src/backend/taler-merchant-httpd_config.c32
-rw-r--r--src/backend/taler-merchant-httpd_get-templates-ID.c15
-rw-r--r--src/backend/taler-merchant-httpd_helper.c4
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-abort.c18
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c371
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-refund.c8
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders-ID.c125
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders.c119
-rw-r--r--src/backend/taler-merchant-httpd_private-get-products.c20
-rw-r--r--src/backend/taler-merchant-httpd_private-get-templates-ID.c13
-rw-r--r--src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c7
-rw-r--r--src/backend/taler-merchant-httpd_private-get-transfers.c63
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-templates-ID.c60
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c3
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c440
-rw-r--r--src/backend/taler-merchant-httpd_private-post-templates.c73
-rw-r--r--src/backend/taler-merchant-httpd_private-post-token-families.c3
-rw-r--r--src/backend/taler-merchant-wirewatch.c5
-rw-r--r--src/backenddb/merchant-0005.sql13
-rw-r--r--src/backenddb/merchantdb_helper.c3
-rw-r--r--src/backenddb/pg_insert_deposit_confirmation.c8
-rw-r--r--src/backenddb/pg_insert_template.c11
-rw-r--r--src/backenddb/pg_insert_transfer_details.sql2
-rw-r--r--src/backenddb/pg_lookup_deposits_by_contract_and_coin.c148
-rw-r--r--src/backenddb/pg_lookup_deposits_by_order.c7
-rw-r--r--src/backenddb/pg_lookup_pending_deposits.c6
-rw-r--r--src/backenddb/pg_lookup_products.c44
-rw-r--r--src/backenddb/pg_lookup_products.h5
-rw-r--r--src/backenddb/pg_lookup_refunds.h12
-rw-r--r--src/backenddb/pg_lookup_refunds_detailed.h11
-rw-r--r--src/backenddb/pg_lookup_template.c17
-rw-r--r--src/backenddb/pg_lookup_templates.c4
-rw-r--r--src/backenddb/pg_update_template.c23
-rw-r--r--src/backenddb/test_merchantdb.c122
-rw-r--r--src/include/taler_merchant_service.h58
-rw-r--r--src/include/taler_merchant_testing_lib.h25
-rw-r--r--src/include/taler_merchantdb_plugin.h25
-rw-r--r--src/lib/merchant_api_get_accounts.c75
-rw-r--r--src/lib/merchant_api_get_config.c71
-rw-r--r--src/lib/merchant_api_get_instances.c112
-rw-r--r--src/lib/merchant_api_get_kyc.c141
-rw-r--r--src/lib/merchant_api_get_orders.c89
-rw-r--r--src/lib/merchant_api_get_otp_devices.c66
-rw-r--r--src/lib/merchant_api_get_products.c93
-rw-r--r--src/lib/merchant_api_get_templates.c65
-rw-r--r--src/lib/merchant_api_get_transfers.c2
-rw-r--r--src/lib/merchant_api_get_webhooks.c64
-rw-r--r--src/lib/merchant_api_merchant_get_order.c169
-rw-r--r--src/lib/merchant_api_post_order_abort.c15
-rw-r--r--src/lib/merchant_api_post_products.c58
-rw-r--r--src/lib/merchant_api_wallet_post_order_refund.c12
-rw-r--r--src/testing/test_kyc_api.c497
-rw-r--r--src/testing/test_merchant_api.c33
-rw-r--r--src/testing/testing_api_cmd_merchant_get_order.c61
-rw-r--r--src/testing/testing_api_cmd_post_orders.c27
-rw-r--r--src/testing/testing_api_cmd_post_products.c28
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);