summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--ChangeLog72
-rwxr-xr-xbootstrap2
-rw-r--r--configure.ac14
-rwxr-xr-xcontrib/ci/jobs/0-codespell/job.sh1
-rwxr-xr-xcontrib/ci/jobs/4-deb-package/version.sh2
m---------contrib/wallet-core0
-rw-r--r--debian/changelog16
-rw-r--r--debian/control6
-rw-r--r--doc/doxygen/taler.doxy2
m---------doc/prebuilt0
-rw-r--r--src/backend/Makefile.am1
-rw-r--r--src/backend/taler-merchant-depositcheck.c8
-rw-r--r--src/backend/taler-merchant-exchange.c53
-rw-r--r--src/backend/taler-merchant-httpd_config.c38
-rw-r--r--src/backend/taler-merchant-httpd_contract.c47
-rw-r--r--src/backend/taler-merchant-httpd_contract.h592
-rw-r--r--src/backend/taler-merchant-httpd_mhd.c1
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c4
-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.c12
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c717
-rw-r--r--src/backenddb/Makefile.am2
-rw-r--r--src/backenddb/pg_insert_token_family.h2
-rw-r--r--src/backenddb/pg_insert_token_family_key.c97
-rw-r--r--src/backenddb/pg_insert_token_family_key.h46
-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_refunds.h12
-rw-r--r--src/backenddb/pg_lookup_refunds_detailed.h11
-rw-r--r--src/backenddb/pg_lookup_token_family.c4
-rw-r--r--src/backenddb/pg_lookup_token_family_key.c161
-rw-r--r--src/backenddb/pg_lookup_token_family_key.h50
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c7
-rw-r--r--src/backenddb/test_merchantdb.c91
-rw-r--r--src/include/taler_merchant_service.h176
-rw-r--r--src/include/taler_merchant_testing_lib.h69
-rw-r--r--src/include/taler_merchantdb_plugin.h63
-rw-r--r--src/lib/Makefile.am1
-rw-r--r--src/lib/merchant_api_get_config.c4
-rw-r--r--src/lib/merchant_api_get_tokenfamily.c210
-rw-r--r--src/lib/merchant_api_merchant_get_order.c5
-rw-r--r--src/lib/merchant_api_post_tokenfamilies.c246
-rw-r--r--src/testing/Makefile.am1
-rw-r--r--src/testing/test_merchant_api.c52
-rwxr-xr-xsrc/testing/test_merchant_order_creation.sh70
-rw-r--r--src/testing/testing_api_cmd_post_orders.c169
-rw-r--r--src/testing/testing_api_cmd_post_products.c2
-rw-r--r--src/testing/testing_api_cmd_post_tokenfamilies.c272
51 files changed, 3479 insertions, 224 deletions
diff --git a/.gitignore b/.gitignore
index c11acb71..053dd776 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,3 +84,5 @@ doc/mdate-sh
doc/texinfo.tex
.private-key
src/merchant-tools/taler-merchant-passwd
+/.cache
+/compile_commands.json
diff --git a/ChangeLog b/ChangeLog
index 8c43a1f4..6bc01461 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+Fri Apr 12 10:43:41 AM CEST 2024
+ Releasing taler-merchant 0.10.2. -CG
+
+Wed Nov 29 09:06:49 AM JST 2023
+ Creating bugfix release taler-merchant 0.9.3a. -CG
+
Wed Nov 29 09:06:49 AM JST 2023
Creating bugfix release taler-merchant 0.9.3a. -CG
@@ -6,61 +12,61 @@ Sat 28 Aug 2021 05:27:02 PM CEST
payment/refund (#6995). -CG/BP
Used database-driven long-polling on all long-pollers
for multi-process deployments. (#6956) -CG
- Releasing taler-merchant 0.8.4. -CG
+ Releasing taler-merchant 0.8.4. -CG
Tue 24 Aug 2021 03:08:53 PM CEST
- Releasing taler-merchant 0.8.3. -CG
+ Releasing taler-merchant 0.8.3. -CG
Tue 17 Aug 2021 03:08:53 PM CEST
- Releasing taler-merchant 0.8.2. -CG
+ Releasing taler-merchant 0.8.2. -CG
Thu 22 Apr 2021 11:30:13 PM CEST
Integrate new single-page app. -CG/SM
Tue 10 Nov 2020 01:08:46 PM CET
- Major revamp of the entire API (now 1:0:0), changing the names of
- many endpoints for better consistency.
- Adding support for (optional) inventory management.
- Releasing taler-merchant 0.8.0. -CG/JB
+ Major revamp of the entire API (now 1:0:0), changing the names of
+ many endpoints for better consistency.
+ Adding support for (optional) inventory management.
+ Releasing taler-merchant 0.8.0. -CG/JB
Sun 12 Apr 2020 08:45:11 PM CEST
- Changed /tip-pickup API to withdraw directly from the exchange
- and return blind signatures, instead of having the wallet do it (#6173). -CG
+ Changed /tip-pickup API to withdraw directly from the exchange
+ and return blind signatures, instead of having the wallet do it (#6173). -CG
Fri 10 Apr 2020 09:01:22 PM CEST
- Changing refund API to have the merchant backend make the /refund
- request to the exchange instead of having the wallet do it (#5299). -CG
+ Changing refund API to have the merchant backend make the /refund
+ request to the exchange instead of having the wallet do it (#5299). -CG
Tue 31 Mar 2020 04:17:58 PM CEST
- Releasing taler-merchant 0.7.0. -CG
+ Releasing taler-merchant 0.7.0. -CG
Tue 24 Dec 2019 11:01:21 PM CET
- Releasing taler-merchant 0.6.0. -CG
+ Releasing taler-merchant 0.6.0. -CG
Sat 17 Aug 2019 10:03:38 PM CEST
- Remove "currency" field from exchange database, as we only
- support one currency per merchant backend anyway. -CG
+ Remove "currency" field from exchange database, as we only
+ support one currency per merchant backend anyway. -CG
Wed Apr 4 00:19:38 CEST 2018
- Releasing taler-merchant-0.5.0 -FD
+ Releasing taler-merchant-0.5.0 -FD
Mon Jan 22 21:54:42 CET 2018
- Address #5262. -CG
+ Address #5262. -CG
Tue Jan 2 00:27:29 2018
- Implement #5158 (proper handling of aborted payments). -CG
+ Implement #5158 (proper handling of aborted payments). -CG
Wed Dec 27 11:21:43 2017
- Complete logic to allow /pay to span coins from multiple exchanges. -CG
+ Complete logic to allow /pay to span coins from multiple exchanges. -CG
Wed Dec 13 21:50:59 2017
- Use new wire transfer logic in payments generator. -CG
+ Use new wire transfer logic in payments generator. -CG
Thu Dec 7 07:42:40 2017
- Implemented new tipping feature (now with private keys in files). -CG
+ Implemented new tipping feature (now with private keys in files). -CG
Wed Oct 18 15:33:23 CEST 2017
- Releasing taler-merchant 0.4.0. -CG
+ Releasing taler-merchant 0.4.0. -CG
Thu Jun 22 15:12:37 CEST 2017
Implementing /refund
@@ -73,16 +79,16 @@ Tue Jun 6 14:30:43 CEST 2017
/history API now has the way to query from a starting base date
ahead into the future.
Memory leak fixes. -CG/MS
- Releasing taler-merchant 0.3.0. -CG
+ Releasing taler-merchant 0.3.0. -CG
Mon Mar 6 17:57:51 CET 2017
- Add support for wire fee calculations to /pay handling (#4935),
- and adding setting "max_wire_fee" and "wire_fee_amortization"
- in contract from defaults. -CG
+ Add support for wire fee calculations to /pay handling (#4935),
+ and adding setting "max_wire_fee" and "wire_fee_amortization"
+ in contract from defaults. -CG
Mon Mar 6 00:59:25 CET 2017
- Implement BIND_TO option to allow binding backend to a particular
- IP address (#4752). Enabling use of dual-stack by default. -CG
+ Implement BIND_TO option to allow binding backend to a particular
+ IP address (#4752). Enabling use of dual-stack by default. -CG
Thu Dec 15 10:37:08 CET 2016
Implementing:
@@ -91,8 +97,8 @@ Thu Dec 15 10:37:08 CET 2016
by looking for their hashcodes.
Fri Nov 18 18:54:07 CET 2016
- Misc. minor updates to match API changes from exchange 0.2.
- Releasing taler-merchant 0.2.0. -CG
+ Misc. minor updates to match API changes from exchange 0.2.
+ Releasing taler-merchant 0.2.0. -CG
Mon Oct 10 16:27:57 CEST 2016
Implementing:
@@ -103,8 +109,8 @@ Mon Oct 10 16:27:57 CEST 2016
same backend. -MS
Tue Jun 7 15:17:45 CEST 2016
- Store signing key used by exchange in DB. Might be useful
- in the future when we implement GC for the backenddb. -CG
+ Store signing key used by exchange in DB. Might be useful
+ in the future when we implement GC for the backenddb. -CG
Wed Jun 1 17:27:36 CEST 2016
- Releasing taler-merchant-0.0.0. -CG
+ Releasing taler-merchant-0.0.0. -CG
diff --git a/bootstrap b/bootstrap
index c3a26432..18789981 100755
--- a/bootstrap
+++ b/bootstrap
@@ -32,7 +32,7 @@ fi
# Generate Makefile.am in contrib/
cd contrib
rm -f Makefile.am
-find wallet-core/backoffice/ -type f -printf ' %p \\\n' | sort > Makefile.am.ext
+find wallet-core/backoffice/ -type f | sort | awk '{print " " $1 " \\" }' > Makefile.am.ext
# Remove extra '\' at the end of the file
truncate -s -2 Makefile.am.ext
cat Makefile.am.in Makefile.am.ext >> Makefile.am
diff --git a/configure.ac b/configure.ac
index 8fd422fc..46eef3a7 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.2],[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
@@ -217,8 +217,18 @@ AS_IF([test $libgnunetutil != 1],
*** https://gnunet.org
*** ]])])
+libgnunetpq=0
AC_CHECK_HEADERS([gnunet/gnunet_pq_lib.h],
- [AC_CHECK_LIB([gnunetpq], [GNUNET_PQ_connect_with_cfg], libgnunetpq=1)])
+ [AC_CHECK_LIB([gnunetpq], [GNUNET_PQ_query_param_blind_sign_priv], libgnunetpq=1)])
+
+AS_IF([test $libgnunetpq != 1],
+ [AC_MSG_ERROR([[
+***
+*** You need libgnunetpq >= 0.21.2 (API v7) to build this program.
+*** This library is part of GNUnet, available at
+*** https://gnunet.org
+*** ]])])
+
AM_CONDITIONAL(HAVE_GNUNETPQ, test x$libgnunetpq = x1)
TALER_LIB_LDFLAGS="-export-dynamic -no-undefined"
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/ci/jobs/4-deb-package/version.sh b/contrib/ci/jobs/4-deb-package/version.sh
index a6e740af..52031b23 100755
--- a/contrib/ci/jobs/4-deb-package/version.sh
+++ b/contrib/ci/jobs/4-deb-package/version.sh
@@ -7,7 +7,7 @@ if [ -z "${BRANCH}" ]; then
else
# "Unshallow" our checkout, but only our current branch, and exclude the submodules.
git fetch --no-recurse-submodules --tags --depth=1000 origin "${BRANCH}"
- RECENT_VERSION_TAG=$(git describe --tags --match 'v*.*.*' --always --abbrev=0 HEAD || exit 1)
+ RECENT_VERSION_TAG=$(git describe --tags --match 'v*.*.*' --exclude '*-dev*' --always --abbrev=0 HEAD || exit 1)
commits="$(git rev-list ${RECENT_VERSION_TAG}..HEAD --count)"
if [ "${commits}" = "0" ]; then
git describe --tag HEAD | sed -r 's/^v//' || exit 1
diff --git a/contrib/wallet-core b/contrib/wallet-core
-Subproject 35212ac57bfe5625fcf574b0fd2d9baf395dc4c
+Subproject 240d647da85de6b575d15c37efec04757541e3d
diff --git a/debian/changelog b/debian/changelog
index 36344f8b..c04b84f3 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,19 @@
+taler-merchant (0.10.2) unstable; urgency=low
+
+ * Update various submodules to latest version.
+
+ -- Christian Grothoff <grothoff@gnu.org> Fri, 12 Apr 2024 09:50:12 +0200
+
+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/control b/debian/control
index 9977ac39..6fa6e6c1 100644
--- a/debian/control
+++ b/debian/control
@@ -9,7 +9,7 @@ Build-Depends:
debhelper-compat (= 12),
gettext,
libgnunet-dev (>=0.21),
- libtalerexchange-dev (>=0.9.4),
+ libtalerexchange-dev (>=0.10.2),
libpq-dev (>=14.0),
po-debconf,
libqrencode-dev,
@@ -48,7 +48,7 @@ Pre-Depends:
${misc:Pre-Depends}
Depends:
libtalermerchant (= ${binary:Version}),
- libtalerexchange (>= 0.9.4),
+ libtalerexchange (>= 0.10.2),
adduser,
lsb-base,
netbase,
@@ -69,7 +69,7 @@ Package: libtalermerchant-dev
Section: libdevel
Architecture: any
Depends:
- libtalerexchange-dev (>= 0.9.4),
+ libtalerexchange-dev (>= 0.10.2),
libgnunet-dev (>=0.21),
${misc:Depends},
${shlibs:Depends}
diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy
index d9785d42..e5185031 100644
--- a/doc/doxygen/taler.doxy
+++ b/doc/doxygen/taler.doxy
@@ -5,7 +5,7 @@
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "GNU Taler: Merchant"
-PROJECT_NUMBER = 0.9.3
+PROJECT_NUMBER = 0.10.2
PROJECT_LOGO = logo.svg
OUTPUT_DIRECTORY = .
CREATE_SUBDIRS = YES
diff --git a/doc/prebuilt b/doc/prebuilt
-Subproject af8c69dfe397ff4bed7abca98ed8f3b2ed70541
+Subproject b8d2d2fa2ed2a771880f451725176f256583cb2
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index d521608d..dbc7cde8 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -25,6 +25,7 @@ bin_PROGRAMS = \
taler_merchant_httpd_SOURCES = \
taler-merchant-httpd.c taler-merchant-httpd.h \
taler-merchant-httpd_config.c taler-merchant-httpd_config.h \
+ taler-merchant-httpd_contract.c taler-merchant-httpd_contract.h \
taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \
taler-merchant-httpd_get-orders-ID.c \
taler-merchant-httpd_get-orders-ID.h \
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c
index af2fa267..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);
@@ -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 e3b0d6c4..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,6 +754,12 @@ 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;
@@ -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 c7dec0f9..d1340249 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 "13:0:9"
+#define MERCHANT_PROTOCOL_VERSION "14:1:10"
/**
@@ -82,16 +82,36 @@ MH_handler_config (struct TMH_RequestHandler *rh,
struct TMH_HandlerContext *hc)
{
static struct MHD_Response *response;
+ static struct GNUNET_TIME_Absolute a;
(void) rh;
(void) hc;
+ if ( (GNUNET_TIME_absolute_is_past (a)) &&
+ (NULL != response) )
+ {
+ MHD_destroy_response (response);
+ response = NULL;
+ }
if (NULL == response)
{
json_t *specs = json_object ();
json_t *exchanges = json_array ();
+ 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 +122,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 +133,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_contract.c b/src/backend/taler-merchant-httpd_contract.c
new file mode 100644
index 00000000..38c82e70
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ (C) 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_contract.c
+ * @brief shared logic for contract terms handling
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "taler-merchant-httpd_contract.h"
+
+enum TALER_MerchantContractInputType
+TMH_string_to_contract_input_type (const char *str)
+{
+ /* For now, only 'token' is the only supported option. */
+ if (0 == strcmp("token", str))
+ {
+ return TALER_MCIT_TOKEN;
+ }
+
+ return TALER_MCIT_INVALID;
+}
+
+enum TALER_MerchantContractOutputType
+TMH_string_to_contract_output_type (const char *str)
+{
+ /* For now, only 'token' is the only supported option. */
+ if (0 == strcmp("token", str))
+ {
+ return TALER_MCOT_TOKEN;
+ }
+
+ return TALER_MCOT_INVALID;
+}
diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h
new file mode 100644
index 00000000..b1e3938c
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -0,0 +1,592 @@
+/*
+ This file is part of TALER
+ (C) 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_contract.h
+ * @brief shared logic for contract terms handling
+ * @author Christian Blättler
+ */
+#include "taler-merchant-httpd.h"
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+
+
+/**
+ * Possible versions of the contract terms.
+ */
+enum TALER_MerchantContractVersion
+{
+
+ /**
+ * Version 0
+ */
+ TALER_MCV_V0 = 0,
+
+ /**
+ * Version 1
+ */
+ TALER_MCV_V1 = 1
+};
+
+/**
+ * Possible token kinds.
+ */
+enum TALER_MerchantContractTokenKind
+{
+
+ /**
+ * Subscription token kind
+ */
+ TALER_MCTK_SUBSCRIPTION = 0,
+
+ /**
+ * Discount token kind
+ */
+ TALER_MCTK_DISCOUNT = 1
+};
+
+/**
+ * Possible input types for the contract terms.
+ */
+enum TALER_MerchantContractInputType
+{
+
+ /**
+ * Input type invalid
+ */
+ TALER_MCIT_INVALID = 0,
+
+ /**
+ * Input type coin
+ */
+ TALER_MCIT_COIN = 1,
+
+ /**
+ * Input type token
+ */
+ TALER_MCIT_TOKEN = 2
+};
+
+/**
+ * Contract input (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractInput
+{
+ /**
+ * Type of the input.
+ */
+ enum TALER_MerchantContractInputType type;
+
+ union
+ {
+ /**
+ * Coin-based input (ration). (Future work, only here for reference)
+ */
+ // struct
+ // {
+ // /**
+ // * Price to be paid.
+ // */
+ // struct TALER_Amount price;
+
+ // /**
+ // * Base URL of the ration authority.
+ // */
+ // const char *ration_authority_url;
+ // } coin;
+
+ /**
+ * Token-based input.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be used.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Start time of the validity period of the token. Base on this timestamp
+ * the wallet can find the correct key for this token in token_authorities.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
+};
+
+/**
+ * Possible output types for the contract terms.
+ */
+enum TALER_MerchantContractOutputType
+{
+
+ /**
+ * Invalid output type
+ */
+ TALER_MCOT_INVALID = 0,
+
+ /**
+ * Output type coin
+ */
+ TALER_MCOT_COIN = 1,
+
+ /**
+ * Output type token
+ */
+ TALER_MCOT_TOKEN = 2,
+
+ /**
+ * Output type tax-receipt
+ */
+ TALER_MCOT_TAX_RECEIPT = 3
+
+};
+
+/**
+ * Contract output (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractOutput
+{
+ /**
+ * Type of the output.
+ */
+ enum TALER_MerchantContractOutputType type;
+
+ union
+ {
+ /**
+ * Coin-based output.
+ */
+ struct {
+ /**
+ * Coins that will be yielded. This excludes any applicable withdraw fees.
+ */
+ struct TALER_Amount brutto_yield;
+
+ /**
+ * Base URL of the exchange that will issue the coins.
+ */
+ const char *exchange_url;
+ } coin;
+
+ /**
+ * Tax-receipt output.
+ */
+ struct
+ {
+ /**
+ * Base URL of the donation authority that will issue the tax receipt.
+ */
+ const char *donau_url;
+ } tax_receipt;
+
+ /**
+ * Token-based output.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be issued.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Start time of the validity period of the token. Base on this timestamp
+ * the wallet can find the correct key for this token in token_authorities.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
+};
+
+/**
+ * Contract choice (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractChoice
+{
+ /**
+ * List of inputs the wallet must provision (all of them) to satisfy the
+ * conditions for the contract.
+ */
+ struct TALER_MerchantContractInput *inputs;
+
+ /**
+ * Length of the @e inputs array.
+ */
+ unsigned int inputs_len;
+
+ /**
+ * List of outputs the merchant promises to yield (all of them) once
+ * the contract is paid.
+ */
+ struct TALER_MerchantContractOutput *outputs;
+
+ /**
+ * Length of the @e outputs array.
+ */
+ unsigned int outputs_len;
+};
+
+struct TALER_MerchantContractLimits
+{
+ /**
+ * Currency these limits are for.
+ */
+ char currency[TALER_CURRENCY_LEN];
+
+ /**
+ * The hash of the merchant instance's wire details.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Wire transfer method identifier for the wire method associated with ``h_wire``.
+ * The wallet may only select exchanges via a matching auditor if the
+ * exchange also supports this wire method.
+ * The wire transfer fees must be added based on this wire transfer method.
+ */
+ char *wire_method;
+
+ /**
+ * Maximum total deposit fee accepted by the merchant for this contract.
+ */
+ struct TALER_Amount max_fee;
+};
+
+struct TALER_MerchantContractTokenAuthority
+{
+ /**
+ * Label of the token authority.
+ */
+ const char *label;
+
+ /**
+ * Human-readable description of the semantics of the tokens issued by
+ * this authority.
+ */
+ char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized description.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Public key used to validate tokens signed by this authority.
+ */
+ struct TALER_TokenFamilyPublicKey *pub;
+
+ /**
+ * When will tokens signed by this key expire?
+ */
+ struct GNUNET_TIME_Timestamp token_expiration;
+
+ /**
+ * Must a wallet understand this token type to process contracts that
+ * consume or yield it?
+ */
+ bool critical;
+
+ /**
+ * Kind of the token.
+ */
+ enum TALER_MerchantContractTokenKind kind;
+
+ /**
+ * Kind-specific information about the token.
+ */
+ union
+ {
+ /**
+ * Subscription token.
+ */
+ struct
+ {
+ /**
+ * When does the subscription period start?
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * When does the subscription period end?
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * Array of domain names where this subscription can be safely used
+ * (e.g. the issuer warrants that these sites will re-issue tokens of
+ * this type if the respective contract says so). May contain "*" for
+ * any domain or subdomain.
+ */
+ const char **trusted_domains;
+
+ /**
+ * Length of the @e trusted_domains array.
+ */
+ unsigned int trusted_domains_len;
+ } subscription;
+
+ /**
+ * Discount token.
+ */
+ struct
+ {
+ /**
+ * Array of domain names where this discount token is intended to be
+ * used. May contain "*" for any domain or subdomain. Users should be
+ * warned about sites proposing to consume discount tokens of this
+ * type that are not in this list that the merchant is accepting a
+ * coupon from a competitor and thus may be attaching different
+ * semantics (like get 20% discount for my competitors 30% discount
+ * token).
+ */
+ const char **expected_domains;
+
+ /**
+ * Length of the @e expected_domains array.
+ */
+ unsigned int expected_domains_len;
+
+ } discount;
+ } details;
+};
+
+/**
+ * Struct to hold contract terms in v0 and v1 format. v0 contracts are modelled
+ * as a v1 contract with a single choice and no inputs and outputs. Use the
+ * version field to explicitly differentiate between v0 and v1 contracts.
+ */
+struct TALER_MerchantContract
+{
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Our order ID.
+ */
+ const char *order_id;
+
+ /**
+ * Merchant base URL.
+ */
+ char *merchant_base_url;
+
+ /**
+ * Merchant information.
+ */
+ struct
+ {
+ /**
+ * Legal name of the instance
+ */
+ char *name;
+
+ /**
+ * Merchant's site url
+ */
+ char *website;
+
+ /**
+ * Email contact for customers
+ */
+ char *email;
+
+ /**
+ * merchant's logo data uri
+ */
+ char *logo;
+
+ /**
+ * Merchant address
+ */
+ json_t *address;
+
+ /**
+ * Jurisdiction of the business
+ */
+ json_t *jurisdiction;
+ } merchant;
+
+ /**
+ * Price to be paid for the transaction. Could be 0. The price is in addition
+ * to other instruments, such as rations and tokens.
+ * The exchange will subtract deposit fees from that amount
+ * before transferring it to the merchant.
+ */
+ struct TALER_Amount brutto;
+
+ /**
+ * Summary of the contract.
+ */
+ const char *summary;
+
+ /**
+ * Internationalized summary.
+ */
+ json_t *summary_i18n;
+
+ /**
+ * URL that will show that the contract was successful
+ * after it has been paid for.
+ */
+ const char *fulfillment_url;
+
+ /**
+ * Message shown to the customer after paying for the contract.
+ * Either fulfillment_url or fulfillment_message must be specified.
+ */
+ const char *fulfillment_message;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized fulfillment messages.
+ */
+ json_t *fulfillment_message_i18n;
+
+ /**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * Timestamp of the contract.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Deadline for refunds.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Specifies for how long the wallet should try to get an
+ * automatic refund for the purchase.
+ */
+ struct GNUNET_TIME_Relative auto_refund;
+
+ /**
+ * Payment deadline.
+ */
+ struct GNUNET_TIME_Timestamp pay_deadline;
+
+ /**
+ * Wire transfer deadline.
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
+
+ /**
+ * Delivery date.
+ */
+ struct GNUNET_TIME_Timestamp delivery_date;
+
+ /**
+ * Delivery location.
+ */
+ json_t *delivery_location;
+
+ /**
+ * Nonce generated by the wallet and echoed by the merchant
+ * in this field when the proposal is generated.
+ */
+ const char *nonce;
+
+ /**
+ * Extra data that is only interpreted by the merchant frontend.
+ */
+ const json_t *extra;
+
+ /**
+ * Specified version of the contract.
+ */
+ enum TALER_MerchantContractVersion version;
+
+ /**
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Array of token authorities.
+ */
+ struct TALER_MerchantContractTokenAuthority *token_authorities;
+
+ /**
+ * Length of the @e token_authorities array.
+ */
+ unsigned int token_authorities_len;
+
+ /**
+ * Maximum fee as given by the client request.
+ */
+ struct TALER_Amount max_fee;
+
+ // TODO: Add exchanges array
+};
+
+enum TALER_MerchantContractInputType
+TMH_string_to_contract_input_type (const char *str);
+
+enum TALER_MerchantContractOutputType
+TMH_string_to_contract_output_type (const char *str);
+
+/**
+ * Serialize @a contract to a JSON object, ready to be stored in the database.
+ * The @a contract can be of v0 or v1.
+ *
+ * @param[in] contract contract struct to serialize
+ * @param[in] instance merchant instance for this contract
+ * @param[in] exchanges JSON array of exchanges
+ * @param[out] out serialized contract as JSON object
+ * @return #GNUNET_OK on success
+ * #GNUNET_NO if @a contract was not valid
+ * #GNUNET_SYSERR on failure
+ */
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out);
+
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract_v0 (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out);
+
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract_v1 (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out); \ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_mhd.c b/src/backend/taler-merchant-httpd_mhd.c
index e96acca5..0bb22b35 100644
--- a/src/backend/taler-merchant-httpd_mhd.c
+++ b/src/backend/taler-merchant-httpd_mhd.c
@@ -59,6 +59,7 @@ TMH_MHD_test_html_desired (struct MHD_Connection *connection)
bool ret = false;
const char *accept;
+ // FIXME: use TALER_MHD_check_accept here!
accept = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT);
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 07a6233a..14edfd55 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -2032,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
@@ -2043,6 +2044,7 @@ 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)
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 5fc91188..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)
{
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index 7ca56319..eedece55 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -22,19 +22,25 @@
* @brief the POST /orders handler
* @author Christian Grothoff
* @author Marcello Stanisci
+ * @author Christian Blättler
*/
#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
+#include <microhttpd.h>
#include <string.h>
+#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler_merchantdb_plugin.h"
/**
@@ -231,43 +237,58 @@ struct OrderContext
struct
{
/**
+ * Version of the contract terms.
+ */
+ enum TALER_MerchantContractVersion version;
+
+ /**
* Our order ID.
*/
const char *order_id;
/**
- * Summary of the order.
- */
+ * Summary of the contract.
+ */
const char *summary;
/**
- * Internationalized summary.
- */
+ * Internationalized summary.
+ */
json_t *summary_i18n;
/**
- * URL where the same contract could be ordered again (if available).
- */
- const char *public_reorder_url;
-
- /**
- * URL that will show that the order was successful
- * after it has been paid for.
- */
+ * URL that will show that the contract was successful
+ * after it has been paid for.
+ */
const char *fulfillment_url;
/**
- * Message shown to the customer after paying for the order.
- * Either fulfillment_url or fulfillment_message must be specified.
- */
+ * Message shown to the customer after paying for the contract.
+ * Either fulfillment_url or fulfillment_message must be specified.
+ */
const char *fulfillment_message;
/**
- * Map from IETF BCP 47 language tags to localized fulfillment messages.
- */
+ * Map from IETF BCP 47 language tags to localized fulfillment messages.
+ */
json_t *fulfillment_message_i18n;
/**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Array of contract choices. Is null for v0 contracts.
+ */
+ const json_t *choices;
+
+ /**
* Merchant base URL.
*/
char *merchant_base_url;
@@ -303,14 +324,9 @@ struct OrderContext
json_t *delivery_location;
/**
- * Array of products that are part of the purchase.
- */
- const json_t *products;
-
- /**
- * Gross amount value of the contract. Used to
- * compute @e max_stefan_fee.
- */
+ * Gross amount value of the contract. Used to
+ * compute @e max_stefan_fee.
+ */
struct TALER_Amount brutto;
/**
@@ -343,6 +359,34 @@ struct OrderContext
} parse_order;
/**
+ * Information set in the ORDER_PHASE_PARSE_CHOICES phase.
+ */
+ struct
+ {
+ /**
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Array of token types referenced in the contract.
+ */
+ struct TALER_MerchantContractTokenAuthority *authorities;
+
+ /**
+ * Length of the @e authorities array.
+ */
+ unsigned int authorities_len;
+ } parse_choices;
+
+ /**
* Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
*/
struct
@@ -477,6 +521,7 @@ struct OrderContext
{
ORDER_PHASE_PARSE_REQUEST,
ORDER_PHASE_PARSE_ORDER,
+ ORDER_PHASE_PARSE_CHOICES,
ORDER_PHASE_MERGE_INVENTORY,
ORDER_PHASE_ADD_PAYMENT_DETAILS,
ORDER_PHASE_SET_EXCHANGES,
@@ -634,6 +679,16 @@ clean_order (void *cls)
json_decref (oc->merge_inventory.products);
oc->merge_inventory.products = NULL;
}
+ // TODO: Check if this is even correct
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ GNUNET_array_grow (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ 0);
+ GNUNET_array_grow (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ 0);
+ }
GNUNET_array_grow (oc->parse_request.inventory_products,
oc->parse_request.inventory_products_length,
0);
@@ -1262,6 +1317,142 @@ get_exchange_keys (void *cls,
rx);
}
+/**
+ * Fetch details about the token family with the given @a slug
+ * and add them to the list of token authorities. Check if the
+ * token family already has a valid key configured and if not,
+ * create a new one.
+ *
+ * @param[in,out] oc order context
+ * @param slug slug of the token family
+ * @param start_date validity start date of the token
+ */
+static MHD_RESULT
+set_token_authority (struct OrderContext *oc,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp start_date)
+{
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
+ struct TALER_MerchantContractTokenAuthority authority;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute min_start_date = GNUNET_TIME_absolute_subtract (
+ start_date.abs_time,
+ // TODO: make this configurable. This is the granularity of token
+ // expiration dates.
+ GNUNET_TIME_UNIT_DAYS
+ );
+
+ qs = TMH_db->lookup_token_family_key (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ slug,
+ GNUNET_TIME_absolute_to_timestamp (min_start_date),
+ start_date,
+ &key_details);
+
+ if (qs <= 0)
+ {
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ unsigned int http_status = 0;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Token family slug unknown\n");
+ http_status = MHD_HTTP_NOT_FOUND;
+ ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+ GNUNET_break (0);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ "token_family_slug");
+ return MHD_NO;
+ }
+
+ if (NULL == key_details.pub)
+ {
+ /* If public key is NULL, private key must also be NULL */
+ GNUNET_assert (NULL == key_details.priv);
+
+ struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Timestamp valid_before = GNUNET_TIME_absolute_to_timestamp(
+ GNUNET_TIME_absolute_add (now,
+ key_details.token_family.duration));
+
+ GNUNET_CRYPTO_blind_sign_keys_create (&priv,
+ &pub,
+ // TODO: Make cipher and key length configurable
+ GNUNET_CRYPTO_BSA_RSA,
+ 4096);
+
+ struct TALER_TokenFamilyPublicKey token_pub = {
+ .public_key = *pub,
+ };
+ struct TALER_TokenFamilyPrivateKey token_priv = {
+ .private_key = *priv,
+ };
+
+ qs = TMH_db->insert_token_family_key (TMH_db->cls,
+ slug,
+ &token_pub,
+ &token_priv,
+ GNUNET_TIME_absolute_to_timestamp (now),
+ valid_before);
+
+ authority.token_expiration = valid_before;
+ authority.pub = &token_pub;
+ // GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key);
+ } else {
+ authority.token_expiration = key_details.valid_before;
+ authority.pub = key_details.pub;
+ }
+
+ authority.label = slug;
+ authority.description = key_details.token_family.description;
+ authority.description_i18n = key_details.token_family.description_i18n;
+
+ GNUNET_free (key_details.token_family.slug);
+ GNUNET_free (key_details.token_family.name);
+ if (NULL != key_details.priv) {
+ GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key);
+ }
+
+ switch (key_details.token_family.kind) {
+ case TALER_MERCHANTDB_TFK_Subscription:
+ authority.kind = TALER_MCTK_SUBSCRIPTION;
+ authority.details.subscription.start_date = key_details.valid_after;
+ authority.details.subscription.end_date = key_details.valid_before;
+ authority.critical = true;
+ // TODO: Set trusted domains
+ break;
+ case TALER_MERCHANTDB_TFK_Discount:
+ authority.kind = TALER_MCTK_DISCOUNT;
+ authority.critical = false;
+ // TODO: Set expected domains
+ break;
+ }
+
+ GNUNET_array_append (oc->parse_choices.authorities,
+ oc->parse_choices.authorities_len,
+ authority);
+
+ return MHD_YES;
+}
/**
* Serialize order into @a oc->serialize_order.contract,
@@ -1276,45 +1467,159 @@ serialize_order (struct OrderContext *oc)
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
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),
+ settings->name),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
- settings->website)),
+ settings->website)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("email",
- settings->email)),
+ settings->email)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("logo",
- settings->logo)));
+ settings->logo)));
GNUNET_assert (NULL != merchant);
{
- json_t *loca = settings->address;
+ 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 (merchant,
- "address",
- loca));
+ json_object_set_new (merchant,
+ "address",
+ loca));
}
}
{
- json_t *juri = settings->jurisdiction;
+ 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 (merchant,
- "jurisdiction",
- juri));
+ json_object_set_new (merchant,
+ "jurisdiction",
+ juri));
}
}
+ 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 referenced 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 output 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)
+ );
+
+ GNUNET_assert (0 ==
+ json_object_set_new (token_types,
+ authority->label,
+ jauthority));
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i];
+
+ json_t *inputs = json_array ();
+ json_t *outputs = json_array ();
+
+ for (unsigned int j = 0; j<choice->inputs_len; j++)
+ {
+ struct TALER_MerchantContractInput *input = &choice->inputs[j];
+
+ json_t *jinput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ input->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)));
+ }
+
+ GNUNET_assert (0 == json_array_append_new (inputs, jinput));
+ }
+
+ for (unsigned int j = 0; j<choice->outputs_len; j++)
+ {
+ struct TALER_MerchantContractOutput *output = &choice->outputs[j];
+
+ json_t *joutput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ output->type)
+ );
+
+ 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 (
+ GNUNET_JSON_pack_int64 ("version",
+ oc->parse_order.version),
GNUNET_JSON_pack_string ("summary",
oc->parse_order.summary),
GNUNET_JSON_pack_allow_null (
@@ -1368,6 +1673,14 @@ serialize_order (struct OrderContext *oc)
TALER_JSON_pack_amount ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("choices",
+ choices)
+ ),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("token_types",
+ token_types)
+ ),
+ GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",
(json_t *) oc->parse_order.extra))
);
@@ -1378,6 +1691,7 @@ serialize_order (struct OrderContext *oc)
"refund_deadline",
GNUNET_JSON_from_timestamp (
oc->parse_order.refund_deadline)));
+
GNUNET_log (
GNUNET_ERROR_TYPE_INFO,
"Refund deadline for contact is %llu\n",
@@ -1401,7 +1715,6 @@ serialize_order (struct OrderContext *oc)
oc->phase++;
}
-
/**
* Set max_fee in @a oc based on STEFAN value if
* not yet present. Upon success, continue
@@ -1437,7 +1750,6 @@ set_max_fee (struct OrderContext *oc)
oc->phase++;
}
-
/**
* Set list of acceptable exchanges in @a oc. Upon success, continue
* processing with set_max_fee().
@@ -1503,8 +1815,8 @@ set_exchanges (struct OrderContext *oc)
/**
- * Add missing fields to the order. Upon success, continue
- * processing with merge_inventory().
+ * Parse the order field of the request. Upon success, continue
+ * processing with parse_choices().
*
* @param[in,out] oc order context
*/
@@ -1514,12 +1826,17 @@ parse_order (struct OrderContext *oc)
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
const char *merchant_base_url = NULL;
+ const char *version = NULL;
const json_t *jmerchant = NULL;
/* auto_refund only needs to be type-checked,
* mostly because in GNUnet relative times can't
* be negative. */
bool no_fee;
struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("version",
+ &version),
+ NULL),
TALER_JSON_spec_amount_any ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_spec_string ("summary",
@@ -1537,10 +1854,6 @@ parse_order (struct OrderContext *oc)
&oc->parse_order.order_id),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("public_reorder_url",
- &oc->parse_order.public_reorder_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("fulfillment_message",
&oc->parse_order.fulfillment_message),
NULL),
@@ -1553,6 +1866,14 @@ parse_order (struct OrderContext *oc)
&oc->parse_order.fulfillment_url),
NULL),
GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("public_reorder_url",
+ &oc->parse_order.public_reorder_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("choices",
+ &oc->parse_order.choices),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_web_url ("merchant_base_url",
&merchant_base_url),
NULL),
@@ -1616,6 +1937,46 @@ parse_order (struct OrderContext *oc)
ret);
return;
}
+ if (NULL == version || 0 == strcmp("0", version))
+ {
+ oc->parse_order.version = TALER_MCV_V0;
+
+ if (NULL != oc->parse_order.choices)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
+ "choices array must be null for v0 contracts");
+ return;
+ }
+ }
+ else if (0 == strcmp("1", version))
+ {
+ oc->parse_order.version = TALER_MCV_V1;
+
+ if (! json_is_array(oc->parse_order.choices))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order.choices is not a valid array");
+ return;
+ }
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_VERSION_MALFORMED,
+ "invalid version specified in order, supported are null, '0' or '1'");
+ return;
+ }
if (! TMH_test_exchange_configured_for_currency (
oc->parse_order.brutto.currency))
{
@@ -1628,9 +1989,9 @@ parse_order (struct OrderContext *oc)
return;
}
if ( (! no_fee) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&oc->parse_order.brutto,
- &oc->parse_order.max_fee)) )
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&oc->parse_order.brutto,
+ &oc->parse_order.max_fee)) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -1907,6 +2268,263 @@ parse_order (struct OrderContext *oc)
oc->phase++;
}
+/**
+ * Parse contract choices. Upon success, continue
+ * processing with merge_inventory().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+parse_choices (struct OrderContext *oc)
+{
+ if (NULL == oc->parse_order.choices)
+ {
+ oc->phase++;
+ return;
+ }
+
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ json_array_size (oc->parse_order.choices));
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ const char *error_name;
+ unsigned int error_line;
+ const json_t *jinputs;
+ const json_t *joutputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i),
+ spec,
+ &error_name,
+ &error_line);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Choice parsing failed: %s:%u\n",
+ error_name,
+ error_line);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "choice");
+ return;
+ }
+
+ if (! json_is_array (jinputs) ||
+ ! json_is_array (joutputs))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inputs or outputs");
+ return;
+ }
+
+ {
+ // TODO: Maybe move to a separate function
+ const json_t *jinput;
+ size_t idx;
+ json_array_foreach ((json_t *) jinputs, idx, jinput)
+ {
+ // TODO: Assuming this struct would have more fields, would i use GNUNET_new then?
+ // Or should i use GNUNET_grow first and then get the element using the index?
+ // Assuming you add a field in the future, it's easier to that way, so you don't
+ // free it.
+ struct TALER_MerchantContractInput input = {.details.token.count = 1};
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &input.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &input.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &input.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jinput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid input #%u for field %s\n",
+ (unsigned int) idx,
+ ierror_name);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ierror_name);
+ return;
+ }
+
+ input.type = TMH_string_to_contract_input_type (kind);
+
+ if (TALER_MCIT_INVALID == input.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in input #%u\n",
+ (unsigned int) idx);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kind");
+ return;
+ }
+
+ if (0 == input.details.token.count)
+ {
+ /* Ignore inputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ input.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ input.details.token.token_family_slug,
+ input.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ input);
+ }
+ }
+
+ {
+ const json_t *joutput;
+ size_t idx;
+ json_array_foreach ((json_t *) joutputs, idx, joutput)
+ {
+ struct TALER_MerchantContractOutput output = { .details.token.count = 1 };
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &output.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &output.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &output.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (joutput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid output #%u for field %s\n",
+ (unsigned int) idx,
+ ierror_name);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ierror_name);
+ return;
+ }
+
+ output.type = TMH_string_to_contract_output_type (kind);
+
+ if (TALER_MCOT_INVALID == output.type)
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in output #%u\n",
+ (unsigned int) idx);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kind");
+ return;
+ }
+
+ if (0 == output.details.token.count)
+ {
+ /* Ignore outputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ output.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ output.details.token.token_family_slug,
+ output.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ output);
+ }
+ }
+ }
+
+ oc->phase++;
+}
/**
* Process the @a payment_target and add the details of how the
@@ -2305,6 +2923,9 @@ TMH_private_post_orders (
case ORDER_PHASE_PARSE_ORDER:
parse_order (oc);
break;
+ case ORDER_PHASE_PARSE_CHOICES:
+ parse_choices (oc);
+ break;
case ORDER_PHASE_MERGE_INVENTORY:
merge_inventory (oc);
break;
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index e7fb15cf..defc3cf9 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -173,6 +173,8 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_lookup_token_families.h pg_lookup_token_families.c \
pg_delete_token_family.h pg_delete_token_family.c \
pg_update_token_family.h pg_update_token_family.c \
+ pg_insert_token_family_key.h pg_insert_token_family_key.c \
+ pg_lookup_token_family_key.h pg_lookup_token_family_key.c \
plugin_merchantdb_postgres.c \
pg_helper.h pg_helper.c
libtaler_plugin_merchantdb_postgres_la_LIBADD = \
diff --git a/src/backenddb/pg_insert_token_family.h b/src/backenddb/pg_insert_token_family.h
index e05755a6..d584f5e7 100644
--- a/src/backenddb/pg_insert_token_family.h
+++ b/src/backenddb/pg_insert_token_family.h
@@ -28,7 +28,7 @@
/**
* @param cls closure
- * @param instance_id instance to insert token family for TODO: Is this needed?
+ * @param instance_id instance to insert token family for
* @param token_family_slug slug of the token family to insert
* @param details the token family details to insert
* @return database result code
diff --git a/src/backenddb/pg_insert_token_family_key.c b/src/backenddb/pg_insert_token_family_key.c
new file mode 100644
index 00000000..b13c8079
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family_key.c
@@ -0,0 +1,97 @@
+/*
+ This file is part of TALER
+ 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_token_family_key.c
+ * @brief Implementation of the insert_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_pq_lib.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_token_family_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family_key (void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct GNUNET_TIME_Timestamp valid_after,
+ const struct GNUNET_TIME_Timestamp valid_before)
+{
+ struct PostgresClosure *pg = cls;
+ const char *cipher = NULL;
+ struct GNUNET_HashCode pub_hash;
+
+ switch (pub->public_key.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = "rsa";
+ GNUNET_CRYPTO_rsa_public_key_hash (pub->public_key.details.rsa_public_key,
+ &pub_hash);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = "cs";
+ GNUNET_CRYPTO_hash (&pub->public_key.details.cs_public_key,
+ sizeof (pub->public_key.details.cs_public_key),
+ &pub_hash);
+ break;
+ case GNUNET_CRYPTO_BSA_INVALID:
+ /* case listed to make compilers happy */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_blind_sign_pub (&pub->public_key),
+ GNUNET_PQ_query_param_auto_from_type (&pub->public_key.pub_key_hash),
+ GNUNET_PQ_query_param_blind_sign_priv (&priv->private_key),
+ GNUNET_PQ_query_param_timestamp (&valid_after),
+ GNUNET_PQ_query_param_timestamp (&valid_before),
+ GNUNET_PQ_query_param_string (cipher),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (pub->public_key.cipher == priv->private_key.cipher);
+
+ GNUNET_assert (0 ==
+ GNUNET_memcmp (&pub_hash,
+ &pub->public_key.pub_key_hash));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ valid_after.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ valid_before.abs_time));
+
+ PREPARE (pg,
+ "token_family_key_insert",
+ "INSERT INTO merchant_token_family_keys "
+ "(token_family_serial"
+ ",pub"
+ ",h_pub"
+ ",priv"
+ ",valid_after"
+ ",valid_before"
+ ",cipher)"
+ " SELECT token_family_serial, $2, $3, $4, $5, $6, $7"
+ " FROM merchant_token_families"
+ " WHERE slug = $1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "token_family_key_insert",
+ params);
+} \ No newline at end of file
diff --git a/src/backenddb/pg_insert_token_family_key.h b/src/backenddb/pg_insert_token_family_key.h
new file mode 100644
index 00000000..c4fc8d85
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family_key.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_token_family_key.h
+ * @brief implementation of the insert_token_family_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_TOKEN_FAMILY_KEY_H
+#define PG_INSERT_TOKEN_FAMILY_KEY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * @param cls closure
+ * @param token_family_slug slug of the token family to insert the key for
+ * @param pub public key to insert
+ * @param priv private key to insert
+ * @param valid_after start of validity period for this key
+ * @param valid_before end of validity period for this key
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family_key (void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct GNUNET_TIME_Timestamp valid_after,
+ const struct GNUNET_TIME_Timestamp valid_before);
+
+#endif
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_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_token_family.c b/src/backenddb/pg_lookup_token_family.c
index 848b79a9..d2c651c9 100644
--- a/src/backenddb/pg_lookup_token_family.c
+++ b/src/backenddb/pg_lookup_token_family.c
@@ -105,9 +105,9 @@ TMH_PG_lookup_token_family (void *cls,
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
- if (strcmp(kind, "discount") == 0)
+ if (0 == strcmp(kind, "discount"))
details->kind = TALER_MERCHANTDB_TFK_Discount;
- else if (strcmp(kind, "subscription") == 0)
+ else if (0 == strcmp(kind, "subscription"))
details->kind = TALER_MERCHANTDB_TFK_Subscription;
else
{
diff --git a/src/backenddb/pg_lookup_token_family_key.c b/src/backenddb/pg_lookup_token_family_key.c
new file mode 100644
index 00000000..f1fa75f3
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family_key.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_token_family_key.c
+ * @brief Implementation of the lookup_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_pq_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <string.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_token_family_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family_key (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_timestamp (&min_valid_after),
+ GNUNET_PQ_query_param_timestamp (&max_valid_after),
+ GNUNET_PQ_query_param_end
+ };
+
+ if (NULL == details)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_token_family_key",
+ params,
+ rs_null);
+ }
+ else
+ {
+ char *kind;
+ details->pub = NULL;
+ details->priv = NULL;
+ details->valid_after = GNUNET_TIME_UNIT_ZERO_TS;
+ details->valid_before = GNUNET_TIME_UNIT_ZERO_TS;
+
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ // GNUNET_PQ_result_spec_allow_null (
+ // GNUNET_PQ_result_spec_blind_sign_pub ("pub",
+ // &details->pub->public_key),
+ // NULL),
+ // GNUNET_PQ_result_spec_allow_null (
+ // GNUNET_PQ_result_spec_blind_sign_priv ("priv",
+ // &details->priv->private_key),
+ // NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_after",
+ &details->valid_after),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_before",
+ &details->valid_before),
+ NULL),
+ GNUNET_PQ_result_spec_string ("slug",
+ &details->token_family.slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &details->token_family.name),
+ GNUNET_PQ_result_spec_string ("description",
+ &details->token_family.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &details->token_family.description_i18n),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &details->token_family.valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &details->token_family.valid_before),
+ GNUNET_PQ_result_spec_relative_time ("duration",
+ &details->token_family.duration),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_uint64 ("issued",
+ &details->token_family.issued),
+ GNUNET_PQ_result_spec_uint64 ("redeemed",
+ &details->token_family.redeemed),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_token_family_key",
+ "SELECT"
+ " h_pub"
+ ",pub"
+ ",priv"
+ ",cipher"
+ ",merchant_token_family_keys.valid_after as key_valid_after"
+ ",merchant_token_family_keys.valid_before as key_valid_before"
+ ",slug"
+ ",name"
+ ",description"
+ ",description_i18n"
+ ",merchant_token_families.valid_after"
+ ",merchant_token_families.valid_before"
+ ",duration"
+ ",kind"
+ ",issued"
+ ",redeemed"
+ " FROM merchant_token_families"
+ " LEFT JOIN merchant_token_family_keys"
+ " ON merchant_token_families.token_family_serial = merchant_token_family_keys.token_family_serial"
+ " AND merchant_token_family_keys.valid_after >= $3"
+ " AND merchant_token_family_keys.valid_after <= $4"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND slug=$2"
+ " LIMIT 1");
+ enum GNUNET_DB_QueryStatus qs;
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_token_family_key",
+ params,
+ rs);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 == strcmp(kind, "discount"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (0 == strcmp(kind, "subscription"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
+ else
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ /* TODO: How to handle multiple results? */
+
+ return qs;
+ }
+} \ No newline at end of file
diff --git a/src/backenddb/pg_lookup_token_family_key.h b/src/backenddb/pg_lookup_token_family_key.h
new file mode 100644
index 00000000..aa7335cf
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family_key.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_token_family_key.h
+ * @brief implementation of the lookup_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_LOOKUP_TOKEN_FAMILY_KEY_H
+#define PG_LOOKUP_TOKEN_FAMILY_KEY_H
+
+#include <gnunet/gnunet_common.h>
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup details about a particular token family key.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family key for
+ * @param token_family_slug slug of token family to lookup
+ * @param min_valid_after lower bound of the start of the key validation period
+ * @param max_valid_after upper bound of the start of the key validation period
+ * @param[out] details set to the token family key details on success, can be NULL
+ * (in that case we only want to check if the token family key exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family_key (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
+
+
+#endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index e09b4827..e5f3f2a1 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -138,6 +138,8 @@
#include "pg_lookup_token_families.h"
#include "pg_delete_token_family.h"
#include "pg_update_token_family.h"
+#include "pg_insert_token_family_key.h"
+#include "pg_lookup_token_family_key.h"
/**
@@ -575,9 +577,14 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_delete_token_family;
plugin->update_token_family
= &TMH_PG_update_token_family;
+ plugin->insert_token_family_key
+ = &TMH_PG_insert_token_family_key;
+ plugin->lookup_token_family_key
+ = &TMH_PG_lookup_token_family_key;
plugin->update_deposit_confirmation_status
= &TMH_PG_update_deposit_confirmation_status;
+
return plugin;
}
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 1a4c15db..fbb662f8 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -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 ********** */
@@ -1136,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];
@@ -1144,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]));
@@ -2195,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,
@@ -2478,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,
@@ -2806,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.
@@ -2817,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;
@@ -2871,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");
@@ -2923,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.
@@ -2934,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,
@@ -4090,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),
@@ -4458,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)
@@ -4666,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)
@@ -4980,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),
@@ -4992,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),
@@ -5016,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 ==
@@ -6568,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;
}
@@ -7020,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 263a6fec..057c9eff 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 **************** */
@@ -1895,6 +1895,172 @@ TALER_MERCHANT_product_delete_cancel (
struct TALER_MERCHANT_ProductDeleteHandle *pdh);
+/* ********************* /tokenfamilies ************************** */
+
+/**
+ * Handle for a GET /tokenfamilies/$SLUG operation.
+ */
+struct TALER_MERCHANT_TokenFamilyGetHandle;
+
+
+/**
+ * Response to GET /tokenfamilies/$SLUG operation.
+ */
+struct TALER_MERCHANT_TokenFamilyGetResponse
+{
+ /**
+ * HTTP response details
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+
+ /**
+ * Identifier for the token family consisting of unreserved characters
+ * according to RFC 3986.
+ */
+ const char *slug;
+
+ /**
+ * Human-readable name for the token family.
+ */
+ const char *name;
+
+ /**
+ * description of the token family
+ */
+ const char *description;
+
+ /**
+ * Optional map from IETF BCP 47 language tags to localized descriptions.
+ */
+ const json_t *description_i18n;
+
+ /**
+ * Start time of the token family's validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * End time of the token family's validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_before;
+
+ /**
+ * Validity duration of an issued token.
+ */
+ struct GNUNET_TIME_Relative duration;
+
+ /**
+ * Kind of token family, "subscription" or "discount".
+ */
+ const char *kind;
+
+ /**
+ * How many tokens have been issued for this family.
+ */
+ uint64_t issued;
+
+ /**
+ * How many tokens have been redeemed for this family.
+ */
+ uint64_t redeemed;
+ } ok;
+
+ } details;
+
+};
+
+/**
+ * Cancel GET /tokenfamilies/$SLUG operation.
+ *
+ * @param handle operation to cancel
+ */
+void
+TALER_MERCHANT_token_family_get_cancel (
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle);
+
+
+/**
+ * Function called with the result of the GET /tokenfamilies/$SLUG operation.
+ *
+ * @param cls closure
+ * @param pgr response details
+ */
+typedef void
+(*TALER_MERCHANT_TokenFamilyGetCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_TokenFamilyGetResponse *pgr);
+
+/**
+ * Handle for a POST /tokenfamilies operation.
+ */
+struct TALER_MERCHANT_TokenFamiliesPostHandle;
+
+
+/**
+ * Function called with the result of the POST /tokenfamilies operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_TokenFamiliesPostCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a POST /tokenfamilies request to add a token family to the
+ * merchant instance.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param slug short, url-safe identifier for the token family
+ * @param name human-readable name for the token family
+ * @param description description of the token family
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param valid_after when the token family becomes valid
+ * @param valid_before when the token family expires
+ * @param duration how long tokens issued by this token family are valid for
+ * @param kind kind of token family, "subscription" or "discount"
+ * @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_TokenFamiliesPostHandle *
+TALER_MERCHANT_token_families_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *slug,
+ const char *name,
+ const char *description,
+ const json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind,
+ TALER_MERCHANT_TokenFamiliesPostCallback cb,
+ void *cb_cls);
+
+/**
+ * Cancel POST /tokenfamilies operation.
+ *
+ * @param handle operation to cancel
+ */
+void
+TALER_MERCHANT_token_families_post_cancel (
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle);
+
/* ********************* /orders ************************** */
@@ -2664,6 +2830,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 b1de5292..47d081fc 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -27,6 +27,7 @@
#ifndef TALER_MERCHANT_TESTING_LIB_H
#define TALER_MERCHANT_TESTING_LIB_H
+#include <gnunet/gnunet_time_lib.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
@@ -605,6 +606,35 @@ TALER_TESTING_cmd_merchant_post_orders3 (
/**
+ * Create an order with a choices array with input and output tokens.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param merchant_url base URL of the merchant serving
+ * the proposal request.
+ * @param http_status expected HTTP status.
+ * @param token_family_reference label of the POST /tokenfamilies cmd.
+ * @param order_id the name of the order to add.
+ * @param refund_deadline the deadline for refunds on this order.
+ * @param pay_deadline the deadline for payment on this order.
+ * @param amount the amount this order is for.
+ * @return the command
+ */
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders_choices (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *token_family_reference,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *amount);
+
+
+/**
* Define a "GET /orders" CMD.
*
* @param label command label.
@@ -1473,6 +1503,40 @@ TALER_TESTING_cmd_merchant_post_using_templates (
unsigned int http_status);
+/* ****** Token Families ******* */
+
+
+/**
+ * Define a "POST /tokenfamilies" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /tokenfamilies request.
+ * @param http_status expected HTTP response code.
+ * @param slug slug of the token family.
+ * @param name name of the token family.
+ * @param description description of the token family.
+ * @param description_i18n internationalized description of the token family.
+ * @param valid_after start of the validity time of the token family.
+ * @param valid_before end of the validity time of the token family.
+ * @param duration validity duration of an issued token of the token family.
+ * @param kind kind of the token family. either "subscription" or "discount".
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_tokenfamilies (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *slug,
+ const char *name,
+ const char *description,
+ json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind);
+
/* ****** Webhooks ******* */
@@ -1737,7 +1801,10 @@ TALER_TESTING_cmd_checkserver2 (const char *label,
op (http_method, const char) \
op (header_template, const char) \
op (body_template, const char) \
- op (summary, const char)
+ op (summary, const char) \
+ op (token_family_slug, const char) \
+ op (token_family_duration, const struct GNUNET_TIME_Relative) \
+ op (token_family_kind, const char)
/**
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index e7eb2d0f..44fdc0ab 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -23,6 +23,8 @@
#ifndef TALER_MERCHANTDB_PLUGIN_H
#define TALER_MERCHANTDB_PLUGIN_H
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_time_lib.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <taler/taler_exchange_service.h>
@@ -932,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
@@ -943,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);
@@ -1092,17 +1096,17 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails
/**
* Token family public key.
*/
- struct TALER_TokenFamilyPublicKey pub;
+ struct TALER_TokenFamilyPublicKey *pub;
/**
- * Hash of the token family public key.
+ * Token family private key.
*/
- struct TALER_TokenFamilyPublicKeyHash pub_h;
+ struct TALER_TokenFamilyPrivateKey *priv;
/**
- * Token family private key.
- */
- struct TALER_TokenFamilyPrivateKey priv;
+ * Details about the token family this key belongs to.
+ */
+ struct TALER_MERCHANTDB_TokenFamilyDetails token_family;
};
/**
@@ -3238,7 +3242,7 @@ struct TALER_MERCHANTDB_Plugin
* Insert details about a particular token family.
*
* @param cls closure
- * @param instance_id instance to insert product for
+ * @param instance_id instance to insert token family for
* @param token_family_slug slug of token family to insert
* @param details the token family details to insert
* @return database result code
@@ -3250,6 +3254,49 @@ struct TALER_MERCHANTDB_Plugin
const char *token_family_slug,
const struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+ /**
+ * Lookup details about a particular token family key.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family key for
+ * @param token_family_slug slug of token family to lookup
+ * @param min_valid_after lower bound of the start of the key validation period
+ * @param max_valid_after upper bound of the start of the key validation period
+ * @param[out] details set to the token family key details on success, can be NULL
+ * (in that case we only want to check if the token family key exists)
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_token_family_key) (
+ void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
+
+
+ /**
+ * Insert details a key pair for a token family.
+ *
+ * @param cls closure
+ * @param token_family_slug slug of token family to insert the key pair for
+ * @param pub token family public key
+ * @param priv token family private key
+ * @param valid_after start of the key validation period
+ * @param valid_before end of the key validation period
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_token_family_key)(
+ void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before);
+
/**
* Lookup deposits that are finished and awaiting a wire transfer.
*
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 04b2b089..1e7430d4 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -62,6 +62,7 @@ libtalermerchant_la_SOURCES = \
merchant_api_post_products.c \
merchant_api_post_transfers.c \
merchant_api_post_templates.c \
+ merchant_api_post_tokenfamilies.c \
merchant_api_post_using_templates.c \
merchant_api_post_webhooks.c \
merchant_api_wallet_get_order.c \
diff --git a/src/lib/merchant_api_get_config.c b/src/lib/merchant_api_get_config.c
index ddbc20a3..b4b700bd 100644
--- a/src/lib/merchant_api_get_config.c
+++ b/src/lib/merchant_api_get_config.c
@@ -34,12 +34,12 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define MERCHANT_PROTOCOL_CURRENT 13
+#define MERCHANT_PROTOCOL_CURRENT 14
/**
* How many configs are we backwards-compatible with?
*/
-#define MERCHANT_PROTOCOL_AGE 1
+#define MERCHANT_PROTOCOL_AGE 2
/**
* How many exchanges do we allow at most per merchant?
diff --git a/src/lib/merchant_api_get_tokenfamily.c b/src/lib/merchant_api_get_tokenfamily.c
new file mode 100644
index 00000000..d7e6b06e
--- /dev/null
+++ b/src/lib/merchant_api_get_tokenfamily.c
@@ -0,0 +1,210 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_get_tokenfamily.c
+ * @brief Implementation of the GET /tokenfamily/$ID request of the merchant's HTTP API
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /tokenfamilies/$SLUG operation.
+ */
+struct TALER_MERCHANT_TokenFamilyGetHandle
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TokenFamilyGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /tokenfamilies/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TokenFamilyGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_token_family_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_TokenFamilyGetResponse res = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ handle->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /tokenfamilies/$ID response with status code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ // Parse token family response
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("slug",
+ &res.details.ok.slug),
+ GNUNET_JSON_spec_string ("name",
+ &res.details.ok.name),
+ GNUNET_JSON_spec_string ("description",
+ &res.details.ok.description),
+ GNUNET_JSON_spec_object_const ("description_i18n",
+ &res.details.ok.description_i18n),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &res.details.ok.valid_after),
+ GNUNET_JSON_spec_timestamp ("valid_before",
+ &res.details.ok.valid_before),
+ GNUNET_JSON_spec_relative_time ("duation",
+ &res.details.ok.duration),
+ GNUNET_JSON_spec_string ("kind",
+ &res.details.ok.kind),
+ GNUNET_JSON_spec_uint64 ("issued",
+ &res.details.ok.issued),
+ GNUNET_JSON_spec_uint64 ("redeemed",
+ &res.details.ok.redeemed),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK ==
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ handle->cb (handle->cb_cls,
+ &res);
+ GNUNET_JSON_parse_free (spec);
+ TALER_MERCHANT_token_family_get_cancel (handle);
+ return;
+ }
+ res.hr.http_status = 0;
+ res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ res.hr.ec = TALER_JSON_get_error_code (json);
+ res.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ res.hr.ec = TALER_JSON_get_error_code (json);
+ res.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ res.hr.ec = TALER_JSON_get_error_code (json);
+ res.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) res.hr.ec);
+ break;
+ }
+}
+
+struct TALER_MERCHANT_TokenFamilyGetHandle *
+TALER_MERCHANT_token_family_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *token_family_slug,
+ TALER_MERCHANT_TokenFamilyGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle;
+ CURL *eh;
+
+ handle = GNUNET_new (struct TALER_MERCHANT_TokenFamilyGetHandle);
+ handle->ctx = ctx;
+ handle->cb = cb;
+ handle->cb_cls = cb_cls;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "private/tokenfamilies/%s",
+ token_family_slug);
+ handle->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == handle->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (handle);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ handle->url);
+ eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
+ handle->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_token_family_finished,
+ handle);
+ return handle;
+}
+
+void
+TALER_MERCHANT_token_family_get_cancel (
+ struct TALER_MERCHANT_TokenFamilyGetHandle *handle)
+{
+ if (NULL != handle->job)
+ GNUNET_CURL_job_cancel (handle->job);
+ GNUNET_free (handle->url);
+ GNUNET_free (handle);
+} \ No newline at end of file
diff --git a/src/lib/merchant_api_merchant_get_order.c b/src/lib/merchant_api_merchant_get_order.c
index 3a49db34..3bd4003b 100644
--- a/src/lib/merchant_api_merchant_get_order.c
+++ b/src/lib/merchant_api_merchant_get_order.c
@@ -202,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 ()
};
diff --git a/src/lib/merchant_api_post_tokenfamilies.c b/src/lib/merchant_api_post_tokenfamilies.c
new file mode 100644
index 00000000..0c5e18c2
--- /dev/null
+++ b/src/lib/merchant_api_post_tokenfamilies.c
@@ -0,0 +1,246 @@
+/*
+ This file is part of TALER
+ 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
+ published by the Free Software Foundation; either version 2.1,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with TALER; see the file COPYING.LGPL.
+ If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_post_tokenfamilies.c
+ * @brief Implementation of the POST /tokenfamilies request
+ * of the merchant's HTTP API
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include "merchant_api_common.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /tokenfamilies operation.
+ */
+struct TALER_MERCHANT_TokenFamiliesPostHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_TokenFamiliesPostCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+};
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /tokenfamilies request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TokenFamiliesPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_token_families_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ handle->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /tokenfamilies completed with response code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case 0:
+ hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* This should never happen, either us
+ * or the merchant is buggy (or API version conflict);
+ * just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we need to authenticate. */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, merchant says we tried to abort the payment
+ * after it was successful. We should pass the JSON reply to the
+ * application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the
+ application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ /* Server had an internal issue; we should retry,
+ but this API leaves this to the application */
+ break;
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &hr);
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ handle->cb (handle->cb_cls,
+ &hr);
+ TALER_MERCHANT_token_families_post_cancel (handle);
+}
+
+struct TALER_MERCHANT_TokenFamiliesPostHandle *
+TALER_MERCHANT_token_families_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *slug,
+ const char *name,
+ const char *description,
+ const json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind,
+ TALER_MERCHANT_TokenFamiliesPostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("slug",
+ slug),
+ GNUNET_JSON_pack_string ("name",
+ name),
+ GNUNET_JSON_pack_string ("description",
+ description),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ (json_t *) description_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("valid_after",
+ valid_after)),
+ GNUNET_JSON_pack_timestamp ("valid_before",
+ valid_before),
+ GNUNET_JSON_pack_time_rel ("duration",
+ duration),
+ GNUNET_JSON_pack_string ("kind",
+ kind));
+ handle = GNUNET_new (struct TALER_MERCHANT_TokenFamiliesPostHandle);
+ handle->ctx = ctx;
+ handle->cb = cb;
+ handle->cb_cls = cb_cls;
+ handle->url = TALER_url_join (backend_url,
+ "private/tokenfamilies",
+ NULL);
+ if (NULL == handle->url)
+ {
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (req_obj);
+ GNUNET_free (handle);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&handle->post_ctx,
+ eh,
+ req_obj));
+ json_decref (req_obj);
+ handle->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ handle->post_ctx.headers,
+ &handle_post_token_families_finished,
+ handle);
+ GNUNET_assert (NULL != handle->job);
+ }
+ return handle;
+}
+
+
+void
+TALER_MERCHANT_token_families_post_cancel (
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *pph)
+{
+ if (NULL != pph->job)
+ {
+ GNUNET_CURL_job_cancel (pph->job);
+ pph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&pph->post_ctx);
+ GNUNET_free (pph->url);
+ GNUNET_free (pph);
+}
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index 49eb8cc8..67bbbced 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -71,6 +71,7 @@ libtalermerchanttesting_la_SOURCES = \
testing_api_cmd_post_products.c \
testing_api_cmd_post_transfers.c \
testing_api_cmd_post_templates.c \
+ testing_api_cmd_post_tokenfamilies.c \
testing_api_cmd_post_using_templates.c \
testing_api_cmd_post_webhooks.c \
testing_api_cmd_refund_order.c \
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index ed07bce6..3f9136bc 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -24,6 +24,7 @@
* @author Marcello Stanisci
*/
#include "platform.h"
+#include <gnunet/gnunet_time_lib.h>
#include <taler/taler_util.h>
#include <taler/taler_signatures.h>
#include <taler/taler_exchange_service.h>
@@ -1656,6 +1657,55 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
+ struct TALER_TESTING_Command tokens[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ cmd_transfer_to_exchange ("create-reserve-tokens",
+ "EUR:10.02"),
+ /**
+ * Make a reserve exist, according to the previous transfer.
+ */
+ cmd_exec_wirewatch ("wirewatch-1"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-tokens",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-tokens"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-tokens",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-tokens",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_tokenfamilies ("create-tokenfamily",
+ merchant_url,
+ MHD_HTTP_NO_CONTENT,
+ "subscription-1",
+ "Subscription",
+ "A subscription.",
+ NULL,
+ GNUNET_TIME_timestamp_get (),
+ GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS),
+ GNUNET_TIME_UNIT_MONTHS,
+ "subscription"),
+ TALER_TESTING_cmd_merchant_post_orders_choices ("create-order-with-choices",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-tokenfamily",
+ "5-choices",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ "EUR:5.0"),
+
+ TALER_TESTING_cmd_end ()
+ };
+
struct TALER_TESTING_Command commands[] = {
/* general setup */
TALER_TESTING_cmd_run_fakebank (
@@ -1978,6 +2028,8 @@ run (void *cls,
auth),
TALER_TESTING_cmd_batch ("repurchase",
repurchase),
+ TALER_TESTING_cmd_batch ("tokens",
+ tokens),
/**
* End the suite.
*/
diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh
index 175667b9..2336ad4e 100755
--- a/src/testing/test_merchant_order_creation.sh
+++ b/src/testing/test_merchant_order_creation.sh
@@ -244,6 +244,76 @@ fi
echo "OK"
#
+# CREATE TOKEN FAMILY AND V1 ORDER WITH CHOICES
+#
+echo -n "Creating token family ..."
+NOW=$(date +%s)
+IN_A_YEAR=$((NOW + 31536000))
+STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
+ -d '{"slug":"test-sub","kind":"subscription","description":"Test token family","name":"Test Subscription","valid_after":{"t_s":'$NOW'},"valid_before":{"t_s":'$IN_A_YEAR'},"duration": {"d_us": 2592000000}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 204, token family created. got: $STATUS"
+fi
+
+echo " OK"
+
+echo -n "Creating v1 order with token family ..."
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"version":"1","amount":"TESTKUDOS:7","summary":"with_subscription","fulfillment_message":"Paid successfully","choices":[{"inputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}],"outputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}]}]}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order created. got: $STATUS"
+fi
+
+echo " OK"
+
+echo -n "Claming order with token family ..."
+
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
+
+STATUS=$(curl http://localhost:9966/orders/"$ORDER_ID"/claim \
+ -d '{"nonce":"","token":"'"$TOKEN"'"}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order claimed. got: $STATUS"
+fi
+
+echo " OK"
+
+# echo -n "Fetching pay URL for order ..."
+# STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+# -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+# if [ "$STATUS" != "200" ]
+# then
+# jq . < "$LAST_RESPONSE"
+# exit_fail "Expected 200, getting order info before claming it. got: $STATUS"
+# fi
+
+# PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
+
+# echo " OK"
+
+# NOW=$(date +%s)
+
+# echo -n "Pay for order ${PAY_URL} ..."
+# taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log
+# taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log
+# NOW2=$(date +%s)
+# echo " OK (took $(( NOW2 - NOW )) secs )"
+
+#
# CREATE ORDER WITH NON-INVENTORY AND CHECK
#
diff --git a/src/testing/testing_api_cmd_post_orders.c b/src/testing/testing_api_cmd_post_orders.c
index d5cfdddc..8f7bd46d 100644
--- a/src/testing/testing_api_cmd_post_orders.c
+++ b/src/testing/testing_api_cmd_post_orders.c
@@ -24,6 +24,10 @@
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+#include <stdint.h>
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
@@ -56,6 +60,11 @@ struct OrdersState
const char *expected_order_id;
/**
+ * Reference to a POST /tokenfamilies command. Can be NULL.
+ */
+ const char *token_family_reference;
+
+ /**
* Contract terms obtained from the backend.
*/
json_t *contract_terms;
@@ -66,6 +75,11 @@ struct OrdersState
json_t *order_terms;
/**
+ * Choices array with inputs and outputs for v1 order.
+ */
+ json_t *choices;
+
+ /**
* Contract terms hash code.
*/
struct TALER_PrivateContractHashP h_contract_terms;
@@ -563,6 +577,118 @@ orders_run2 (void *cls,
/**
+ * Constructs the json for a the choices of an order request.
+ *
+ * @param slug the name of the order to add, can be NULL.
+ * @param valid_after valid_after date for the input and output token.
+ * @param[out] choices where to write the json string.
+ */
+static void
+make_choices_json (
+ const char *input_slug,
+ const char *output_slug,
+ uint16_t input_count,
+ uint16_t output_count,
+ struct GNUNET_TIME_Timestamp input_valid_after,
+ struct GNUNET_TIME_Timestamp output_valid_after,
+ json_t **choices)
+{
+ json_t *c;
+
+ c = json_pack("[{s:o, s:o}]",
+ "inputs", json_pack("[{s:s, s:i, s:s, s:o}]",
+ "kind", "token",
+ "count", input_count,
+ "token_family_slug", input_slug,
+ "valid_after", GNUNET_JSON_from_timestamp(input_valid_after)),
+ "outputs", json_pack("[{s:s, s:i, s:s, s:o}]",
+ "kind", "token",
+ "count", output_count,
+ "token_family_slug", output_slug,
+ "valid_after", GNUNET_JSON_from_timestamp(output_valid_after)));
+
+ *choices = c;
+}
+
+
+/**
+ * Run a "orders" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+orders_run3 (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct OrdersState *ps = cls;
+ struct GNUNET_TIME_Absolute now;
+ const char *slug;
+
+ ps->is = is;
+ now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
+ if (NULL == json_object_get (ps->order_terms,
+ "order_id"))
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &now,
+ sizeof (now));
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "order_id",
+ json_string (order_id)));
+ GNUNET_free (order_id);
+ }
+
+ {
+ const struct TALER_TESTING_Command *token_family_cmd;
+ token_family_cmd =
+ TALER_TESTING_interpreter_lookup_command (is,
+ ps->token_family_reference);
+ if (NULL == token_family_cmd)
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_token_family_slug (token_family_cmd,
+ &slug))
+ TALER_TESTING_FAIL (is);
+ }
+ make_choices_json (slug, slug,
+ 1, 1,
+ GNUNET_TIME_absolute_to_timestamp(now),
+ GNUNET_TIME_absolute_to_timestamp(now),
+ &ps->choices);
+
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "choices",
+ ps->choices)
+ );
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "version",
+ json_string ("1"))
+ );
+
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ps->nonce,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ ps->po = TALER_MERCHANT_orders_post (TALER_TESTING_interpreter_get_context (
+ is),
+ ps->merchant_url,
+ ps->order_terms,
+ GNUNET_TIME_UNIT_ZERO,
+ &order_cb,
+ ps);
+ GNUNET_assert (NULL != ps->po);
+}
+
+
+/**
* Free the state of a "orders" CMD, and possibly
* cancel it if it did not complete.
*
@@ -651,8 +777,7 @@ make_order_json (const char *order_id,
"dummy_array", /* For testing forgetting parts of arrays */
"item", "speakers",
"item", "headphones",
- "item", "earbuds"
- );
+ "item", "earbuds");
GNUNET_assert (GNUNET_OK ==
TALER_JSON_expand_path (contract_terms,
"$.dummy_obj",
@@ -828,3 +953,43 @@ TALER_TESTING_cmd_merchant_post_orders3 (
return cmd;
}
}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders_choices (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *token_family_reference,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *amount)
+{
+ struct OrdersState *ps;
+
+ ps = GNUNET_new (struct OrdersState);
+ ps->cfg = cfg;
+ make_order_json (order_id,
+ refund_deadline,
+ pay_deadline,
+ amount,
+ &ps->order_terms);
+ ps->http_status = http_status;
+ ps->token_family_reference = token_family_reference;
+ ps->expected_order_id = order_id;
+ ps->merchant_url = merchant_url;
+ ps->with_claim = true;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &orders_run3,
+ .cleanup = &orders_cleanup,
+ .traits = &orders_traits
+ };
+
+ return cmd;
+ }
+} \ No newline at end of file
diff --git a/src/testing/testing_api_cmd_post_products.c b/src/testing/testing_api_cmd_post_products.c
index 4ffafddc..c841f1b1 100644
--- a/src/testing/testing_api_cmd_post_products.c
+++ b/src/testing/testing_api_cmd_post_products.c
@@ -35,7 +35,7 @@ struct PostProductsState
{
/**
- * Handle for a "GET product" request.
+ * Handle for a "POST /products" request.
*/
struct TALER_MERCHANT_ProductsPostHandle *iph;
diff --git a/src/testing/testing_api_cmd_post_tokenfamilies.c b/src/testing/testing_api_cmd_post_tokenfamilies.c
new file mode 100644
index 00000000..aafff9ef
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_tokenfamilies.c
@@ -0,0 +1,272 @@
+/*
+ This file is part of TALER
+ 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 Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file testing_api_cmd_post_tokenfamilies.c
+ * @brief command to run POST /tokenfamilies
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_time_lib.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /tokenfamilies" CMD.
+ */
+struct PostTokenFamiliesState
+{
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Handle for a "POST /tokenfamilies" request.
+ */
+ struct TALER_MERCHANT_TokenFamiliesPostHandle *handle;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Slug of the token family.
+ */
+ const char *slug;
+
+ /**
+ * Name of the token family.
+ */
+ const char *name;
+
+ /**
+ * Description of the token family.
+ */
+ const char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized descriptions.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Start of the validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * End of the validity period.
+ */
+ struct GNUNET_TIME_Timestamp valid_before;
+
+ /**
+ * Validity duation of issued tokens of this family.
+ */
+ struct GNUNET_TIME_Relative duration;
+
+ /**
+ * Kind of the token family. "subscription" or "discount".
+ */
+ const char *kind;
+};
+
+
+/**
+ * Callback for a POST /tokenfamilies operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+post_tokenfamilies_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PostTokenFamiliesState *state = cls;
+
+ state->handle = NULL;
+ if (state->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (state->is));
+ TALER_TESTING_interpreter_fail (state->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ default:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for POST /tokenfamilies.\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (state->is);
+}
+
+/**
+ * Run the "POST /tokenfamilies" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_tokenfamilies_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostTokenFamiliesState *state = cls;
+
+ state->is = is;
+ state->handle = TALER_MERCHANT_token_families_post (
+ TALER_TESTING_interpreter_get_context (is),
+ state->merchant_url,
+ state->slug,
+ state->name,
+ state->description,
+ state->description_i18n,
+ state->valid_after,
+ state->valid_before,
+ state->duration,
+ state->kind,
+ &post_tokenfamilies_cb,
+ state);
+ GNUNET_assert (NULL != state->handle);
+}
+
+/**
+ * Offers information from the "POST /tokenfamilies" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+post_tokenfamilies_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PostTokenFamiliesState *state = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_token_family_slug (state->slug),
+ TALER_TESTING_make_trait_timestamp (0,
+ &state->valid_after),
+ TALER_TESTING_make_trait_timestamp (1,
+ &state->valid_before),
+ TALER_TESTING_make_trait_token_family_duration (&state->duration),
+ TALER_TESTING_make_trait_token_family_kind (state->kind),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+/**
+ * Free the state of a "POST /tokenfamilies" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_tokenfamilies_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostTokenFamiliesState *state = cls;
+
+ if (NULL != state->handle)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /tokenfamilies operation did not complete\n");
+ TALER_MERCHANT_token_families_post_cancel (state->handle);
+ }
+ json_decref (state->description_i18n);
+ GNUNET_free (state);
+}
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_tokenfamilies (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *slug,
+ const char *name,
+ const char *description,
+ json_t *description_i18n,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before,
+ struct GNUNET_TIME_Relative duration,
+ const char *kind) /* "subscription" or "discount" */
+{
+ struct PostTokenFamiliesState *state;
+
+ GNUNET_assert ((NULL == description_i18n) ||
+ json_is_object (description_i18n));
+ state = GNUNET_new (struct PostTokenFamiliesState);
+ state->merchant_url = merchant_url;
+ state->http_status = http_status;
+ state->slug = slug;
+ state->name = name;
+ state->description = description;
+ state->description_i18n = description_i18n; /* ownership taken */
+ state->valid_after = valid_after;
+ state->valid_before = valid_before;
+ state->duration = duration;
+ state->kind = kind;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = state,
+ .label = label,
+ .run = &post_tokenfamilies_run,
+ .cleanup = &post_tokenfamilies_cleanup,
+ .traits = &post_tokenfamilies_traits
+ };
+
+ return cmd;
+ }
+} \ No newline at end of file