commit e3a8b30d8681cbc10f0357e74f8a09fa63f14c36
parent 5da68e54540be098f405ceef00fb648478bb73df
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 19 Jan 2016 14:39:23 +0100
Merge branch 'master' of git.taler.net:/var/git/merchant
Diffstat:
38 files changed, 5442 insertions(+), 419 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -25,3 +25,7 @@ GPATH
GRTAGS
GTAGS
*.swp
+src/backend/taler-merchant-httpd
+src/lib/test_merchant_api
+taler_merchant_config.h
+taler_merchant_config.h.in
diff --git a/configure.ac b/configure.ac
@@ -126,7 +126,8 @@ AS_IF([test $jansson = 0],
*** ]])])
# check for libgnurl
-LIBGNURL_CHECK_CONFIG([], [7.34.0], [gnurl=1], [gnurl=0])
+# libgnurl
+LIBGNURL_CHECK_CONFIG(,7.34.0,gnurl=1,gnurl=0)
if test "$gnurl" = 1
then
AM_CONDITIONAL(HAVE_LIBGNURL, true)
@@ -134,11 +135,52 @@ then
else
AM_CONDITIONAL(HAVE_LIBGNURL, false)
fi
-AS_IF([test $gnurl = 0],
- [AC_MSG_ERROR([[
-***
-*** You need libgnurl to build this program.
-*** ]])])
+
+# libcurl-gnutls
+LIBCURL_CHECK_CONFIG(,7.34.0,[curl=true],[curl=false])
+if test "x$curl" = xtrue
+then
+ AC_CHECK_HEADERS([curl/curl.h],
+ AC_CHECK_DECLS(CURLINFO_TLS_SESSION,[curl=true],[curl=false],[[#include <curl/curl.h>]]),
+ [curl=false])
+ # need libcurl-gnutls.so, everything else is not acceptable
+ AC_CHECK_LIB([curl-gnutls],[curl_easy_getinfo],,[curl=false])
+ # cURL must support CURLINFO_TLS_SESSION, version >= 7.34
+
+fi
+if test x$curl = xfalse
+then
+ AM_CONDITIONAL(HAVE_LIBCURL, false)
+if test "$gnurl" = 0
+then
+ AC_MSG_WARN([GNUnet requires libcurl-gnutls >= 7.34])
+fi
+else
+ AM_CONDITIONAL(HAVE_LIBCURL, true)
+ AC_DEFINE([HAVE_LIBCURL],[1],[Have CURL])
+fi
+
+# libgnurl
+if test "x$gnurl" = "x0"
+then
+ if test "x$curl" = "x0"
+ then
+ AC_MSG_NOTICE([NOTICE: libgnurl not found. http client support will not be compiled.])
+ AC_MSG_WARN([ERROR: libgnurl not found. hostlist daemon will not be compiled, and you probably WANT the hostlist daemon])
+ else
+ AC_MSG_NOTICE([WARNING: libgnurl not found, trying to use libcurl-gnutls instead.])
+ fi
+fi
+
+# gcov compilation
+AC_MSG_CHECKING(whether to compile with support for code coverage analysis)
+AC_ARG_ENABLE([coverage],
+ AS_HELP_STRING([--enable-coverage],
+ [compile the library with code coverage support]),
+ [use_gcov=${enableval}],
+ [use_gcov=no])
+AC_MSG_RESULT($use_gcov)
+AM_CONDITIONAL([USE_COVERAGE], [test "x$use_gcov" = "xyes"])
# Require minimum libgcrypt version
need_libgcrypt_version=1.6.1
@@ -173,5 +215,6 @@ AC_CONFIG_FILES([Makefile
src/Makefile
src/include/Makefile
src/backenddb/Makefile
-src/backend/Makefile])
+src/backend/Makefile
+src/lib/Makefile])
AC_OUTPUT
diff --git a/contrib/merchant.conf b/contrib/merchant.conf
@@ -1,14 +0,0 @@
-[merchant]
-PORT = 4251
-
-# List of mints the merchant trusts delimited by a single space
-TRUSTED_MINTS = taler
-
-[mint-taler]
-HOSTNAME = taler.org
-PORT = 4241
-# The public key of this mint
-PUBKEY = ...
-
-[merchant-db]
-CONFIG = postgres:///taler
-\ No newline at end of file
diff --git a/m4/libcurl.m4 b/m4/libcurl.m4
@@ -0,0 +1,251 @@
+# LIBCURL_CHECK_CONFIG ([DEFAULT-ACTION], [MINIMUM-VERSION],
+# [ACTION-IF-YES], [ACTION-IF-NO])
+# ----------------------------------------------------------
+# David Shaw <dshaw@jabberwocky.com> May-09-2006
+#
+# Checks for libcurl. DEFAULT-ACTION is the string yes or no to
+# specify whether to default to --with-libcurl or --without-libcurl.
+# If not supplied, DEFAULT-ACTION is yes. MINIMUM-VERSION is the
+# minimum version of libcurl to accept. Pass the version as a regular
+# version number like 7.10.1. If not supplied, any version is
+# accepted. ACTION-IF-YES is a list of shell commands to run if
+# libcurl was successfully found and passed the various tests.
+# ACTION-IF-NO is a list of shell commands that are run otherwise.
+# Note that using --without-libcurl does run ACTION-IF-NO.
+#
+# This macro #defines HAVE_LIBCURL if a working libcurl setup is
+# found, and sets @LIBCURL@ and @LIBCURL_CPPFLAGS@ to the necessary
+# values. Other useful defines are LIBCURL_FEATURE_xxx where xxx are
+# the various features supported by libcurl, and LIBCURL_PROTOCOL_yyy
+# where yyy are the various protocols supported by libcurl. Both xxx
+# and yyy are capitalized. See the list of AH_TEMPLATEs at the top of
+# the macro for the complete list of possible defines. Shell
+# variables $libcurl_feature_xxx and $libcurl_protocol_yyy are also
+# defined to 'yes' for those features and protocols that were found.
+# Note that xxx and yyy keep the same capitalization as in the
+# curl-config list (e.g. it's "HTTP" and not "http").
+#
+# Users may override the detected values by doing something like:
+# LIBCURL="-lcurl" LIBCURL_CPPFLAGS="-I/usr/myinclude" ./configure
+#
+# For the sake of sanity, this macro assumes that any libcurl that is
+# found is after version 7.7.2, the first version that included the
+# curl-config script. Note that it is very important for people
+# packaging binary versions of libcurl to include this script!
+# Without curl-config, we can only guess what protocols are available,
+# or use curl_version_info to figure it out at runtime.
+
+AC_DEFUN([LIBCURL_CHECK_CONFIG],
+[
+ AH_TEMPLATE([LIBCURL_FEATURE_SSL],[Defined if libcurl supports SSL])
+ AH_TEMPLATE([LIBCURL_FEATURE_KRB4],[Defined if libcurl supports KRB4])
+ AH_TEMPLATE([LIBCURL_FEATURE_IPV6],[Defined if libcurl supports IPv6])
+ AH_TEMPLATE([LIBCURL_FEATURE_LIBZ],[Defined if libcurl supports libz])
+ AH_TEMPLATE([LIBCURL_FEATURE_ASYNCHDNS],[Defined if libcurl supports AsynchDNS])
+ AH_TEMPLATE([LIBCURL_FEATURE_IDN],[Defined if libcurl supports IDN])
+ AH_TEMPLATE([LIBCURL_FEATURE_SSPI],[Defined if libcurl supports SSPI])
+ AH_TEMPLATE([LIBCURL_FEATURE_NTLM],[Defined if libcurl supports NTLM])
+
+ AH_TEMPLATE([LIBCURL_PROTOCOL_HTTP],[Defined if libcurl supports HTTP])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_HTTPS],[Defined if libcurl supports HTTPS])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_FTP],[Defined if libcurl supports FTP])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_FTPS],[Defined if libcurl supports FTPS])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_FILE],[Defined if libcurl supports FILE])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_TELNET],[Defined if libcurl supports TELNET])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_LDAP],[Defined if libcurl supports LDAP])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_DICT],[Defined if libcurl supports DICT])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_TFTP],[Defined if libcurl supports TFTP])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_RTSP],[Defined if libcurl supports RTSP])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_POP3],[Defined if libcurl supports POP3])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_IMAP],[Defined if libcurl supports IMAP])
+ AH_TEMPLATE([LIBCURL_PROTOCOL_SMTP],[Defined if libcurl supports SMTP])
+
+ AC_ARG_WITH(libcurl,
+ AC_HELP_STRING([--with-libcurl=PREFIX],[look for the curl library in PREFIX/lib and headers in PREFIX/include]),
+ [_libcurl_with=$withval],[_libcurl_with=ifelse([$1],,[yes],[$1])])
+
+ if test "$_libcurl_with" != "no" ; then
+
+ AC_PROG_AWK
+
+ _libcurl_version_parse="eval $AWK '{split(\$NF,A,\".\"); X=256*256*A[[1]]+256*A[[2]]+A[[3]]; print X;}'"
+
+ _libcurl_try_link=yes
+
+ if test -d "$_libcurl_with" ; then
+ LIBCURL_CPPFLAGS="-I$withval/include"
+ _libcurl_ldflags="-L$withval/lib"
+ AC_PATH_PROG([_libcurl_config],[curl-config],[],
+ ["$withval/bin"])
+ else
+ AC_PATH_PROG([_libcurl_config],[curl-config],[],[$PATH])
+ fi
+
+ if test x$_libcurl_config != "x" ; then
+ AC_CACHE_CHECK([for the version of libcurl],
+ [libcurl_cv_lib_curl_version],
+ [libcurl_cv_lib_curl_version=`$_libcurl_config --version | $AWK '{print $[]2}'`])
+
+ _libcurl_version=`echo $libcurl_cv_lib_curl_version | $_libcurl_version_parse`
+ _libcurl_wanted=`echo ifelse([$2],,[0],[$2]) | $_libcurl_version_parse`
+
+ if test $_libcurl_wanted -gt 0 ; then
+ AC_CACHE_CHECK([for libcurl >= version $2],
+ [libcurl_cv_lib_version_ok],
+ [
+ if test $_libcurl_version -ge $_libcurl_wanted ; then
+ libcurl_cv_lib_version_ok=yes
+ else
+ libcurl_cv_lib_version_ok=no
+ fi
+ ])
+ fi
+
+ if test $_libcurl_wanted -eq 0 || test x$libcurl_cv_lib_version_ok = xyes ; then
+ if test x"$LIBCURL_CPPFLAGS" = "x" ; then
+ LIBCURL_CPPFLAGS=`$_libcurl_config --cflags`
+ fi
+ if test x"$LIBCURL" = "x" ; then
+ LIBCURL=`$_libcurl_config --libs`
+
+ # This is so silly, but Apple actually has a bug in their
+ # curl-config script. Fixed in Tiger, but there are still
+ # lots of Panther installs around.
+ case "${host}" in
+ powerpc-apple-darwin7*)
+ LIBCURL=`echo $LIBCURL | sed -e 's|-arch i386||g'`
+ ;;
+ esac
+ fi
+
+ # All curl-config scripts support --feature
+ _libcurl_features=`$_libcurl_config --feature`
+
+ # Is it modern enough to have --protocols? (7.12.4)
+ if test $_libcurl_version -ge 461828 ; then
+ _libcurl_protocols=`$_libcurl_config --protocols`
+ fi
+ else
+ _libcurl_try_link=no
+ fi
+
+ unset _libcurl_wanted
+ fi
+
+ if test $_libcurl_try_link = yes ; then
+
+ # we didn't find curl-config, so let's see if the user-supplied
+ # link line (or failing that, "-lcurl") is enough.
+ LIBCURL=${LIBCURL-"$_libcurl_ldflags -lcurl"}
+
+ AC_CACHE_CHECK([whether libcurl is usable],
+ [libcurl_cv_lib_curl_usable],
+ [
+ _libcurl_save_cppflags=$CPPFLAGS
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ _libcurl_save_libs=$LIBS
+ LIBS="$LIBCURL $LIBS"
+
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <curl/curl.h>]],[[
+/* Try and use a few common options to force a failure if we are
+ missing symbols or can't link. */
+int x;
+curl_easy_setopt(NULL,CURLOPT_URL,NULL);
+x=CURL_ERROR_SIZE;
+x=CURLOPT_WRITEFUNCTION;
+x=CURLOPT_WRITEDATA;
+x=CURLOPT_ERRORBUFFER;
+x=CURLOPT_STDERR;
+x=CURLOPT_VERBOSE;
+if (x) ;
+]])],libcurl_cv_lib_curl_usable=yes,libcurl_cv_lib_curl_usable=no)
+
+ CPPFLAGS=$_libcurl_save_cppflags
+ LIBS=$_libcurl_save_libs
+ unset _libcurl_save_cppflags
+ unset _libcurl_save_libs
+ ])
+
+ if test $libcurl_cv_lib_curl_usable = yes ; then
+
+ # Does curl_free() exist in this version of libcurl?
+ # If not, fake it with free()
+
+ _libcurl_save_cppflags=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $LIBCURL_CPPFLAGS"
+ _libcurl_save_libs=$LIBS
+ LIBS="$LIBS $LIBCURL"
+
+ AC_CHECK_FUNC(curl_free,,
+ AC_DEFINE(curl_free,free,
+ [Define curl_free() as free() if our version of curl lacks curl_free.]))
+
+ CPPFLAGS=$_libcurl_save_cppflags
+ LIBS=$_libcurl_save_libs
+ unset _libcurl_save_cppflags
+ unset _libcurl_save_libs
+
+ AC_DEFINE(HAVE_LIBCURL,1,
+ [Define to 1 if you have a functional curl library.])
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL)
+
+ for _libcurl_feature in $_libcurl_features ; do
+ AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_feature_$_libcurl_feature),[1])
+ eval AS_TR_SH(libcurl_feature_$_libcurl_feature)=yes
+ done
+
+ if test "x$_libcurl_protocols" = "x" ; then
+
+ # We don't have --protocols, so just assume that all
+ # protocols are available
+ _libcurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP"
+
+ if test x$libcurl_feature_SSL = xyes ; then
+ _libcurl_protocols="$_libcurl_protocols HTTPS"
+
+ # FTPS wasn't standards-compliant until version
+ # 7.11.0 (0x070b00 == 461568)
+ if test $_libcurl_version -ge 461568; then
+ _libcurl_protocols="$_libcurl_protocols FTPS"
+ fi
+ fi
+
+ # RTSP, IMAP, POP3 and SMTP were added in
+ # 7.20.0 (0x071400 == 463872)
+ if test $_libcurl_version -ge 463872; then
+ _libcurl_protocols="$_libcurl_protocols RTSP IMAP POP3 SMTP"
+ fi
+ fi
+
+ for _libcurl_protocol in $_libcurl_protocols ; do
+ AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_protocol_$_libcurl_protocol),[1])
+ eval AS_TR_SH(libcurl_protocol_$_libcurl_protocol)=yes
+ done
+ else
+ unset LIBCURL
+ unset LIBCURL_CPPFLAGS
+ fi
+ fi
+
+ unset _libcurl_try_link
+ unset _libcurl_version_parse
+ unset _libcurl_config
+ unset _libcurl_feature
+ unset _libcurl_features
+ unset _libcurl_protocol
+ unset _libcurl_protocols
+ unset _libcurl_version
+ unset _libcurl_ldflags
+ fi
+
+ if test x$_libcurl_with = xno || test x$libcurl_cv_lib_curl_usable != xyes ; then
+ # This is the IF-NO path
+ ifelse([$4],,:,[$4])
+ else
+ # This is the IF-YES path
+ ifelse([$3],,:,[$3])
+ fi
+
+ unset _libcurl_with
+])dnl
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf
@@ -25,7 +25,7 @@ EDATE = 3 week
DB = postgres
[mint-taler]
-URI = mint.demo.taler.net
+URI = http://mint.demo.taler.net/
MASTER_KEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0
# Auditors must be in sections "auditor-", the rest of the section
@@ -46,9 +46,10 @@ PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
[merchantdb-postgres]
CONFIG = postgres:///talerdemo
-
# "wire-" sections include wire details, here for SEPA.
[wire-sepa]
IBAN = DE67830654080004822650
NAME = GNUNET E.V
BIC = GENODEF1SRL
+ADDRESS = "Garching"
+SALT = 12345
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -334,6 +334,7 @@ parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg)
unsigned long long salt;
char *iban;
char *name;
+ char *address;
char *bic;
if (GNUNET_OK !=
@@ -366,21 +367,35 @@ parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg)
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
"wire-sepa",
+ "ADDRESS",
+ &address))
+ {
+ GNUNET_free (iban);
+ GNUNET_free (name);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "wire-sepa",
"BIC",
&bic))
{
GNUNET_free (iban);
GNUNET_free (name);
GNUNET_free (bic);
+ GNUNET_free (address);
+ return GNUNET_SYSERR;
}
- j_wire = json_pack ("{s:s, s:s, s:s, s:s, s:o}",
+ j_wire = json_pack ("{s:s, s:s, s:s, s:s, s:o, s:s}",
"type", "SEPA",
"IBAN", iban,
"name", name,
"bic", bic,
- "r", json_integer (salt));
+ "r", json_integer (salt),
+ "address", address);
GNUNET_free (iban);
GNUNET_free (name);
+ GNUNET_free (address);
GNUNET_free (bic);
if (NULL == j_wire)
return GNUNET_SYSERR;
@@ -454,6 +469,7 @@ prepare_daemon ()
tv = GNUNET_TIME_UNIT_FOREVER_REL;
GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding run_daemon select task\n");
ret =
GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
tv, wrs, wws,
@@ -491,7 +507,7 @@ run (void *cls,
TMH_AUDITORS_init (config));
/* FIXME: for now, we just support SEPA here: */
EXITIF (GNUNET_OK !=
- parse_wireformat_sepa (config));
+ parse_wireformat_sepa (config));
EXITIF (GNUNET_OK !=
validate_and_hash_wireformat ("SEPA"));
EXITIF (GNUNET_OK !=
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
@@ -136,19 +136,29 @@ extern json_t *j_wire;
*/
extern struct GNUNET_HashCode h_wire;
-
+/**
+ * Our private key (for the merchant to sign contracts).
+ */
extern struct GNUNET_CRYPTO_EddsaPrivateKey *privkey;
+/**
+ * Our public key, corresponds to #privkey.
+ */
extern struct TALER_MerchantPublicKeyP pubkey;
-
+/**
+ * Handle to the database backend.
+ */
extern struct TALER_MERCHANTDB_Plugin *db;
-
-
+/**
+ * If the frontend does NOT specify an execution date, how long should
+ * we tell the mint to wait to aggregate transactions before
+ * executing? This delay is added to the current time when we
+ * generate the advisory execution time for the mint.
+ */
extern struct GNUNET_TIME_Relative edate_delay;
-
/**
* Kick MHD to run now, to be called after MHD_resume_connection().
* Basically, we need to explicitly resume MHD's event loop whenever
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
@@ -52,8 +52,10 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
size_t *upload_data_size)
{
json_t *root;
+ json_t *jcontract;
+ json_t *pay_url;
+ json_t *exec_url;
int res;
- struct GNUNET_HashCode h_wire;
struct TALER_ContractPS contract;
struct GNUNET_CRYPTO_EddsaSignature contract_sig;
@@ -68,39 +70,64 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
if ((GNUNET_NO == res) || (NULL == root))
return MHD_YES;
- /* add fields to the "root" that the backend should provide */
- json_object_set (root,
+ jcontract = json_object_get (root, "contract");
+
+ if (NULL == jcontract)
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ "contract request malformed");
+ }
+
+ /* add fields to the contract that the backend should provide */
+ json_object_set (jcontract,
"mints",
trusted_mints);
- json_object_set (root,
+ json_object_set (jcontract,
"auditors",
j_auditors);
- json_object_set_new (root,
+ json_object_set_new (jcontract,
"H_wire",
TALER_json_from_data (&h_wire,
sizeof (h_wire)));
- json_object_set_new (root,
+ json_object_set_new (jcontract,
"merchant_pub",
TALER_json_from_data (&pubkey,
sizeof (pubkey)));
/* create contract signature */
GNUNET_assert (GNUNET_OK ==
- TALER_hash_json (root,
+ TALER_hash_json (jcontract,
&contract.h_contract));
contract.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT);
contract.purpose.size = htonl (sizeof (contract));
GNUNET_CRYPTO_eddsa_sign (privkey,
&contract.purpose,
&contract_sig);
+
+ pay_url = json_object_get (root, "pay_url");
+ if (NULL == pay_url)
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ "pay url missing");
+ }
+ exec_url = json_object_get (root, "exec_url");
+ if (NULL == exec_url)
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ "exec url missing");
+ }
/* return final response */
- return TMH_RESPONSE_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:o, s:o, s:o}",
- "contract", root,
- "sig", TALER_json_from_data (&contract_sig,
- sizeof (contract_sig)),
- "H_contract", TALER_json_from_data (&contract.h_contract,
- sizeof (contract.h_contract)));
+ res = TMH_RESPONSE_reply_json_pack (connection,
+ MHD_HTTP_OK,
+ "{s:O, s:O, s:O, s:o, s:o}",
+ "contract", jcontract,
+ "exec_url", exec_url,
+ "pay_url", pay_url,
+ "sig", TALER_json_from_data (&contract_sig,
+ sizeof (contract_sig)),
+ "H_contract", TALER_json_from_data (&contract.h_contract,
+ sizeof (contract.h_contract)));
+ json_decref (root);
+ return res;
}
/* end of taler-merchant-httpd_contract.c */
diff --git a/src/backend/taler-merchant-httpd_mints.c b/src/backend/taler-merchant-httpd_mints.c
@@ -230,6 +230,8 @@ context_task (void *cls,
struct GNUNET_NETWORK_FDSet *ws;
struct GNUNET_TIME_Relative delay;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "In mint context polling task\n");
+
poller_task = NULL;
TALER_MINT_perform (ctx);
max_fd = -1;
@@ -243,6 +245,9 @@ context_task (void *cls,
&except_fd_set,
&max_fd,
&timeout);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "In mint context polling task, max_fd=%d, timeout=%ld\n",
+ max_fd, timeout);
if (timeout >= 0)
delay =
GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
@@ -319,6 +324,11 @@ TMH_MINTS_find_mint (const char *chosen_mint,
GNUNET_break (0);
return NULL;
}
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Trying to find chosen mint `%s'\n",
+ chosen_mint);
+
/* Check if the mint is known */
for (mint = mint_head; NULL != mint; mint = mint->next)
/* test it by checking public key --- FIXME: hostname or public key!?
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c
@@ -15,8 +15,9 @@
*/
/**
* @file backend/taler-merchant-httpd_pay.c
- * @brief HTTP serving layer mainly intended to communicate with the frontend
+ * @brief handling of /pay requests
* @author Marcello Stanisci
+ * @author Christian Grothoff
*/
#include "platform.h"
#include <jansson.h>
@@ -67,6 +68,11 @@ struct MERCHANT_DepositConfirmation
struct TALER_Amount percoin_amount;
/**
+ * Amount this coin contributes to the total purchase price.
+ */
+ struct TALER_Amount amount_without_fee;
+
+ /**
* Public key of the coin.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
@@ -112,6 +118,12 @@ struct PayContext
struct MHD_Connection *connection;
/**
+ * Handle to the mint that we are doing the payment with.
+ * (initially NULL while @e fo is trying to find a mint).
+ */
+ struct TALER_MINT_Handle *mh;
+
+ /**
* Handle for operation to lookup /keys (and auditors) from
* the mint used for this transaction; NULL if no operation is
* pending.
@@ -219,6 +231,29 @@ resume_pay_with_response (struct PayContext *pc,
/**
+ * Abort all pending /deposit operations.
+ *
+ * @param pc pay context to abort
+ */
+static void
+abort_deposit (struct PayContext *pc)
+{
+ unsigned int i;
+
+ for (i=0;i<pc->coins_cnt;i++)
+ {
+ struct MERCHANT_DepositConfirmation *dci = &pc->dc[i];
+
+ if (NULL != dci->dh)
+ {
+ TALER_MINT_deposit_cancel (dci->dh);
+ dci->dh = NULL;
+ }
+ }
+}
+
+
+/**
* Callback to handle a deposit permission's response.
*
* @param cls a `struct MERCHANT_DepositConfirmation` (i.e. a pointer
@@ -245,26 +280,38 @@ deposit_cb (void *cls,
pc->pending--;
if (MHD_HTTP_OK != http_status)
{
- unsigned int i;
-
/* Transaction failed; stop all other ongoing deposits */
- for (i=0;i<pc->coins_cnt;i++)
- {
- struct MERCHANT_DepositConfirmation *dci = &pc->dc[i];
-
- if (NULL != dci->dh)
- {
- TALER_MINT_deposit_cancel (dci->dh);
- dci->dh = NULL;
- }
- }
+ abort_deposit (pc);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Deposit operation failed with HTTP code %u\n",
+ http_status);
/* Forward error including 'proof' for the body */
resume_pay_with_response (pc,
http_status,
TMH_RESPONSE_make_json (proof));
return;
}
- /* FIXME: store result to DB here somewhere! */
+ /* store result to DB */
+ if (GNUNET_OK !=
+ db->store_payment (db->cls,
+ &pc->h_contract,
+ &h_wire,
+ pc->transaction_id,
+ pc->timestamp,
+ pc->refund_deadline,
+ &dc->amount_without_fee,
+ &dc->coin_pub,
+ proof))
+ {
+ GNUNET_break (0);
+ /* internal error */
+ abort_deposit (pc);
+ /* Forward error including 'proof' for the body */
+ resume_pay_with_response (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TMH_RESPONSE_make_internal_error ("Merchant database error"));
+ return;
+ }
if (0 != pc->pending)
return; /* still more to do */
resume_pay_with_response (pc,
@@ -351,6 +398,7 @@ process_pay_with_mint (void *cls,
TMH_RESPONSE_make_external_error ("mint not supported"));
return;
}
+ pc->mh = mh;
keys = TALER_MINT_get_keys (mh);
if (NULL == keys)
@@ -372,6 +420,7 @@ process_pay_with_mint (void *cls,
&dc->denom);
if (NULL == denom_details)
{
+ GNUNET_break_op (0);
resume_pay_with_response (pc,
MHD_HTTP_BAD_REQUEST,
TMH_RESPONSE_make_json_pack ("{s:s, s:o}",
@@ -384,6 +433,7 @@ process_pay_with_mint (void *cls,
denom_details,
mint_trusted))
{
+ GNUNET_break_op (0);
resume_pay_with_response (pc,
MHD_HTTP_BAD_REQUEST,
TMH_RESPONSE_make_json_pack ("{s:s, s:o}",
@@ -398,12 +448,37 @@ process_pay_with_mint (void *cls,
}
else
{
- TALER_amount_add (&acc_fee,
- &denom_details->fee_deposit,
- &acc_fee);
- TALER_amount_add (&acc_amount,
- &dc->percoin_amount,
- &acc_amount);
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&acc_fee,
+ &denom_details->fee_deposit,
+ &acc_fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_add (&acc_amount,
+ &dc->percoin_amount,
+ &acc_amount)) )
+ {
+ GNUNET_break_op (0);
+ /* Overflow in these amounts? Very strange. */
+ resume_pay_with_response (pc,
+ MHD_HTTP_BAD_REQUEST,
+ TMH_RESPONSE_make_internal_error ("Overflow adding up amounts"));
+ return;
+ }
+ }
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&dc->amount_without_fee,
+ &dc->percoin_amount,
+ &denom_details->fee_deposit))
+ {
+ GNUNET_break_op (0);
+ /* fee higher than residual coin value, makes no sense. */
+ resume_pay_with_response (pc,
+ MHD_HTTP_BAD_REQUEST,
+ TMH_RESPONSE_make_json_pack ("{s:s, s:o, s:o}",
+ "hint", "fee higher than coin value",
+ "f", TALER_json_from_amount (&dc->percoin_amount),
+ "fee_deposit", TALER_json_from_amount (&denom_details->fee_deposit)));
+ return;
}
}
@@ -436,6 +511,7 @@ process_pay_with_mint (void *cls,
if (-1 == TALER_amount_cmp (&acc_amount,
&total_needed))
{
+ GNUNET_break_op (0);
resume_pay_with_response (pc,
MHD_HTTP_NOT_ACCEPTABLE,
TMH_RESPONSE_make_external_error ("insufficient funds (including excessive mint fees to be covered by customer)"));
@@ -448,6 +524,7 @@ process_pay_with_mint (void *cls,
if (-1 == TALER_amount_cmp (&acc_amount,
&pc->amount))
{
+ GNUNET_break_op (0);
resume_pay_with_response (pc,
MHD_HTTP_NOT_ACCEPTABLE,
TMH_RESPONSE_make_external_error ("insufficient funds"));
@@ -477,11 +554,14 @@ process_pay_with_mint (void *cls,
dc);
if (NULL == dc->dh)
{
+ /* Signature was invalid. If the mint was unavailable,
+ * we'd get that information in the callback. */
+ GNUNET_break_op (0);
resume_pay_with_response (pc,
- MHD_HTTP_SERVICE_UNAVAILABLE,
+ MHD_HTTP_UNAUTHORIZED,
TMH_RESPONSE_make_json_pack ("{s:s, s:i}",
- "mint", pc->chosen_mint,
- "transaction_id", pc->transaction_id));
+ "hint", "Coin signature invalid.",
+ "coin_idx", i));
return;
}
}
@@ -511,6 +591,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
int res;
json_t *root;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "In handler for /pay.\n");
+
if (NULL == *connection_cls)
{
pc = GNUNET_new (struct PayContext);
@@ -528,6 +610,7 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
/* We are *done* processing the request, just queue the response (!) */
if (UINT_MAX == pc->response_code)
return MHD_NO; /* hard error */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Queueing response for /pay.\n");
res = MHD_queue_response (connection,
pc->response_code,
pc->response);
@@ -549,23 +632,6 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
if ((GNUNET_NO == res) || (NULL == root))
return MHD_YES; /* the POST's body has to be further fetched */
- /* We got no 'edate' from frontend. Generate it here; it will be
- timestamp plus the edate_delay supplied in config file */
- if (NULL == json_object_get (root, "edate"))
- {
- pc->edate = GNUNET_TIME_absolute_add (pc->timestamp, // FIXME: uninit!
- edate_delay);
- if (-1 ==
- json_object_set (root,
- "edate",
- TALER_json_from_abs (pc->edate)))
- {
- GNUNET_break (0);
- json_decref (root);
- return MHD_NO;
- }
- }
-
/* Got the JSON upload, parse it */
{
json_t *coins;
@@ -580,19 +646,47 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
TMH_PARSE_member_time_abs ("timestamp", &pc->timestamp),
TMH_PARSE_member_time_abs ("refund_deadline", &pc->refund_deadline),
TMH_PARSE_member_fixed ("H_contract", &pc->h_contract),
- TMH_PARSE_member_time_abs ("edate", &pc->edate),
TMH_PARSE_MEMBER_END
};
res = TMH_PARSE_json_data (connection,
root,
spec);
+
if (GNUNET_YES != res)
{
json_decref (root);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Parsed JSON for /pay.\n");
+
+ /* 'edate' is optional, if it is not present, generate it here; it
+ will be timestamp plus the edate_delay supplied in config
+ file */
+ if (NULL == json_object_get (root, "edate"))
+ {
+ pc->edate = GNUNET_TIME_absolute_add (pc->timestamp,
+ edate_delay);
+ }
+ else
+ {
+ struct TMH_PARSE_FieldSpecification espec[] = {
+ TMH_PARSE_member_time_abs ("edate", &pc->edate),
+ TMH_PARSE_MEMBER_END
+ };
+
+ res = TMH_PARSE_json_data (connection,
+ root,
+ espec);
+ if (GNUNET_YES != res)
+ {
+ json_decref (root);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ }
+
+
pc->coins_cnt = json_array_size (coins);
if (0 == pc->coins_cnt)
{
@@ -600,9 +694,15 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
return TMH_RESPONSE_reply_external_error (connection,
"no coins given");
}
+ /* note: 1 coin = 1 deposit confirmation expected */
pc->dc = GNUNET_new_array (pc->coins_cnt,
struct MERCHANT_DepositConfirmation);
+ {
+ char *s = json_dumps (coins, JSON_INDENT(2));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Coins json is: %s\n", s);
+ }
+
json_array_foreach (coins, coins_index, coin)
{
struct MERCHANT_DepositConfirmation *dc = &pc->dc[coins_index];
@@ -623,10 +723,26 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
json_decref (root);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
+
+ {
+ char *s;
+ s = TALER_amount_to_string (&dc->percoin_amount);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Coin #%i has f %s\n", coins_index, s);
+ GNUNET_free (s);
+ }
+
dc->index = coins_index;
+ dc->pc = pc;
}
}
+ /* Check if this payment attempt has already taken place */
+ if (GNUNET_OK == db->check_payment (db->cls, pc->transaction_id))
+ return TMH_RESPONSE_reply_external_error (connection, "payment already attempted");
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Looking up chosen mint '%s'\n", pc->chosen_mint);
+
/* Find the responsible mint, this may take a while... */
pc->pending = pc->coins_cnt;
pc->fo = TMH_MINTS_find_mint (pc->chosen_mint,
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
@@ -70,14 +70,14 @@ postgres_initialize (void *cls,
"CREATE %1$s TABLE IF NOT EXISTS payments ("
"h_contract BYTEA NOT NULL,"
"h_wire BYTEA NOT NULL,"
- "transaction_id INT8 PRIMARY KEY,"
+ "transaction_id INT8," /*WARNING: this column used to be primary key, but that wrong since multiple coins belong to the same id*/
"timestamp INT8 NOT NULL,"
"refund_deadline INT8 NOT NULL,"
"amount_without_fee_val INT8 NOT NULL,"
"amount_without_fee_frac INT4 NOT NULL,"
"amount_without_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL,"
"coin_pub BYTEA NOT NULL,"
- "mint_sig BYTEA NOT NULL);",
+ "mint_proof BYTEA NOT NULL);",
tmp_str);
ret = GNUNET_POSTGRES_exec (pg->conn,
sql);
@@ -96,7 +96,7 @@ postgres_initialize (void *cls,
",amount_without_fee_frac"
",amount_without_fee_curr"
",coin_pub"
- ",mint_sig) VALUES "
+ ",mint_proof) VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
10, NULL))) ||
(PGRES_COMMAND_OK != (status = PQresultStatus(res))) )
@@ -108,6 +108,22 @@ postgres_initialize (void *cls,
}
return GNUNET_SYSERR;
}
+ if ( (NULL == (res = PQprepare (pg->conn,
+ "check_payment",
+ "SELECT * "
+ "FROM payments "
+ "WHERE transaction_id=$1",
+ 1, NULL))) ||
+ (PGRES_COMMAND_OK != (status = PQresultStatus(res))) )
+ {
+ if (NULL != res)
+ {
+ PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res);
+ PQclear (res);
+ }
+ return GNUNET_SYSERR;
+ }
+
PQclear (res);
return GNUNET_OK;
}
@@ -124,7 +140,7 @@ postgres_initialize (void *cls,
* @param refund refund deadline
* @param amount_without_fee amount the mint will deposit
* @param coin_pub public key of the coin
- * @param merchant_pub our public key
+ * @param mint_proof proof from the mint that coin was accepted
* @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
*/
static int
@@ -136,7 +152,7 @@ postgres_store_payment (void *cls,
struct GNUNET_TIME_Absolute refund,
const struct TALER_Amount *amount_without_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MintSignatureP *mint_sig)
+ json_t *mint_proof)
{
struct PostgresClosure *pg = cls;
PGresult *res;
@@ -150,7 +166,7 @@ postgres_store_payment (void *cls,
TALER_PQ_query_param_absolute_time (&refund),
TALER_PQ_query_param_amount (amount_without_fee),
TALER_PQ_query_param_auto_from_type (coin_pub),
- TALER_PQ_query_param_auto_from_type (mint_sig),
+ TALER_PQ_query_param_json (mint_proof),
TALER_PQ_query_param_end
};
@@ -190,7 +206,61 @@ postgres_store_payment (void *cls,
return GNUNET_OK;
}
+/**
+ * Check whether a payment has already been stored
+ *
+ * @param cls our plugin handle
+ * @param transaction_id the transaction id to search into
+ * the db
+ *
+ * @return GNUNET_OK if found, GNUNET_NO if not, GNUNET_SYSERR
+ * upon error
+ */
+static int
+postgres_check_payment(void *cls,
+ uint64_t transaction_id)
+{
+ struct PostgresClosure *pg = cls;
+ PGresult *res;
+ ExecStatusType status;
+ struct TALER_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_uint64 (&transaction_id),
+ TALER_PQ_query_param_end
+ };
+ res = TALER_PQ_exec_prepared (pg->conn,
+ "check_payment",
+ params);
+
+ status = PQresultStatus (res);
+ if (PGRES_TUPLES_OK != status)
+ {
+ const char *sqlstate;
+
+ sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE);
+ if (NULL == sqlstate)
+ {
+ /* very unexpected... */
+ GNUNET_break (0);
+ PQclear (res);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not check if contract %llu is in DB: %s\n",
+ transaction_id,
+ sqlstate);
+ PQclear (res);
+ return GNUNET_SYSERR;
+ }
+ /* count rows */
+ if (PQntuples (res) > 0)
+ return GNUNET_OK;
+ return GNUNET_NO;
+
+
+
+}
/**
* Initialize Postgres database subsystem.
*
@@ -220,6 +290,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->cls = pg;
plugin->initialize = &postgres_initialize;
plugin->store_payment = &postgres_store_payment;
+ plugin->check_payment = &postgres_check_payment;
return plugin;
}
diff --git a/src/frontend/checkout.php b/src/frontend/checkout.php
@@ -1,7 +1,8 @@
<!DOCTYPE html>
<html>
<head>
-<title>Taler's "Demo" Shop: Choose payment method</title>
+ <title>Toy Store - Payment method - Taler Demo</title>
+ <link rel="stylesheet" type="text/css" href="style.css">
<script>
/*
@licstart The following is the entire license notice for the
@@ -31,7 +32,7 @@
</head>
<body onload="signal_taler_wallet_onload()">
<!--
- This page's main aim is to show to the customer all the accepted
+ This main goal of this page is to show to the customer all the accepted
payments methods and actually implementing just Taler; technically
the steps are:
@@ -50,52 +51,78 @@
$donation_currency = $_POST['donation_currency'];
// get frational part
- list ($donation_value, $donation_fraction) = split ("\.", $donation_amount, 2);
+ list ($donation_value, $donation_fraction) = explode (".", $donation_amount, 2);
// create PHP session and store donation information in session
$donation_fraction = (float) ("0." . $donation_fraction);
session_start();
+ session_unset();
$_SESSION['receiver'] = $donation_receiver;
$_SESSION['amount_value'] = (int) $donation_amount;
$_SESSION['amount_fraction'] = (int) ($donation_fraction * 1000000);
$_SESSION['currency'] = $donation_currency;
?>
-<h2>Select your payment option</h2>
-<div>
-This is an example for a "checkout" page of a Web shop.
-On the previous page, you have created the shopping cart
-and decided which product to buy (i.e. which project to
-donate KUDOS to). Now in this page, you are asked to
-select a payment option. As Taler is not yet universally
-used, we expect merchants will offer various payment options.
-<p>
-The page also demonstrates how to only enable (or show) the Taler
-option if Taler is actually supported by the browser. For example,
-if you disable the Taler extension now, the Taler payment option
-will be disabled in the page. Naturally, you could also trivially
-hide the Taler option entirely by changing the visibility instead.
-<p>
-Note that you MUST select Taler here for the demo to continue,
-as the other payment options are just placeholders and not
-really working in the demonstration. Also, it is of course
-possible to ask the user to make this choice already on the
-previous page (with the shopping cart), we just separated the
-two steps to keep each step as simple as possible.
-</div>
-<form name="tform" action="" method="POST">
- <div id="opt-form" align="left"><br>
- <input type="radio" name="payment_system" value="lisa"
- id="lisa-radio-button-id">Lisa</input>
- <br>
- <input type="radio" name="payment_system" value="ycard">You Card</input>
- <br>
- <input type="radio" name="payment_system" value="cardme">Card Me</input>
- <br>
- <input type="radio" name="payment_system" value="taler"
- id="taler-radio-button-id" disabled="true">Taler</input>
- <br>
- <input type="button" onclick="pay(this.form)" value="Ok">
- </div>
-</form>
+
+ <header>
+ <div id="logo">
+ <svg height="100" width="100">
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
+ </svg>
+ </div>
+
+ <h1>Toy Store - Select payment method</h1>
+ </header>
+
+ <aside class="sidebar" id="left">
+ </aside>
+
+ <section id="main">
+ <article>
+
+ <h1>Select your payment method</h1>
+
+ <p>
+ This is an example for a "checkout" page of a Web shop.
+ On the previous page, you have created the shopping cart
+ and decided which product to buy (i.e. which project to
+ donate KUDOS to). Now in this page, you are asked to
+ select a payment option. As Taler is not yet universally
+ used, we expect merchants will offer various payment options.
+ </p>
+ <p>
+ The page also demonstrates how to only enable (or show) the Taler
+ option if Taler is actually supported by the browser. For example,
+ if you disable the Taler extension now, the Taler payment option
+ will be disabled in the page. Naturally, you could also trivially
+ hide the Taler option entirely by changing the visibility instead.
+ </p>
+ <p>
+ Note that you MUST select Taler here for the demo to continue,
+ as the other payment options are just placeholders and not
+ really working in the demonstration. Also, it is of course
+ possible to ask the user to make this choice already on the
+ previous page (with the shopping cart), we just separated the
+ two steps to keep each step as simple as possible.
+ </p>
+
+ <form name="tform" action="" method="POST">
+ <div id="opt-form" align="left"><br>
+ <input type="radio" name="payment_system" value="lisa"
+ id="lisa-radio-button-id">Lisa</input>
+ <br/>
+ <input type="radio" name="payment_system" value="ycard">You Card</input>
+ <br/>
+ <input type="radio" name="payment_system" value="cardme">Card Me</input>
+ <br/>
+ <input type="radio" name="payment_system" value="taler"
+ id="taler-radio-button-id" disabled="true">Taler</input>
+ <br/>
+ <input type="button" onclick="pay(this.form)" value="Ok"></input>
+ </div>
+ </form>
+
+ </article>
+ </section>
<script type="text/javascript">
@@ -106,7 +133,7 @@ function handle_contract(json_contract)
{
var cEvent = new CustomEvent('taler-contract', { detail: json_contract });
- document.body.dispatchEvent(cEvent);
+ document.dispatchEvent(cEvent);
};
@@ -121,7 +148,7 @@ function taler_pay(form)
have its own way of generating and transmitting the
contract, there just must be a way to get the contract
and to pass it to the wallet when the user selects 'Pay'. */
- contract_request.open("GET", "/generate_taler_contract.php", true);
+ contract_request.open("GET", "generate_taler_contract.php", true);
contract_request.onload = function (e)
{
if (contract_request.readyState == 4)
@@ -129,9 +156,9 @@ function taler_pay(form)
if (contract_request.status == 200)
{
/* display contract_requestificate (i.e. it sends the JSON string
- to the extension) alert (contract_request.responseText); */
+ to the extension) alert (contract_request.responseText); */
+ console.log("response text:", contract_request.responseText);
handle_contract(contract_request.responseText);
-
}
else
{
@@ -211,7 +238,7 @@ function taler_wallet_unload_cb(aEvent)
function signal_taler_wallet_onload()
{
var eve = new Event('taler-checkout-probe');
- document.body.dispatchEvent(eve);
+ document.dispatchEvent(eve);
};
@@ -228,21 +255,20 @@ function test_without_wallet(){
// Register event to be triggered by the wallet as a response to our
// first event
-document.body.addEventListener("taler-wallet-present",
- has_taler_wallet_cb,
- false);
+document.addEventListener("taler-wallet-present",
+ has_taler_wallet_cb,
+ false);
// Register event to be triggered by the wallet when it gets enabled while
// the user is on the payment page
-document.body.addEventListener("taler-load",
- signal_taler_wallet_onload,
- false);
+document.addEventListener("taler-load",
+ signal_taler_wallet_onload,
+ false);
// Register event to be triggered by the wallet when it is unloaded
-document.body.addEventListener("taler-unload",
- taler_wallet_unload_cb,
- false);
-
+document.addEventListener("taler-unload",
+ taler_wallet_unload_cb,
+ false);
</script>
</body>
</html>
diff --git a/src/frontend/execute.js b/src/frontend/execute.js
@@ -0,0 +1,33 @@
+"use strict";
+// JSX literals are compiled to calls to React.createElement calls.
+let React = {
+ createElement: function (tag, props, ...children) {
+ let e = document.createElement(tag);
+ for (let k in props) {
+ e.setAttribute(k, props[k]);
+ }
+ for (let child of children) {
+ if ("string" === typeof child || "number" == typeof child) {
+ child = document.createTextNode(child);
+ }
+ e.appendChild(child);
+ }
+ return e;
+ }
+};
+document.addEventListener("DOMContentLoaded", function (e) {
+ var eve = new CustomEvent('taler-execute-payment', { detail: { H_contract: h_contract } });
+ document.dispatchEvent(eve);
+});
+function replace(el, r) {
+ el.parentNode.replaceChild(r, el);
+}
+document.addEventListener("taler-payment-result", function (e) {
+ if (!e.detail.success) {
+ alert("Payment failed\n" + JSON.stringify(e.detail));
+ return;
+ }
+ console.log("finished payment");
+ let msg = React.createElement("div", null, "Payment successful. View your ", React.createElement("a", {"href": e.detail.fulfillmentUrl}, "product"), ".");
+ replace(document.getElementById("loading"), msg);
+});
diff --git a/src/frontend/execute.php b/src/frontend/execute.php
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Toy Store - Taler Demo</title>
+ <link rel="stylesheet" type="text/css" href="style.css">
+ <script> /* @licstart The following is the entire license notice for the
+ JavaScript code in this page.
+
+ Copyright (C) 2015 GNUnet e.V.
+
+ The JavaScript code in this page is free software: you can
+ redistribute it and/or modify it under the terms of the GNU
+ Lesser General Public License (GNU LGPL) as published by the Free Software
+ Foundation, either version 2.1 of the License, or (at your option)
+ any later version. The code is distributed WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU LGPL for more details.
+
+ As additional permission under GNU LGPL version 2.1 section 7, you
+ may distribute non-source (e.g., minimized or compacted) forms of
+ that code without the copy of the GNU LGPL normally required by
+ section 4, provided you include this license notice and a URL
+ through which recipients can access the Corresponding Source.
+
+ @licend The above is the entire license notice
+ for the JavaScript code in this page.
+ */
+ </script>
+ <script type="text/javascript">
+<?php
+session_start();
+echo "var h_contract=\"$_SESSION[H_contract]\";\n";
+?>
+ </script>
+ <script type="text/javascript" src="execute.js"></script>
+</head>
+
+<body>
+ <header>
+ <div id="logo">
+ <svg height="100" width="100">
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
+ </svg>
+ </div>
+ <h1>Toy Store - Taler Demo</h1>
+ </header>
+
+ <aside class="sidebar" id="left">
+ </aside>
+
+ <section id="main">
+ <h1>Executing Payment ...</h1>
+ <div id="loading">Loading...</div>
+</body>
+</html>
diff --git a/src/frontend/execute.tsx b/src/frontend/execute.tsx
@@ -0,0 +1,41 @@
+"use strict";
+
+
+// JSX literals are compiled to calls to React.createElement calls.
+let React = {
+ createElement: function(tag, props, ...children) {
+ let e = document.createElement(tag);
+ for (let k in props) {
+ e.setAttribute(k, props[k]);
+ }
+ for (let child of children) {
+ if ("string" === typeof child || "number" == typeof child) {
+ child = document.createTextNode(child);
+ }
+ e.appendChild(child);
+ }
+ return e;
+ }
+};
+
+declare var h_contract: string;
+
+document.addEventListener("DOMContentLoaded", function (e) {
+ var eve = new CustomEvent('taler-execute-payment', {detail: {H_contract: h_contract}});
+ document.dispatchEvent(eve);
+});
+
+function replace(el, r) {
+ el.parentNode.replaceChild(r, el);
+}
+
+document.addEventListener("taler-payment-result", function (e: CustomEvent) {
+ if (!e.detail.success) {
+ alert("Payment failed\n" + JSON.stringify(e.detail));
+ return;
+ }
+ console.log("finished payment");
+ let msg =
+ <div>Payment successful. View your <a href={e.detail.fulfillmentUrl}>product</a>.</div>;
+ replace(document.getElementById("loading"), msg);
+});
diff --git a/src/frontend/fulfillment.php b/src/frontend/fulfillment.php
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Taler's "Demo" Shop</title>
+ <link rel="stylesheet" type="text/css" href="style.css">
+</head>
+<body>
+
+ <header>
+ <div id="logo">
+ <svg height="100" width="100">
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
+ </svg>
+ </div>
+
+ <h1>Toy Store - Payment succeeded</h1>
+ </header>
+
+ <aside class="sidebar" id="left">
+ </aside>
+
+ <section id="main">
+ <article>
+<?php
+/*
+ This file is part of GNU TALER.
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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. If not, If not, see <http://www.gnu.org/licenses/>
+
+*/
+
+$cli_debug = false;
+$backend_test = true;
+
+function generate_msg ($link){
+ $msg = "<p>Thanks for donating to " . $_SESSION['receiver'] . ".</p>";
+ if (false != $link)
+ $msg .= "<p>Check our latest <a href=\"" . $link . "\">news!</a></p>";
+ return $msg;
+}
+
+if ($_GET['cli_debug'] == 'yes')
+ $cli_debug = true;
+
+if ($_GET['backend_test'] == 'no')
+{
+ $cli_debug = true;
+ $backend_test = false;
+}
+
+session_start();
+
+if (! isset ($_SESSION['payment_ok']))
+ echo "<p>Please land here after a successful payment!</p>";
+else{
+ $news = false;
+ switch ($_SESSION['receiver']){
+ case "Taler":
+ $news = "https://taler.net/news";
+ break;
+ case "GNUnet":
+ $news = "https://gnunet.org/";
+ break;
+ case "Tor":
+ $news = "https://www.torproject.org/press/press.html.en";
+ break;
+ }
+ echo generate_msg ($news);
+}
+
+?>
+ </article>
+ </section>
+</body>
+</html>
diff --git a/src/frontend/fullfillment.php b/src/frontend/fullfillment.php
@@ -1,50 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <title>Taler's "Demo" Shop</title>
- <link rel="stylesheet" type="text/css" href="style.css">
-</head>
-<body>
-
-<?php
-/*
- This file is part of GNU TALER.
- Copyright (C) 2014, 2015 GNUnet e.V.
-
- 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. If not, If not, see <http://www.gnu.org/licenses/>
-
-*/
-
-$cli_debug = false;
-$backend_test = true;
-
-if ($_GET['cli_debug'] == 'yes')
- $cli_debug = true;
-
-if ($_GET['backend_test'] == 'no')
-{
- $cli_debug = true;
- $backend_test = false;
-}
-
-session_start();
-
-
-if (! isset ($_SESSION['payment_ok']))
- echo "Please land here after a successful payment!";
-else
- echo "Thanks for donating to " . $_SESSION['receiver'];
-
-?>
-
-</body>
-</html>
diff --git a/src/frontend/generate_taler_contract.php b/src/frontend/generate_taler_contract.php
@@ -35,10 +35,10 @@
$cli_debug = false;
$backend_test = true;
-if ($_GET['cli_debug'] == 'yes')
+if (isset($_GET['cli_debug']) && $_GET['cli_debug'] == 'yes')
$cli_debug = true;
-if ($_GET['backend_test'] == 'no')
+if (isset($_GET['backend_test']) && $_GET['backend_test'] == 'no')
{
$cli_debug = true;
$backend_test = false;
@@ -90,9 +90,12 @@ $teatax = array ('value' => 1,
// Take a timestamp
$now = new DateTime('now');
+$PAY_URL = "pay.php";
+$EXEC_URL = "execute.php";
+
// pack the JSON for the contract
// --- FIXME: exact format needs review!
-$json = json_encode (array ('amount' => array ('value' => $amount_value,
+$contract = array ('amount' => array ('value' => $amount_value,
'fraction' => $amount_fraction,
'currency' => $currency),
'max_fee' => array ('value' => 3,
@@ -110,7 +113,8 @@ $json = json_encode (array ('amount' => array ('value' => $amount_value,
'delivery_date' => "Some Date Format",
'delivery_location' => 'LNAME1')),
'timestamp' => "/Date(" . $now->getTimestamp() . ")/",
- 'pay_url' => "/taler/pay",
+ 'pay_url' => $PAY_URL,
+ 'exec_url' => $EXEC_URL,
'expiry' => "/Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")/",
'refund_deadline' => "/Date(" . $now->add(new DateInterval('P3M'))->getTimestamp() . ")/",
'merchant' => array ('address' => 'LNAME2',
@@ -138,19 +142,22 @@ $json = json_encode (array ('amount' => array ('value' => $amount_value,
'state' => 'Test State',
'region' => 'Test Region',
'province' => 'Test Province',
- 'ZIP code' => 4908))), JSON_PRETTY_PRINT);
+ 'ZIP code' => 4908)));
+$json = json_encode (array ('contract' => $contract, 'exec_url' => $EXEC_URL, 'pay_url' => $PAY_URL), JSON_PRETTY_PRINT);
if ($cli_debug && !$backend_test)
{
echo $json . "\n";
exit;
}
-// Craft the HTTP request, note that the backend
-// could be on an entirely different machine if
-// desired.
-$req = new http\Client\Request ("POST",
- "http://" . $_SERVER["SERVER_NAME"] . "/backend/contract",
- array ("Content-Type" => "application/json"));
+
+$url = (new http\URL("http://".$_SERVER["HTTP_HOST"]))
+ ->mod(array ("path" => "backend/contract"), http\Url::JOIN_PATH);
+
+$req = new http\Client\Request("POST",
+ $url,
+ array ("Content-Type" => "application/json"));
+
$req->getBody()->append ($json);
// Execute the HTTP request
@@ -168,10 +175,11 @@ http_response_code ($status_code);
if ($status_code != 200)
{
echo "Error while generating the contract";
+ echo $resp->body->toString ();
}
else
-{
- // send the contract back to the wallet without touching it
+{ $got_json = json_decode ($resp->body->toString ());
+ $_SESSION['H_contract'] = $got_json->H_contract;
echo $resp->body->toString ();
}
?>
diff --git a/src/frontend/index.html b/src/frontend/index.html
@@ -1,36 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
- <title>Taler's "Demo" Shop</title>
+ <title>Toy "Store" - Taler Demo</title>
<link rel="stylesheet" type="text/css" href="style.css">
- <script> /* @licstart The following is the entire license notice for the
- JavaScript code in this page.
-
- Copyright (C) 2015 GNUnet e.V.
-
- The JavaScript code in this page is free software: you can
- redistribute it and/or modify it under the terms of the GNU
- Lesser General Public License (GNU LGPL) as published by the Free Software
- Foundation, either version 2.1 of the License, or (at your option)
- any later version. The code is distributed WITHOUT ANY WARRANTY;
- without even the implied warranty of MERCHANTABILITY or FITNESS
- FOR A PARTICULAR PURPOSE. See the GNU LGPL for more details.
-
- As additional permission under GNU LGPL version 2.1 section 7, you
- may distribute non-source (e.g., minimized or compacted) forms of
- that code without the copy of the GNU LGPL normally required by
- section 4, provided you include this license notice and a URL
- through which recipients can access the Corresponding Source.
-
- @licend The above is the entire license notice
- for the JavaScript code in this page.
- */
- </script>
+ <script type="text/javascript">
+ /* @licstart The following is the entire license notice for the
+ JavaScript code in this page.
+
+ Copyright (C) 2015 GNUnet e.V.
+
+ The JavaScript code in this page is free software: you can
+ redistribute it and/or modify it under the terms of the GNU
+ Lesser General Public License (GNU LGPL) as published by the Free Software
+ Foundation, either version 2.1 of the License, or (at your option)
+ any later version. The code is distributed WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU LGPL for more details.
+
+ As additional permission under GNU LGPL version 2.1 section 7, you
+ may distribute non-source (e.g., minimized or compacted) forms of
+ that code without the copy of the GNU LGPL normally required by
+ section 4, provided you include this license notice and a URL
+ through which recipients can access the Corresponding Source.
+
+ @licend The above is the entire license notice
+ for the JavaScript code in this page.
+ */
+ function handleInstall() {
+ var b = document.getElementById("main");
+ b.classList.add("installed");
+ };
+
+ function handleUninstall() {
+ var b = document.getElementById("main");
+ b.classList.remove("installed");
+ };
+
+ function probeTaler() {
+ var eve = new Event("taler-probe");
+ document.dispatchEvent(eve);
+ }
+
+
+ // Only probe taler once the DOM is ready and
+ // we can manipulate it.
+ window.addEventListener("load", probeTaler, false);
+
+ document.addEventListener("taler-wallet-present", handleInstall, false);
+ document.addEventListener("taler-unload", handleUninstall, false);
+ document.addEventListener("taler-load", handleInstall, false);
+
+ </script>
+ <style>
+ *:not(.installed) .if-installed { display: none }
+ .installed .if-not-installed { display: none }
+ *:not(.installed) .if-not-installed { display: initial }
+ .installed .if-installed { display: initial }
+ </style>
</head>
-<body id="css-zen-garden" onload="signal_me()">
- <div class="supporting" id="welcome" role="main">
- <div class="explanation" id="zen-explanation" role="article">
- <h1>Welcome to the Taler "Demo" Shop</h1>
+
+<body>
+ <header>
+ <div id="logo">
+ <svg height="100" width="100">
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
+ </svg>
+ </div>
+
+ <h1>Toy "Store" - Taler Demo</h1>
+ </header>
+
+ <aside class="sidebar" id="left">
+ </aside>
+
+ <section id="main">
+ <article>
+ <h1>Welcome to the Taler Demo Shop</h1>
+
<p>This "toy" website provides you with the ability to
experience using the
<a href="https://www.taler.net/">GNU Taler</a>
@@ -45,152 +92,93 @@
<tt>mint.demo.taler.net</tt> and
<tt>bank.demo.taler.net</tt>, correspond to a Taler mint
and bank with tight Taler integration respectively.
- <!-- TODO: maybe offer the wallet at 'taler.net/extension' in the
- future, instead of at 'demo.taler.net/'? -->
- </p>
- </div>
- </div>
- <div class="supporting" id="instructions" role="main">
- <div class="explanation" id="installation" role="article">
- <h2>Step 1: Installing the Taler wallet (once)</h2>
- <p>First, you need to install the Taler wallet browser extension.
- It is currently only available for Firefox. If you run
- Firefox, simply click <a href="http://demo.taler.net/extension">here</a>
- to download and install the extension. You will then have to
- click on "allow" and "install" dialogs shown by Firefox.
- After that, the Taler logo should appear on the right side
- of your navigation bar.
- <!-- TODO: insert screenshot highlighting the icon? -->
- </p>
- </div>
- <div class="explanation" id="installation-done" role="article" style="display:none;">
- <h2>Step 1: Installing the Taler wallet (done!)</h2>
- <p>Congratulations, you have installed the Taler wallet correctly.
- You can now proceed with the next steps.
</p>
- </div>
- <div class="explanation" id="wire" role="article" style="display:none;">
- <h2>Step 2: Withdraw coins (occasionally)</h2>
- <p>The next step is to withdraw coins, after all you cannot
- pay with an empty wallet. To be allowed to withdraw
- coins from a mint, you first need to transfer currency to the mint
- using the normal banking system, for example by using a
- wire transfer. If the bank offers a tight integration with Taler, it
- may also support this directly over the home banking online interface.
- <br>
- For the demonstration, we have created a "bank" that
- allows you to "wire" funds (in KUDOS) to the mint simply by
- filling in the desired amount into a form. Naturally, when
- using a real bank with real money, you would have to authenticate
- and authorize the transfer.
- <br>
- Note that you would not do this step for each purchase or each shop.
- Payment with Taler is like paying
- with cash: you withdraw currency at the bank (or an ATM) and then
- pay at many merchants without having to authenticate each time.
- <br>
- So, unless you have already done so, please go ahead and withdraw
- KUDOS at the
- <a href="http://bank.demo.taler.net/" target="_blank">Demo bank</a>
- (opens in a new tab).</p>
- </p>
- </div>
- <div class="explanation" id="payment" role="article" style="display:none;">
- <h2>Step 3: Shop! (as long as you have KUDOS left in the wallet)</h2>
- <p>Now it is time to spend your hard earned KUDOS.
- Note that we cannot really tell if you got any yet,
- as your Taler wallet balance is visible to you, but
- of course is hidden entirely from the shop.</p>
- <p>The form that follows corresponds to the shopping
- cart of a real Web shop; however, we kept it
- very simple for the demonstration.</p>
- <p>So, please choose a project and the amount of KUDOS you
- wish to donate:</p>
- <form name="tform" action="/checkout.php" method="POST">
- <div class="participation" id="fake-shop">
- <br>
- <input type="radio" name="donation_receiver" value="Taler" checked="true">GNU Taler</input>
- <br>
- <input type="radio" name="donation_receiver" value="Tor">Tor</input>
- <br>
- <input type="radio" name="donation_receiver" value="GNUnet">GNUnet</input>
+ </article>
+
+ <section>
+
+ <article>
+ <h2>Step 1: Installing the Taler wallet</h2>
+ <p class="if-not-installed">
+ First, you need to <a href="http://demo.taler.net/">install</a>
+ the Taler wallet browser extension.
+ </p>
+ <p class="if-installed">
+ Congratulations, you have installed the Taler wallet correctly.
+ You can now proceed with the next steps.
+ </p>
+ </article>
+
+ <article class="if-installed">
+ <h2>Step 2: Withdraw coins <sup>(occasionally)</sup></h2>
+
+ <p>The next step is to withdraw coins, after all you cannot
+ pay with an empty wallet. To be allowed to withdraw
+ coins from a mint, you first need to transfer currency to the mint
+ using the normal banking system, for example by using a
+ wire transfer. If the bank offers a tight integration with Taler, it
+ may also support this directly over the home banking online interface.
<br>
- <select id="taler-donation" name="donation_amount">
- <option value="0.1">0.1 KUDOS</option>
- <option value="1.0">1 KUDOS</option>
- <option value="6.0">5 KUDOS (*)</option>
- <option value="10.0">10 KUDOS</option>
- </select>
- <input type="hidden" name="donation_currency" value="KUDOS"/>
- <input type="submit" name="keyName" value="Donate!"/>
+ For the demonstration, we have created a "bank" that
+ allows you to "wire" funds (in KUDOS) to the mint simply by
+ filling in the desired amount into a form. Naturally, when
+ using a real bank with real money, you would have to authenticate
+ and authorize the transfer.
<br>
+ Note that you would not do this step for each purchase or each shop.
+ Payment with Taler is like paying
+ with cash: you withdraw currency at the bank (or an ATM) and then
+ pay at many merchants without having to authenticate each time.
<br>
- </div>
- </form>
- <p>(*) To make it a bit more fun, the 5 KUDOS option
- is deliberately implemented with a fault: the merchant will try to
- make you donate 6 KUDOS instead of the 5 KUDOS you got to see. But do
- not worry, you will be given the opportunity to review the
- final offer from the merchant in a window secured
- by the Taler extension. That way, you can spot the
- error before committing to an incorrect contract.</p>
- </p>
- </div>
- </div>
- <script type="text/javascript">
- /* This function is called if/when a Wallet is installed.
- It should enable the parts of the page that only make
- sense after the Wallet has been loaded.
- */
- function wallet_installed_cb(){
- b = document.getElementById("installation");
- b.style.display = 'none';
- b = document.getElementById("installation-done");
- b.style.display = '';
- b = document.getElementById("wire");
- b.style.display = '';
- b = document.getElementById("payment");
- b.style.display = '';
- };
+ So, unless you have already done so, please go ahead and withdraw
+ KUDOS at the
+ <a href="http://bank.demo.taler.net/" target="_blank">Demo bank</a>
+ (opens in a new tab).</p>
+ </article>
- function wallet_uninstalled_cb(){
- b = document.getElementById("installation");
- b.style.display = '';
- b = document.getElementById("installation-done");
- b.style.display = 'none';
- b = document.getElementById("wire");
- b.style.display = 'none';
- b = document.getElementById("payment");
- b.style.display = 'none';
- };
+ <article class="if-installed">
+ <h2>Step 3: Shop! <sup>(as long as you have KUDOS left)</sup></h2>
- /* The merchant signals its taler-friendlyness to the client */
- function signal_me()
- {
- var eve = new Event('taler-checkout-probe');
- document.body.dispatchEvent(eve);
- //alert("signaling");
- };
+ <p>Now it is time to spend your hard earned KUDOS.
+ Note that we cannot really tell if you got any yet,
+ as your Taler wallet balance is visible to you, but
+ of course is hidden entirely from the shop.</p>
+ <p>The form that follows corresponds to the shopping
+ cart of a real Web shop; however, we kept it
+ very simple for the demonstration.</p>
+ <p>So, please choose a project and the amount of KUDOS you
+ wish to donate:</p>
- function test_without_wallet(){
- wallet_installed_cb();
- }
-
- document.body.addEventListener("taler-unload",
- wallet_uninstalled_cb,
- false, false);
-
- /* Set up a listener to be called whenever a Wallet gets installed
- so that the user is led towards the demo's steps progressively */
- document.body.addEventListener("taler-wallet-present",
- wallet_installed_cb,
- false, false);
-
- /* Setup callback to be called whenever the wallet is loaded/enabled
- while the browser is already on this page */
- document.body.addEventListener("taler-load",
- signal_me,
- false);
- </script>
+ <form name="tform" action="checkout.php" method="POST">
+ <div class="participation" id="fake-shop">
+ <br>
+ <input type="radio" name="donation_receiver" value="Taler" checked="true">GNU Taler</input>
+ <br>
+ <input type="radio" name="donation_receiver" value="Tor">Tor</input>
+ <br>
+ <input type="radio" name="donation_receiver" value="GNUnet">GNUnet</input>
+ <br>
+ <select id="taler-donation" name="donation_amount">
+ <option value="0.1">0.1 KUDOS</option>
+ <option value="1.0">1 KUDOS</option>
+ <option value="6.0">5 KUDOS (*)</option>
+ <option value="10.0">10 KUDOS</option>
+ </select>
+ <input type="hidden" name="donation_currency" value="KUDOS"/>
+ <input type="submit" name="keyName" value="Donate!"/>
+ <br>
+ <br>
+ </div>
+ </form>
+ <p>(*) To make it a bit more fun, the 5 KUDOS option
+ is deliberately implemented with a fault: the merchant will try to
+ make you donate 6 KUDOS instead of the 5 KUDOS you got to see. But do
+ not worry, you will be given the opportunity to review the
+ final offer from the merchant in a window secured
+ by the Taler extension. That way, you can spot the
+ error before committing to an incorrect contract.</p>
+ </article>
+ </section>
+ </section>
</body>
</html>
diff --git a/src/frontend/pay.php b/src/frontend/pay.php
@@ -23,39 +23,39 @@
NOTE: 'max_fee' must be consistent with the same value indicated within
the contract; thus, a "real" merchant must implement such a mapping
-*/
+ */
+
+session_start();
$cli_debug = false;
$backend_test = true;
-if ($_GET['cli_debug'] == 'yes')
+if (isset($_GET['cli_debug']) && $_GET['cli_debug'] == 'yes')
+{
$cli_debug = true;
+}
-if ($_GET['backend_test'] == 'no')
+if (isset($_GET['backend_test']) && $_GET['backend_test'] == 'no')
{
$cli_debug = true;
$backend_test = false;
}
-session_start();
-
-if (!$cli_debug && (! isset($_SESSION['receiver'])))
+if (!isset($_SESSION['H_contract']))
{
- http_response_code(400);
- echo "Please, donate to someone before landing here!";
- exit();
+ echo "No session active.";
+ http_response_code (301);
+ return;
}
-$cli_debug = false;
-$backend_test = true;
-
-if ($_GET['cli_debug'] == 'yes')
- $cli_debug = true;
-
-if ($_GET['backend_test'] == 'no')
+if (isset($_SESSION['payment_ok']) && $_SESSION['payment_ok'] == true)
{
- $cli_debug = true;
- $backend_test = false;
+ $_SESSION['payment_ok'] = true;
+ http_response_code (301);
+ $url = (new http\URL($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']))
+ ->mod(array ("path" => "fulfillment.php"), http\Url::JOIN_PATH);
+ header("Location: $url");
+ die();
}
$post_body = file_get_contents('php://input');
@@ -66,15 +66,15 @@ $edate = array ('edate' =>
$deposit_permission = json_decode ($post_body, true);
-$to_add = array ('max_fee' => array ('value' => 3,
- 'fraction' => 8,
- 'currency' => $_SESSION['currency']),
- 'amount' => array ('value' => $_SESSION['amount_value'],
- 'fraction' => $_SESSION['amount_fraction'],
- 'currency' => $_SESSION['currency']));
+$to_add = array('max_fee' => array('value' => 3,
+ 'fraction' => 8,
+ 'currency' => $_SESSION['currency']),
+ 'amount' => array('value' => $_SESSION['amount_value'],
+ 'fraction' => $_SESSION['amount_fraction'],
+ 'currency' => $_SESSION['currency']));
-$new_deposit_permission = array_merge ($deposit_permission, $to_add);
-$new_deposit_permission_edate = array_merge ($new_deposit_permission, $edate);
+$new_deposit_permission = array_merge($deposit_permission, $to_add);
+$new_deposit_permission_edate = array_merge($new_deposit_permission, $edate);
/* Craft the HTTP request, note that the backend
could be on an entirely different machine if
@@ -85,14 +85,25 @@ if ($cli_debug && !$backend_test)
/* DO NOTE the newline at the end of 'echo's argument */
//echo json_encode ($new_deposit_permission_edate, JSON_PRETTY_PRINT)
- echo json_encode ($new_deposit_permission, JSON_PRETTY_PRINT)
+ echo json_encode($new_deposit_permission, JSON_PRETTY_PRINT)
. "\n";
exit;
}
-$req = new http\Client\Request ("POST",
- "http://" . $_SERVER["SERVER_NAME"] . "/backend/pay",
- array ("Content-Type" => "application/json"));
+
+// Backend is relative to the shop site.
+/**
+ * WARNING: the "shop site" is '"http://".$_SERVER["HTTP_HOST"]'
+ * So do not attach $_SERVER["REQUEST_URI"] before proxying requests
+ * to the backend
+ */
+//$url = (new http\URL("http://".$_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"]))
+$url = (new http\URL("http://".$_SERVER["HTTP_HOST"]))
+ ->mod(array ("path" => "backend/pay"), http\Url::JOIN_PATH);
+
+$req = new http\Client\Request("POST",
+ $url,
+ array ("Content-Type" => "application/json"));
$req->getBody()->append (json_encode ($new_deposit_permission));
// Execute the HTTP request
@@ -112,15 +123,19 @@ if ($status_code != 200)
/* error: just forwarding to the wallet what
gotten from the backend (which is forwarding 'as is'
the error gotten from the mint) */
+ echo json_encode ($new_deposit_permission);
+ echo "Error came from the backend, status $status_code\n";
+ echo "\n";
echo $resp->body->toString ();
-
}
else
{
-$_SESSION['payment_ok'] = true;
-http_response_code (301);
-header("Location: http://" . $_SERVER["SERVER_NAME"] . "/fullfillment");
-die();
+ $_SESSION['payment_ok'] = true;
+ http_response_code (301);
+ $url = (new http\URL($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']))
+ ->mod(array ("path" => "fulfillment.php"), http\Url::JOIN_PATH);
+ header("Location: $url");
+ die();
}
?>
diff --git a/src/frontend/style.css b/src/frontend/style.css
@@ -0,0 +1,123 @@
+body {
+ background-color: white;
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, sans;
+}
+
+header {
+ width: 100%;
+ height: 100px;
+ margin: 0;
+ padding: 0;
+ border-bottom: 1px solid black;
+}
+
+header h1 {
+ font-size: 200%;
+ margin: 0;
+ padding: 0 0 0 120px;
+ position: relative;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+header #logo {
+ float: left;
+ width: 100px;
+ padding: 0;
+ margin: 0;
+ text-align: center;
+ border-right: 1px solid black;
+}
+
+aside {
+ width: 100px;
+ float: left;
+}
+
+section#main {
+ margin: 0 0 0 100px;
+ padding: 20px;
+ border-left: 1px solid black;
+ height: 100%;
+ max-width: 40em;
+}
+
+section#main h1:first-child {
+ margin-top: 0;
+}
+
+h1 {
+ font-size: 160%;
+}
+
+h2 {
+ font-size: 140%;
+}
+
+h3 {
+ font-size: 120%;
+}
+
+h4, h5, h6 {
+ font-size: 100%;
+}
+
+.loader {
+ font-size: 10px;
+ margin: 50px auto;
+ text-indent: -9999em;
+ width: 11em;
+ height: 11em;
+ border-radius: 50%;
+ background: #ffffff;
+ background: -moz-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ background: -webkit-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ background: -o-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ background: -ms-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ position: relative;
+ -webkit-animation: load3 1.4s infinite linear;
+ animation: load3 1.4s infinite linear;
+ -webkit-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+}
+
+.loader:after {
+ background: #fff;
+ width: 75%;
+ height: 75%;
+ border-radius: 50%;
+ content: '';
+ margin: auto;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
+
+@-webkit-keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
@@ -5,4 +5,5 @@ EXTRA_DIST = \
talerincludedir = $(includedir)/taler
talerinclude_HEADERS = \
- taler_merchantdb_lib.h
+ taler_merchantdb_lib.h \
+ taler_merchant_service.h
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
@@ -0,0 +1,306 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/taler_merchant_service.h
+ * @brief C interface of libtalermerchant, a C library to use merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#ifndef _TALER_MERCHANT_SERVICE_H
+#define _TALER_MERCHANT_SERVICE_H
+
+#include <taler/taler_util.h>
+
+/* ********************* event loop *********************** */
+
+/**
+ * @brief Handle to this library context. This is where the
+ * main event loop logic lives.
+ */
+struct TALER_MERCHANT_Context;
+
+
+/**
+ * Initialise a context. A context should be used for each thread and should
+ * not be shared among multiple threads.
+ *
+ * @return the context, NULL on error (failure to initialize)
+ */
+struct TALER_MERCHANT_Context *
+TALER_MERCHANT_init (void);
+
+
+/**
+ * Obtain the information for a select() call to wait until
+ * #TALER_MERCHANT_perform() is ready again. Note that calling
+ * any other TALER_MERCHANT-API may also imply that the library
+ * is again ready for #TALER_MERCHANT_perform().
+ *
+ * Basically, a client should use this API to prepare for select(),
+ * then block on select(), then call #TALER_MERCHANT_perform() and then
+ * start again until the work with the context is done.
+ *
+ * This function will NOT zero out the sets and assumes that @a max_fd
+ * and @a timeout are already set to minimal applicable values. It is
+ * safe to give this API FD-sets and @a max_fd and @a timeout that are
+ * already initialized to some other descriptors that need to go into
+ * the select() call.
+ *
+ * @param ctx context to get the event loop information for
+ * @param read_fd_set will be set for any pending read operations
+ * @param write_fd_set will be set for any pending write operations
+ * @param except_fd_set is here because curl_multi_fdset() has this argument
+ * @param max_fd set to the highest FD included in any set;
+ * if the existing sets have no FDs in it, the initial
+ * value should be "-1". (Note that `max_fd + 1` will need
+ * to be passed to select().)
+ * @param timeout set to the timeout in milliseconds (!); -1 means
+ * no timeout (NULL, blocking forever is OK), 0 means to
+ * proceed immediately with #TALER_MERCHANT_perform().
+ */
+void
+TALER_MERCHANT_get_select_info (struct TALER_MERCHANT_Context *ctx,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ int *max_fd,
+ long *timeout);
+
+
+/**
+ * Run the main event loop for the Taler interaction.
+ *
+ * @param ctx the library context
+ */
+void
+TALER_MERCHANT_perform (struct TALER_MERCHANT_Context *ctx);
+
+
+/**
+ * Cleanup library initialisation resources. This function should be called
+ * after using this library to cleanup the resources occupied during library's
+ * initialisation.
+ *
+ * @param ctx the library context
+ */
+void
+TALER_MERCHANT_fini (struct TALER_MERCHANT_Context *ctx);
+
+
+/* ********************* /pay *********************** */
+
+
+/**
+ * @brief Handle to a /pay operation at a merchant. Note that we use
+ * the same handle for interactions with frontends (API for wallets)
+ * or backends (API for frontends). The difference is that for the
+ * frontend API, we need the private keys of the coins, while for
+ * the backend API we need the public keys and signatures received
+ * from the wallet. Also, the frontend returns a redirect URI on
+ * success, while the backend just returns a success status code.
+ */
+struct TALER_MERCHANT_Pay;
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * /pay request to a merchant.
+ *
+ * @param cls closure
+ * @param http_status HTTP response code, 200 or 300-level response codes
+ * can indicate success, depending on whether the interaction
+ * was with a merchant frontend or backend;
+ * 0 if the merchant's reply is bogus (fails to follow the protocol)
+ * @param redirect_uri URI for the redirect, if the request was successful and we were talking to a frontend;
+ * NULL if the request failed or if were were talking to a backend
+ * @param obj the received JSON reply, with error details if the request failed
+ */
+typedef void
+(*TALER_MERCHANT_PayCallback) (void *cls,
+ unsigned int http_status,
+ const char *redirect_uri,
+ json_t *obj);
+
+
+/**
+ * Information we need from the wallet for each coin when doing the
+ * payment.
+ */
+struct TALER_MERCHANT_PayCoin
+{
+ /**
+ * Denomination key with which the coin is signed
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Mint’s unblinded signature of the coin
+ */
+ struct TALER_DenominationSignature denom_sig;
+
+ /**
+ * Coin's private key.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Amount this coin is to contribute (including fee).
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Amount this coin is to contribute (without fee).
+ */
+ struct TALER_Amount amount_without_fee;
+};
+
+
+/**
+ * Pay a merchant. API for wallets that have the coin's private keys.
+ *
+ * @param merchant the merchant context
+ * @param merchant_uri URI of the merchant
+ * @param mint_uri URI of the mint that the coins belong to
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
+ * @param transaction_id transaction id for the transaction between merchant and customer
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_Pay *
+TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
+ const char *mint_uri,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute timestamp,
+ uint64_t transaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PayCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls);
+
+
+/**
+ * Information we need from the frontend when forwarding
+ * a payment to the backend.
+ */
+struct TALER_MERCHANT_PaidCoin
+{
+ /**
+ * Denomination key with which the coin is signed
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Mint’s unblinded signature of the coin
+ */
+ struct TALER_DenominationSignature denom_sig;
+
+ /**
+ * Coin's public key.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Coin's signature key.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Amount this coin is to contribute (including fee).
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Amount this coin is to contribute (without fee).
+ */
+ struct TALER_Amount amount_without_fee;
+
+};
+
+
+/**
+ * Pay a merchant. API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures. Note the sublte difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ *
+ * @param merchant the merchant context
+ * @param merchant_uri URI of the merchant
+ * @param mint_uri URI of the mint that the coins belong to
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
+ * @param transaction_id transaction id for the transaction between merchant and customer
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed)
+ * @param execution_deadline date by which the merchant would like the mint to execute the transaction (can be zero if there is no specific date desired by the frontend)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_Pay *
+TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
+ const char *mint_uri,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute timestamp,
+ uint64_t transaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ struct GNUNET_TIME_Absolute execution_deadline,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PaidCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls);
+
+
+/**
+ * Cancel a /pay request. Note that if you cancel a request like
+ * this, you have no assurance that the request has not yet been
+ * forwarded to the merchant. Thus, the payment may still succeed or
+ * fail. Re-issue the original /pay request to resume/retry and
+ * obtain a definitive result, or /refresh the coins involved to
+ * ensure that the merchant can no longer complete the payment.
+ *
+ * @param wh the wire information request handle
+ */
+void
+TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *ph);
+
+
+#endif /* _TALER_MERCHANT_SERVICE_H */
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -69,7 +69,7 @@ struct TALER_MERCHANTDB_Plugin
* @param refund refund deadline
* @param amount_without_fee amount the mint will deposit
* @param coin_pub public key of the coin
- * @param merchant_pub our public key
+ * @param mint_proof proof from mint that coin was accepted
* @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
*/
int
@@ -81,8 +81,21 @@ struct TALER_MERCHANTDB_Plugin
struct GNUNET_TIME_Absolute refund,
const struct TALER_Amount *amount_without_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MintSignatureP *mint_sig);
+ json_t *mint_proof);
-};
+ /**
+ * Check whether a payment has already been stored
+ *
+ * @param cls our plugin handle
+ * @param transaction_id the transaction id to search into
+ * the db
+ *
+ * @return GNUNET_OK if found, GNUNET_NO if not, GNUNET_SYSERR
+ * upon error
+ */
+ int
+ (*check_payment) (void *cls,
+ uint64_t transaction_id);
+};
#endif
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
@@ -0,0 +1,51 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libtalermerchant.la
+
+libtalermerchant_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+
+libtalermerchant_la_SOURCES = \
+ merchant_api_context.c merchant_api_context.h \
+ merchant_api_json.c merchant_api_json.h \
+ merchant_api_pay.c
+
+libtalermerchant_la_LIBADD = \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+if HAVE_LIBCURL
+libtalermerchant_la_LIBADD += -lcurl
+else
+if HAVE_LIBGNURL
+libtalermerchant_la_LIBADD += -lgnurl
+endif
+endif
+
+check_PROGRAMS = \
+ test_merchant_api
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_merchant_api_SOURCES = \
+ test_merchant_api.c
+test_merchant_api_LDADD = \
+ libtalermerchant.la \
+ $(LIBGCRYPT_LIBS) \
+ -ltalermint \
+ -ltalerutil \
+ -lgnunetutil \
+ -ljansson
+
+EXTRA_DIST = \
+ test_merchant.conf
diff --git a/src/lib/merchant_api_context.c b/src/lib/merchant_api_context.c
@@ -0,0 +1,536 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant-lib/merchant_api_context.c
+ * @brief Implementation of the context part of the merchant's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_context.h"
+
+
+/**
+ * Log error related to CURL operations.
+ *
+ * @param type log level
+ * @param function which function failed to run
+ * @param code what was the curl error code
+ */
+#define CURL_STRERROR(type, function, code) \
+ GNUNET_log (type, \
+ "Curl function `%s' has failed at `%s:%d' with error: %s\n", \
+ function, __FILE__, __LINE__, curl_easy_strerror (code));
+
+/**
+ * Print JSON parsing related error information
+ */
+#define JSON_WARN(error) \
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
+ "JSON parsing failed at %s:%u: %s (%s)\n", \
+ __FILE__, __LINE__, error.text, error.source)
+
+
+/**
+ * Failsafe flag. Raised if our constructor fails to initialize
+ * the Curl library.
+ */
+static int TALER_MERCHANT_curl_fail;
+
+
+/**
+ * Jobs are CURL requests running within a `struct TALER_MERCHANT_Context`.
+ */
+struct MAC_Job
+{
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *next;
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *prev;
+
+ /**
+ * Easy handle of the job.
+ */
+ CURL *easy_handle;
+
+ /**
+ * Context this job runs in.
+ */
+ struct TALER_MERCHANT_Context *ctx;
+
+ /**
+ * Function to call upon completion.
+ */
+ MAC_JobCompletionCallback jcc;
+
+ /**
+ * Closure for @e jcc.
+ */
+ void *jcc_cls;
+
+};
+
+
+/**
+ * Context
+ */
+struct TALER_MERCHANT_Context
+{
+ /**
+ * Curl multi handle
+ */
+ CURLM *multi;
+
+ /**
+ * Curl share handle
+ */
+ CURLSH *share;
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *jobs_head;
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *jobs_tail;
+
+ /**
+ * HTTP header "application/json", created once and used
+ * for all requests that need it.
+ */
+ struct curl_slist *json_header;
+
+};
+
+
+/**
+ * Initialise this library. This function should be called before using any of
+ * the following functions.
+ *
+ * @return library context
+ */
+struct TALER_MERCHANT_Context *
+TALER_MERCHANT_init ()
+{
+ struct TALER_MERCHANT_Context *ctx;
+ CURLM *multi;
+ CURLSH *share;
+
+ if (TALER_MERCHANT_curl_fail)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Curl was not initialised properly\n");
+ return NULL;
+ }
+ if (NULL == (multi = curl_multi_init ()))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to create a Curl multi handle\n");
+ return NULL;
+ }
+ if (NULL == (share = curl_share_init ()))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to create a Curl share handle\n");
+ return NULL;
+ }
+ ctx = GNUNET_new (struct TALER_MERCHANT_Context);
+ ctx->multi = multi;
+ ctx->share = share;
+ GNUNET_assert (NULL != (ctx->json_header =
+ curl_slist_append (NULL,
+ "Content-Type: application/json")));
+ return ctx;
+}
+
+
+/**
+ * Schedule a CURL request to be executed and call the given @a jcc
+ * upon its completion. Note that the context will make use of the
+ * CURLOPT_PRIVATE facility of the CURL @a eh. Applications can
+ * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument
+ * from a valid @a eh afterwards.
+ *
+ * This function modifies the CURL handle to add the
+ * "Content-Type: application/json" header if @a add_json is set.
+ *
+ * @param ctx context to execute the job in
+ * @param eh curl easy handle for the request, will
+ * be executed AND cleaned up
+ * @param add_json add "application/json" content type header
+ * @param jcc callback to invoke upon completion
+ * @param jcc_cls closure for @a jcc
+ */
+struct MAC_Job *
+MAC_job_add (struct TALER_MERCHANT_Context *ctx,
+ CURL *eh,
+ int add_json,
+ MAC_JobCompletionCallback jcc,
+ void *jcc_cls)
+{
+ struct MAC_Job *job;
+
+ if (GNUNET_YES == add_json)
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HTTPHEADER,
+ ctx->json_header));
+
+ job = GNUNET_new (struct MAC_Job);
+ job->easy_handle = eh;
+ job->ctx = ctx;
+ job->jcc = jcc;
+ job->jcc_cls = jcc_cls;
+ GNUNET_CONTAINER_DLL_insert (ctx->jobs_head,
+ ctx->jobs_tail,
+ job);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_PRIVATE,
+ job));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_SHARE,
+ ctx->share));
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_add_handle (ctx->multi,
+ eh));
+ return job;
+}
+
+
+/**
+ * Obtain the `jcc_cls` argument from an `eh` that was
+ * given to #MAC_job_add().
+ *
+ * @param eh easy handle that was used
+ * @return the `jcc_cls` that was given to #MAC_job_add().
+ */
+void *
+MAC_easy_to_closure (CURL *eh)
+{
+ struct MAC_Job *job;
+
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_getinfo (eh,
+ CURLINFO_PRIVATE,
+ (char **) &job));
+ return job->jcc_cls;
+}
+
+
+/**
+ * Cancel a job. Must only be called before the job completion
+ * callback is called for the respective job.
+ *
+ * @param job job to cancel
+ */
+void
+MAC_job_cancel (struct MAC_Job *job)
+{
+ struct TALER_MERCHANT_Context *ctx = job->ctx;
+
+ GNUNET_CONTAINER_DLL_remove (ctx->jobs_head,
+ ctx->jobs_tail,
+ job);
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_remove_handle (ctx->multi,
+ job->easy_handle));
+ curl_easy_cleanup (job->easy_handle);
+ GNUNET_free (job);
+}
+
+
+/**
+ * Run the main event loop for the Taler interaction.
+ *
+ * @param ctx the library context
+ */
+void
+TALER_MERCHANT_perform (struct TALER_MERCHANT_Context *ctx)
+{
+ CURLMsg *cmsg;
+ struct MAC_Job *job;
+ int n_running;
+ int n_completed;
+
+ (void) curl_multi_perform (ctx->multi,
+ &n_running);
+ while (NULL != (cmsg = curl_multi_info_read (ctx->multi,
+ &n_completed)))
+ {
+ /* Only documented return value is CURLMSG_DONE */
+ GNUNET_break (CURLMSG_DONE == cmsg->msg);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_getinfo (cmsg->easy_handle,
+ CURLINFO_PRIVATE,
+ (char *) &job));
+ GNUNET_assert (job->ctx == ctx);
+ job->jcc (job->jcc_cls,
+ cmsg->easy_handle);
+ MAC_job_cancel (job);
+ }
+}
+
+
+/**
+ * Obtain the information for a select() call to wait until
+ * #TALER_MERCHANT_perform() is ready again. Note that calling
+ * any other TALER_MERCHANT-API may also imply that the library
+ * is again ready for #TALER_MERCHANT_perform().
+ *
+ * Basically, a client should use this API to prepare for select(),
+ * then block on select(), then call #TALER_MERCHANT_perform() and then
+ * start again until the work with the context is done.
+ *
+ * This function will NOT zero out the sets and assumes that @a max_fd
+ * and @a timeout are already set to minimal applicable values. It is
+ * safe to give this API FD-sets and @a max_fd and @a timeout that are
+ * already initialized to some other descriptors that need to go into
+ * the select() call.
+ *
+ * @param ctx context to get the event loop information for
+ * @param read_fd_set will be set for any pending read operations
+ * @param write_fd_set will be set for any pending write operations
+ * @param except_fd_set is here because curl_multi_fdset() has this argument
+ * @param max_fd set to the highest FD included in any set;
+ * if the existing sets have no FDs in it, the initial
+ * value should be "-1". (Note that `max_fd + 1` will need
+ * to be passed to select().)
+ * @param timeout set to the timeout in milliseconds (!); -1 means
+ * no timeout (NULL, blocking forever is OK), 0 means to
+ * proceed immediately with #TALER_MERCHANT_perform().
+ */
+void
+TALER_MERCHANT_get_select_info (struct TALER_MERCHANT_Context *ctx,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ int *max_fd,
+ long *timeout)
+{
+ long to;
+ int m;
+
+ m = -1;
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_fdset (ctx->multi,
+ read_fd_set,
+ write_fd_set,
+ except_fd_set,
+ &m));
+ *max_fd = GNUNET_MAX (m, *max_fd);
+ to = *timeout;
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_timeout (ctx->multi,
+ &to));
+ /* Only if what we got back from curl is smaller than what we
+ already had (-1 == infinity!), then update timeout */
+ if ( (to < *timeout) &&
+ (-1 != to) )
+ *timeout = to;
+ if ( (-1 == (*timeout)) &&
+ (NULL != ctx->jobs_head) )
+ *timeout = 1000 * 60 * 5; /* curl is not always good about giving timeouts */
+}
+
+
+/**
+ * Cleanup library initialisation resources. This function should be called
+ * after using this library to cleanup the resources occupied during library's
+ * initialisation.
+ *
+ * @param ctx the library context
+ */
+void
+TALER_MERCHANT_fini (struct TALER_MERCHANT_Context *ctx)
+{
+ /* all jobs must have been cancelled at this time, assert this */
+ GNUNET_assert (NULL == ctx->jobs_head);
+ curl_share_cleanup (ctx->share);
+ curl_multi_cleanup (ctx->multi);
+ curl_slist_free_all (ctx->json_header);
+ GNUNET_free (ctx);
+}
+
+
+/**
+ * Callback used when downloading the reply to an HTTP request.
+ * Just appends all of the data to the `buf` in the
+ * `struct MAC_DownloadBuffer` for further processing. The size of
+ * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if
+ * the download exceeds this size, we abort with an error.
+ *
+ * @param bufptr data downloaded via HTTP
+ * @param size size of an item in @a bufptr
+ * @param nitems number of items in @a bufptr
+ * @param cls the `struct KeysRequest`
+ * @return number of bytes processed from @a bufptr
+ */
+size_t
+MAC_download_cb (char *bufptr,
+ size_t size,
+ size_t nitems,
+ void *cls)
+{
+ struct MAC_DownloadBuffer *db = cls;
+ size_t msize;
+ void *buf;
+
+ if (0 == size * nitems)
+ {
+ /* Nothing (left) to do */
+ return 0;
+ }
+ msize = size * nitems;
+ if ( (msize + db->buf_size) >= GNUNET_MAX_MALLOC_CHECKED)
+ {
+ db->eno = ENOMEM;
+ return 0; /* signals an error to curl */
+ }
+ db->buf = GNUNET_realloc (db->buf,
+ db->buf_size + msize);
+ buf = db->buf + db->buf_size;
+ memcpy (buf, bufptr, msize);
+ db->buf_size += msize;
+ return msize;
+}
+
+
+/**
+ * Obtain information about the final result about the
+ * HTTP download. If the download was successful, parses
+ * the JSON in the @a db and returns it. Also returns
+ * the HTTP @a response_code. If the download failed,
+ * the return value is NULL. The response code is set
+ * in any case, on download errors to zero.
+ *
+ * Calling this function also cleans up @a db.
+ *
+ * @param db download buffer
+ * @param eh CURL handle (to get the response code)
+ * @param[out] response_code set to the HTTP response code
+ * (or zero if we aborted the download, i.e.
+ * because the response was too big, or if
+ * the JSON we received was malformed).
+ * @return NULL if downloading a JSON reply failed
+ */
+json_t *
+MAC_download_get_result (struct MAC_DownloadBuffer *db,
+ CURL *eh,
+ long *response_code)
+{
+ json_t *json;
+ json_error_t error;
+ char *ct;
+
+ if ( (CURLE_OK !=
+ curl_easy_getinfo (eh,
+ CURLINFO_CONTENT_TYPE,
+ &ct)) ||
+ (NULL == ct) ||
+ (0 != strcasecmp (ct,
+ "application/json")) )
+ {
+ /* No content type or explicitly not JSON, refuse to parse
+ (but keep response code) */
+ if (CURLE_OK !=
+ curl_easy_getinfo (eh,
+ CURLINFO_RESPONSE_CODE,
+ response_code))
+ {
+ /* unexpected error... */
+ GNUNET_break (0);
+ *response_code = 0;
+ }
+ return NULL;
+ }
+
+ json = NULL;
+ if (0 == db->eno)
+ {
+ json = json_loadb (db->buf,
+ db->buf_size,
+ JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
+ &error);
+ if (NULL == json)
+ {
+ JSON_WARN (error);
+ *response_code = 0;
+ }
+ }
+ GNUNET_free_non_null (db->buf);
+ db->buf = NULL;
+ db->buf_size = 0;
+ if (NULL != json)
+ {
+ if (CURLE_OK !=
+ curl_easy_getinfo (eh,
+ CURLINFO_RESPONSE_CODE,
+ response_code))
+ {
+ /* unexpected error... */
+ GNUNET_break (0);
+ *response_code = 0;
+ }
+ }
+ return json;
+}
+
+
+/**
+ * Initial global setup logic, specifically runs the Curl setup.
+ */
+__attribute__ ((constructor))
+void
+TALER_MERCHANT_constructor__ (void)
+{
+ CURLcode ret;
+
+ if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
+ {
+ CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR,
+ "curl_global_init",
+ ret);
+ TALER_MERCHANT_curl_fail = 1;
+ }
+}
+
+
+/**
+ * Cleans up after us, specifically runs the Curl cleanup.
+ */
+__attribute__ ((destructor))
+void
+TALER_MERCHANT_destructor__ (void)
+{
+ if (TALER_MERCHANT_curl_fail)
+ return;
+ curl_global_cleanup ();
+}
+
+/* end of merchant_api_context.c */
diff --git a/src/lib/merchant_api_context.h b/src/lib/merchant_api_context.h
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant-lib/merchant_api_context.h
+ * @brief Internal interface to the context part of the merchant's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Entry in the context's job queue.
+ */
+struct MAC_Job;
+
+/**
+ * Function to call upon completion of a job.
+ *
+ * @param cls closure
+ * @param eh original easy handle (for inspection)
+ */
+typedef void
+(*MAC_JobCompletionCallback)(void *cls,
+ CURL *eh);
+
+
+/**
+ * Schedule a CURL request to be executed and call the given @a jcc
+ * upon its completion. Note that the context will make use of the
+ * CURLOPT_PRIVATE facility of the CURL @a eh. Applications can
+ * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument
+ * from a valid @a eh afterwards.
+ *
+ * This function modifies the CURL handle to add the
+ * "Content-Type: application/json" header if @a add_json is set.
+ *
+ * @param ctx context to execute the job in
+ * @param eh curl easy handle for the request, will
+ * be executed AND cleaned up
+ * @param add_json add "application/json" content type header
+ * @param jcc callback to invoke upon completion
+ * @param jcc_cls closure for @a jcc
+ */
+struct MAC_Job *
+MAC_job_add (struct TALER_MERCHANT_Context *ctx,
+ CURL *eh,
+ int add_json,
+ MAC_JobCompletionCallback jcc,
+ void *jcc_cls);
+
+
+/**
+ * Obtain the `jcc_cls` argument from an `eh` that was
+ * given to #MAC_job_add().
+ *
+ * @param eh easy handle that was used
+ * @return the `jcc_cls` that was given to #MAC_job_add().
+ */
+void *
+MAC_easy_to_closure (CURL *eh);
+
+
+/**
+ * Cancel a job. Must only be called before the job completion
+ * callback is called for the respective job.
+ *
+ * @param job job to cancel
+ */
+void
+MAC_job_cancel (struct MAC_Job *job);
+
+
+/**
+ * @brief Buffer data structure we use to buffer the HTTP download
+ * before giving it to the JSON parser.
+ */
+struct MAC_DownloadBuffer
+{
+
+ /**
+ * Download buffer
+ */
+ void *buf;
+
+ /**
+ * The size of the download buffer
+ */
+ size_t buf_size;
+
+ /**
+ * Error code (based on libc errno) if we failed to download
+ * (i.e. response too large).
+ */
+ int eno;
+
+};
+
+
+/**
+ * Callback used when downloading the reply to an HTTP request.
+ * Just appends all of the data to the `buf` in the
+ * `struct MAC_DownloadBuffer` for further processing. The size of
+ * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if
+ * the download exceeds this size, we abort with an error.
+ *
+ * Should be used by the various routines as the
+ * CURLOPT_WRITEFUNCTION. A `struct MAC_DownloadBuffer` needs to be
+ * passed to the CURLOPT_WRITEDATA.
+ *
+ * Afterwards, `eno` needs to be checked to ensure that the download
+ * completed correctly.
+ *
+ * @param bufptr data downloaded via HTTP
+ * @param size size of an item in @a bufptr
+ * @param nitems number of items in @a bufptr
+ * @param cls the `struct KeysRequest`
+ * @return number of bytes processed from @a bufptr
+ */
+size_t
+MAC_download_cb (char *bufptr,
+ size_t size,
+ size_t nitems,
+ void *cls);
+
+
+/**
+ * Obtain information about the final result about the
+ * HTTP download. If the download was successful, parses
+ * the JSON in the @a db and returns it. Also returns
+ * the HTTP @a response_code. If the download failed,
+ * the return value is NULL. The response code is set
+ * in any case, on download errors to zero.
+ *
+ * Calling this function also cleans up @a db.
+ *
+ * @param db download buffer
+ * @param eh CURL handle (to get the response code)
+ * @param[out] response_code set to the HTTP response code
+ * (or zero if we aborted the download, i.e.
+ * because the response was too big, or if
+ * the JSON we received was malformed).
+ * @return NULL if downloading a JSON reply failed
+ */
+json_t *
+MAC_download_get_result (struct MAC_DownloadBuffer *db,
+ CURL *eh,
+ long *response_code);
+
+
+/* end of merchant_api_context.h */
diff --git a/src/lib/merchant_api_json.c b/src/lib/merchant_api_json.c
@@ -0,0 +1,491 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_json.c
+ * @brief functions to parse incoming requests (JSON snippets)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "merchant_api_json.h"
+
+/**
+ * Navigate and parse data in a JSON tree.
+ *
+ * @param root the JSON node to start the navigation at.
+ * @param spec parse specification array
+ * @return offset in @a spec where parsing failed, -1 on success (!)
+ */
+static int
+parse_json (json_t *root,
+ struct MAJ_Specification *spec)
+{
+ int i;
+ json_t *pos; /* what's our current position? */
+
+ pos = root;
+ for (i=0;MAJ_CMD_END != spec[i].cmd;i++)
+ {
+ pos = json_object_get (root,
+ spec[i].field);
+ if (NULL == pos)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ switch (spec[i].cmd)
+ {
+ case MAJ_CMD_END:
+ GNUNET_assert (0);
+ return i;
+ case MAJ_CMD_AMOUNT:
+ if (GNUNET_OK !=
+ TALER_json_to_amount (pos,
+ spec[i].details.amount))
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ break;
+ case MAJ_CMD_TIME_ABSOLUTE:
+ if (GNUNET_OK !=
+ TALER_json_to_abs (pos,
+ spec[i].details.abs_time))
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ break;
+
+ case MAJ_CMD_STRING:
+ {
+ const char *str;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.strptr = str;
+ }
+ break;
+
+ case MAJ_CMD_BINARY_FIXED:
+ {
+ const char *str;
+ int res;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ res = GNUNET_STRINGS_string_to_data (str, strlen (str),
+ spec[i].details.fixed_data.dest,
+ spec[i].details.fixed_data.dest_size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ }
+ break;
+
+ case MAJ_CMD_BINARY_VARIABLE:
+ {
+ const char *str;
+ size_t size;
+ void *data;
+ int res;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ size = (strlen (str) * 5) / 8;
+ if (size >= 1024)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ data = GNUNET_malloc (size);
+ res = GNUNET_STRINGS_string_to_data (str,
+ strlen (str),
+ data,
+ size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (data);
+ return i;
+ }
+ *spec[i].details.variable_data.dest_p = data;
+ *spec[i].details.variable_data.dest_size_p = size;
+ }
+ break;
+
+ case MAJ_CMD_RSA_PUBLIC_KEY:
+ {
+ size_t size;
+ const char *str;
+ int res;
+ void *buf;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ size = (strlen (str) * 5) / 8;
+ buf = GNUNET_malloc (size);
+ res = GNUNET_STRINGS_string_to_data (str,
+ strlen (str),
+ buf,
+ size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_free (buf);
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (buf,
+ size);
+ GNUNET_free (buf);
+ if (NULL == spec[i].details.rsa_public_key)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ }
+ break;
+
+ case MAJ_CMD_RSA_SIGNATURE:
+ {
+ size_t size;
+ const char *str;
+ int res;
+ void *buf;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ size = (strlen (str) * 5) / 8;
+ buf = GNUNET_malloc (size);
+ res = GNUNET_STRINGS_string_to_data (str,
+ strlen (str),
+ buf,
+ size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_free (buf);
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_decode (buf,
+ size);
+ GNUNET_free (buf);
+ if (NULL == spec[i].details.rsa_signature)
+ return i;
+ }
+ break;
+
+ case MAJ_CMD_UINT16:
+ {
+ json_int_t val;
+
+ if (! json_is_integer (pos))
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ val = json_integer_value (pos);
+ if ( (0 > val) || (val > UINT16_MAX) )
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.u16 = (uint16_t) val;
+ }
+ break;
+
+ case MAJ_CMD_JSON_OBJECT:
+ {
+ if (! (json_is_object (pos) || json_is_array (pos)) )
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ json_incref (pos);
+ *spec[i].details.obj = pos;
+ }
+ break;
+
+ default:
+ GNUNET_break (0);
+ return i;
+ }
+ }
+ return -1; /* all OK! */
+}
+
+
+/**
+ * Free all elements allocated during a
+ * #MAJ_parse_json() operation.
+ *
+ * @param spec specification of the parse operation
+ * @param end number of elements in @a spec to process
+ */
+static void
+parse_free (struct MAJ_Specification *spec,
+ int end)
+{
+ int i;
+
+ for (i=0;i<end;i++)
+ {
+ switch (spec[i].cmd)
+ {
+ case MAJ_CMD_END:
+ GNUNET_assert (0);
+ return;
+ case MAJ_CMD_AMOUNT:
+ break;
+ case MAJ_CMD_TIME_ABSOLUTE:
+ break;
+ case MAJ_CMD_BINARY_FIXED:
+ break;
+ case MAJ_CMD_STRING:
+ break;
+ case MAJ_CMD_BINARY_VARIABLE:
+ GNUNET_free (*spec[i].details.variable_data.dest_p);
+ *spec[i].details.variable_data.dest_p = NULL;
+ *spec[i].details.variable_data.dest_size_p = 0;
+ break;
+ case MAJ_CMD_RSA_PUBLIC_KEY:
+ GNUNET_CRYPTO_rsa_public_key_free (*spec[i].details.rsa_public_key);
+ *spec[i].details.rsa_public_key = NULL;
+ break;
+ case MAJ_CMD_RSA_SIGNATURE:
+ GNUNET_CRYPTO_rsa_signature_free (*spec[i].details.rsa_signature);
+ *spec[i].details.rsa_signature = NULL;
+ break;
+ case MAJ_CMD_JSON_OBJECT:
+ json_decref (*spec[i].details.obj);
+ *spec[i].details.obj = NULL;
+ break;
+ default:
+ GNUNET_break (0);
+ break;
+ }
+ }
+}
+
+
+/**
+ * Navigate and parse data in a JSON tree.
+ *
+ * @param root the JSON node to start the navigation at.
+ * @param spec parse specification array
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+int
+MAJ_parse_json (const json_t *root,
+ struct MAJ_Specification *spec)
+{
+ int ret;
+
+ ret = parse_json ((json_t *) root,
+ spec);
+ if (-1 == ret)
+ return GNUNET_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "JSON field `%s` (%d) had unexpected value\n",
+ spec[ret].field,
+ ret);
+ parse_free (spec, ret);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Free all elements allocated during a
+ * #MAJ_parse_json() operation.
+ *
+ * @param spec specification of the parse operation
+ */
+void
+MAJ_parse_free (struct MAJ_Specification *spec)
+{
+ int i;
+
+ for (i=0;MAJ_CMD_END != spec[i].cmd;i++) ;
+ parse_free (spec, i);
+}
+
+
+/**
+ * The expected field stores a string.
+ *
+ * @param name name of the JSON field
+ * @param strptr where to store a pointer to the field
+ */
+struct MAJ_Specification
+MAJ_spec_string (const char *name,
+ const char **strptr)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_STRING,
+ .field = name,
+ .details.strptr = strptr
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an absolute time value.
+ *
+ * @param name name of the JSON field
+ * @param at where to store the absolute time found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_absolute_time (const char *name,
+ struct GNUNET_TIME_Absolute *at)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_TIME_ABSOLUTE,
+ .field = name,
+ .details.abs_time = at
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an amount value.
+ *
+ * @param name name of the JSON field
+ * @param amount where to store the amount found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_amount (const char *name,
+ struct TALER_Amount *amount)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_AMOUNT,
+ .field = name,
+ .details.amount = amount
+ };
+ return ret;
+}
+
+
+/**
+ * 16-bit integer.
+ *
+ * @param name name of the JSON field
+ * @param[out] u16 where to store the integer found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_uint16 (const char *name,
+ uint16_t *u16)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_UINT16,
+ .field = name,
+ .details.u16 = u16
+ };
+ return ret;
+}
+
+
+/**
+ * JSON object.
+ *
+ * @param name name of the JSON field
+ * @param[out] jsonp where to store the JSON found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_json (const char *name,
+ json_t **jsonp)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_JSON_OBJECT,
+ .field = name,
+ .details.obj = jsonp
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an RSA public key.
+ *
+ * @param name name of the JSON field
+ * @param pk where to store the RSA key found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_public_key (const char *name,
+ struct GNUNET_CRYPTO_rsa_PublicKey **pk)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_RSA_PUBLIC_KEY,
+ .field = name,
+ .details.rsa_public_key = pk
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an RSA signature.
+ *
+ * @param name name of the JSON field
+ * @param sig where to store the RSA signature found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_signature (const char *name,
+ struct GNUNET_CRYPTO_rsa_Signature **sig)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_RSA_SIGNATURE,
+ .field = name,
+ .details.rsa_signature = sig
+ };
+ return ret;
+}
+
+
+/* end of merchant_api_json.c */
diff --git a/src/lib/merchant_api_json.h b/src/lib/merchant_api_json.h
@@ -0,0 +1,331 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_json.h
+ * @brief functions to parse incoming requests (JSON snippets)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_util.h>
+#include <jansson.h>
+
+
+/**
+ * Enumeration with the various commands for the
+ * #MAJ_parse_json interpreter.
+ */
+enum MAJ_Command
+{
+
+ /**
+ * End of command list.
+ */
+ MAJ_CMD_END,
+
+ /**
+ * Parse amount at current position.
+ */
+ MAJ_CMD_AMOUNT,
+
+ /**
+ * Parse absolute time at current position.
+ */
+ MAJ_CMD_TIME_ABSOLUTE,
+
+ /**
+ * Parse fixed binary value at current position.
+ */
+ MAJ_CMD_BINARY_FIXED,
+
+ /**
+ * Parse variable-size binary value at current position.
+ */
+ MAJ_CMD_BINARY_VARIABLE,
+
+ /**
+ * Parse RSA public key at current position.
+ */
+ MAJ_CMD_RSA_PUBLIC_KEY,
+
+ /**
+ * Parse RSA signature at current position.
+ */
+ MAJ_CMD_RSA_SIGNATURE,
+
+ /**
+ * Parse `const char *` JSON string at current position.
+ */
+ MAJ_CMD_STRING,
+
+ /**
+ * Parse `uint16_t` integer at the current position.
+ */
+ MAJ_CMD_UINT16,
+
+ /**
+ * Parse JSON object at the current position.
+ */
+ MAJ_CMD_JSON_OBJECT,
+
+ /**
+ * Parse ??? at current position.
+ */
+ MAJ_CMD_C
+
+};
+
+
+/**
+ * @brief Entry in parser specification for #MAJ_parse_json.
+ */
+struct MAJ_Specification
+{
+
+ /**
+ * Command to execute.
+ */
+ enum MAJ_Command cmd;
+
+ /**
+ * Name of the field to access.
+ */
+ const char *field;
+
+ /**
+ * Further details for the command.
+ */
+ union {
+
+ /**
+ * Where to store amount for #MAJ_CMD_AMOUNT.
+ */
+ struct TALER_Amount *amount;
+
+ /**
+ * Where to store time, for #MAJ_CMD_TIME_ABSOLUTE.
+ */
+ struct GNUNET_TIME_Absolute *abs_time;
+
+ /**
+ * Where to write binary data, for #MAJ_CMD_BINARY_FIXED.
+ */
+ struct {
+ /**
+ * Where to write the data.
+ */
+ void *dest;
+
+ /**
+ * How many bytes to write to @e dest.
+ */
+ size_t dest_size;
+
+ } fixed_data;
+
+ /**
+ * Where to write binary data, for #MAJ_CMD_BINARY_VARIABLE.
+ */
+ struct {
+ /**
+ * Where to store the pointer with the data (is allocated).
+ */
+ void **dest_p;
+
+ /**
+ * Where to store the number of bytes allocated at `*dest`.
+ */
+ size_t *dest_size_p;
+
+ } variable_data;
+
+ /**
+ * Where to store the RSA public key for #MAJ_CMD_RSA_PUBLIC_KEY
+ */
+ struct GNUNET_CRYPTO_rsa_PublicKey **rsa_public_key;
+
+ /**
+ * Where to store the RSA signature for #MAJ_CMD_RSA_SIGNATURE
+ */
+ struct GNUNET_CRYPTO_rsa_Signature **rsa_signature;
+
+ /**
+ * Details for #MAJ_CMD_EDDSA_SIGNATURE
+ */
+ struct {
+
+ /**
+ * Where to store the purpose.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose **purpose_p;
+
+ /**
+ * Key to verify the signature against.
+ */
+ const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key;
+
+ } eddsa_signature;
+
+ /**
+ * Where to store a pointer to the string.
+ */
+ const char **strptr;
+
+ /**
+ * Where to store 16-bit integer.
+ */
+ uint16_t *u16;
+
+ /**
+ * Where to store a JSON object.
+ */
+ json_t **obj;
+
+ } details;
+
+};
+
+
+/**
+ * Navigate and parse data in a JSON tree.
+ *
+ * @param root the JSON node to start the navigation at.
+ * @param spec parse specification array
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+int
+MAJ_parse_json (const json_t *root,
+ struct MAJ_Specification *spec);
+
+
+/**
+ * Free all elements allocated during a
+ * #MAJ_parse_json() operation.
+ *
+ * @param spec specification of the parse operation
+ */
+void
+MAJ_parse_free (struct MAJ_Specification *spec);
+
+
+/**
+ * End of a parser specification.
+ */
+#define MAJ_spec_end { .cmd = MAJ_CMD_END }
+
+/**
+ * Fixed size object (in network byte order, encoded using Crockford
+ * Base32hex encoding).
+ *
+ * @param name name of the JSON field
+ * @param obj pointer where to write the data (type of `*obj` will determine size)
+ */
+#define MAJ_spec_fixed_auto(name,obj) { .cmd = MAJ_CMD_BINARY_FIXED, .field = name, .details.fixed_data.dest = obj, .details.fixed_data.dest_size = sizeof (*obj) }
+
+
+/**
+ * Variable size object (in network byte order, encoded using Crockford
+ * Base32hex encoding).
+ *
+ * @param name name of the JSON field
+ * @param obj pointer where to write the data (a `void **`)
+ * @param size where to store the number of bytes allocated for @a obj (of type `size_t *`
+ */
+#define MAJ_spec_varsize(name,obj,size) { .cmd = MAJ_CMD_BINARY_VARIABLE, .field = name, .details.variable_data.dest_p = obj, .details.variable_data.dest_size_p = size }
+
+
+/**
+ * The expected field stores a string.
+ *
+ * @param name name of the JSON field
+ * @param strptr where to store a pointer to the field
+ */
+struct MAJ_Specification
+MAJ_spec_string (const char *name,
+ const char **strptr);
+
+
+/**
+ * Absolute time.
+ *
+ * @param name name of the JSON field
+ * @param[out] at where to store the absolute time found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_absolute_time (const char *name,
+ struct GNUNET_TIME_Absolute *at);
+
+
+/**
+ * 16-bit integer.
+ *
+ * @param name name of the JSON field
+ * @param[out] u16 where to store the integer found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_uint16 (const char *name,
+ uint16_t *u16);
+
+
+/**
+ * JSON object.
+ *
+ * @param name name of the JSON field
+ * @param[out] jsonp where to store the JSON found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_json (const char *name,
+ json_t **jsonp);
+
+
+/**
+ * Specification for parsing an amount value.
+ *
+ * @param name name of the JSON field
+ * @param amount where to store the amount under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_amount (const char *name,
+ struct TALER_Amount *amount);
+
+
+/**
+ * Specification for parsing an RSA public key.
+ *
+ * @param name name of the JSON field
+ * @param pk where to store the RSA key found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_public_key (const char *name,
+ struct GNUNET_CRYPTO_rsa_PublicKey **pk);
+
+
+/**
+ * Specification for parsing an RSA signature.
+ *
+ * @param name name of the JSON field
+ * @param sig where to store the RSA signature found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_signature (const char *name,
+ struct GNUNET_CRYPTO_rsa_Signature **sig);
+
+
+
+
+/* end of merchant_api_json.h */
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
@@ -0,0 +1,490 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_pay.c
+ * @brief Implementation of the /pay request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.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_json.h"
+#include "merchant_api_context.h"
+#include <taler/taler_signatures.h>
+
+
+/**
+ * @brief A Pay Handle
+ */
+struct TALER_MERCHANT_Pay
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct MAC_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_PayCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Download buffer
+ */
+ struct MAC_DownloadBuffer db;
+
+ /**
+ * Reference to the merchant.
+ */
+ struct TALER_MERCHANT_Context *merchant;
+};
+
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /pay request.
+ *
+ * @param cls the `struct TALER_MERCHANT_Pay`
+ * @param eh the curl request handle
+ */
+static void
+handle_pay_finished (void *cls,
+ CURL *eh)
+{
+ /* FIXME: this function is not yet implemented!!! */
+ struct TALER_MERCHANT_Pay *ph = cls;
+ long response_code;
+ json_t *json;
+
+ ph->job = NULL;
+ json = MAC_download_get_result (&ph->db,
+ eh,
+ &response_code);
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* 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_FORBIDDEN:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, merchant says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ ph->cb (ph->cb_cls,
+ response_code,
+ "FIXME-redirect-URI",
+ json);
+ json_decref (json);
+ TALER_MERCHANT_pay_cancel (ph);
+}
+
+
+/**
+ * Pay a merchant. API for wallets that have the coin's private keys.
+ *
+ * @param merchant the merchant context
+ * @param mint_uri URI of the mint that the coins belong to
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
+ * @param transaction_id transaction id for the transaction between merchant and customer
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_Pay *
+TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
+ const char *mint_uri,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute timestamp,
+ uint64_t transaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PayCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls)
+{
+ unsigned int i;
+ struct TALER_DepositRequestPS dr;
+ struct TALER_MERCHANT_PaidCoin pc[num_coins];
+
+ dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
+ dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS));
+ dr.h_contract = *h_contract;
+ dr.h_wire = *h_wire;
+ dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
+ dr.transaction_id = GNUNET_htonll (transaction_id);
+ dr.merchant = *merchant_pub;
+ for (i=0;i<num_coins;i++)
+ {
+ const struct TALER_MERCHANT_PayCoin *coin = &coins[i];
+ struct TALER_MERCHANT_PaidCoin *p = &pc[i];
+ struct TALER_Amount fee;
+
+ /* prepare 'dr' for this coin to generate coin signature */
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv,
+ &dr.coin_pub.eddsa_pub);
+ TALER_amount_hton (&dr.amount_with_fee,
+ &coin->amount_with_fee);
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&fee,
+ &coin->amount_with_fee,
+ &coin->amount_without_fee))
+ {
+ /* Integer underflow, fee larger than total amount?
+ This should not happen (client violated API!) */
+ GNUNET_break (0);
+ return NULL;
+ }
+ TALER_amount_hton (&dr.deposit_fee,
+ &fee);
+ GNUNET_CRYPTO_eddsa_sign (&coin->coin_priv.eddsa_priv,
+ &dr.purpose,
+ &p->coin_sig.eddsa_signature);
+ p->denom_pub = coin->denom_pub;
+ p->denom_sig = coin->denom_sig;
+ p->coin_pub = dr.coin_pub;
+ p->amount_with_fee = coin->amount_with_fee;
+ p->amount_without_fee = coin->amount_without_fee;
+ }
+ return TALER_MERCHANT_pay_frontend (merchant,
+ merchant_uri,
+ mint_uri,
+ h_wire,
+ h_contract,
+ timestamp,
+ transaction_id,
+ merchant_pub,
+ refund_deadline,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ num_coins,
+ pc,
+ max_fee,
+ amount,
+ pay_cb,
+ pay_cb_cls);
+}
+
+
+/**
+ * Pay a merchant. API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures. Note the sublte difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ *
+ * @param merchant the merchant context
+ * @param mint_uri URI of the mint that the coins belong to
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
+ * @param transaction_id transaction id for the transaction between merchant and customer
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed)
+ * @param execution_deadline date by which the merchant would like the mint to execute the transaction (can be zero if there is no specific date desired by the frontend)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_Pay *
+TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
+ const char *mint_uri,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute timestamp,
+ uint64_t transaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ struct GNUNET_TIME_Absolute execution_deadline,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PaidCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls)
+{
+ struct TALER_MERCHANT_Pay *ph;
+ json_t *pay_obj;
+ json_t *j_coins;
+ CURL *eh;
+ struct TALER_Amount total_fee;
+ struct TALER_Amount total_amount;
+ unsigned int i;
+
+ if (0 == num_coins)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ j_coins = json_array ();
+ for (i=0;i<num_coins;i++)
+ {
+ json_t *j_coin;
+ const struct TALER_MERCHANT_PaidCoin *pc = &coins[i];
+ struct TALER_Amount fee;
+
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&fee,
+ &pc->amount_with_fee,
+ &pc->amount_without_fee))
+ {
+ /* Integer underflow, fee larger than total amount?
+ This should not happen (client violated API!) */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ if (0 == i)
+ {
+ total_fee = fee;
+ total_amount = pc->amount_with_fee;
+ }
+ else
+ {
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&total_fee,
+ &total_fee,
+ &fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_add (&total_amount,
+ &total_amount,
+ &pc->amount_with_fee)) )
+ {
+ /* integer overflow */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ }
+
+ /* create JSON for this coin */
+ j_coin = json_pack ("{s:o, s:o," /* f/coin_pub */
+ " s:o, s:o," /* denom_pub / ub_sig */
+ " s:o}", /* coin_sig */
+ "f", TALER_json_from_amount (&pc->amount_with_fee),
+ "coin_pub", TALER_json_from_data (&pc->coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP)),
+ "denom_pub", TALER_json_from_rsa_public_key (pc->denom_pub.rsa_public_key),
+ "ub_sig", TALER_json_from_rsa_signature (pc->denom_sig.rsa_signature),
+ "coin_sig", TALER_json_from_data (&pc->coin_sig,
+ sizeof (struct TALER_CoinSpendSignatureP))
+ );
+ json_array_append (j_coins,
+ j_coin);
+ }
+
+ { /* Sanity check that total_amount and total_fee
+ match amount/max_fee requirements */
+ struct TALER_Amount fee_left;
+
+ if (GNUNET_OK ==
+ TALER_amount_subtract (&fee_left,
+ &total_fee,
+ max_fee))
+ {
+ /* Wallet must cover part of the fee! */
+ struct TALER_Amount new_amount;
+
+ if (GNUNET_OK !=
+ TALER_amount_add (&new_amount,
+ &fee_left,
+ amount))
+ {
+ /* integer overflow */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ if (1 ==
+ TALER_amount_cmp (&new_amount,
+ &total_amount))
+ {
+ /* new_amount > total_amount: all of the coins (total_amount)
+ do not add up to at least the new_amount owed to the
+ merchant, this request is bogus, abort */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ }
+ else
+ {
+ /* Full fee covered by merchant, but our total
+ must at least cover the total contract amount */
+ if (1 ==
+ TALER_amount_cmp (amount,
+ &total_amount))
+ {
+ /* amount > total_amount: all of the coins (total_amount) do
+ not add up to at least the amount owed to the merchant,
+ this request is bogus, abort */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ }
+ } /* end of sanity check */
+ pay_obj = json_pack ("{s:o, s:o," /* H_wire/H_contract */
+ " s:I, s:o," /* transaction id, timestamp */
+ " s:o, s:s," /* refund_deadline, mint */
+ " s:o, s:o," /* coins, max_fee */
+ " s:o}", /* amount */
+ "H_wire", TALER_json_from_data (&h_wire,
+ sizeof (h_wire)),
+ "H_contract", TALER_json_from_data (h_contract,
+ sizeof (struct GNUNET_HashCode)),
+ "transaction_id", (json_int_t) transaction_id,
+ "timestamp", TALER_json_from_abs (timestamp),
+ "refund_deadline", TALER_json_from_abs (refund_deadline),
+ "mint", mint_uri,
+ "coins", j_coins,
+ "max_fee", TALER_json_from_amount (max_fee),
+ "amount", TALER_json_from_amount (amount)
+ );
+
+ if (0 != execution_deadline.abs_value_us)
+ {
+ /* Frontend did have an execution date in mind, add it */
+ json_object_set_new (pay_obj,
+ "edate",
+ TALER_json_from_abs (execution_deadline));
+ }
+
+ ph = GNUNET_new (struct TALER_MERCHANT_Pay);
+ ph->merchant = merchant;
+ ph->cb = pay_cb;
+ ph->cb_cls = pay_cb_cls;
+ ph->url = GNUNET_strdup (merchant_uri);
+
+ eh = curl_easy_init ();
+ GNUNET_assert (NULL != (ph->json_enc =
+ json_dumps (pay_obj,
+ JSON_COMPACT)));
+ json_decref (pay_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ph->url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ ph->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (ph->json_enc)));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_WRITEFUNCTION,
+ &MAC_download_cb));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_WRITEDATA,
+ &ph->db));
+ ph->job = MAC_job_add (merchant,
+ eh,
+ GNUNET_YES,
+ &handle_pay_finished,
+ ph);
+ return ph;
+}
+
+
+/**
+ * Cancel a pay permission request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param pay the pay permission request handle
+ */
+void
+TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *pay)
+{
+ if (NULL != pay->job)
+ {
+ MAC_job_cancel (pay->job);
+ pay->job = NULL;
+ }
+ GNUNET_free_non_null (pay->db.buf);
+ GNUNET_free (pay->url);
+ GNUNET_free (pay->json_enc);
+ GNUNET_free (pay);
+}
+
+
+/* end of merchant_api_pay.c */
diff --git a/src/lib/test-mint-home/config/mint-common.conf b/src/lib/test-mint-home/config/mint-common.conf
@@ -0,0 +1,30 @@
+[mint]
+# Currency supported by the mint (can only be one)
+CURRENCY = EUR
+
+# Wire format supported by the mint
+# We use 'test' for testing of the actual
+# coin operations, and 'sepa' to test SEPA-specific routines.
+WIREFORMAT = test sepa
+
+# HTTP port the mint listens to
+PORT = 8081
+
+# Master public key used to sign the mint's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+# Is this is a testcase, use transient DB actions?
+TESTRUN = YES
+
+[mintdb-postgres]
+
+DB_CONN_STR = "postgres:///talercheck"
+
+[mint-wire-sepa]
+SEPA_RESPONSE_FILE = "test-mint-home/sepa.json"
+
+[mint-wire-test]
+REDIRECT_URL = "http://www.taler.net/"
diff --git a/src/lib/test-mint-home/config/mint-keyup.conf b/src/lib/test-mint-home/config/mint-keyup.conf
@@ -0,0 +1,86 @@
+[mint_keys]
+
+# how long is one signkey valid?
+signkey_duration = 4 weeks
+
+# how long are the signatures with the signkey valid?
+legal_duration = 2 years
+
+# how long do we generate denomination and signing keys
+# ahead of time?
+lookahead_sign = 32 weeks 1 day
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 4 weeks 1 day
+
+
+# Coin definitions are detected because the section
+# name begins with "coin_". The rest of the
+# name is free, but of course following the convention
+# of "coin_$CURRENCY[_$SUBUNIT]_$VALUE" make sense.
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_10]
+value = EUR:10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_1000]
+value = EUR:1000
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 2048
diff --git a/src/lib/test-mint-home/master.priv b/src/lib/test-mint-home/master.priv
@@ -0,0 +1 @@
+pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ
+\ No newline at end of file
diff --git a/src/lib/test-mint-home/sepa.json b/src/lib/test-mint-home/sepa.json
@@ -0,0 +1,6 @@
+{
+ "receiver_name": "Max Mustermann",
+ "iban": "DE89370400440532013000",
+ "bic": "COBADEFF370",
+ "sig": "8M5YJXM68PRAXKH76HYEBCJW657B23JA0RFGNDMZK2379YZMT626H1BN89KC0M1KJBWGYEN5Z763Q0Y7MCTZQ6BPPT7D9KFCTW60C10"
+}
+\ No newline at end of file
diff --git a/src/lib/test_merchant.conf b/src/lib/test_merchant.conf
@@ -0,0 +1,55 @@
+# Sample configuration file for a merchant.
+[merchant]
+
+# Which port do we run the backend on? (HTTP server)
+PORT = 8082
+
+# FIXME: is this one used?
+HOSTNAME = localhost
+
+# Where is our private key?
+KEYFILE = test_merchant.priv
+
+# What currency does this backend accept?
+CURRENCY = EUR
+
+# FIXME: to be revised
+TRUSTED_MINTS = taler
+
+# How quickly do we want the mint to send us our money?
+# Used only if the frontend does not specify a value.
+# FIXME: EDATE is a bit short, 'execution_delay'?
+EDATE = 3 week
+
+# Which plugin (backend) do we use for the DB.
+DB = postgres
+
+[mint-taler]
+URI = http://localhost:8081/
+MASTER_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# Auditors must be in sections "auditor-", the rest of the section
+# name could be anything.
+[auditor-ezb]
+# Informal name of the auditor. Just for the user.
+NAME = European Central Bank
+
+# URI of the auditor (especially for in the future, when the
+# auditor offers an automated issue reporting system).
+# Not really used today.
+URI = http://taler.ezb.eu/
+
+# This is the important bit: the signing key of the auditor.
+PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
+
+# This specifies which database we use.
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+
+# "wire-" sections include wire details, here for SEPA.
+[wire-sepa]
+IBAN = DE67830654080004822650
+NAME = GNUNET E.V
+BIC = GENODEF1SRL
+SALT = 17919252168512238964
+ADDRESS = "Garching"
diff --git a/src/lib/test_merchant.priv b/src/lib/test_merchant.priv
@@ -0,0 +1 @@
+`ì&-Èí–ñ./öÀ¿ jxÌGÝ¢O:6l,ζXT4í
+\ No newline at end of file
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
@@ -0,0 +1,1583 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/test_merchant_api.c
+ * @brief testcase to test merchant's HTTP API interface
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_mint_service.h>
+#include "taler_merchant_service.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+
+/**
+ * URI under which the merchant is reachable during the testcase.
+ */
+#define MERCHANT_URI "http://localhost:8082/"
+
+/**
+ * URI under which the mint is reachable during the testcase.
+ */
+#define MINT_URI "http://localhost:8081/"
+
+/**
+ * Main execution context for the main loop of the mint.
+ */
+static struct TALER_MINT_Context *ctx;
+
+/**
+ * Handle to access the mint.
+ */
+static struct TALER_MINT_Handle *mint;
+
+/**
+ * Main execution context for the main loop of the mint.
+ */
+static struct TALER_MERCHANT_Context *merchant;
+
+/**
+ * Public key of the merchant, matches the private
+ * key from "test_merchant.priv".
+ */
+static struct TALER_MerchantPublicKeyP merchant_pub;
+
+/**
+ * Task run on shutdown.
+ */
+static struct GNUNET_SCHEDULER_Task *shutdown_task;
+
+/**
+ * Task that runs the main event loop.
+ */
+static struct GNUNET_SCHEDULER_Task *ctx_task;
+
+/**
+ * Result of the testcases, #GNUNET_OK on success
+ */
+static int result;
+
+
+/**
+ * Opcodes for the interpreter.
+ */
+enum OpCode
+{
+ /**
+ * Termination code, stops the interpreter loop (with success).
+ */
+ OC_END = 0,
+
+ /**
+ * Add funds to a reserve by (faking) incoming wire transfer.
+ */
+ OC_ADMIN_ADD_INCOMING,
+
+ /**
+ * Check status of a reserve.
+ */
+ OC_WITHDRAW_STATUS,
+
+ /**
+ * Withdraw a coin from a reserve.
+ */
+ OC_WITHDRAW_SIGN,
+
+ /**
+ * Pay with coins.
+ */
+ OC_PAY
+
+};
+
+
+/**
+ * Structure specifying details about a coin to be melted.
+ * Used in a NULL-terminated array as part of command
+ * specification.
+ */
+struct MeltDetails
+{
+
+ /**
+ * Amount to melt (including fee).
+ */
+ const char *amount;
+
+ /**
+ * Reference to reserve_withdraw operations for coin to
+ * be used for the /refresh/melt operation.
+ */
+ const char *coin_ref;
+
+};
+
+
+/**
+ * Information about a fresh coin generated by the refresh operation.
+ */
+struct FreshCoin
+{
+
+ /**
+ * If @e amount is NULL, this specifies the denomination key to
+ * use. Otherwise, this will be set (by the interpreter) to the
+ * denomination PK matching @e amount.
+ */
+ const struct TALER_MINT_DenomPublicKey *pk;
+
+ /**
+ * Set (by the interpreter) to the mint's signature over the
+ * coin's public key.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Set (by the interpreter) to the coin's private key.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+};
+
+
+/**
+ * Details for a mint operation to execute.
+ */
+struct Command
+{
+ /**
+ * Opcode of the command.
+ */
+ enum OpCode oc;
+
+ /**
+ * Label for the command, can be NULL.
+ */
+ const char *label;
+
+ /**
+ * Which response code do we expect for this command?
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Details about the command.
+ */
+ union
+ {
+
+ /**
+ * Information for a #OC_ADMIN_ADD_INCOMING command.
+ */
+ struct
+ {
+
+ /**
+ * Label to another admin_add_incoming command if we
+ * should deposit into an existing reserve, NULL if
+ * a fresh reserve should be created.
+ */
+ const char *reserve_reference;
+
+ /**
+ * String describing the amount to add to the reserve.
+ */
+ const char *amount;
+
+ /**
+ * Wire details (JSON).
+ */
+ const char *wire;
+
+ /**
+ * Set (by the interpreter) to the reserve's private key
+ * we used to fill the reserve.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Set to the API's handle during the operation.
+ */
+ struct TALER_MINT_AdminAddIncomingHandle *aih;
+
+ } admin_add_incoming;
+
+ /**
+ * Information for a #OC_WITHDRAW_STATUS command.
+ */
+ struct
+ {
+
+ /**
+ * Label to the #OC_ADMIN_ADD_INCOMING command which
+ * created the reserve.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Set to the API's handle during the operation.
+ */
+ struct TALER_MINT_ReserveStatusHandle *wsh;
+
+ /**
+ * Expected reserve balance.
+ */
+ const char *expected_balance;
+
+ } reserve_status;
+
+ /**
+ * Information for a #OC_WITHDRAW_SIGN command.
+ */
+ struct
+ {
+
+ /**
+ * Which reserve should we withdraw from?
+ */
+ const char *reserve_reference;
+
+ /**
+ * String describing the denomination value we should withdraw.
+ * A corresponding denomination key must exist in the mint's
+ * offerings. Can be NULL if @e pk is set instead.
+ */
+ const char *amount;
+
+ /**
+ * If @e amount is NULL, this specifies the denomination key to
+ * use. Otherwise, this will be set (by the interpreter) to the
+ * denomination PK matching @e amount.
+ */
+ const struct TALER_MINT_DenomPublicKey *pk;
+
+ /**
+ * Set (by the interpreter) to the mint's signature over the
+ * coin's public key.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Set (by the interpreter) to the coin's private key.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Blinding key used for the operation.
+ */
+ struct TALER_DenominationBlindingKey blinding_key;
+
+ /**
+ * Withdraw handle (while operation is running).
+ */
+ struct TALER_MINT_ReserveWithdrawHandle *wsh;
+
+ } reserve_withdraw;
+
+ /**
+ * Information for a #OC_PAY command.
+ * FIXME: support tests where we pay with multiple coins at once.
+ */
+ struct
+ {
+
+ /**
+ * Amount to pay (total for the entire contract).
+ */
+ const char *total_amount;
+
+ /**
+ * Maximum fee covered by merchant.
+ */
+ const char *max_fee;
+
+ /**
+ * Reference to a reserve_withdraw operation for a coin to
+ * be used for the /deposit operation.
+ */
+ const char *coin_ref;
+
+ /**
+ * If this @e coin_ref refers to an operation that generated
+ * an array of coins, this value determines which coin to use.
+ */
+ unsigned int coin_idx;
+
+ /**
+ * Amount to pay (from the coin, including fee).
+ */
+ const char *amount_with_fee;
+
+ /**
+ * Amount to pay (from the coin, excluding fee). The sum of the
+ * deltas between all @e amount_with_fee and the @e
+ * amount_without_fee must be less than max_fee, and the sum of
+ * the @e amount_with_fee must be larger than the @e
+ * total_amount.
+ */
+ const char *amount_without_fee;
+
+ /**
+ * JSON string describing the merchant's "wire details".
+ */
+ const char *wire_details;
+
+ /**
+ * JSON string describing the contract between the two parties.
+ */
+ const char *contract;
+
+ /**
+ * Transaction ID to use.
+ */
+ uint64_t transaction_id;
+
+ /**
+ * Relative time (to add to 'now') to compute the refund deadline.
+ * Zero for no refunds.
+ */
+ struct GNUNET_TIME_Relative refund_deadline;
+
+ /**
+ * Deposit handle while operation is running.
+ */
+ struct TALER_MERCHANT_Pay *ph;
+
+ } pay;
+
+ } details;
+
+};
+
+
+/**
+ * State of the interpreter loop.
+ */
+struct InterpreterState
+{
+ /**
+ * Keys from the mint.
+ */
+ const struct TALER_MINT_Keys *keys;
+
+ /**
+ * Commands the interpreter will run.
+ */
+ struct Command *commands;
+
+ /**
+ * Interpreter task (if one is scheduled).
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Instruction pointer. Tells #interpreter_run() which
+ * instruction to run next.
+ */
+ unsigned int ip;
+
+};
+
+
+/**
+ * Task that runs the context's event loop with the GNUnet scheduler.
+ *
+ * @param cls unused
+ * @param tc scheduler context (unused)
+ */
+static void
+context_task (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc);
+
+
+/**
+ * Run the context task, the working set has changed.
+ */
+static void
+trigger_context_task ()
+{
+ GNUNET_SCHEDULER_cancel (ctx_task);
+ ctx_task = GNUNET_SCHEDULER_add_now (&context_task,
+ NULL);
+}
+
+
+/**
+ * The testcase failed, return with an error code.
+ *
+ * @param is interpreter state to clean up
+ */
+static void
+fail (struct InterpreterState *is)
+{
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Find a command by label.
+ *
+ * @param is interpreter state to search
+ * @param label label to look for
+ * @return NULL if command was not found
+ */
+static const struct Command *
+find_command (const struct InterpreterState *is,
+ const char *label)
+{
+ unsigned int i;
+ const struct Command *cmd;
+
+ if (NULL == label)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Attempt to lookup command for empty label\n");
+ return NULL;
+ }
+ for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
+ if ( (NULL != cmd->label) &&
+ (0 == strcmp (cmd->label,
+ label)) )
+ return cmd;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command not found: %s\n",
+ label);
+ return NULL;
+}
+
+
+/**
+ * Run the main interpreter loop that performs mint operations.
+ *
+ * @param cls contains the `struct InterpreterState`
+ * @param tc scheduler context
+ */
+static void
+interpreter_run (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc);
+
+
+/**
+ * Function called upon completion of our /admin/add/incoming request.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param full_response full response from the mint (for logging, in case of errors)
+ */
+static void
+add_incoming_cb (void *cls,
+ unsigned int http_status,
+ json_t *full_response)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.admin_add_incoming.aih = NULL;
+ if (MHD_HTTP_OK != http_status)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Check if the given historic event @a h corresponds to the given
+ * command @a cmd.
+ *
+ * @param h event in history
+ * @param cmd an #OC_ADMIN_ADD_INCOMING command
+ * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not
+ */
+static int
+compare_admin_add_incoming_history (const struct TALER_MINT_ReserveHistory *h,
+ const struct Command *cmd)
+{
+ struct TALER_Amount amount;
+
+ if (TALER_MINT_RTT_DEPOSIT != h->type)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (cmd->details.admin_add_incoming.amount,
+ &amount));
+ if (0 != TALER_amount_cmp (&amount,
+ &h->amount))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the given historic event @a h corresponds to the given
+ * command @a cmd.
+ *
+ * @param h event in history
+ * @param cmd an #OC_WITHDRAW_SIGN command
+ * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not
+ */
+static int
+compare_reserve_withdraw_history (const struct TALER_MINT_ReserveHistory *h,
+ const struct Command *cmd)
+{
+ struct TALER_Amount amount;
+ struct TALER_Amount amount_with_fee;
+
+ if (TALER_MINT_RTT_WITHDRAWAL != h->type)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (cmd->details.reserve_withdraw.amount,
+ &amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&amount_with_fee,
+ &amount,
+ &cmd->details.reserve_withdraw.pk->fee_withdraw));
+ if (0 != TALER_amount_cmp (&amount_with_fee,
+ &h->amount))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with the result of a /reserve/status request.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param[in] json original response in JSON format (useful only for diagnostics)
+ * @param balance current balance in the reserve, NULL on error
+ * @param history_length number of entries in the transaction history, 0 on error
+ * @param history detailed transaction history, NULL on error
+ */
+static void
+reserve_status_cb (void *cls,
+ unsigned int http_status,
+ json_t *json,
+ const struct TALER_Amount *balance,
+ unsigned int history_length,
+ const struct TALER_MINT_ReserveHistory *history)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+ struct Command *rel;
+ unsigned int i;
+ unsigned int j;
+ struct TALER_Amount amount;
+
+ cmd->details.reserve_status.wsh = NULL;
+ if (cmd->expected_response_code != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s\n",
+ http_status,
+ cmd->label);
+ GNUNET_break (0);
+ json_dumpf (json, stderr, 0);
+ fail (is);
+ return;
+ }
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ j = 0;
+ for (i=0;i<is->ip;i++)
+ {
+ switch ((rel = &is->commands[i])->oc)
+ {
+ case OC_ADMIN_ADD_INCOMING:
+ if ( ( (NULL != rel->label) &&
+ (0 == strcmp (cmd->details.reserve_status.reserve_reference,
+ rel->label) ) ) ||
+ ( (NULL != rel->details.admin_add_incoming.reserve_reference) &&
+ (0 == strcmp (cmd->details.reserve_status.reserve_reference,
+ rel->details.admin_add_incoming.reserve_reference) ) ) )
+ {
+ if (GNUNET_OK !=
+ compare_admin_add_incoming_history (&history[j],
+ rel))
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ j++;
+ }
+ break;
+ case OC_WITHDRAW_SIGN:
+ if (0 == strcmp (cmd->details.reserve_status.reserve_reference,
+ rel->details.reserve_withdraw.reserve_reference))
+ {
+ if (GNUNET_OK !=
+ compare_reserve_withdraw_history (&history[j],
+ rel))
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ j++;
+ }
+ break;
+ default:
+ /* unreleated, just skip */
+ break;
+ }
+ }
+ if (j != history_length)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ if (NULL != cmd->details.reserve_status.expected_balance)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (cmd->details.reserve_status.expected_balance,
+ &amount));
+ if (0 != TALER_amount_cmp (&amount,
+ balance))
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ }
+ break;
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_break (0);
+ break;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Function called upon completion of our /reserve/withdraw request.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param sig signature over the coin, NULL on error
+ * @param full_response full response from the mint (for logging, in case of errors)
+ */
+static void
+reserve_withdraw_cb (void *cls,
+ unsigned int http_status,
+ const struct TALER_DenominationSignature *sig,
+ json_t *full_response)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.reserve_withdraw.wsh = NULL;
+ if (cmd->expected_response_code != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s\n",
+ http_status,
+ cmd->label);
+ json_dumpf (full_response, stderr, 0);
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ if (NULL == sig)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ cmd->details.reserve_withdraw.sig.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* nothing to check */
+ break;
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_break (0);
+ break;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Function called with the result of a /pay operation.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param obj the received JSON reply, should be kept as proof (and, in case of errors,
+ * be forwarded to the customer)
+ */
+static void
+pay_cb (void *cls,
+ unsigned int http_status,
+ const char *redirect_uri,
+ json_t *obj)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.pay.ph = NULL;
+ if (cmd->expected_response_code != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s\n",
+ http_status,
+ cmd->label);
+ json_dumpf (obj, stderr, 0);
+ fail (is);
+ return;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Find denomination key matching the given amount.
+ *
+ * @param keys array of keys to search
+ * @param amount coin value to look for
+ * @return NULL if no matching key was found
+ */
+static const struct TALER_MINT_DenomPublicKey *
+find_pk (const struct TALER_MINT_Keys *keys,
+ const struct TALER_Amount *amount)
+{
+ unsigned int i;
+ struct GNUNET_TIME_Absolute now;
+ struct TALER_MINT_DenomPublicKey *pk;
+ char *str;
+
+ now = GNUNET_TIME_absolute_get ();
+ for (i=0;i<keys->num_denom_keys;i++)
+ {
+ pk = &keys->denom_keys[i];
+ if ( (0 == TALER_amount_cmp (amount,
+ &pk->value)) &&
+ (now.abs_value_us >= pk->valid_from.abs_value_us) &&
+ (now.abs_value_us < pk->withdraw_valid_until.abs_value_us) )
+ return pk;
+ }
+ /* do 2nd pass to check if expiration times are to blame for failure */
+ str = TALER_amount_to_string (amount);
+ for (i=0;i<keys->num_denom_keys;i++)
+ {
+ pk = &keys->denom_keys[i];
+ if ( (0 == TALER_amount_cmp (amount,
+ &pk->value)) &&
+ ( (now.abs_value_us < pk->valid_from.abs_value_us) ||
+ (now.abs_value_us > pk->withdraw_valid_until.abs_value_us) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Have denomination key for `%s', but with wrong expiration range %llu vs [%llu,%llu)\n",
+ str,
+ now.abs_value_us,
+ pk->valid_from.abs_value_us,
+ pk->withdraw_valid_until.abs_value_us);
+ GNUNET_free (str);
+ return NULL;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No denomination key for amount %s found\n",
+ str);
+ GNUNET_free (str);
+ return NULL;
+}
+
+
+/**
+ * Run the main interpreter loop that performs mint operations.
+ *
+ * @param cls contains the `struct InterpreterState`
+ * @param tc scheduler context
+ */
+static void
+interpreter_run (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+ const struct Command *ref;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Absolute execution_date;
+ json_t *wire;
+
+ is->task = NULL;
+ if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
+ {
+ fprintf (stderr,
+ "Test aborted by shutdown request\n");
+ fail (is);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Interpreter runs command %u/%s(%u)\n",
+ is->ip,
+ cmd->label,
+ cmd->oc);
+ switch (cmd->oc)
+ {
+ case OC_END:
+ result = GNUNET_OK;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case OC_ADMIN_ADD_INCOMING:
+ if (NULL !=
+ cmd->details.admin_add_incoming.reserve_reference)
+ {
+ ref = find_command (is,
+ cmd->details.admin_add_incoming.reserve_reference);
+ GNUNET_assert (NULL != ref);
+ GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+ cmd->details.admin_add_incoming.reserve_priv
+ = ref->details.admin_add_incoming.reserve_priv;
+ }
+ else
+ {
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+
+ priv = GNUNET_CRYPTO_eddsa_key_create ();
+ cmd->details.admin_add_incoming.reserve_priv.eddsa_priv = *priv;
+ GNUNET_free (priv);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.admin_add_incoming.reserve_priv.eddsa_priv,
+ &reserve_pub.eddsa_pub);
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.admin_add_incoming.amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ cmd->details.admin_add_incoming.amount,
+ is->ip);
+ fail (is);
+ return;
+ }
+ wire = json_loads (cmd->details.admin_add_incoming.wire,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == wire)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse wire details `%s' at %u\n",
+ cmd->details.admin_add_incoming.wire,
+ is->ip);
+ fail (is);
+ return;
+ }
+ execution_date = GNUNET_TIME_absolute_get ();
+ TALER_round_abs_time (&execution_date);
+ cmd->details.admin_add_incoming.aih
+ = TALER_MINT_admin_add_incoming (mint,
+ &reserve_pub,
+ &amount,
+ execution_date,
+ wire,
+ &add_incoming_cb,
+ is);
+ if (NULL == cmd->details.admin_add_incoming.aih)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ trigger_context_task ();
+ return;
+ case OC_WITHDRAW_STATUS:
+ GNUNET_assert (NULL !=
+ cmd->details.reserve_status.reserve_reference);
+ ref = find_command (is,
+ cmd->details.reserve_status.reserve_reference);
+ GNUNET_assert (NULL != ref);
+ GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ref->details.admin_add_incoming.reserve_priv.eddsa_priv,
+ &reserve_pub.eddsa_pub);
+ cmd->details.reserve_status.wsh
+ = TALER_MINT_reserve_status (mint,
+ &reserve_pub,
+ &reserve_status_cb,
+ is);
+ trigger_context_task ();
+ return;
+ case OC_WITHDRAW_SIGN:
+ GNUNET_assert (NULL !=
+ cmd->details.reserve_withdraw.reserve_reference);
+ ref = find_command (is,
+ cmd->details.reserve_withdraw.reserve_reference);
+ GNUNET_assert (NULL != ref);
+ GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+ if (NULL != cmd->details.reserve_withdraw.amount)
+ {
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.reserve_withdraw.amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ cmd->details.reserve_withdraw.amount,
+ is->ip);
+ fail (is);
+ return;
+ }
+ cmd->details.reserve_withdraw.pk = find_pk (is->keys,
+ &amount);
+ }
+ if (NULL == cmd->details.reserve_withdraw.pk)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to determine denomination key at %u\n",
+ is->ip);
+ fail (is);
+ return;
+ }
+
+ /* create coin's private key */
+ {
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+
+ priv = GNUNET_CRYPTO_eddsa_key_create ();
+ cmd->details.reserve_withdraw.coin_priv.eddsa_priv = *priv;
+ GNUNET_free (priv);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.reserve_withdraw.coin_priv.eddsa_priv,
+ &coin_pub.eddsa_pub);
+ cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key
+ = GNUNET_CRYPTO_rsa_blinding_key_create (GNUNET_CRYPTO_rsa_public_key_len (cmd->details.reserve_withdraw.pk->key.rsa_public_key));
+ cmd->details.reserve_withdraw.wsh
+ = TALER_MINT_reserve_withdraw (mint,
+ cmd->details.reserve_withdraw.pk,
+ &ref->details.admin_add_incoming.reserve_priv,
+ &cmd->details.reserve_withdraw.coin_priv,
+ &cmd->details.reserve_withdraw.blinding_key,
+ &reserve_withdraw_cb,
+ is);
+ if (NULL == cmd->details.reserve_withdraw.wsh)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ trigger_context_task ();
+ return;
+ case OC_PAY:
+ {
+ struct TALER_MERCHANT_PayCoin pc;
+ struct TALER_Amount amount;
+ struct TALER_Amount max_fee;
+ json_t *wire;
+ json_t *contract;
+ struct GNUNET_HashCode h_wire;
+ struct GNUNET_HashCode h_contract;
+ struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Absolute timestamp;
+
+ /* get amount */
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.pay.total_amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse total amount `%s' at %u\n",
+ cmd->details.pay.total_amount,
+ is->ip);
+ fail (is);
+ return;
+ }
+
+ /* get max_fee */
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.pay.max_fee,
+ &max_fee))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse max_fee `%s' at %u\n",
+ cmd->details.pay.max_fee,
+ is->ip);
+ fail (is);
+ return;
+ }
+
+ /* parse wire details */
+ wire = json_loads (cmd->details.pay.wire_details,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == wire)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse wire details `%s' at %u\n",
+ cmd->details.pay.wire_details,
+ is->ip);
+ fail (is);
+ return;
+ }
+ TALER_hash_json (wire,
+ &h_wire);
+ json_decref (wire);
+
+ /* parse contract */
+ contract = json_loads (cmd->details.pay.contract,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == contract)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse contract details `%s' at instruction %u\n",
+ cmd->details.pay.contract,
+ is->ip);
+ fail (is);
+ return;
+ }
+ TALER_hash_json (contract,
+ &h_contract);
+ json_decref (contract);
+
+ /* initialize 'pc' (FIXME: to do in a loop later...) */
+ {
+ const struct Command *ref;
+
+ memset (&pc, 0, sizeof (pc));
+ ref = find_command (is,
+ cmd->details.pay.coin_ref);
+ GNUNET_assert (NULL != ref);
+ switch (ref->oc)
+ {
+ case OC_WITHDRAW_SIGN:
+ pc.coin_priv = ref->details.reserve_withdraw.coin_priv;
+ pc.denom_pub = ref->details.reserve_withdraw.pk->key;
+ pc.denom_sig = ref->details.reserve_withdraw.sig;
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.pay.amount_without_fee,
+ &pc.amount_without_fee))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ cmd->details.pay.amount_without_fee,
+ is->ip);
+ fail (is);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.pay.amount_with_fee,
+ &pc.amount_with_fee))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ cmd->details.pay.amount_with_fee,
+ is->ip);
+ fail (is);
+ return;
+ }
+ }
+
+ if (0 == cmd->details.pay.refund_deadline.rel_value_us)
+ refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS; /* no refunds */
+ else
+ refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.pay.refund_deadline);
+ TALER_round_abs_time (&refund_deadline);
+ timestamp = GNUNET_TIME_absolute_get ();
+ TALER_round_abs_time (×tamp);
+ cmd->details.pay.ph
+ = TALER_MERCHANT_pay_wallet (merchant,
+ MERCHANT_URI "pay",
+ MINT_URI,
+ &h_wire,
+ &h_contract,
+ timestamp,
+ cmd->details.pay.transaction_id,
+ &merchant_pub,
+ refund_deadline,
+ 1 /* num_coins */,
+ &pc /* coins */,
+ &max_fee,
+ &amount,
+ &pay_cb,
+ is);
+ }
+ if (NULL == cmd->details.pay.ph)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ trigger_context_task ();
+ return;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unknown instruction %d at %u (%s)\n",
+ cmd->oc,
+ is->ip,
+ cmd->label);
+ fail (is);
+ return;
+ }
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Function run when the test terminates (good or bad).
+ * Cleans up our state.
+ *
+ * @param cls the interpreter state.
+ * @param tc unused
+ */
+static void
+do_shutdown (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd;
+ unsigned int i;
+
+ shutdown_task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Shutdown executing\n");
+ for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
+ {
+ switch (cmd->oc)
+ {
+ case OC_END:
+ GNUNET_assert (0);
+ break;
+ case OC_ADMIN_ADD_INCOMING:
+ if (NULL != cmd->details.admin_add_incoming.aih)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MINT_admin_add_incoming_cancel (cmd->details.admin_add_incoming.aih);
+ cmd->details.admin_add_incoming.aih = NULL;
+ }
+ break;
+ case OC_WITHDRAW_STATUS:
+ if (NULL != cmd->details.reserve_status.wsh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MINT_reserve_status_cancel (cmd->details.reserve_status.wsh);
+ cmd->details.reserve_status.wsh = NULL;
+ }
+ break;
+ case OC_WITHDRAW_SIGN:
+ if (NULL != cmd->details.reserve_withdraw.wsh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MINT_reserve_withdraw_cancel (cmd->details.reserve_withdraw.wsh);
+ cmd->details.reserve_withdraw.wsh = NULL;
+ }
+ if (NULL != cmd->details.reserve_withdraw.sig.rsa_signature)
+ {
+ GNUNET_CRYPTO_rsa_signature_free (cmd->details.reserve_withdraw.sig.rsa_signature);
+ cmd->details.reserve_withdraw.sig.rsa_signature = NULL;
+ }
+ if (NULL != cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key)
+ {
+ GNUNET_CRYPTO_rsa_blinding_key_free (cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key);
+ cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key = NULL;
+ }
+ break;
+ case OC_PAY:
+ if (NULL != cmd->details.pay.ph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MERCHANT_pay_cancel (cmd->details.pay.ph);
+ cmd->details.pay.ph = NULL;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unknown instruction %d at %u (%s)\n",
+ cmd->oc,
+ i,
+ cmd->label);
+ break;
+ }
+ }
+ if (NULL != is->task)
+ {
+ GNUNET_SCHEDULER_cancel (is->task);
+ is->task = NULL;
+ }
+ GNUNET_free (is);
+ if (NULL != ctx_task)
+ {
+ GNUNET_SCHEDULER_cancel (ctx_task);
+ ctx_task = NULL;
+ }
+ if (NULL != mint)
+ {
+ TALER_MINT_disconnect (mint);
+ mint = NULL;
+ }
+ if (NULL != merchant)
+ {
+ TALER_MERCHANT_fini (merchant);
+ merchant = NULL;
+ }
+ if (NULL != ctx)
+ {
+ TALER_MINT_fini (ctx);
+ ctx = NULL;
+ }
+}
+
+
+/**
+ * Functions of this type are called to provide the retrieved signing and
+ * denomination keys of the mint. No TALER_MINT_*() functions should be called
+ * in this callback.
+ *
+ * @param cls closure
+ * @param keys information about keys of the mint
+ */
+static void
+cert_cb (void *cls,
+ const struct TALER_MINT_Keys *keys)
+{
+ struct InterpreterState *is = cls;
+
+ /* check that keys is OK */
+#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0)
+ ERR (NULL == keys);
+ ERR (0 == keys->num_sign_keys);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Read %u signing keys\n",
+ keys->num_sign_keys);
+ ERR (0 == keys->num_denom_keys);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Read %u denomination keys\n",
+ keys->num_denom_keys);
+#undef ERR
+
+ /* run actual tests via interpreter-loop */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Certificate callback invoked, starting interpreter\n");
+ is->keys = keys;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Task that runs the context's event loop with the GNUnet scheduler.
+ *
+ * @param cls unused
+ * @param tc scheduler context (unused)
+ */
+static void
+context_task (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ long timeout;
+ int max_fd;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ struct GNUNET_NETWORK_FDSet *rs;
+ struct GNUNET_NETWORK_FDSet *ws;
+ struct GNUNET_TIME_Relative delay;
+
+ ctx_task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Event loop running\n");
+ TALER_MINT_perform (ctx);
+ TALER_MERCHANT_perform (merchant);
+ max_fd = -1;
+ timeout = -1;
+ FD_ZERO (&read_fd_set);
+ FD_ZERO (&write_fd_set);
+ FD_ZERO (&except_fd_set);
+ TALER_MINT_get_select_info (ctx,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set,
+ &max_fd,
+ &timeout);
+ TALER_MERCHANT_get_select_info (merchant,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set,
+ &max_fd,
+ &timeout);
+ if (timeout >= 0)
+ delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout);
+ else
+ delay = GNUNET_TIME_UNIT_FOREVER_REL;
+ rs = GNUNET_NETWORK_fdset_create ();
+ GNUNET_NETWORK_fdset_copy_native (rs,
+ &read_fd_set,
+ max_fd + 1);
+ ws = GNUNET_NETWORK_fdset_create ();
+ GNUNET_NETWORK_fdset_copy_native (ws,
+ &write_fd_set,
+ max_fd + 1);
+ ctx_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
+ delay,
+ rs,
+ ws,
+ &context_task,
+ cls);
+ GNUNET_NETWORK_fdset_destroy (rs);
+ GNUNET_NETWORK_fdset_destroy (ws);
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct InterpreterState *is;
+ static struct Command commands[] =
+ {
+ /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */
+ { .oc = OC_ADMIN_ADD_INCOMING,
+ .label = "create-reserve-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":42 }",
+ .details.admin_add_incoming.amount = "EUR:5.01" },
+ /* Withdraw a 5 EUR coin, at fee of 1 ct */
+ { .oc = OC_WITHDRAW_SIGN,
+ .label = "withdraw-coin-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.reserve_withdraw.reserve_reference = "create-reserve-1",
+ .details.reserve_withdraw.amount = "EUR:5" },
+ /* Check that deposit and withdraw operation are in history, and
+ that the balance is now at zero */
+ { .oc = OC_WITHDRAW_STATUS,
+ .label = "withdraw-status-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.reserve_status.reserve_reference = "create-reserve-1",
+ .details.reserve_status.expected_balance = "EUR:0" },
+ /* Try to pay with the 5 EUR coin (in full) */
+ { .oc = OC_PAY,
+ .label = "deposit-simple",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.pay.total_amount = "EUR:5",
+ .details.pay.max_fee = "EUR:0.5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.amount_with_fee = "EUR:5",
+ .details.pay.amount_without_fee = "EUR:4.99",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.pay.contract = "{ \"items\":[ {\"name\":\"ice cream\", \"value\":1} ] }",
+ .details.pay.transaction_id = 1 },
+
+ /* Try to double-spend the 5 EUR coin with different wire details */
+ { .oc = OC_PAY,
+ .label = "deposit-double-1",
+ .expected_response_code = MHD_HTTP_FORBIDDEN,
+ .details.pay.total_amount = "EUR:5",
+ .details.pay.max_fee = "EUR:0.5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.amount_with_fee = "EUR:5",
+ .details.pay.amount_without_fee = "EUR:4.99",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":43 }",
+ .details.pay.contract = "{ \"items\":[{ \"name\":\"ice cream\", \"value\":1} ] }",
+ .details.pay.transaction_id = 1 },
+ /* Try to double-spend the 5 EUR coin at the same merchant (but different
+ transaction ID) */
+ { .oc = OC_PAY,
+ .label = "deposit-double-2",
+ .expected_response_code = MHD_HTTP_FORBIDDEN,
+ .details.pay.total_amount = "EUR:5",
+ .details.pay.max_fee = "EUR:0.5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.amount_with_fee = "EUR:5",
+ .details.pay.amount_without_fee = "EUR:4.99",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.pay.contract = "{ \"items\":[ {\"name\":\"ice cream\", \"value\":1} ] }",
+ .details.pay.transaction_id = 2 },
+ /* Try to double-spend the 5 EUR coin at the same merchant (but different
+ contract) */
+ { .oc = OC_PAY,
+ .label = "deposit-double-3",
+ .expected_response_code = MHD_HTTP_FORBIDDEN,
+ .details.pay.total_amount = "EUR:5",
+ .details.pay.max_fee = "EUR:0.5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.amount_with_fee = "EUR:5",
+ .details.pay.amount_without_fee = "EUR:4.99",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.pay.contract = "{ \"items\":[ {\"name\":\"ice cream\", \"value\":2} ] }",
+ .details.pay.transaction_id = 1 },
+
+ { .oc = OC_END }
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Interpreter initializing\n");
+ is = GNUNET_new (struct InterpreterState);
+ is->commands = commands;
+
+ ctx = TALER_MINT_init ();
+ GNUNET_assert (NULL != ctx);
+ merchant = TALER_MERCHANT_init ();
+ GNUNET_assert (NULL != merchant);
+ ctx_task = GNUNET_SCHEDULER_add_now (&context_task,
+ ctx);
+ mint = TALER_MINT_connect (ctx,
+ MINT_URI,
+ &cert_cb, is,
+ TALER_MINT_OPTION_END);
+ GNUNET_assert (NULL != mint);
+ shutdown_task
+ = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_SECONDS, 150),
+ &do_shutdown, is);
+}
+
+
+/**
+ * Main function for the testcase for the mint API.
+ *
+ * @param argc expected to be 1
+ * @param argv expected to only contain the program name
+ */
+int
+main (int argc,
+ char * const *argv)
+{
+ /* Value from "gnunet-ecc -p test_merchant.priv" */
+ const char *merchant_pub_str
+ = "5TRNSWAWHKBJ7G4T3PKRCQA6MCB3MX82F4M2XXS1653KE1V8RFPG";
+ struct GNUNET_OS_Process *proc;
+ struct GNUNET_OS_Process *mintd;
+ struct GNUNET_OS_Process *merchantd;
+
+ GNUNET_log_setup ("test-merchant-api",
+ "WARNING",
+ NULL);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_STRINGS_string_to_data (merchant_pub_str,
+ strlen (merchant_pub_str),
+ &merchant_pub,
+ sizeof (merchant_pub)));
+ proc = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-mint-keyup",
+ "taler-mint-keyup",
+ "-d", "test-mint-home",
+ "-m", "test-mint-home/master.priv",
+ NULL);
+ GNUNET_OS_process_wait (proc);
+ GNUNET_OS_process_destroy (proc);
+ mintd = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-mint-httpd",
+ "taler-mint-httpd",
+ "-d", "test-mint-home",
+ NULL);
+ /* give child time to start and bind against the socket */
+ fprintf (stderr, "Waiting for taler-mint-httpd to be ready");
+ do
+ {
+ fprintf (stderr, ".");
+ sleep (1);
+ }
+ while (0 != system ("wget -q -t 1 -T 1 " MINT_URI "keys -o /dev/null -O /dev/null"));
+ fprintf (stderr, "\n");
+ merchantd = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-httpd",
+ "taler-merchant-httpd",
+ "-c", "test_merchant.conf",
+ NULL);
+ /* give child time to start and bind against the socket */
+ fprintf (stderr, "Waiting for taler-merchant-httpd to be ready");
+ do
+ {
+ fprintf (stderr, ".");
+ sleep (1);
+ }
+ while (0 != system ("wget -q -t 1 -T 1 " MERCHANT_URI " -o /dev/null -O /dev/null"));
+ fprintf (stderr, "\n");
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_run (&run, NULL);
+ GNUNET_OS_process_kill (merchantd,
+ SIGTERM);
+ GNUNET_OS_process_wait (merchantd);
+ GNUNET_OS_process_destroy (merchantd);
+ GNUNET_OS_process_kill (mintd,
+ SIGTERM);
+ GNUNET_OS_process_wait (mintd);
+ GNUNET_OS_process_destroy (mintd);
+ return (GNUNET_OK == result) ? 0 : 1;
+}
+
+/* end of test_merchant_api.c */
diff --git a/src/tsconfig.json b/src/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "jsx": "react"
+ },
+ "files": [
+ "frontend/execute.tsx"
+ ]
+}
+