gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-merchant] branch stable updated (417a952 -> 72eb4f9)


From: gnunet
Subject: [GNUnet-SVN] [taler-merchant] branch stable updated (417a952 -> 72eb4f9)
Date: Mon, 11 Dec 2017 11:12:20 +0100

This is an automated email from the git hooks/post-receive script.

marcello pushed a change to branch stable
in repository merchant.

    from 417a952  add refund permissions to /pay
     add 46032ac  fix rules for 'make dist'
     add 019f6fd  bumping versions for v0.4.0 release
     add 431db2d  first (incomplete) skeleton for tipping API
     add 72afe27  add new functions for tipping (unimplemented) to merchantdb
     add d2c33c7  implement postgres_enable_tip_reserve (but SQL statemets are 
missing)
     add 598f45b  add SQL for postgres_enable_tip_reserve
     add 1b746ee  implementing backenddb logic for tipping (not tested)
     add 45daded  todo was imported into the bug tracker
     add c94688c  work around build error for incomplete /tip implementation
     add 868a853  add testcases for merchantdb tipping functions, fix bugs found
     add 1e2a852  fix #5108
     add 9a950e3  design C API of merchant for tipping
     add 3344dac  max deposit fee picked from defaults
     add 84b27ab  implement TALER_MERCHANT_tip_enable
     add 501ce6d  implement TALER_MERCHANT_tip_authorize
     add 7388865  implement TALER_MERCHANT_tip_pickup
     add bffe349  specify default fees for testcase
     add b2d9e1c  starting design of test harness for tipping
     add 29a2264  implement logic for OC_TIP_ENABLE
     add 4525219  fix indentation
     add 22b8ebf  implement OC_TIP_AUTHORIZE
     add 90106da  doxygen fixes
     add 2ea6c04  implement getting reserve_priv from configuration in test and 
backend
     add f637ee5  work on new tipping test logic, also refactoring to match 
latest exchange API (as that was changed to support tipping test logic better)
     add df73a80  finish tip_pickup command in test_merchant_api interpreter
     add 2ce7e16  implement logic for spending from tips in test interpreter
     add 9e9dd55  starting with testing tipping
     add 26f8dda  test tip_enable API
     add 3f1cb92  tests and bugfixes for /tip-authorize
     add 78e7999  towards implementing /tip-pickup (incomplete)
     add e1ea7e3  work on /tip-pickup implemenation (WiP)
     add 73be11d  use talercheck for testing, like exchange does
     add d4fe427  deal with soft errors by retrying in merchantdb (for tipping)
     add b01dd2f  fix response code generation
     add 4e61ed4  add new tables to drop method
     add 44bb2aa  misc bugfixes to get first /tip-pickup test to pass
     add dc03bcf  complete /tip-pickup testing
     add 65d6a48  add taler-merchant-tip-enable, including docs
     add 361b91d  paid should be a boolean, not a blog, simplify SQL (#5157)
     add 70caa1b  document tipping
     add 57bb974  typo
     add c35fc42  mention time format
     add 39a47a6  fix how reserve priv is fetched from config
     add e364bc3  manual
     add d2db3ad  implement /tip-query
     add 1d3ce16  add mising files
     add eaaebef  uri->url, add tip_token with pickup_url in /tip-authorize
     add 62b3113  also use exchange_url in test
     add 8f0a7ae  also add pickup_url in lib
     add 280aecd  add critical missing comma
     add d5cde48  add amount to tip-token
     add 78f0bae  fix #5187
     add 9f1ca17  changing tip_reserve_priv to tip_reserve_priv_filename (#5188)
     add a8f3c5f  fix config
     add 3aa4e09  make "tip enable" tool pick tip-reserve priv from filename
     add e8c65b9  also expand filename for tipping reserve in backend
     add cea6fc5  add a bit more diagnostics for Florian's assertion on line 
370, plus likely a related fix as some error cases did not properly return and 
instead continued execution
     add b5222ad  fix issues related to unrounded times
     add 9be3e28  fix typo
     add 1a59eda  fix enum confusion
     add ee8199b  assert on the right value
     add 74608a0  handle localtime() failure
     add f9ff079  check return values
     add 7d4b310  simplify logic
     add dbd20b2  use another port for the test, since it's already used on 
taler.net (see #5209)
     add 5be5ce5  extend test_merchant_api interpreter with command to run 
wirewatch (not yet in use)
     add d19fdbc  fix double free
     add 4e4adad  update .gitignore
     add 72eb4f9  use bank /admin/add/incoming instead of exchange's in testcase

No new revisions were added by this update.

Summary of changes:
 .gitignore                                         |    4 +
 ChangeLog                                          |    3 +
 Makefile.am                                        |    8 +
 TODO                                               |   10 -
 configure.ac                                       |   11 +-
 doc/Makefile.am                                    |   21 +-
 doc/manual.texi                                    |  178 ++
 doc/taler-merchant-httpd.1                         |   37 +
 doc/taler-merchant-tip-enable.1                    |   47 +
 src/backend/Makefile.am                            |    5 +-
 src/backend/taler-merchant-httpd.c                 |  118 +-
 src/backend/taler-merchant-httpd.h                 |   19 +
 src/backend/taler-merchant-httpd_exchanges.c       |    6 +-
 src/backend/taler-merchant-httpd_history.c         |    2 +-
 src/backend/taler-merchant-httpd_pay.c             |   16 +-
 src/backend/taler-merchant-httpd_proposal.c        |   24 +-
 src/backend/taler-merchant-httpd_refund.c          |    4 +-
 src/backend/taler-merchant-httpd_responses.c       |   24 +
 src/backend/taler-merchant-httpd_responses.h       |   19 +-
 src/backend/taler-merchant-httpd_tip-authorize.c   |  223 +++
 ...tion.h => taler-merchant-httpd_tip-authorize.h} |   25 +-
 src/backend/taler-merchant-httpd_tip-enable.c      |  157 ++
 ...tpd_pay.h => taler-merchant-httpd_tip-enable.h} |   34 +-
 src/backend/taler-merchant-httpd_tip-pickup.c      |  573 +++++++
 ...saction.h => taler-merchant-httpd_tip-pickup.h} |   25 +-
 src/backend/taler-merchant-httpd_tip-query.c       |  102 ++
 ...nsaction.h => taler-merchant-httpd_tip-query.h} |   26 +-
 src/backenddb/Makefile.am                          |    2 +-
 src/backenddb/plugin_merchantdb_postgres.c         |  779 ++++++++-
 src/backenddb/test-merchantdb-postgres.conf        |    2 +-
 src/backenddb/test_merchantdb.c                    |  261 ++-
 src/include/taler_merchant_service.h               |  202 ++-
 src/include/taler_merchantdb_plugin.h              |  122 +-
 src/lib/Makefile.am                                |    6 +-
 src/lib/merchant_api_tip_authorize.c               |  276 ++++
 src/lib/merchant_api_tip_enable.c                  |  227 +++
 src/lib/merchant_api_tip_pickup.c                  |  336 ++++
 src/lib/merchant_api_track_transaction.c           |   35 +-
 src/lib/reserve_dtip.priv                          |    1 +
 src/lib/reserve_tip.priv                           |  Bin 0 -> 32 bytes
 src/lib/test_merchant_api.c                        | 1735 +++++++++++++++-----
 src/lib/test_merchant_api.conf                     |   30 +-
 src/merchant-tools/Makefile.am                     |   14 +-
 .../taler-merchant-generate-payments.c             |   60 +-
 src/merchant-tools/taler-merchant-tip-enable.c     |  277 ++++
 45 files changed, 5451 insertions(+), 635 deletions(-)
 delete mode 100644 TODO
 create mode 100644 doc/taler-merchant-httpd.1
 create mode 100644 doc/taler-merchant-tip-enable.1
 create mode 100644 src/backend/taler-merchant-httpd_tip-authorize.c
 copy src/backend/{taler-merchant-httpd_track-transaction.h => 
taler-merchant-httpd_tip-authorize.h} (64%)
 create mode 100644 src/backend/taler-merchant-httpd_tip-enable.c
 copy src/backend/{taler-merchant-httpd_pay.h => 
taler-merchant-httpd_tip-enable.h} (64%)
 create mode 100644 src/backend/taler-merchant-httpd_tip-pickup.c
 copy src/backend/{taler-merchant-httpd_track-transaction.h => 
taler-merchant-httpd_tip-pickup.h} (64%)
 create mode 100644 src/backend/taler-merchant-httpd_tip-query.c
 copy src/backend/{taler-merchant-httpd_track-transaction.h => 
taler-merchant-httpd_tip-query.h} (64%)
 create mode 100644 src/lib/merchant_api_tip_authorize.c
 create mode 100644 src/lib/merchant_api_tip_enable.c
 create mode 100644 src/lib/merchant_api_tip_pickup.c
 create mode 100644 src/lib/reserve_dtip.priv
 create mode 100644 src/lib/reserve_tip.priv
 create mode 100644 src/merchant-tools/taler-merchant-tip-enable.c

diff --git a/.gitignore b/.gitignore
index 5c692c1..7ef1c79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ taler_merchant_config.h.in
 doc/*
 !doc/*.texi
 !doc/*.css
+!doc/*.1
 doc/version.texi
 !doc/*.am
 !doc/*.sh
@@ -46,3 +47,6 @@ src/merchant-tools/mitm/taler-merchant-mitm
 src/merchant-tools/mitm/merchant-mitm.wsgi
 doxygen-doc/
 contrib/taler-merchant.tag
+src/merchant-tools/taler-merchant-tip-enable
+src/lib/reserve_dkey.priv
+src/lib/reserve_key.priv
diff --git a/ChangeLog b/ChangeLog
index 749eede..5b0dfa3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+Wed Oct 18 15:33:23 CEST 2017
+       Releasing taler-merchant 0.4.0. -CG
+
 Thu Jun 22 15:12:37 CEST 2017
         Implementing /refund
 
diff --git a/Makefile.am b/Makefile.am
index c42e33d..9309390 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,9 +1,17 @@
 # This Makefile is in the public domain
 
 if DOC_ONLY
+if ENABLE_DOC
   SUBDIRS = . doc
 else
+  SUBDIRS = .
+endif
+else
+if ENABLE_DOC
   SUBDIRS = . src doc
+else
+  SUBDIRS = . src doc
+endif
 endif
 
 @DX_RULES@
diff --git a/TODO b/TODO
deleted file mode 100644
index cffc334..0000000
--- a/TODO
+++ /dev/null
@@ -1,10 +0,0 @@
-Major issues with /pay:
-
-1) Why is there a 'paid' field in merchant_contract_terms DB table?
-
-2) If we do have the row_id of merchant_contract_terms, why do we store
-   the full h_contract_terms/merchant_pub again and again in the other tables?
-
-3) What happens if we store in merchant_transactions some timestamp/exchange
-   and then the deposits (partially) fail? Do we undo the deposits? Do we
-   allow a 2nd round of /pay? What happens if the exchange differs the 2nd 
time?
diff --git a/configure.ac b/configure.ac
index f3f7b23..b13a58a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,7 +4,7 @@
 # This configure file is in the public domain
 
 AC_PREREQ([2.69])
-AC_INIT([taler-merchant], [0.3.0], address@hidden)
+AC_INIT([taler-merchant], [0.4.0], address@hidden)
 AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c])
 AC_CONFIG_HEADERS([taler_merchant_config.h])
 # support for non-recursive builds
@@ -153,7 +153,7 @@ AS_IF([test $microhttpd = 0],
 *** ]])])
 
 jansson=0
-PKG_CHECK_MODULES([JANSSON], [jansson >= 2.3], 
+PKG_CHECK_MODULES([JANSSON], [jansson >= 2.3],
                   [LDFLAGS="$JANSSON_LIBS $LDFLAGS"
                    CPPFLAGS="$JANSSON_CFLAGS $CPPFLAGS"],
                   [AC_MSG_ERROR([[
@@ -265,6 +265,12 @@ AC_CHECK_PROG([tsc],[tsc],[yes],[no])
 AM_CONDITIONAL([HAVE_TSC], [test "x$tsc" = xyes])
 
 
+AC_ARG_ENABLE([[doc]],
+  [AS_HELP_STRING([[--disable-doc]], [do not build any documentation])], ,
+    [enable_doc=yes])
+test "x$enable_doc" = "xno" || enable_doc=yes
+AM_CONDITIONAL([ENABLE_DOC], [test "x$enable_doc" = "xyes"])
+
 else
 
 # logic if doc_only is set, make sure conditionals are still defined
@@ -275,6 +281,7 @@ AM_CONDITIONAL([HAVE_LIBCURL], [false])
 AM_CONDITIONAL([HAVE_LIBGNURL], [false])
 AM_CONDITIONAL([HAVE_TSC], [false])
 AM_CONDITIONAL([USE_COVERAGE], [false])
+AM_CONDITIONAL([ENABLE_DOC], [true])
 
 
 # end of 'doc_only'
diff --git a/doc/Makefile.am b/doc/Makefile.am
index fdfea96..e20e333 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -11,13 +11,26 @@ arch.jpg: arch.dot
 
 AM_MAKEINFOHTMLFLAGS = --no-split --css-ref=docstyle.css 
--css-ref=brown-paper.css
 
+man_MANS = \
+  taler-merchant-tip-enable.1 \
+  taler-merchant-httpd.1
+
 info_TEXINFOS = manual.texi
+
 manual_TEXINFOS = version.texi
 
+extra_TEXINFOS = \
+  fdl-1.3.texi \
+  agpl.texi \
+  syntax.texi
+
 EXTRA_DIST = \
   arch.dot \
-  lgpl.texi \
-  agpl.texi \
-  fdl-1.3.texi \
+  $(extra_TEXINFOS) \
   docstyle.css \
-  brown-paper.css
+  brown-paper.css \
+  $(man_MANS)
+
+DISTCLEANFILES = \
+  manual.cps \
+  manual.dvi
diff --git a/doc/manual.texi b/doc/manual.texi
index 0893b8e..731b219 100644
--- a/doc/manual.texi
+++ b/doc/manual.texi
@@ -853,6 +853,7 @@ $ taler-merchant-dbinit -r
 * Using taler-config::      Introduction to the taler-config tool
 * Key management::          Managing the merchant's cryptographic keys
 * SEPA configuration::      Configuring a SEPA bank account
+* Tipping visitors::        Giving money to Web site visitors with Taler
 @end menu
 
 @node Configuration in Taler
@@ -1030,6 +1031,183 @@ we expect future versions of the Taler backend to ship 
with
 pre-configured exchanges and auditors for common denominations.
 
 
+
address@hidden Tipping visitors
address@hidden Tipping visitors
address@hidden tipping
+
+Taler can also be used to tip Web site visitors.  For example, you may
+be running an online survey, and you want to reward those people that have
+dutifully completed the survey.  If they have installed a Taler wallet,
+you can provide them with a tip for their deeds.  This section describes
+how to setup the Taler merchant backend for tipping.
+
+There are four basic steps that must happen to tip a visitor.
+
address@hidden Configure a reserve and exchange for tipping
address@hidden gnunet-ecc
address@hidden reserve key
+
address@hidden TODO: We should probably create a tool that automates the
address@hidden configuration process and simply outputs the wire transfer
address@hidden subject of the reserve.
+
+To tip users, you first need to create a reserve.  A reserve is a pool
+of money held in escrow at the Taler exchange.  This is the source of
+the funds for the tips.  Tipping will fail (resulting in disappointed
+visitors) if you do not have enough funds in your reserve! To create a
+reserve for tipping, you need to first create a tipping key.  Use
+
address@hidden
+$ gnunet-ecc -g 1 tip.priv
address@hidden example
+
+to create a file with the private key that will be used to identify the
+reserve.
+
+Now you can configure your backend.  You need to enable tipping for
+each instance separately, or you can use an instance only for
+tipping.  To configure the ``default'' instance for tipping, use
+the following configuration:
+
address@hidden
+[merchant-instance-default]
+# this is NOT the tip.priv
+KEYFILE = signing_key.priv
+# replace the URL with the URL of the exchange you will use
+TIP_EXCHANGE = https://exchange:443/
+# here put the path to the file created with "gnunet-ecc -g1 tip.priv"
+TIP_RESERVE_PRIV_FILENAME = tip.priv
address@hidden example
+
+Note that the KEYFILE option should have already been present for
+the instance. It has nothing to do with the ``tip.priv'' file we
+created above, and you should probably use a different file here.
+
+Instead of manually editing the configuration, you could also run:
+
address@hidden
+$ taler-config -s merchant-instance-default \
+    -o TIP_RESERVE_PRIV_FILENAME \
+    -V tip.priv
+$ taler-config -s merchant-instance-default \
+    -o TIP_EXCHANGE \
+    -V https://exchange:443/
address@hidden example
+
+Now you can (re)start the backend with the new configuration.
+
address@hidden Fund the reserve
address@hidden reserve
address@hidden close
+
+To fund the reserve, you must first extract the public key from ``tip.priv'':
+
address@hidden
+$ gnunet-ecc --print-public-key tip.priv
address@hidden example
+
+In our example, the output for the public key is:
+
address@hidden
+QPE24X8PBX3BZ6E7GQ5VAVHV32FWTTCADR0TRQ183MSSJD2CHNEG
address@hidden example
+
+You now need to make a wire transfer to the exchange's bank account
+using the public key as the wire transfer subject.  The exchange's
+bank account details can be found in JSON format at
+``https://exchange:443//wire/METHOD'' where METHOD is the respective
+wire method (i.e. ``sepa'').  Depending on the exchange's operator,
+you may also be able to find the bank details in a human-readable
+format on the main page of the exchange.
+
+Make your wire transfer and (optionally) check at
+``https://exchange:443/reserve/status/reserve_pub=QPE24X...''
+whether your transfer has arrived at the exchange.
address@hidden FIXME: we should create a nicer tool to do this check!
+Once the funds have arrived, you can now enable tipping using:
+
address@hidden
+$ taler-merchant-tip-enable \
+    --amount=AMOUNT \
+    --backend=BACKEND_URI \
+    --credit-uuid=CREDIT_UUID \
+    --instance=INSTANCE \
+    --expiration=EXPIRATION
address@hidden example
+For ``AMOUNT'', specify the amount you transferred in the usual Taler
+format of ``CURRENCY:VALUE[.FRACTION]'', i.e. ``EUR:50''.  The
+``BACKEND_URI'' should be the URI where your Taler merchant backend is
+running.  For ``CREDIT_UUID'', you need to specify a unique number
+that identifies your wire transfer.  You may have gotten one from your
+bank, or you can just make one up! The important thing is that you
+must never use the same UUID twice, except to repeat a failed command.
+For INSTANCE, specify the backend instance (i.e. ``default'').
+Finally, for EXPIRATION, pick a date two weeks after the wire
+transfer, unless you know that the exchange that is being used has a
+different period for closing reserves.  The format @code{YYYY-MM-DD}
+is accepted.
+
+Note that an exchange will typically close a reserve after two weeks,
+wiring all remaining funds back to the sender's account.  Thus, you
+should plan to wire funds corresponding to a campaign of about two
+weeks to the exchange initially. If your campaign runs longer, you
+should wire further funds to the reserve every week to prevent it from
+expiring.  You need to run the ``taler-merchant-tip-enable'' command
+each time after you wire more funds to the reserve.
+
+
address@hidden Authorize a tip
+
+When your front end has reached the point where a client is supposed
+to receive a tip, it needs to first authorize the tip. For this,
+the frontend must use the ``/tip-authorize'' API of the backend.
+To authorize a tip, the frontend has to provide the amount to
+authorize, the name of the instance, and a justification.  The
+justification is just a string that is stored in the database with
+the transaction.  It is not meaningful for Taler.
+
+In response to this triplet, the backend will return a tip identifier,
+an expiration time and the exchange URI.  The expiration time will
+indicate how long the tip is valid (when the reserve expires).  The
+tip identifier allows the client's wallet to pick up the tip.  The
+frontend must now send the tip identifier, expiration time, EXCHANGE
+URI and the total amount to the browser in a special ``402 Payment Required''
+response with the following headers:
+
address@hidden
+X-Taler-Tipping-Url: PICKUP URL with tip ID
+X-Taler-Tipping-Exchange: EXCHANGE URL
+X-Taler-Tipping-Amount: AMOUNT
+X-Taler-Tipping-Deadline: EXPIRATION
address@hidden example
+
+The first header line must include a Web URL for picking up the tip.
+It should include the tip ID (or at least some information that the
+frontend can use to lookup the tip ID).  For example, it might be
+``https://shop/tip-pickup?tip_id=ID'' where ``ID'' is the tip ID
+that was returned from the backend.  Finally, the frontend must
+implement this ``/tip-pickup'' handler, as described in the next
+section.
+
+The frontend should handle errors returned by the backend, such
+as missconfigured instances or a lack of remaining funds for tipping.
+
+
address@hidden Picking up of the tip
+
+The wallet will POST a JSON object with a single member ``planchets''
+to the shop's ``/tip-pickup'' handler.  The frontend must then add the
+``tip_id'' field to this JSON body and forward it to the
+``/tip-pickup'' handler of the backend.  The response generated by the
+backend can then be forwarded directly to the wallet.  However, the
+frontend may want to add a field ``next_url'' to provide a suggestion
+as to where the wallet should navigate after picking up the tip.  A
+common value for ``next_url'' would be the home page of the shop.
+
address@hidden FIXME: document ``next_url'' somewhere in the API specs?
+
+
 @c **********************************************************
 @c *******************  Appendices  *************************
 @c **********************************************************
diff --git a/doc/taler-merchant-httpd.1 b/doc/taler-merchant-httpd.1
new file mode 100644
index 0000000..7e34587
--- /dev/null
+++ b/doc/taler-merchant-httpd.1
@@ -0,0 +1,37 @@
+.TH TALER\-MERCHANT\-HTTPD 1 "Nov 4, 2017" "GNU Taler"
+
+.SH NAME
+taler\-merchant\-httpd \- Run Taler merchant backend (with RESTful API)
+
+.SH SYNOPSIS
+.B taler\-merchant\-httpd
+.RI [ options ]
+.br
+
+.SH DESCRIPTION
+\fBtaler\-merchant\-httpd\fP is a command line tool to run the Taler merchant 
(HTTP backend).  The required configuration and database must exist before 
running this command.
+
+.SH OPTIONS
+.B
+.IP "\-C,  \-\-connection-close"
+Force each HTTP connection to be closed after each request (useful in 
combination with \-f to avoid having to wait for nc to time out).
+.B
+.IP "\-c FILENAME,  \-\-config=FILENAME"
+Use the configuration and other resources for the merchant to operate from 
FILENAME.
+.B
+.IP "\-h, \-\-help"
+Print short help on options.
+.B
+.IP "\-v, \-\-version"
+Print version information.
+
+.SH SIGNALS
+.B
+.IP SIGTERM
+Sending a SIGTERM to the process will cause it to shutdown cleanly.
+
+.SH BUGS
+Report bugs by using Mantis <https://gnunet.org/bugs/> or by sending 
electronic mail to <address@hidden>
+
+.SH "SEE ALSO"
+\fBtaler\-merchant\-dbinit\fP(1), \fBtaler\-merchant\-tip\-enable\fP(1), 
\fBtaler.conf\fP(5)
diff --git a/doc/taler-merchant-tip-enable.1 b/doc/taler-merchant-tip-enable.1
new file mode 100644
index 0000000..b61504b
--- /dev/null
+++ b/doc/taler-merchant-tip-enable.1
@@ -0,0 +1,47 @@
+.TH TALER\-MERCHANT\-TIP\-ENABLE 1 "Nov 4, 2017" "GNU Taler"
+
+.SH NAME
+taler\-merchant\-tip\-enable \- Tell Taler merchant backend about reserve 
funding for tipping
+
+.SH SYNOPSIS
+.B taler\-merchant\-tip\-enable
+.RI [ options ]
+.br
+
+.SH DESCRIPTION
+\fBtaler\-merchant\-tip\-enable\fP is a command line tool to inform the Taler 
merchant backend that a wire transfer was made to enable tipping from the 
backend.  Note that the command cannot check that the wire transfer was made 
correctly (with the correct wire subject and the specified amount), and will 
thus just trust the operator.  Enabling tipping at an instance without a wire 
transfer may cause visitors to receive unfunded tips and experience error 
messages.  You should read the man [...]
+
+.SH OPTIONS
+.B
+.IP "\-a VALUE,  \-\-amount=VALUE"
+Which amount was transferred into the reserve at the exchange.  Must be of the 
format CUR:VALUE.FRACTION.
+.B
+.IP "\-b URI,  \-\-backend=URI"
+At which URI does the backend run that we are to inform about the availability 
of funding for tipping?
+.B
+.IP "\-C UUID,  \-\-credit-uuid=UUID"
+What is the UUID of the wire transfer.  The backend will automatically detect 
if the same UUID is used repeatedly, and ignore multiple invocations.  Pass a 
UUID generated by the wire transfer of the bank.
+.B
+.IP "\-e TIMESTAMP,  \-\-expiration=TIMESTAMP"
+When does the reserve expire.  Determining this value may today require 
information from the exchange operator.
+.B
+.IP "\-h, \-\-help"
+Print short help on options.
+.B
+.IP "\-i NAME, \-\-instance=NAME"
+Name of the instance where tipping is to be enabled.  The instance must have 
the reserve (private) key and exchange URI already configured. Note that this 
command-line tool must also have access to the same configuration with the 
instance's private key in it.
+.B
+.IP "\-v, \-\-version"
+Print version information.
+.B
+
+.SH SIGNALS
+.B
+.IP SIGTERM
+Sending a SIGTERM to the process will cause it to shutdown cleanly.
+
+.SH BUGS
+Report bugs by using Mantis <https://gnunet.org/bugs/> or by sending 
electronic mail to <address@hidden>
+
+.SH "SEE ALSO"
+\fBtaler\-merchant\-httpd\fP(1), \fBtaler.conf\fP(5)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 1233ba8..c73c7e6 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -13,7 +13,6 @@ bin_PROGRAMS = \
   taler-merchant-httpd
 
 taler_merchant_httpd_SOURCES = \
-  taler-merchant-httpd_map.h \
   taler-merchant-httpd.c taler-merchant-httpd.h \
   taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \
   taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \
@@ -23,6 +22,10 @@ taler_merchant_httpd_SOURCES = \
   taler-merchant-httpd_proposal.c taler-merchant-httpd_proposal.h \
   taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h \
   taler-merchant-httpd_history.c taler-merchant-httpd_history.h \
+  taler-merchant-httpd_tip-authorize.c taler-merchant-httpd_tip-authorize.h \
+  taler-merchant-httpd_tip-enable.c taler-merchant-httpd_tip-enable.h \
+  taler-merchant-httpd_tip-pickup.c taler-merchant-httpd_tip-pickup.h \
+  taler-merchant-httpd_tip-query.c taler-merchant-httpd_tip-query.h \
   taler-merchant-httpd_track-transaction.c 
taler-merchant-httpd_track-transaction.h \
   taler-merchant-httpd_track-transfer.c taler-merchant-httpd_track-transfer.h \
   taler-merchant-httpd_refund.c taler-merchant-httpd_refund.h
diff --git a/src/backend/taler-merchant-httpd.c 
b/src/backend/taler-merchant-httpd.c
index 15086cc..c9b66a4 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -39,6 +39,10 @@
 #include "taler-merchant-httpd_pay.h"
 #include "taler-merchant-httpd_track-transaction.h"
 #include "taler-merchant-httpd_track-transfer.h"
+#include "taler-merchant-httpd_tip-authorize.h"
+#include "taler-merchant-httpd_tip-enable.h"
+#include "taler-merchant-httpd_tip-pickup.h"
+#include "taler-merchant-httpd_tip-query.h"
 #include "taler-merchant-httpd_history.h"
 #include "taler-merchant-httpd_refund.h"
 
@@ -85,6 +89,13 @@ struct GNUNET_TIME_Relative default_pay_deadline;
 struct TALER_Amount default_max_wire_fee;
 
 /**
+ * Default max deposit fee that the merchant is willing to
+ * pay; if deposit costs more, then the customer will cover
+ * the difference.
+ */
+struct TALER_Amount default_max_deposit_fee;
+
+/**
  * Default factor for wire fee amortization.
  */
 unsigned long long default_wire_fee_amortization;
@@ -220,6 +231,27 @@ url_handler (void *cls,
       { "/refund", NULL, "application/json",
         "Only POST/GET are allowed", 0,
         &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED},
+      { "/tip-authorize", MHD_HTTP_METHOD_POST, "text/plain",
+        NULL, 0,
+        &MH_handler_tip_authorize, MHD_HTTP_OK},
+      { "/tip-authorize", NULL, "application/json",
+        "Only POST is allowed", 0,
+        &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED},
+      { "/tip-pickup", MHD_HTTP_METHOD_POST, "text/plain",
+        NULL, 0,
+        &MH_handler_tip_pickup, MHD_HTTP_OK},
+      { "/tip-pickup", NULL, "application/json",
+        "Only POST is allowed", 0,
+        &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED},
+      { "/tip-enable", MHD_HTTP_METHOD_POST, "text/plain",
+        NULL, 0,
+        &MH_handler_tip_enable, MHD_HTTP_OK},
+      { "/tip-enable", NULL, "application/json",
+        "Only POST is allowed", 0,
+        &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED},
+      { "/tip-query", MHD_HTTP_METHOD_GET, "text/plain",
+        NULL, 0,
+        &MH_handler_tip_query, MHD_HTTP_OK},
       {NULL, NULL, NULL, NULL, 0, 0 }
     };
   static struct TMH_RequestHandler h404 =
@@ -280,6 +312,7 @@ hashmap_free (void *cls,
   json_decref (mi->j_wire);
   GNUNET_free (mi->id);
   GNUNET_free (mi->keyfile);
+  GNUNET_free_non_null (mi->tip_exchange);
   GNUNET_free (mi);
   return GNUNET_YES;
 }
@@ -511,8 +544,47 @@ instances_iterator_cb (void *cls,
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
+  if (GNUNET_OK ==
+      GNUNET_CONFIGURATION_get_value_string (iic->config,
+                                             section,
+                                             "TIP_EXCHANGE",
+                                             &mi->tip_exchange))
+  {
+    char *tip_reserves;
+    struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_filename (iic->config,
+                                                 section,
+                                                 "TIP_RESERVE_PRIV_FILENAME",
+                                                 &tip_reserves))
+    {
+      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "TIP_RESERVE_PRIV_FILENAME");
+      GNUNET_free (mi);
+      GNUNET_SCHEDULER_shutdown ();
+      return;
+    }
+    pk = GNUNET_CRYPTO_eddsa_key_create_from_file (tip_reserves);
+    if (NULL == pk)
+    {
+      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "TIP_RESERVE_PRIV_FILENAME",
+                                 "Failed to read private key");
+      GNUNET_free (tip_reserves);
+      GNUNET_free (mi);
+      GNUNET_SCHEDULER_shutdown ();
+      return;
+    }
+    mi->tip_reserve.eddsa_priv = *pk;
+    GNUNET_free (pk);
+    GNUNET_free (tip_reserves);
+  }
 
-  if (GNUNET_YES != GNUNET_DISK_file_test (mi->keyfile))
+  if (GNUNET_YES !=
+      GNUNET_DISK_file_test (mi->keyfile))
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Merchant private key `%s' does not exist yet, creating it!\n",
                 mi->keyfile);
@@ -730,7 +802,6 @@ iterate_instances (const struct GNUNET_CONFIGURATION_Handle 
*config,
   return GNUNET_SYSERR;
 }
 
-
 /**
  * Main function that will be run by the scheduler.
  *
@@ -815,33 +886,24 @@ run (void *cls,
                               "DEFAULT_MAX_WIRE_FEE",
                               &default_max_wire_fee))
   {
-    char *currency;
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "merchant",
+                               "DEFAULT_MAX_WIRE_FEE");
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
 
-    if (GNUNET_OK !=
-        GNUNET_CONFIGURATION_get_value_string (config,
-                                               "taler",
-                                               "CURRENCY",
-                                               &currency))
-    {
-      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                                 "taler",
-                                 "CURRENCY");
-      GNUNET_SCHEDULER_shutdown ();
-      return;
-    }
-    if (GNUNET_OK !=
-        TALER_amount_get_zero (currency,
-                               &default_max_wire_fee))
-    {
-      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
-                                 "taler",
-                                 "CURRENCY",
-                                 "Specified value not legal for a Taler 
currency");
-      GNUNET_SCHEDULER_shutdown ();
-      GNUNET_free (currency);
-      return;
-    }
-    GNUNET_free (currency);
+  if (GNUNET_OK !=
+      TALER_config_get_denom (config,
+                              "merchant",
+                              "DEFAULT_MAX_DEPOSIT_FEE",
+                              &default_max_deposit_fee))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "merchant",
+                               "DEFAULT_MAX_DEPOSIT_FEE");
+    GNUNET_SCHEDULER_shutdown ();
+    return;
   }
 
   if (GNUNET_OK !=
diff --git a/src/backend/taler-merchant-httpd.h 
b/src/backend/taler-merchant-httpd.h
index c67a971..6418801 100644
--- a/src/backend/taler-merchant-httpd.h
+++ b/src/backend/taler-merchant-httpd.h
@@ -119,6 +119,18 @@ struct MerchantInstance
    */
   struct TALER_MerchantPublicKeyP pubkey;
 
+  /**
+   * Exchange this instance uses for tipping, NULL if tipping
+   * is not supported.
+   */
+  char *tip_exchange;
+
+  /**
+   * What is the private key of the reserve used for signing tips by this 
exchange?
+   * Only valid if @e tip_exchange is non-null.
+   */
+  struct TALER_ReservePrivateKeyP tip_reserve;
+
 };
 
 
@@ -231,6 +243,13 @@ extern json_t *j_wire;
 extern struct TALER_Amount default_max_wire_fee;
 
 /**
+ * Default max deposit fee that the merchant is willing to
+ * pay; if deposit costs more, then the customer will cover
+ * the difference.
+ */
+extern struct TALER_Amount default_max_deposit_fee;
+
+/**
  * Default factor for wire fee amortization.
  */
 extern unsigned long long default_wire_fee_amortization;
diff --git a/src/backend/taler-merchant-httpd_exchanges.c 
b/src/backend/taler-merchant-httpd_exchanges.c
index 9833181..4484689 100644
--- a/src/backend/taler-merchant-httpd_exchanges.c
+++ b/src/backend/taler-merchant-httpd_exchanges.c
@@ -789,6 +789,7 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
 {
   struct Exchange *exchange;
   struct TMH_EXCHANGES_FindOperation *fo;
+  struct GNUNET_TIME_Absolute now;
 
   if (NULL == merchant_curl_ctx)
   {
@@ -840,11 +841,12 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
   GNUNET_CONTAINER_DLL_insert (exchange->fo_head,
                                exchange->fo_tail,
                                fo);
-
+  now = GNUNET_TIME_absolute_get ();
+  (void) GNUNET_TIME_round_abs (&now);
   if ( (GNUNET_YES != exchange->pending) &&
        ( (NULL == fo->wire_method) ||
          (NULL != get_wire_fees (exchange,
-                                 GNUNET_TIME_absolute_get (),
+                                 now,
                                  fo->wire_method)) ) )
   {
     /* We are not currently waiting for a reply, immediately
diff --git a/src/backend/taler-merchant-httpd_history.c 
b/src/backend/taler-merchant-httpd_history.c
index 99c71a4..53adf30 100644
--- a/src/backend/taler-merchant-httpd_history.c
+++ b/src/backend/taler-merchant-httpd_history.c
@@ -137,7 +137,7 @@ MH_handler_history (struct TMH_RequestHandler *rh,
                                      MHD_GET_ARGUMENT_KIND,
                                      "date");
   date = GNUNET_TIME_absolute_get ();
-
+  (void) GNUNET_TIME_round_abs (&date);
   if (NULL != str)
   {
     if (1 != sscanf (str,
diff --git a/src/backend/taler-merchant-httpd_pay.c 
b/src/backend/taler-merchant-httpd_pay.c
index 7769ef2..ff8d59c 100644
--- a/src/backend/taler-merchant-httpd_pay.c
+++ b/src/backend/taler-merchant-httpd_pay.c
@@ -469,6 +469,7 @@ deposit_cb (void *cls,
   enum GNUNET_DB_QueryStatus qs;
 
   dc->dh = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
   pc->pending--;
   if (MHD_HTTP_OK != http_status)
   {
@@ -551,17 +552,25 @@ deposit_cb (void *cls,
                                &pc->h_contract_terms,
                                &pc->mi->pubkey);
   if (0 > qs)
+  {
+    abort_deposit (pc);
+    db->rollback (db->cls);
     resume_pay_with_response (pc,
                               MHD_HTTP_INTERNAL_SERVER_ERROR,
                               TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
                                                                 "Merchant 
database error: could not mark proposal as 'paid'"));
+    return;
+  }
   qs = db->commit (db->cls);
   if (0 > qs)
+  {
+    abort_deposit (pc);
     resume_pay_with_response (pc,
                               MHD_HTTP_INTERNAL_SERVER_ERROR,
                               TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
                                                                 "Merchant 
database error: could not commit"));
-
+    return;
+  }
   resume_pay_with_response (pc,
                             MHD_HTTP_OK,
                             sign_success_response (pc));
@@ -701,6 +710,7 @@ process_pay_with_exchange (void *cls,
   enum GNUNET_DB_QueryStatus qs;
 
   pc->fo = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
   if (NULL == mh)
   {
     /* The exchange on offer is not in the set of our (trusted)
@@ -725,6 +735,7 @@ process_pay_with_exchange (void *cls,
   }
 
   /* Total up the fees and the value of the deposited coins! */
+  GNUNET_assert (0 != pc->coins_cnt);
   for (unsigned int i=0;i<pc->coins_cnt;i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
@@ -1115,6 +1126,7 @@ handle_pay_timeout (void *cls)
   struct PayContext *pc = cls;
 
   pc->timeout_task = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Resuming /pay with error after timeout\n");
   if (NULL != pc->fo)
@@ -1452,7 +1464,7 @@ parse_pay (struct MHD_Connection *connection,
     if (GNUNET_YES != res)
     {
       GNUNET_JSON_parse_free (spec);
-      GNUNET_break (0);
+      GNUNET_break_op (0);
       return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
     }
 
diff --git a/src/backend/taler-merchant-httpd_proposal.c 
b/src/backend/taler-merchant-httpd_proposal.c
index 59bd62a..f3fee47 100644
--- a/src/backend/taler-merchant-httpd_proposal.c
+++ b/src/backend/taler-merchant-httpd_proposal.c
@@ -138,7 +138,6 @@ proposal_put (struct MHD_Connection *connection,
   struct TALER_ProposalDataPS pdps;
   struct GNUNET_CRYPTO_EddsaSignature merchant_sig;
   struct TALER_Amount total;
-  struct TALER_Amount max_fee;
   const char *order_id;
   json_t *products;
   json_t *merchant;
@@ -148,9 +147,9 @@ proposal_put (struct MHD_Connection *connection,
   struct GNUNET_JSON_Specification spec[] = {
     TALER_JSON_spec_amount ("amount", &total),
     GNUNET_JSON_spec_string ("order_id", &order_id),
-    TALER_JSON_spec_amount ("max_fee", &max_fee),
-    /* The following entries we don't actually need, except to check that
-       the order is well-formed */
+    /**
+     * The following entries we don't actually need,
+     * except to check that the order is well-formed */
     GNUNET_JSON_spec_json ("products", &products),
     GNUNET_JSON_spec_json ("merchant", &merchant),
     GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
@@ -172,7 +171,12 @@ proposal_put (struct MHD_Connection *connection,
 
     time (&timer);
     tm_info = localtime (&timer);
-
+    if (NULL == tm_info)
+    {
+      return TMH_RESPONSE_reply_internal_error (connection,
+                                                TALER_EC_PROPOSAL_NO_LOCALTIME,
+                                                "failed to determine local 
time");
+    }
     off = strftime (buf,
                     sizeof (buf),
                     "%H:%M:%S",
@@ -229,6 +233,14 @@ proposal_put (struct MHD_Connection *connection,
   }
 
   if (NULL == json_object_get (order,
+                               "max_fee"))
+  {
+    json_object_set_new (order,
+                         "max_fee",
+                         TALER_JSON_from_amount (&default_max_deposit_fee));
+  }
+
+  if (NULL == json_object_get (order,
                                "wire_fee_amortization"))
   {
     json_object_set_new (order,
@@ -247,7 +259,7 @@ proposal_put (struct MHD_Connection *connection,
   if (GNUNET_SYSERR == res)
   {
     return TMH_RESPONSE_reply_internal_error (connection,
-                                             TALER_EC_NONE,
+                                             
TALER_EC_PROPOSAL_ORDER_PARSE_ERROR,
                                              "Impossible to parse the order");
   }
 
diff --git a/src/backend/taler-merchant-httpd_refund.c 
b/src/backend/taler-merchant-httpd_refund.c
index 9c5df27..e0c326f 100644
--- a/src/backend/taler-merchant-httpd_refund.c
+++ b/src/backend/taler-merchant-httpd_refund.c
@@ -266,7 +266,9 @@ MH_handler_refund_increase (struct TMH_RequestHandler *rh,
 
   confirmation.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND_OK);
   confirmation.purpose.size = htonl (sizeof (struct 
TALER_MerchantRefundConfirmationPS));
-
+  GNUNET_CRYPTO_hash (order_id,
+                      strlen (order_id),
+                      &confirmation.h_order_id);
   if (GNUNET_OK !=
       GNUNET_CRYPTO_eddsa_sign (&mi->privkey.eddsa_priv,
                                &confirmation.purpose,
diff --git a/src/backend/taler-merchant-httpd_responses.c 
b/src/backend/taler-merchant-httpd_responses.c
index f5f994e..1417fa2 100644
--- a/src/backend/taler-merchant-httpd_responses.c
+++ b/src/backend/taler-merchant-httpd_responses.c
@@ -239,6 +239,30 @@ TMH_RESPONSE_reply_request_too_large (struct 
MHD_Connection *connection)
 
 
 /**
+ * Send a response indicating that we did not find the @a object
+ * needed for the reply.
+ *
+ * @param connection the MHD connection to use
+ * @param response_code response code to use
+ * @param ec error code to return
+ * @param msg human-readable diagnostic
+ * @return a MHD result code
+ */
+int
+TMH_RESPONSE_reply_rc (struct MHD_Connection *connection,
+                       unsigned int response_code,
+                       enum TALER_ErrorCode ec,
+                       const char *msg)
+{
+  return TMH_RESPONSE_reply_json_pack (connection,
+                                       response_code,
+                                       "{s:I, s:s}",
+                                      "code", (json_int_t) ec,
+                                       "error", msg);
+}
+
+
+/**
  * Send a response indicating that the JSON was malformed.
  *
  * @param connection the MHD connection to use
diff --git a/src/backend/taler-merchant-httpd_responses.h 
b/src/backend/taler-merchant-httpd_responses.h
index ea290c5..a96b696 100644
--- a/src/backend/taler-merchant-httpd_responses.h
+++ b/src/backend/taler-merchant-httpd_responses.h
@@ -101,6 +101,23 @@ TMH_RESPONSE_reply_invalid_json (struct MHD_Connection 
*connection);
  * needed for the reply.
  *
  * @param connection the MHD connection to use
+ * @param response_code response code to use
+ * @param ec error code to return
+ * @param msg human-readable diagnostic
+ * @return a MHD result code
+ */
+int
+TMH_RESPONSE_reply_rc (struct MHD_Connection *connection,
+                       unsigned int response_code,
+                       enum TALER_ErrorCode ec,
+                       const char *msg);
+
+
+/**
+ * Send a response indicating that we did not find the @a object
+ * needed for the reply.
+ *
+ * @param connection the MHD connection to use
  * @param ec error code to return
  * @param object name of the object we did not find
  * @return a MHD result code
@@ -149,7 +166,7 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection 
*connection,
 struct MHD_Response *
 TMH_RESPONSE_make_internal_error (enum TALER_ErrorCode ec,
                                  const char *hint);
-  
+
 
 /**
  * Send a response indicating an external error.
diff --git a/src/backend/taler-merchant-httpd_tip-authorize.c 
b/src/backend/taler-merchant-httpd_tip-authorize.c
new file mode 100644
index 0000000..bcfca94
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_tip-authorize.c
@@ -0,0 +1,223 @@
+/*
+  This file is part of TALER
+  (C) 2014-2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/taler-merchant-httpd_tip-authorize.c
+ * @brief implement API for authorizing tips to be paid to visitors
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_parsing.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_responses.h"
+#include "taler-merchant-httpd_tip-authorize.h"
+
+
+/**
+ * Information we keep for individual calls
+ * to requests that parse JSON, but keep no other state.
+ */
+struct TMH_JsonParseContext
+{
+
+  /**
+   * This field MUST be first.
+   * FIXME: Explain why!
+   */
+  struct TM_HandlerContext hc;
+
+  /**
+   * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
+   */
+  void *json_parse_context;
+};
+
+
+/**
+ * Custom cleanup routine for a `struct TMH_JsonParseContext`.
+ *
+ * @param hc the `struct TMH_JsonParseContext` to clean up.
+ */
+static void
+json_parse_cleanup (struct TM_HandlerContext *hc)
+{
+  struct TMH_JsonParseContext *jpc = (struct TMH_JsonParseContext *) hc;
+
+  TMH_PARSE_post_cleanup_callback (jpc->json_parse_context);
+  GNUNET_free (jpc);
+}
+
+
+/**
+ * Handle a "/tip-authorize" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_tip_authorize (struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          void **connection_cls,
+                          const char *upload_data,
+                          size_t *upload_data_size)
+{
+  struct MerchantInstance *mi;
+  int res;
+  struct TALER_Amount amount;
+  const char *instance;
+  const char *justification;
+  const char *pickup_url;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount ("amount", &amount),
+    GNUNET_JSON_spec_string ("instance", &instance),
+    GNUNET_JSON_spec_string ("justification", &justification),
+    GNUNET_JSON_spec_string ("pickup_url", &pickup_url),
+    GNUNET_JSON_spec_end()
+  };
+  json_t *root;
+  struct GNUNET_TIME_Absolute expiration;
+  struct GNUNET_HashCode tip_id;
+  struct TMH_JsonParseContext *ctx;
+  enum TALER_ErrorCode ec;
+
+  if (NULL == *connection_cls)
+  {
+    ctx = GNUNET_new (struct TMH_JsonParseContext);
+    ctx->hc.cc = &json_parse_cleanup;
+    *connection_cls = ctx;
+  }
+  else
+  {
+    ctx = *connection_cls;
+  }
+  res = TMH_PARSE_post_json (connection,
+                             &ctx->json_parse_context,
+                             upload_data,
+                             upload_data_size,
+                             &root);
+  if (GNUNET_SYSERR == res)
+    return MHD_NO;
+  /* the POST's body has to be further fetched */
+  if ( (GNUNET_NO == res) ||
+       (NULL == root) )
+    return MHD_YES;
+
+  res = TMH_PARSE_json_data (connection,
+                             root,
+                             spec);
+  if (GNUNET_YES != res)
+  {
+    GNUNET_break_op (0);
+    json_decref (root);
+    return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+  }
+
+  mi = TMH_lookup_instance (instance);
+  if (NULL == mi)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Instance `%s' not configured\n",
+                instance);
+    json_decref (root);  
+    return TMH_RESPONSE_reply_not_found (connection,
+                                        
TALER_EC_TIP_AUTHORIZE_INSTANCE_UNKNOWN,
+                                        "unknown instance");
+  }
+  if (NULL == mi->tip_exchange)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Instance `%s' not configured for tipping\n",
+                instance);
+    json_decref (root);
+    return TMH_RESPONSE_reply_not_found (connection,
+                                        
TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP,
+                                        "exchange for tipping not configured 
for the instance");
+  }
+  ec = db->authorize_tip (db->cls,
+                          justification,
+                          &amount,
+                          &mi->tip_reserve,
+                         mi->tip_exchange,
+                          &expiration,
+                          &tip_id);
+  if (TALER_EC_NONE != ec)
+  {
+    unsigned int rc;
+
+    switch (ec)
+    {
+    case TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS:
+      rc = MHD_HTTP_PRECONDITION_FAILED;
+      break;
+    case TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED:
+      rc = MHD_HTTP_PRECONDITION_FAILED;
+      break;
+    case TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN:
+      rc = MHD_HTTP_NOT_FOUND;
+      break;
+    case TALER_EC_TIP_AUTHORIZE_RESERVE_NOT_ENABLED:
+      rc = MHD_HTTP_NOT_FOUND;
+      break;
+    default:
+      rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      break;
+    }
+    json_decref (root);      
+    return TMH_RESPONSE_reply_rc (connection,
+                                 rc,
+                                 ec,
+                                 "Database error approving tip");
+  }
+  if (0)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Insufficient funds to authorize tip over `%s' at instance 
`%s'\n",
+                TALER_amount2s (&amount),
+                instance);
+    json_decref (root);
+    return TMH_RESPONSE_reply_rc (connection,
+                                  MHD_HTTP_PRECONDITION_FAILED,
+                                  TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS,
+                                  "Insufficient funds for tip");
+  }
+  json_t *tip_token = json_pack ("{s:o, s:o, s:o, s:s, s:s}",
+                                 "tip_id", GNUNET_JSON_from_data_auto 
(&tip_id),
+                                 "expiration", GNUNET_JSON_from_time_abs 
(expiration),
+                                 "amount", TALER_JSON_from_amount (&amount),
+                                 "exchange_url", mi->tip_exchange,
+                                 "pickup_url", pickup_url);
+  char *tip_token_str = json_dumps (tip_token,  JSON_ENSURE_ASCII | 
JSON_COMPACT);
+  json_decref (tip_token);
+  json_decref (root);
+  int ret = TMH_RESPONSE_reply_json_pack (connection,
+                                          MHD_HTTP_OK,
+                                          "{s:o, s:o, s:s, s:s}",
+                                          "tip_id", GNUNET_JSON_from_data_auto 
(&tip_id),
+                                          "expiration", 
GNUNET_JSON_from_time_abs (expiration),
+                                          "exchange_url", mi->tip_exchange,
+                                          "tip_token", tip_token_str);
+  GNUNET_free (tip_token_str);
+  return ret;
+}
+
+/* end of taler-merchant-httpd_tip-authorize.c */
diff --git a/src/backend/taler-merchant-httpd_track-transaction.h 
b/src/backend/taler-merchant-httpd_tip-authorize.h
similarity index 64%
copy from src/backend/taler-merchant-httpd_track-transaction.h
copy to src/backend/taler-merchant-httpd_tip-authorize.h
index 30896bf..b7c3b9a 100644
--- a/src/backend/taler-merchant-httpd_track-transaction.h
+++ b/src/backend/taler-merchant-httpd_tip-authorize.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015 GNUnet e.V.
+  (C) 2017 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -14,18 +14,18 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_track-transaction.h
- * @brief headers for /track/transaction handler
+ * @file backend/taler-merchant-httpd_tip-authorize.h
+ * @brief headers for /tip-authorize handler
  * @author Christian Grothoff
- * @author Marcello Stanisci
  */
-#ifndef TALER_MERCHANT_HTTPD_TRACK_TRANSACTION_H
-#define TALER_MERCHANT_HTTPD_TRACK_TRANSACTION_H
+#ifndef TALER_MERCHANT_HTTPD_TIP_AUTHORIZE_H
+#define TALER_MERCHANT_HTTPD_TIP_AUTHORIZE_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 /**
- * Handle a "/track/transaction" request.
+ * Manages a /tip-authorize call, creating a TIP ID and storing the
+ * authorization in our DB.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -35,11 +35,10 @@
  * @return MHD result code
  */
 int
-MH_handler_track_transaction (struct TMH_RequestHandler *rh,
-                              struct MHD_Connection *connection,
-                              void **connection_cls,
-                              const char *upload_data,
-                              size_t *upload_data_size);
-
+MH_handler_tip_authorize (struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          void **connection_cls,
+                          const char *upload_data,
+                          size_t *upload_data_size);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_tip-enable.c 
b/src/backend/taler-merchant-httpd_tip-enable.c
new file mode 100644
index 0000000..a96613e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_tip-enable.c
@@ -0,0 +1,157 @@
+/*
+  This file is part of TALER
+  (C) 2014-2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/taler-merchant-httpd_tip-enable.c
+ * @brief implement API for authorizing tips to be paid to visitors
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_parsing.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_responses.h"
+#include "taler-merchant-httpd_tip-enable.h"
+
+
+/**
+ * Information we keep for individual calls
+ * to requests that parse JSON, but keep no other state.
+ */
+struct TMH_JsonParseContext
+{
+
+  /**
+   * This field MUST be first.
+   * FIXME: Explain why!
+   */
+  struct TM_HandlerContext hc;
+
+  /**
+   * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
+   */
+  void *json_parse_context;
+};
+
+
+/**
+ * Custom cleanup routine for a `struct TMH_JsonParseContext`.
+ *
+ * @param hc the `struct TMH_JsonParseContext` to clean up.
+ */
+static void
+json_parse_cleanup (struct TM_HandlerContext *hc)
+{
+  struct TMH_JsonParseContext *jpc = (struct TMH_JsonParseContext *) hc;
+
+  TMH_PARSE_post_cleanup_callback (jpc->json_parse_context);
+  GNUNET_free (jpc);
+}
+
+
+/**
+ * Handle a "/tip-enable" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_tip_enable (struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          void **connection_cls,
+                          const char *upload_data,
+                          size_t *upload_data_size)
+{
+  enum GNUNET_DB_QueryStatus qs;
+  int res;
+  struct TALER_Amount credit;
+  struct GNUNET_TIME_Absolute expiration;
+  struct TALER_ReservePrivateKeyP reserve_priv;
+  struct GNUNET_HashCode credit_uuid;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount ("credit", &credit),
+    GNUNET_JSON_spec_absolute_time ("expiration", &expiration),
+    GNUNET_JSON_spec_fixed_auto ("reserve_priv", &reserve_priv),
+    GNUNET_JSON_spec_fixed_auto ("credit_uuid", &credit_uuid),
+    GNUNET_JSON_spec_end()
+  };
+  struct TMH_JsonParseContext *ctx;
+  json_t *root;
+
+  if (NULL == *connection_cls)
+  {
+    ctx = GNUNET_new (struct TMH_JsonParseContext);
+    ctx->hc.cc = &json_parse_cleanup;
+    *connection_cls = ctx;
+  }
+  else
+  {
+    ctx = *connection_cls;
+  }
+  res = TMH_PARSE_post_json (connection,
+                             &ctx->json_parse_context,
+                             upload_data,
+                             upload_data_size,
+                             &root);
+  if (GNUNET_SYSERR == res)
+    return MHD_NO;
+  /* the POST's body has to be further fetched */
+  if ( (GNUNET_NO == res) ||
+       (NULL == root) )
+    return MHD_YES;
+
+  res = TMH_PARSE_json_data (connection,
+                             root,
+                             spec);
+  json_decref (root);
+  if (GNUNET_YES != res)
+  {
+    GNUNET_break_op (0);
+    return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+  }
+
+  qs = db->enable_tip_reserve (db->cls,
+                               &reserve_priv,
+                               &credit_uuid,
+                               &credit,
+                               expiration);
+  if (0 > qs)
+  {
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    return TMH_RESPONSE_reply_internal_error (connection,
+                                             
TALER_EC_TIP_ENABLE_DB_TRANSACTION_ERROR,
+                                              "Database error approving tip");
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    return TMH_RESPONSE_reply_json_pack (connection,
+                                         MHD_HTTP_OK,
+                                         "{s:s}",
+                                         "status", "duplicate submission");
+  }
+  return TMH_RESPONSE_reply_json_pack (connection,
+                                       MHD_HTTP_OK,
+                                       "{s:s}",
+                                       "status", "ok");
+}
+
+/* end of taler-merchant-httpd_tip-enable.c */
diff --git a/src/backend/taler-merchant-httpd_pay.h 
b/src/backend/taler-merchant-httpd_tip-enable.h
similarity index 64%
copy from src/backend/taler-merchant-httpd_pay.h
copy to src/backend/taler-merchant-httpd_tip-enable.h
index d4f4958..2981ef4 100644
--- a/src/backend/taler-merchant-httpd_pay.h
+++ b/src/backend/taler-merchant-httpd_tip-enable.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2017 GNUnet e.V.
+  (C) 2017 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -14,26 +14,18 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_pay.h
- * @brief headers for /pay handler
- * @author Marcello Stanisci
+ * @file backend/taler-merchant-httpd_tip-enable.h
+ * @brief headers for /tip-enable handler
+ * @author Christian Grothoff
  */
-#ifndef TALER_EXCHANGE_HTTPD_PAY_H
-#define TALER_EXCHANGE_HTTPD_PAY_H
+#ifndef TALER_MERCHANT_HTTPD_TIP_ENABLE_H
+#define TALER_MERCHANT_HTTPD_TIP_ENABLE_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
-
-/**
- * Force all pay contexts to be resumed as we are about
- * to shut down MHD.
- */
-void
-MH_force_pc_resume (void);
-
-
 /**
- * Manage a payment
+ * Manages a /tip-enable call, storing information about the
+ * reserve.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -43,10 +35,10 @@ MH_force_pc_resume (void);
  * @return MHD result code
  */
 int
-MH_handler_pay (struct TMH_RequestHandler *rh,
-                struct MHD_Connection *connection,
-                void **connection_cls,
-                const char *upload_data,
-                size_t *upload_data_size);
+MH_handler_tip_enable (struct TMH_RequestHandler *rh,
+                       struct MHD_Connection *connection,
+                       void **connection_cls,
+                       const char *upload_data,
+                       size_t *upload_data_size);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_tip-pickup.c 
b/src/backend/taler-merchant-httpd_tip-pickup.c
new file mode 100644
index 0000000..76bf7ce
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_tip-pickup.c
@@ -0,0 +1,573 @@
+/*
+  This file is part of TALER
+  (C) 2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/taler-merchant-httpd_tip-pickup.c
+ * @brief implementation of /tip-pickup handler
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <jansson.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_parsing.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_responses.h"
+#include "taler-merchant-httpd_tip-pickup.h"
+
+
+/**
+ * Details about a planchet that the customer wants to obtain
+ * a withdrawal authorization.  This is the information that
+ * will need to be sent to the exchange to obtain the blind
+ * signature required to turn a planchet into a coin.
+ */
+struct PlanchetDetail
+{
+
+  /**
+   * The complete withdraw request that we are building to sign.
+   * Built incrementally during the processing of the request.
+   */
+  struct TALER_WithdrawRequestPS wr;
+
+  /**
+   * Blinded coin (see GNUNET_CRYPTO_rsa_blind()).  Note: is malloc()'ed!
+   */
+  char *coin_ev;
+
+  /**
+   * Number of bytes in @a coin_ev.
+   */
+  size_t coin_ev_size;
+
+};
+
+
+/**
+ * Information we keep for individual calls
+ * to requests that parse JSON, but keep no other state.
+ */
+struct PickupContext
+{
+
+  /**
+   * This field MUST be first.
+   * FIXME: Explain why!
+   */
+  struct TM_HandlerContext hc;
+
+  /**
+   * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
+   */
+
+  void *json_parse_context;
+
+  /**
+   * URI of the exchange this tip uses.
+   */
+  char *exchange_uri;
+
+  /**
+   * Operation we run to find the exchange (and get its /keys).
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Array of planchets of length @e planchets_len.
+   */
+  struct PlanchetDetail *planchets;
+
+  /**
+   * The connection we are processing.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Tip ID that was supplied by the client.
+   */
+  struct GNUNET_HashCode tip_id;
+
+  /**
+   * Unique identifier for the pickup operation, used to detect
+   * duplicate requests (retries).
+   */
+  struct GNUNET_HashCode pickup_id;
+
+  /**
+   * Total value of the coins we are withdrawing.
+   */
+  struct TALER_Amount total;
+
+  /**
+   * Length of @e planchets.
+   */
+  unsigned int planchets_len;
+
+  /**
+   * Error code, #TALER_EC_NONE as long as all is fine.
+   */
+  enum TALER_ErrorCode ec;
+
+  /**
+   * HTTP status code to return in combination with @e ec
+   * if @e ec is not #TALER_EC_NONE.
+   */
+  unsigned int response_code;
+
+  /**
+   * Human-readable error hint to return.
+   * if @e ec is not #TALER_EC_NONE.
+   */
+  const char *error_hint;
+
+};
+
+
+/**
+ * Custom cleanup routine for a `struct PickupContext`.
+ *
+ * @param hc the `struct PickupContext` to clean up.
+ */
+static void
+pickup_cleanup (struct TM_HandlerContext *hc)
+{
+  struct PickupContext *pc = (struct PickupContext *) hc;
+
+  if (NULL != pc->planchets)
+  {
+    for (unsigned int i=0;i<pc->planchets_len;i++)
+      GNUNET_free_non_null (pc->planchets[i].coin_ev);
+    GNUNET_free (pc->planchets);
+    pc->planchets = NULL;
+  }
+  if (NULL != pc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
+    pc->fo = NULL;
+  }
+  TMH_PARSE_post_cleanup_callback (pc->json_parse_context);
+  GNUNET_free_non_null (pc->exchange_uri);
+  GNUNET_free (pc);
+}
+
+
+/**
+ * Prepare (and eventually execute) a pickup.  Computes
+ * the "pickup ID" (by hashing the planchets and denomination keys),
+ * resolves the denomination keys and calculates the total
+ * amount to be picked up.  Then runs the pick up execution logic.
+ *
+ * @param connection MHD connection for sending the response
+ * @param tip_id which tip are we picking up
+ * @param pc pickup context
+ * @return #MHD_YES upon success, #MHD_NO if
+ *         the connection ought to be dropped
+ */
+static int
+run_pickup (struct MHD_Connection *connection,
+           struct PickupContext *pc)
+{
+  struct TALER_ReservePrivateKeyP reserve_priv;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  enum TALER_ErrorCode ec;
+  json_t *sigs;
+
+  if (TALER_EC_NONE != pc->ec)
+  {
+    return TMH_RESPONSE_reply_rc (connection,
+                                 pc->response_code,
+                                 pc->ec,
+                                 pc->error_hint);
+  }
+  ec = db->pickup_tip (db->cls,
+                      &pc->total,
+                      &pc->tip_id,
+                      &pc->pickup_id,
+                      &reserve_priv);
+  if (TALER_EC_NONE != ec)
+  {
+    unsigned int response_code;
+
+    switch (ec)
+    {
+    case TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN:
+      response_code = MHD_HTTP_NOT_FOUND;
+      break;
+    case TALER_EC_TIP_PICKUP_NO_FUNDS:
+      response_code = MHD_HTTP_SERVICE_UNAVAILABLE;
+      break;
+    default:
+      response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      break;
+    }
+    return TMH_RESPONSE_reply_rc (connection,
+                                 response_code,
+                                 ec,
+                                 "Database error approving tip");
+  }
+  sigs = json_array ();
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv.eddsa_priv,
+                                     &reserve_pub.eddsa_pub);
+  for (unsigned int i=0;i<pc->planchets_len;i++)
+  {
+    struct PlanchetDetail *pd = &pc->planchets[i];
+    struct TALER_ReserveSignatureP reserve_sig;
+
+    pd->wr.reserve_pub = reserve_pub;
+    GNUNET_assert (GNUNET_OK ==
+                  GNUNET_CRYPTO_eddsa_sign (&reserve_priv.eddsa_priv,
+                                            &pd->wr.purpose,
+                                            &reserve_sig.eddsa_signature));
+    json_array_append_new (sigs,
+                           json_pack ("{s:o}",
+                                      "reserve_sig",
+                                      GNUNET_JSON_from_data_auto 
(&reserve_sig)));
+  }
+  return TMH_RESPONSE_reply_json_pack (connection,
+                                       MHD_HTTP_OK,
+                                       "{s:o, s:o}",
+                                       "reserve_pub", 
GNUNET_JSON_from_data_auto (&reserve_pub),
+                                      "reserve_sigs", sigs);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @param cls closure with the `struct PickupContext`
+ * @param eh handle to the exchange context
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ */
+static void
+exchange_found_cb (void *cls,
+                  struct TALER_EXCHANGE_Handle *eh,
+                  const struct TALER_Amount *wire_fee,
+                  int exchange_trusted)
+{
+  struct PickupContext *pc = cls;
+  const struct TALER_EXCHANGE_Keys *keys;
+  struct GNUNET_HashContext *hc;
+  struct TALER_Amount total;
+  int ae;
+
+  pc->fo = NULL;
+  MHD_resume_connection (pc->connection);
+  if (NULL == eh)
+  {
+    pc->ec = TALER_EC_TIP_PICKUP_EXCHANGE_DOWN;
+    pc->error_hint = "failed to contact exchange, check URL";
+    pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
+    TMH_trigger_daemon ();
+    return;
+  }
+  keys = TALER_EXCHANGE_get_keys (eh);
+  if (NULL == keys)
+  {
+    pc->ec = TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEYS;
+    pc->error_hint = "could not obtain denomination keys from exchange, check 
URL";
+    pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
+    TMH_trigger_daemon ();
+    return;
+  }
+  GNUNET_assert (0 != pc->planchets_len);
+  ae = GNUNET_NO;
+  memset (&total,
+         0,
+         sizeof (total));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Calculating tip amount over %u planchets!\n",
+              pc->planchets_len);
+  hc = GNUNET_CRYPTO_hash_context_start ();
+  for (unsigned int i=0;i<pc->planchets_len;i++)
+  {
+    struct PlanchetDetail *pd = &pc->planchets[i];
+    const struct TALER_EXCHANGE_DenomPublicKey *dk;
+    struct TALER_Amount amount_with_fee;
+
+    dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                     
&pd->wr.h_denomination_pub);
+    if (NULL == dk)
+    {
+      pc->ec = TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEY;
+      pc->error_hint = "could not find matching denomination key";
+      pc->response_code = MHD_HTTP_NOT_FOUND;
+      GNUNET_CRYPTO_hash_context_abort (hc);
+      TMH_trigger_daemon ();
+      return;
+    }
+    GNUNET_CRYPTO_hash_context_read (hc,
+                                    &pd->wr.h_denomination_pub,
+                                    sizeof (struct GNUNET_HashCode));
+    GNUNET_CRYPTO_hash_context_read (hc,
+                                    pd->coin_ev,
+                                    pd->coin_ev_size);
+    if (GNUNET_OK !=
+       TALER_amount_add (&amount_with_fee,
+                         &dk->value,
+                         &dk->fee_withdraw))
+    {
+      ae = GNUNET_YES;
+    }
+    if (0 == i)
+    {
+      total = amount_with_fee;
+    }
+    else
+    {
+      if (GNUNET_OK !=
+         TALER_amount_add (&total,
+                           &total,
+                           &amount_with_fee))
+      {
+       ae = GNUNET_YES;
+      }
+    }
+    TALER_amount_hton (&pd->wr.withdraw_fee,
+                      &dk->fee_withdraw);
+    TALER_amount_hton (&pd->wr.amount_with_fee,
+                      &amount_with_fee);
+  }
+  GNUNET_CRYPTO_hash_context_finish (hc,
+                                    &pc->pickup_id);
+  if (GNUNET_YES == ae)
+  {
+    pc->ec = TALER_EC_TIP_PICKUP_EXCHANGE_AMOUNT_OVERFLOW;
+    pc->error_hint = "error computing total value of the tip";
+    pc->response_code = MHD_HTTP_BAD_REQUEST;
+    TMH_trigger_daemon ();
+    return;
+  }
+  pc->total = total;
+  TMH_trigger_daemon ();
+}
+
+
+/**
+ * Prepare (and eventually execute) a pickup. Finds the exchange
+ * handle we need for #run_pickup().
+ *
+ * @param pc pickup context
+ * @return #MHD_YES upon success, #MHD_NO if
+ *         the connection ought to be dropped
+ */
+static int
+prepare_pickup (struct PickupContext *pc)
+{
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = db->lookup_tip_by_id (db->cls,
+                             &pc->tip_id,
+                             &pc->exchange_uri,
+                             NULL, NULL);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+  {
+    unsigned int response_code;
+    enum TALER_ErrorCode ec;
+
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      ec = TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN;
+      response_code = MHD_HTTP_NOT_FOUND;
+      break;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      ec = TALER_EC_TIP_PICKUP_DB_ERROR_SOFT;
+      response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      break;
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      ec = TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+      response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      break;
+    default:
+      GNUNET_break (0);
+      ec = TALER_EC_INTERNAL_LOGIC_ERROR;
+      response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      break;
+    }
+    return TMH_RESPONSE_reply_rc (pc->connection,
+                                 response_code,
+                                 ec,
+                                 "Could not determine exchange URI for the 
given tip id");
+
+  }
+  pc->fo = TMH_EXCHANGES_find_exchange (pc->exchange_uri,
+                                       NULL,
+                                       &exchange_found_cb,
+                                       pc);
+  if (NULL == pc->fo)
+  {
+    return TMH_RESPONSE_reply_rc (pc->connection,
+                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                 TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                 "consult server logs");
+  }
+  MHD_suspend_connection (pc->connection);
+  return MHD_YES;
+}
+
+
+/**
+ * Parse the given @a planchet into the @a pd.
+ *
+ * @param connection connection to use for error reporting
+ * @param planchet planchet data in JSON format
+ * @param[out] pd where to store the binary data
+ * @return #GNUNET_OK upon success, #GNUNET_NO if a response
+ *    was generated, #GNUNET_SYSERR to drop the connection
+ */
+static int
+parse_planchet (struct MHD_Connection *connection,
+               const json_t *planchet,
+               struct PlanchetDetail *pd)
+{
+  int ret;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                &pd->wr.h_denomination_pub),
+    GNUNET_JSON_spec_varsize ("coin_ev",
+                             (void **) &pd->coin_ev,
+                             &pd->coin_ev_size),
+    GNUNET_JSON_spec_end()
+  };
+
+  ret = TMH_PARSE_json_data (connection,
+                            planchet,
+                            spec);
+  if (GNUNET_OK != ret)
+    return ret;
+  pd->wr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
+  pd->wr.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS));
+  GNUNET_CRYPTO_hash (pd->coin_ev,
+                     pd->coin_ev_size,
+                     &pd->wr.h_coin_envelope);
+  return ret;
+}
+
+
+/**
+ * Manages a /tip-pickup call, checking that the tip is authorized,
+ * and if so, returning the withdrawal permissions.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_tip_pickup (struct TMH_RequestHandler *rh,
+                       struct MHD_Connection *connection,
+                       void **connection_cls,
+                       const char *upload_data,
+                       size_t *upload_data_size)
+{
+  int res;
+  struct GNUNET_HashCode tip_id;
+  json_t *planchets;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("tip_id", &tip_id),
+    GNUNET_JSON_spec_json ("planchets", &planchets),
+    GNUNET_JSON_spec_end()
+  };
+  struct PickupContext *pc;
+  json_t *root;
+
+  if (NULL == *connection_cls)
+  {
+    pc = GNUNET_new (struct PickupContext);
+    pc->hc.cc = &pickup_cleanup;
+    pc->connection = connection;
+    *connection_cls = pc;
+  }
+  else
+  {
+    pc = *connection_cls;
+  }
+  if (NULL != pc->planchets)
+  {
+    /* This actually happens when we are called much later
+       after an exchange /keys' request to obtain the DKs
+       (and not for each request). */
+    return run_pickup (connection,
+                      pc);
+  }
+  res = TMH_PARSE_post_json (connection,
+                             &pc->json_parse_context,
+                             upload_data,
+                             upload_data_size,
+                             &root);
+  if (GNUNET_SYSERR == res)
+    return MHD_NO;
+  /* the POST's body has to be further fetched */
+  if ( (GNUNET_NO == res) ||
+       (NULL == root) )
+    return MHD_YES;
+
+  res = TMH_PARSE_json_data (connection,
+                             root,
+                             spec);
+  if (GNUNET_YES != res)
+  {
+    GNUNET_break_op (0);
+    json_decref (root);
+    return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+  }
+  pc->planchets_len = json_array_size (planchets);
+  if (pc->planchets_len > 1024)
+  {
+    GNUNET_JSON_parse_free (spec);
+    json_decref (root);
+    return TMH_RESPONSE_reply_rc (connection,
+                                 MHD_HTTP_BAD_REQUEST,
+                                 
TALER_EC_TIP_PICKUP_EXCHANGE_TOO_MANY_PLANCHETS,
+                                 "limit of 1024 planchets exceeded by 
request");
+  }
+  if (0 == pc->planchets_len)
+  {
+    GNUNET_JSON_parse_free (spec);
+    json_decref (root);
+    return TMH_RESPONSE_reply_rc (connection,
+                                 MHD_HTTP_BAD_REQUEST,
+                                 TALER_EC_PARAMETER_MALFORMED,
+                                 "no planchets specified");
+  }
+  pc->planchets = GNUNET_new_array (pc->planchets_len,
+                                   struct PlanchetDetail);
+  for (unsigned int i=0;i<pc->planchets_len;i++)
+  {
+    if (GNUNET_OK !=
+       (res = parse_planchet (connection,
+                              json_array_get (planchets,
+                                              i),
+                              &pc->planchets[i])))
+    {
+      GNUNET_JSON_parse_free (spec);
+      json_decref (root);
+      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+    }
+  }
+  pc->tip_id = tip_id;
+  res = prepare_pickup (pc);
+  GNUNET_JSON_parse_free (spec);
+  json_decref (root);
+  return res;
+}
diff --git a/src/backend/taler-merchant-httpd_track-transaction.h 
b/src/backend/taler-merchant-httpd_tip-pickup.h
similarity index 64%
copy from src/backend/taler-merchant-httpd_track-transaction.h
copy to src/backend/taler-merchant-httpd_tip-pickup.h
index 30896bf..da42eaa 100644
--- a/src/backend/taler-merchant-httpd_track-transaction.h
+++ b/src/backend/taler-merchant-httpd_tip-pickup.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015 GNUnet e.V.
+  (C) 2017 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -14,18 +14,18 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_track-transaction.h
- * @brief headers for /track/transaction handler
+ * @file backend/taler-merchant-httpd_tip-pickup.h
+ * @brief headers for /tip-pickup handler
  * @author Christian Grothoff
- * @author Marcello Stanisci
  */
-#ifndef TALER_MERCHANT_HTTPD_TRACK_TRANSACTION_H
-#define TALER_MERCHANT_HTTPD_TRACK_TRANSACTION_H
+#ifndef TALER_MERCHANT_HTTPD_TIP_PICKUP_H
+#define TALER_MERCHANT_HTTPD_TIP_PICKUP_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 /**
- * Handle a "/track/transaction" request.
+ * Manages a /tip-pickup call, checking that the tip is authorized,
+ * and if so, returning the withdrawal permissions.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -35,11 +35,10 @@
  * @return MHD result code
  */
 int
-MH_handler_track_transaction (struct TMH_RequestHandler *rh,
-                              struct MHD_Connection *connection,
-                              void **connection_cls,
-                              const char *upload_data,
-                              size_t *upload_data_size);
-
+MH_handler_tip_pickup (struct TMH_RequestHandler *rh,
+                       struct MHD_Connection *connection,
+                       void **connection_cls,
+                       const char *upload_data,
+                       size_t *upload_data_size);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_tip-query.c 
b/src/backend/taler-merchant-httpd_tip-query.c
new file mode 100644
index 0000000..7e236f8
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_tip-query.c
@@ -0,0 +1,102 @@
+/*
+  This file is part of TALER
+  (C) 2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/taler-merchant-httpd_tip-query.c
+ * @brief implementation of /tip-query handler
+ * @author Christian Grothoff
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <jansson.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_parsing.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_responses.h"
+#include "taler-merchant-httpd_tip-query.h"
+
+
+/**
+ * Manages a /tip-query call, checking if a tip authorization
+ * exists and, if so, returning its details.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_tip_query (struct TMH_RequestHandler *rh,
+                       struct MHD_Connection *connection,
+                       void **connection_cls,
+                       const char *upload_data,
+                       size_t *upload_data_size)
+{
+  const char *tip_id_str;
+  struct GNUNET_HashCode tip_id;
+
+  tip_id_str = MHD_lookup_connection_value (connection,
+                                            MHD_GET_ARGUMENT_KIND,
+                                            "tip_id");
+  if (NULL == tip_id_str)
+    return TMH_RESPONSE_reply_arg_missing (connection,
+                                          TALER_EC_PARAMETER_MISSING,
+                                           "tip_id");
+
+  if (GNUNET_OK != GNUNET_STRINGS_string_to_data (tip_id_str,
+                                                  strlen (tip_id_str), &tip_id,
+                                                  sizeof (struct 
GNUNET_HashCode)))
+    return TMH_RESPONSE_reply_arg_invalid (connection,
+                                          TALER_EC_PARAMETER_MISSING,
+                                           "tip_id");
+
+  enum GNUNET_DB_QueryStatus qs;
+  struct TALER_Amount tip_amount;
+  struct GNUNET_TIME_Absolute tip_timestamp;
+  char *tip_exchange_uri;
+
+  qs = db->lookup_tip_by_id (db->cls,
+                             &tip_id,
+                             &tip_exchange_uri,
+                             &tip_amount,
+                             &tip_timestamp);
+
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    return TMH_RESPONSE_reply_rc (connection,
+                                  MHD_HTTP_NOT_FOUND,
+                                  TALER_EC_TIP_QUERY_TIP_ID_UNKNOWN,
+                                  "tip id not found");
+  }
+  else if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  {
+      return TMH_RESPONSE_reply_json_pack (connection,
+                                           MHD_HTTP_OK,
+                                           "{s:s, s:s}",
+                                           "exchange", tip_exchange_uri,
+                                           "timestamp", 
GNUNET_JSON_from_time_abs (tip_timestamp),
+                                           "amount", TALER_JSON_from_amount 
(&tip_amount));
+  }
+  return TMH_RESPONSE_reply_rc (connection,
+                                MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                "tip lookup failure");
+}
diff --git a/src/backend/taler-merchant-httpd_track-transaction.h 
b/src/backend/taler-merchant-httpd_tip-query.h
similarity index 64%
copy from src/backend/taler-merchant-httpd_track-transaction.h
copy to src/backend/taler-merchant-httpd_tip-query.h
index 30896bf..ec8358d 100644
--- a/src/backend/taler-merchant-httpd_track-transaction.h
+++ b/src/backend/taler-merchant-httpd_tip-query.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015 GNUnet e.V.
+  (C) 2017 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -14,18 +14,19 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_track-transaction.h
- * @brief headers for /track/transaction handler
+ * @file backend/taler-merchant-httpd_tip-query.h
+ * @brief headers for /tip-query handler
  * @author Christian Grothoff
- * @author Marcello Stanisci
+ * @author Florian Dold
  */
-#ifndef TALER_MERCHANT_HTTPD_TRACK_TRANSACTION_H
-#define TALER_MERCHANT_HTTPD_TRACK_TRANSACTION_H
+#ifndef TALER_MERCHANT_HTTPD_TIP_QUERY_H
+#define TALER_MERCHANT_HTTPD_TIP_QUERY_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 /**
- * Handle a "/track/transaction" request.
+ * Manages a /tip-query call, checking if a tip authorization
+ * exists and, if so, returning its details.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -35,11 +36,10 @@
  * @return MHD result code
  */
 int
-MH_handler_track_transaction (struct TMH_RequestHandler *rh,
-                              struct MHD_Connection *connection,
-                              void **connection_cls,
-                              const char *upload_data,
-                              size_t *upload_data_size);
-
+MH_handler_tip_query (struct TMH_RequestHandler *rh,
+                      struct MHD_Connection *connection,
+                      void **connection_cls,
+                      const char *upload_data,
+                      size_t *upload_data_size);
 
 #endif
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index a33ced8..e374403 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -29,7 +29,7 @@ libtalermerchantdb_la_LIBADD = \
 
 libtalermerchantdb_la_LDFLAGS = \
   $(POSTGRESQL_LDFLAGS) \
-  -version-info 1:0:0 \
+  -version-info 2:0:0 \
   -no-undefined
 
 libtaler_plugin_merchantdb_postgres_la_SOURCES = \
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index d0265d5..cb1d9c6 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -28,6 +28,11 @@
 #include <taler/taler_json_lib.h>
 #include "taler_merchantdb_plugin.h"
 
+/**
+ * How often do we re-try if we run into a DB serialization error?
+ */
+#define MAX_RETRIES 3
+
 
 /**
  * Type of the "cls" argument given to each of the functions in
@@ -67,6 +72,10 @@ postgres_drop_tables (void *cls)
     GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_contract_terms 
CASCADE;"),
     GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_refunds 
CASCADE;"),
     GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS exchange_wire_fees 
CASCADE;"),
+    GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_tips CASCADE;"),
+    GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_tip_pickups 
CASCADE;"),
+    GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS 
merchant_tip_reserve_credits CASCADE;"),
+    GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_tip_reserves 
CASCADE;"),
     GNUNET_PQ_EXECUTE_STATEMENT_END
   };
 
@@ -94,7 +103,7 @@ postgres_initialize (void *cls)
                             ",h_contract_terms BYTEA NOT NULL CHECK 
(LENGTH(h_contract_terms)=64)"
                             ",timestamp INT8 NOT NULL"
                             ",row_id BIGSERIAL UNIQUE"
-                            ",paid BYTEA NOT NULL " /* WHY is this a BYTEA!? 
Why does this EXIST!? */
+                            ",paid boolean DEFAULT FALSE NOT NULL"
                             ",PRIMARY KEY (order_id, merchant_pub)"
                            ",UNIQUE (h_contract_terms, merchant_pub)"
                             ");"),
@@ -179,6 +188,49 @@ postgres_initialize (void *cls)
                             ",refund_fee_frac INT4 NOT NULL"
                             ",refund_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR 
") NOT NULL"
                             ");"),
+    /* balances of the reserves available for tips */
+    GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_reserves 
("
+                            " reserve_priv BYTEA NOT NULL CHECK 
(LENGTH(reserve_priv)=32)"
+                            ",expiration INT8 NOT NULL"
+                            ",balance_val INT8 NOT NULL"
+                            ",balance_frac INT4 NOT NULL"
+                            ",balance_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") 
NOT NULL"
+                            ",PRIMARY KEY (reserve_priv)"
+                            ");"),
+    /* table where we remember when tipping reserves where established / 
enabled */
+    GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS 
merchant_tip_reserve_credits ("
+                            " reserve_priv BYTEA NOT NULL CHECK 
(LENGTH(reserve_priv)=32)"
+                            ",credit_uuid BYTEA NOT NULL CHECK 
(LENGTH(credit_uuid)=64)"
+                            ",timestamp INT8 NOT NULL"
+                            ",amount_val INT8 NOT NULL"
+                            ",amount_frac INT4 NOT NULL"
+                            ",amount_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") 
NOT NULL"
+                            ",PRIMARY KEY (credit_uuid)"
+                            ");"),
+    /* tips that have been authorized */
+    GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tips ("
+                            " reserve_priv BYTEA NOT NULL CHECK 
(LENGTH(reserve_priv)=32)"
+                            ",tip_id BYTEA NOT NULL CHECK (LENGTH(tip_id)=64)"
+                           ",exchange_uri VARCHAR NOT NULL"
+                            ",justification VARCHAR NOT NULL"
+                            ",timestamp INT8 NOT NULL"
+                            ",amount_val INT8 NOT NULL" /* overall tip amount 
*/
+                            ",amount_frac INT4 NOT NULL"
+                            ",amount_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") 
NOT NULL"
+                            ",left_val INT8 NOT NULL" /* tip amount not yet 
picked up */
+                            ",left_frac INT4 NOT NULL"
+                            ",left_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") 
NOT NULL"
+                            ",PRIMARY KEY (tip_id)"
+                            ");"),
+    /* tips that have been picked up */
+    GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_pickups ("
+                            " tip_id BYTEA NOT NULL REFERENCES merchant_tips 
(tip_id) ON DELETE CASCADE"
+                            ",pickup_id BYTEA NOT NULL CHECK 
(LENGTH(pickup_id)=64)"
+                            ",amount_val INT8 NOT NULL"
+                            ",amount_frac INT4 NOT NULL"
+                            ",amount_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") 
NOT NULL"
+                            ",PRIMARY KEY (pickup_id)"
+                            ");"),
     GNUNET_PQ_EXECUTE_STATEMENT_END
   };
   struct GNUNET_PQ_PreparedStatement ps[] = {
@@ -250,17 +302,16 @@ postgres_initialize (void *cls)
                             "(order_id"
                             ",merchant_pub"
                             ",timestamp"
-                            ",paid"
                             ",contract_terms"
                             ",h_contract_terms)"
                             " VALUES "
-                            "($1, $2, $3, $4, $5, $6)",
-                            6),
+                            "($1, $2, $3, $4, $5)",
+                            5),
     GNUNET_PQ_make_prepare ("mark_proposal_paid",
                             "UPDATE merchant_contract_terms SET"
-                            " paid=$1 WHERE h_contract_terms=$2"
-                            " AND merchant_pub=$3",
-                            3),
+                            " paid=TRUE WHERE h_contract_terms=$1"
+                            " AND merchant_pub=$2",
+                            2),
     GNUNET_PQ_make_prepare ("insert_wire_fee",
                             "INSERT INTO exchange_wire_fees"
                             "(exchange_pub"
@@ -321,8 +372,8 @@ postgres_initialize (void *cls)
                             " WHERE"
                             " order_id=$1"
                             " AND merchant_pub=$2"
-                            " AND paid=$3",
-                            3),
+                            " AND paid=TRUE",
+                            2),
     GNUNET_PQ_make_prepare ("find_contract_terms",
                             "SELECT"
                             " contract_terms"
@@ -331,7 +382,6 @@ postgres_initialize (void *cls)
                             " order_id=$1"
                             " AND merchant_pub=$2",
                             2),
-
     GNUNET_PQ_make_prepare ("find_contract_terms_by_date",
                             "SELECT"
                             " contract_terms"
@@ -341,10 +391,10 @@ postgres_initialize (void *cls)
                             " WHERE"
                             " timestamp<$1"
                             " AND merchant_pub=$2"
-                            " AND paid=$4"
+                            " AND paid=TRUE"
                             " ORDER BY row_id DESC, timestamp DESC"
                             " LIMIT $3",
-                            4),
+                            3),
     GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash",
                             "SELECT"
                            " coin_pub"
@@ -370,10 +420,10 @@ postgres_initialize (void *cls)
                             " timestamp<$1"
                             " AND merchant_pub=$2"
                             " AND row_id<$3"
-                            " AND paid=$5"
+                            " AND paid=TRUE"
                             " ORDER BY row_id DESC, timestamp DESC"
                             " LIMIT $4",
-                            5),
+                            4),
     GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_future",
                             "SELECT"
                             " contract_terms"
@@ -384,10 +434,10 @@ postgres_initialize (void *cls)
                             " timestamp>$1"
                             " AND merchant_pub=$2"
                             " AND row_id>$3"
-                            " AND paid=$5"
+                            " AND paid=TRUE"
                             " ORDER BY row_id DESC, timestamp DESC"
                             " LIMIT $4",
-                            5),
+                            4),
     GNUNET_PQ_make_prepare ("find_transaction",
                             "SELECT"
                             " exchange_uri"
@@ -473,6 +523,105 @@ postgres_initialize (void *cls)
                             " WHERE wtid=$1"
                             "  AND exchange_uri=$2",
                             2),
+    GNUNET_PQ_make_prepare ("lookup_tip_reserve_balance",
+                            "SELECT"
+                            " expiration"
+                            ",balance_val"
+                            ",balance_frac"
+                            ",balance_curr"
+                            " FROM merchant_tip_reserves"
+                            " WHERE reserve_priv=$1",
+                            1),
+    GNUNET_PQ_make_prepare ("update_tip_reserve_balance",
+                            "UPDATE merchant_tip_reserves SET"
+                            " expiration=$2"
+                            ",balance_val=$3"
+                            ",balance_frac=$4"
+                            ",balance_curr=$5"
+                            " WHERE reserve_priv=$1",
+                            5),
+    GNUNET_PQ_make_prepare ("insert_tip_reserve_balance",
+                            "INSERT INTO merchant_tip_reserves"
+                            "(reserve_priv"
+                            ",expiration"
+                            ",balance_val"
+                            ",balance_frac"
+                            ",balance_curr"
+                            ") VALUES "
+                            "($1, $2, $3, $4, $5)",
+                            5),
+    GNUNET_PQ_make_prepare ("insert_tip_justification",
+                            "INSERT INTO merchant_tips"
+                            "(reserve_priv"
+                            ",tip_id"
+                           ",exchange_uri"
+                            ",justification"
+                            ",timestamp"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",amount_curr"
+                            ",left_val"
+                            ",left_frac"
+                            ",left_curr"
+                            ") VALUES "
+                            "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
+                            11),
+    GNUNET_PQ_make_prepare ("lookup_reserve_by_tip_id",
+                            "SELECT"
+                            " reserve_priv"
+                            ",left_val"
+                            ",left_frac"
+                            ",left_curr"
+                            " FROM merchant_tips"
+                            " WHERE tip_id=$1",
+                            1),
+    GNUNET_PQ_make_prepare ("lookup_amount_by_pickup",
+                            "SELECT"
+                            " amount_val"
+                            ",amount_frac"
+                            ",amount_curr"
+                            " FROM merchant_tip_pickups"
+                            " WHERE pickup_id=$1"
+                            " AND tip_id=$2",
+                            2),
+    GNUNET_PQ_make_prepare ("find_tip_by_id",
+                            "SELECT"
+                            " exchange_uri"
+                            ",timestamp"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",amount_curr"
+                            " FROM merchant_tips"
+                            " WHERE tip_id=$1",
+                            1),
+    GNUNET_PQ_make_prepare ("update_tip_balance",
+                            "UPDATE merchant_tips SET"
+                            " left_val=$2"
+                            ",left_frac=$3"
+                            ",left_curr=$4"
+                            " WHERE tip_id=$1",
+                            4),
+    GNUNET_PQ_make_prepare ("insert_pickup_id",
+                            "INSERT INTO merchant_tip_pickups"
+                            "(tip_id"
+                            ",pickup_id"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",amount_curr"
+                            ") VALUES "
+                            "($1, $2, $3, $4, $5)",
+                            5),
+    GNUNET_PQ_make_prepare ("insert_tip_credit_uuid",
+                            "INSERT INTO merchant_tip_reserve_credits"
+                            "(reserve_priv"
+                            ",credit_uuid"
+                            ",timestamp"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",amount_curr)"
+                            " VALUES "
+                            "($1, $2, $3, $4, $5, $6)",
+                            6),
     GNUNET_PQ_PREPARED_STATEMENT_END
   };
 
@@ -672,6 +821,8 @@ postgres_find_contract_terms (void *cls,
  *
  * @param cls closure
  * @param order_id identificator of the proposal being stored
+ * @param merchant_pub merchant's public key
+ * @param timestamp timestamp of this proposal data
  * @param contract_terms proposal data to store
  * @return transaction status
  */
@@ -684,13 +835,10 @@ postgres_insert_contract_terms (void *cls,
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_HashCode h_contract_terms;
-  unsigned int no = GNUNET_NO;
-
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (order_id),
     GNUNET_PQ_query_param_auto_from_type (merchant_pub),
     GNUNET_PQ_query_param_absolute_time (&timestamp),
-    GNUNET_PQ_query_param_auto_from_type (&no),
     TALER_PQ_query_param_json (contract_terms),
     GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
     GNUNET_PQ_query_param_end
@@ -717,21 +865,26 @@ postgres_insert_contract_terms (void *cls,
 
 /**
  * Mark contract terms as payed.  Needed by /history as only payed
- * contracts must be shown.  NOTE: we can't get the list of (payed)
- * contracts from the transactions table because it lacks contract_terms
- * plain JSON.  In facts, the protocol doesn't allow to store contract_terms
- * in transactions table, as /pay handler doesn't receive this data
- * (only /proposal does).
+ * contracts must be shown.
+ *
+ * NOTE: we can't get the list of (payed) contracts from the
+ * transactions table because it lacks contract_terms plain JSON.  In
+ * facts, the protocol doesn't allow to store contract_terms in
+ * transactions table, as /pay handler doesn't receive this data (only
+ * /proposal does).
+ *
+ * @param cls closure
+ * @param h_contract_terms hash of the contract that is now paid
+ * @param merchant_pub merchant's public key
+ * @return transaction status
  */
 enum GNUNET_DB_QueryStatus
 postgres_mark_proposal_paid (void *cls,
                              const struct GNUNET_HashCode *h_contract_terms,
                              const struct TALER_MerchantPublicKeyP 
*merchant_pub)
 {
-  unsigned int yes = GNUNET_YES;
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (&yes),
     GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
     GNUNET_PQ_query_param_auto_from_type (merchant_pub),
     GNUNET_PQ_query_param_end
@@ -936,12 +1089,9 @@ postgres_find_contract_terms_history (void *cls,
   struct PostgresClosure *pg = cls;
   json_t *contract_terms;
   enum GNUNET_DB_QueryStatus qs;
-  unsigned int yes = GNUNET_YES;
-
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (order_id),
     GNUNET_PQ_query_param_auto_from_type (merchant_pub),
-    GNUNET_PQ_query_param_auto_from_type (&yes),
     GNUNET_PQ_query_param_end
   };
   struct GNUNET_PQ_ResultSpec rs[] = {
@@ -1054,7 +1204,7 @@ find_contracts_cb (void *cls,
  * furtherly older records, and so on. Alternatively, you can use always
  * the same timestamp and just go behind in history by tuning `start`.
  * @param nrows only nrows rows are returned.
- * @param future if set to GNUNET_YES, retrieves rows younger than `date`.
+ * @param future if set to #GNUNET_YES, retrieves rows younger than `date`.
  * This is tipically used to show live updates on the merchant's backoffice
  * Web interface.
  * @param cb function to call with transaction data, can be NULL.
@@ -1072,14 +1222,11 @@ postgres_find_contract_terms_by_date_and_range (void 
*cls,
                                                void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
-  unsigned int yes = GNUNET_YES;
-
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_absolute_time (&date),
     GNUNET_PQ_query_param_auto_from_type (merchant_pub),
     GNUNET_PQ_query_param_uint64 (&start),
     GNUNET_PQ_query_param_uint64 (&nrows),
-    GNUNET_PQ_query_param_auto_from_type (&yes),
     GNUNET_PQ_query_param_end
   };
   const char *stmt;
@@ -1129,13 +1276,10 @@ postgres_find_contract_terms_by_date (void *cls,
                                      void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
-  unsigned int yes = GNUNET_YES;
-
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_absolute_time (&date),
     GNUNET_PQ_query_param_auto_from_type (merchant_pub),
     GNUNET_PQ_query_param_uint64 (&nrows),
-    GNUNET_PQ_query_param_auto_from_type (&yes),
     GNUNET_PQ_query_param_end
   };
   enum GNUNET_DB_QueryStatus qs;
@@ -2465,6 +2609,561 @@ postgres_find_proof_by_wtid (void *cls,
 
 
 /**
+ * Add @a credit to a reserve to be used for tipping.  Note that
+ * this function does not actually perform any wire transfers to
+ * credit the reserve, it merely tells the merchant backend that
+ * a reserve was topped up.  This has to happen before tips can be
+ * authorized.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param reserve_priv which reserve is topped up or created
+ * @param credit_uuid unique identifier for the credit operation
+ * @param credit how much money was added to the reserve
+ * @param expiration when does the reserve expire?
+ * @return transaction status, usually
+ *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_enable_tip_reserve (void *cls,
+                             const struct TALER_ReservePrivateKeyP 
*reserve_priv,
+                             const struct GNUNET_HashCode *credit_uuid,
+                             const struct TALER_Amount *credit,
+                             struct GNUNET_TIME_Absolute expiration)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute old_expiration;
+  struct TALER_Amount old_balance;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_TIME_Absolute new_expiration;
+  struct TALER_Amount new_balance;
+  unsigned int retries;
+
+  retries = 0;
+  check_connection (pg);
+ RETRY:
+  if (MAX_RETRIES < ++retries)
+    return GNUNET_DB_STATUS_SOFT_ERROR;
+  if (GNUNET_OK !=
+      postgres_start (pg))
+  {
+    GNUNET_break (0);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  /* ensure that credit_uuid is new/unique */
+  {
+    struct GNUNET_TIME_Absolute now;
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_auto_from_type (credit_uuid),
+      GNUNET_PQ_query_param_absolute_time (&now),
+      TALER_PQ_query_param_amount (credit),
+      GNUNET_PQ_query_param_end
+    };
+
+    now = GNUNET_TIME_absolute_get ();
+    (void) GNUNET_TIME_round_abs (&now);
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_tip_credit_uuid",
+                                             params);
+    if (0 > qs)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return qs;
+    }
+    /* UUID already exists, we are done! */
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      postgres_rollback (pg);
+      return qs;
+    }
+  }
+
+  /* Obtain existing reserve balance */
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_end
+    };
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                           &old_expiration),
+      TALER_PQ_result_spec_amount ("balance",
+                                   &old_balance),
+      GNUNET_PQ_result_spec_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   
"lookup_tip_reserve_balance",
+                                                   params,
+                                                   rs);
+  }
+  if (0 > qs)
+  {
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+    postgres_rollback (pg);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto RETRY;
+    return qs;
+  }
+  if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
+       (GNUNET_TIME_absolute_get_remaining (old_expiration).rel_value_us > 0) )
+  {
+    new_expiration = GNUNET_TIME_absolute_max (old_expiration,
+                                               expiration);
+    if (GNUNET_OK !=
+        TALER_amount_add (&new_balance,
+                          credit,
+                          &old_balance))
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+  }
+  else
+  {
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Old reserve balance of %s had expired at %s, not carrying 
it over!\n",
+                  TALER_amount2s (&old_balance),
+                  GNUNET_STRINGS_absolute_time_to_string (old_expiration));
+    }
+    new_expiration = expiration;
+    new_balance = *credit;
+  }
+
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_absolute_time (&new_expiration),
+      TALER_PQ_query_param_amount (&new_balance),
+      GNUNET_PQ_query_param_end
+    };
+    const char *stmt;
+
+    stmt = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+      ? "update_tip_reserve_balance"
+      : "insert_tip_reserve_balance";
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             stmt,
+                                             params);
+    if (0 > qs)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return qs;
+    }
+  }
+  qs = postgres_commit (pg);
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+  GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    goto RETRY;
+  return qs;
+}
+
+
+/**
+ * Authorize a tip over @a amount from reserve @a reserve_priv.  Remember
+ * the authorization under @a tip_id for later, together with the
+ * @a justification.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param justification why was the tip approved
+ * @param amount how high is the tip (with fees)
+ * @param reserve_priv which reserve is debited
+ * @param exchange_uri which exchange manages the tip
+ * @param[out] expiration set to when the tip expires
+ * @param[out] tip_id set to the unique ID for the tip
+ * @return taler error code
+ *      #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but 
has expired
+ *      #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+ *      #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has 
insufficient funds left
+ *      #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+ *      #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client should 
retry)
+ *      #TALER_EC_NONE upon success
+ */
+static enum TALER_ErrorCode
+postgres_authorize_tip (void *cls,
+                        const char *justification,
+                        const struct TALER_Amount *amount,
+                        const struct TALER_ReservePrivateKeyP *reserve_priv,
+                       const char *exchange_uri,
+                        struct GNUNET_TIME_Absolute *expiration,
+                        struct GNUNET_HashCode *tip_id)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_TIME_Absolute old_expiration;
+  struct TALER_Amount old_balance;
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                         &old_expiration),
+    TALER_PQ_result_spec_amount ("balance",
+                                 &old_balance),
+    GNUNET_PQ_result_spec_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+  struct TALER_Amount new_balance;
+  unsigned int retries;
+
+  retries = 0;
+  check_connection (pg);
+ RETRY:
+  if (MAX_RETRIES < ++retries)
+    return TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR;
+  if (GNUNET_OK !=
+      postgres_start (pg))
+  {
+    GNUNET_break (0);
+    return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR;
+  }
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                "lookup_tip_reserve_balance",
+                                                params,
+                                                rs);
+  if (0 >= qs)
+  {
+    /* reserve unknown */
+    postgres_rollback (pg);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto RETRY;
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      return TALER_EC_TIP_AUTHORIZE_RESERVE_NOT_ENABLED;
+    return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR;
+  }
+  if (0 == GNUNET_TIME_absolute_get_remaining (old_expiration).rel_value_us)
+  {
+    /* reserve expired, can't be used */
+    postgres_rollback (pg);
+    return TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED;
+  }
+  if (GNUNET_SYSERR ==
+      TALER_amount_subtract (&new_balance,
+                             &old_balance,
+                             amount))
+  {
+    /* insufficient funds left in reserve */
+    postgres_rollback (pg);
+    return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS;
+  }
+  /* Update reserve balance */
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_absolute_time (&old_expiration),
+      TALER_PQ_query_param_amount (&new_balance),
+      GNUNET_PQ_query_param_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "update_tip_reserve_balance",
+                                             params);
+    if (0 > qs)
+    {
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR;
+    }
+  }
+  /* Generate and store tip ID */
+  *expiration = old_expiration;
+  GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG,
+                                    tip_id);
+  {
+    struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_auto_from_type (tip_id),
+      GNUNET_PQ_query_param_string (exchange_uri),
+      GNUNET_PQ_query_param_string (justification),
+      GNUNET_PQ_query_param_absolute_time (&now),
+      TALER_PQ_query_param_amount (amount), /* overall amount */
+      TALER_PQ_query_param_amount (amount), /* amount left */
+      GNUNET_PQ_query_param_end
+    };
+
+    (void) GNUNET_TIME_round_abs (&now);
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_tip_justification",
+                                             params);
+    if (0 > qs)
+    {
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR;
+    }
+  }
+  qs = postgres_commit (pg);
+  if (0 <= qs)
+    return TALER_EC_NONE; /* success! */
+  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    goto RETRY;
+  return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR;
+}
+
+
+/**
+ * Find out tip authorization details associated with @a tip_id
+ *
+ * @param cls closure, typically a connection to the d
+ * @param tip_id the unique ID for the tip
+ * @param[out] exchange_uri set to the URI of the exchange (unless NULL)
+ * @param[out] amount set to the authorized amount (unless NULL)
+ * @param[out] timestamp set to the timestamp of the tip authorization (unless 
NULL)
+ * @return transaction status, usually
+ *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_tip_by_id (void *cls,
+                           const struct GNUNET_HashCode *tip_id,
+                           char **exchange_uri,
+                           struct TALER_Amount *amount,
+                           struct GNUNET_TIME_Absolute *timestamp)
+{
+  char *res_exchange_uri;
+  struct TALER_Amount res_amount;
+  struct GNUNET_TIME_Absolute res_timestamp;
+
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (tip_id),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_string ("exchange_uri",
+                                 &res_exchange_uri),
+    GNUNET_PQ_result_spec_absolute_time ("timestamp",
+                                         &res_timestamp),
+    TALER_PQ_result_spec_amount ("amount",
+                                 &res_amount),
+    GNUNET_PQ_result_spec_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                "find_tip_by_id",
+                                                params,
+                                                rs);
+  if (0 >= qs)
+  {
+    if (NULL != exchange_uri)
+      *exchange_uri = NULL;
+    return qs;
+  }
+  if (NULL != exchange_uri)
+    *exchange_uri = strdup (res_exchange_uri);
+  if (NULL != amount)
+    *amount = res_amount;
+  if (NULL != timestamp)
+    *timestamp = res_timestamp;
+  GNUNET_PQ_cleanup_result (rs);
+  return qs;
+}
+
+
+/**
+ * Pickup a tip over @a amount using pickup id @a pickup_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param amount how high is the amount picked up (with fees)
+ * @param tip_id the unique ID from the tip authorization
+ * @param pickup_id the unique ID identifying the pick up operation
+ *        (to allow replays, hash over the coin envelope and denomination key)
+ * @param[out] reserve_priv which reserve key to use to sign
+ * @return taler error code
+ *      #TALER_EC_TIP_PICKUP_ID_UNKNOWN if @a tip_id is unknown
+ *      #TALER_EC_TIP_PICKUP_NO_FUNDS if @a tip_id has insufficient funds left
+ *      #TALER_EC_TIP_PICKUP_DB_ERROR_HARD on hard database errors
+ *      #TALER_EC_TIP_PICKUP_AMOUNT_CHANGED if @a amount is different for 
known @a pickup_id
+ *      #TALER_EC_TIP_PICKUP_DB_ERROR_SOFT on soft database errors (client 
should retry)
+ *      #TALER_EC_NONE upon success (@a reserve_priv was set)
+ */
+static enum TALER_ErrorCode
+postgres_pickup_tip (void *cls,
+                     const struct TALER_Amount *amount,
+                     const struct GNUNET_HashCode *tip_id,
+                     const struct GNUNET_HashCode *pickup_id,
+                     struct TALER_ReservePrivateKeyP *reserve_priv)
+{
+  struct PostgresClosure *pg = cls;
+  struct TALER_Amount left_amount;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (tip_id),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
+                                          reserve_priv),
+    TALER_PQ_result_spec_amount ("left",
+                                 &left_amount),
+    GNUNET_PQ_result_spec_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+  unsigned int retries;
+
+  retries = 0;
+  check_connection (pg);
+ RETRY:
+  if (MAX_RETRIES < ++retries)
+    return TALER_EC_TIP_PICKUP_DB_ERROR_SOFT;
+  if (GNUNET_OK !=
+      postgres_start (pg))
+  {
+    GNUNET_break (0);
+    return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+  }
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                "lookup_reserve_by_tip_id",
+                                                params,
+                                                rs);
+  if (0 >= qs)
+  {
+    /* tip ID unknown */
+    memset (reserve_priv,
+            0,
+            sizeof (*reserve_priv));
+    postgres_rollback (pg);
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      return TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN;
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto RETRY;
+    return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+  }
+
+  /* Check if pickup_id already exists */
+  {
+    struct TALER_Amount existing_amount;
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (pickup_id),
+      GNUNET_PQ_query_param_auto_from_type (tip_id),
+      GNUNET_PQ_query_param_end
+    };
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      TALER_PQ_result_spec_amount ("amount",
+                                   &existing_amount),
+      GNUNET_PQ_result_spec_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_amount_by_pickup",
+                                                   params,
+                                                   rs);
+    if (0 > qs)
+    {
+      /* DB error */
+      memset (reserve_priv,
+              0,
+              sizeof (*reserve_priv));
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    {
+      if (0 !=
+          TALER_amount_cmp (&existing_amount,
+                            amount))
+      {
+        GNUNET_break_op (0);
+        postgres_rollback (pg);
+        return TALER_EC_TIP_PICKUP_AMOUNT_CHANGED;
+      }
+      return TALER_EC_NONE; /* we are done! */
+    }
+  }
+
+  /* Calculate new balance */
+  {
+    struct TALER_Amount new_left;
+
+    if (GNUNET_SYSERR ==
+        TALER_amount_subtract (&new_left,
+                               &left_amount,
+                               amount))
+    {
+      /* attempt to take more tips than the tipping amount */
+      GNUNET_break_op (0);
+      memset (reserve_priv,
+              0,
+              sizeof (*reserve_priv));
+      postgres_rollback (pg);
+      return TALER_EC_TIP_PICKUP_NO_FUNDS;
+    }
+
+    /* Update DB: update balance */
+    {
+      struct GNUNET_PQ_QueryParam params[] = {
+        GNUNET_PQ_query_param_auto_from_type (tip_id),
+        TALER_PQ_query_param_amount (&new_left),
+        GNUNET_PQ_query_param_end
+      };
+
+      qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                               "update_tip_balance",
+                                               params);
+      if (0 > qs)
+      {
+        postgres_rollback (pg);
+        memset (reserve_priv,
+                0,
+                sizeof (*reserve_priv));
+        if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+          goto RETRY;
+        return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+      }
+    }
+
+    /* Update DB: remember pickup_id */
+    {
+      struct GNUNET_PQ_QueryParam params[] = {
+        GNUNET_PQ_query_param_auto_from_type (tip_id),
+        GNUNET_PQ_query_param_auto_from_type (pickup_id),
+        TALER_PQ_query_param_amount (amount),
+        GNUNET_PQ_query_param_end
+      };
+
+      qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                               "insert_pickup_id",
+                                               params);
+      if (0 > qs)
+      {
+        postgres_rollback (pg);
+        memset (reserve_priv,
+                0,
+                sizeof (*reserve_priv));
+        if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+          goto RETRY;
+        return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+      }
+    }
+  }
+  qs = postgres_commit (pg);
+  if (0 <= qs)
+    return TALER_EC_NONE; /* success  */
+  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    goto RETRY;
+  return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+}
+
+
+/**
  * Initialize Postgres database subsystem.
  *
  * @param cls a configuration instance
@@ -2531,8 +3230,12 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
   plugin->find_contract_terms_from_hash = 
&postgres_find_contract_terms_from_hash;
   plugin->get_refunds_from_contract_terms_hash = 
&postgres_get_refunds_from_contract_terms_hash;
   plugin->lookup_wire_fee = &postgres_lookup_wire_fee;
-  plugin->increase_refund_for_contract = postgres_increase_refund_for_contract;
-  plugin->mark_proposal_paid = postgres_mark_proposal_paid;
+  plugin->increase_refund_for_contract = 
&postgres_increase_refund_for_contract;
+  plugin->mark_proposal_paid = &postgres_mark_proposal_paid;
+  plugin->enable_tip_reserve = &postgres_enable_tip_reserve;
+  plugin->authorize_tip = &postgres_authorize_tip;
+  plugin->lookup_tip_by_id = &postgres_lookup_tip_by_id;
+  plugin->pickup_tip = &postgres_pickup_tip;
   plugin->start = postgres_start;
   plugin->commit = postgres_commit;
   plugin->rollback = postgres_rollback;
diff --git a/src/backenddb/test-merchantdb-postgres.conf 
b/src/backenddb/test-merchantdb-postgres.conf
index f88fcef..e2a78af 100644
--- a/src/backenddb/test-merchantdb-postgres.conf
+++ b/src/backenddb/test-merchantdb-postgres.conf
@@ -2,4 +2,4 @@
 DB = postgres
 
 [merchantdb-postgres]
-CONFIG = postgres:///talertest
+CONFIG = postgres:///talercheck
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index eb4fe14..0bbea3f 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -17,6 +17,7 @@
  * @file merchant/test_merchantdb_postgres.c
  * @brief testcase for merchant's postgres db plugin
  * @author Marcello Stanisci
+ * @author Christian Grothoff
  */
 
 #include "platform.h"
@@ -391,6 +392,7 @@ test_wire_fee ()
   RND_BLK (&h_wire_method);
   RND_BLK (&exchange_sig);
   date1 = GNUNET_TIME_absolute_get ();
+  (void) GNUNET_TIME_round_abs (&date1);
   date2 = GNUNET_TIME_absolute_add (date1,
                                    GNUNET_TIME_UNIT_DAYS);
   date3 = GNUNET_TIME_absolute_add (date2,
@@ -510,6 +512,254 @@ test_wire_fee ()
 
 
 /**
+ * Test APIs related to tipping.
+ *
+ * @return #GNUNET_OK upon success
+ */
+static int
+test_tipping ()
+{
+  struct TALER_ReservePrivateKeyP tip_reserve_priv;
+  struct TALER_ReservePrivateKeyP pres;
+  struct GNUNET_HashCode tip_id;
+  struct GNUNET_HashCode tip_credit_uuid;
+  struct GNUNET_HashCode pickup_id;
+  struct GNUNET_TIME_Absolute tip_expiration;
+  struct GNUNET_TIME_Absolute reserve_expiration;
+  struct TALER_Amount total;
+  struct TALER_Amount amount;
+  struct TALER_Amount inc;
+  char *uri;
+
+  RND_BLK (&tip_reserve_priv);
+  if (TALER_EC_TIP_AUTHORIZE_RESERVE_NOT_ENABLED !=
+      plugin->authorize_tip (plugin->cls,
+                             "testing tips reserve unknown",
+                             &amount,
+                             &tip_reserve_priv,
+                            "http://localhost:8081/";,
+                             &tip_expiration,
+                             &tip_id))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  RND_BLK (&tip_credit_uuid);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":5",
+                                         &total));
+  /* Pick short expiration, but long enough to
+     run 2 DB interactions even on very slow systems. */
+  reserve_expiration = GNUNET_TIME_relative_to_absolute 
(GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+                                                                               
         2));
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->enable_tip_reserve (plugin->cls,
+                                  &tip_reserve_priv,
+                                  &tip_credit_uuid,
+                                  &total,
+                                  reserve_expiration))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* check idempotency */
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+      plugin->enable_tip_reserve (plugin->cls,
+                                  &tip_reserve_priv,
+                                  &tip_credit_uuid,
+                                  &total,
+                                  reserve_expiration))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* Make sure it has expired, so at this point the value is back at ZERO! */
+  sleep (3);
+  if (TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED !=
+      plugin->authorize_tip (plugin->cls,
+                             "testing tips too late",
+                             &amount,
+                             &tip_reserve_priv,
+                            "http://localhost:8081/";,
+                             &tip_expiration,
+                             &tip_id))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Re-add some funds again */
+  RND_BLK (&tip_credit_uuid);
+  reserve_expiration = GNUNET_TIME_relative_to_absolute 
(GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+                                                                               
         2));
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->enable_tip_reserve (plugin->cls,
+                                  &tip_reserve_priv,
+                                  &tip_credit_uuid,
+                                  &total,
+                                  reserve_expiration))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* top it up by adding more with a fresh UUID
+     and even longer expiration time (until end of test) */
+  RND_BLK (&tip_credit_uuid);
+  reserve_expiration = GNUNET_TIME_relative_to_absolute 
(GNUNET_TIME_UNIT_DAYS);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->enable_tip_reserve (plugin->cls,
+                                  &tip_reserve_priv,
+                                  &tip_credit_uuid,
+                                  &total,
+                                  reserve_expiration))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Now authorize some tips */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":4",
+                                         &amount));
+  if (TALER_EC_NONE !=
+      plugin->authorize_tip (plugin->cls,
+                             "testing tips",
+                             &amount,
+                             &tip_reserve_priv,
+                            "http://localhost:8081/";,
+                             &tip_expiration,
+                             &tip_id))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (tip_expiration.abs_value_us != reserve_expiration.abs_value_us)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->lookup_tip_by_id (plugin->cls,
+                                     &tip_id,
+                                     &uri,
+                                      NULL, NULL))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 != strcmp ("http://localhost:8081/";,
+                  uri))
+  {
+    GNUNET_free (uri);
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_free (uri);
+  if (TALER_EC_NONE !=
+      plugin->authorize_tip (plugin->cls,
+                             "testing tips more",
+                             &amount,
+                             &tip_reserve_priv,
+                            "http://localhost:8081/";,
+                             &tip_expiration,
+                             &tip_id))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (tip_expiration.abs_value_us != reserve_expiration.abs_value_us)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Let's try to pick up the authorized tip in 2 increments */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":2",
+                                         &inc));
+  RND_BLK (&pickup_id);
+  if (TALER_EC_NONE !=
+      plugin->pickup_tip (plugin->cls,
+                          &inc,
+                          &tip_id,
+                          &pickup_id,
+                          &pres))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 != memcmp (&pres,
+                   &tip_reserve_priv,
+                   sizeof (pres)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  RND_BLK (&pickup_id);
+  if (TALER_EC_NONE !=
+      plugin->pickup_tip (plugin->cls,
+                          &inc,
+                          &tip_id,
+                          &pickup_id,
+                          &pres))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 != memcmp (&pres,
+                   &tip_reserve_priv,
+                   sizeof (pres)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Third attempt should fail, as we've picked up 4/4 in amount */
+  RND_BLK (&pickup_id);
+  if (TALER_EC_TIP_PICKUP_NO_FUNDS !=
+      plugin->pickup_tip (plugin->cls,
+                          &inc,
+                          &tip_id,
+                          &pickup_id,
+                          &pres))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* We authorized 8 out of 10, so going for another 4 should fail with 
insufficient funds */
+  if (TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS !=
+      plugin->authorize_tip (plugin->cls,
+                             "testing tips insufficient funds",
+                             &amount,
+                             &tip_reserve_priv,
+                            "http://localhost:8081/";,
+                             &tip_expiration,
+                             &tip_id))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Test that picking up with random (unauthorized) tip_id fails as well */
+  RND_BLK (&tip_id);
+  RND_BLK (&pickup_id);
+  if (TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN !=
+      plugin->pickup_tip (plugin->cls,
+                          &inc,
+                          &tip_id,
+                          &pickup_id,
+                          &pres))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  return GNUNET_OK;
+}
+
+
+/**
  * Main function that will be run by the scheduler.
  *
  * @param cls closure with config
@@ -519,6 +769,7 @@ run (void *cls)
 {
   struct GNUNET_CONFIGURATION_Handle *cfg = cls;
   struct GNUNET_TIME_Absolute fake_now;
+  json_t *out;
   /* Data for 'store_payment()' */
 
   if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
@@ -549,11 +800,11 @@ run (void *cls)
   RND_BLK (&merchant_pub);
   RND_BLK (&wtid);
   timestamp = GNUNET_TIME_absolute_get ();
-  GNUNET_TIME_round_abs (&timestamp);
+  (void) GNUNET_TIME_round_abs (&timestamp);
   delta = GNUNET_TIME_UNIT_MINUTES;
   fake_now = GNUNET_TIME_absolute_add (timestamp, delta);
   refund_deadline = GNUNET_TIME_absolute_get();
-  GNUNET_TIME_round_abs (&refund_deadline);
+  (void) GNUNET_TIME_round_abs (&refund_deadline);
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":5",
                                          &amount_with_fee));
@@ -615,7 +866,6 @@ run (void *cls)
                                       &h_contract_terms,
                                       &merchant_pub));
 
-  json_t *out;
 
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->find_contract_terms (plugin->cls,
@@ -645,7 +895,7 @@ run (void *cls)
                                                         &pd_cb,
                                                         NULL));
   timestamp = GNUNET_TIME_absolute_get ();
-  GNUNET_TIME_round_abs (&timestamp);
+  (void) GNUNET_TIME_round_abs (&timestamp);
 
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->insert_contract_terms (plugin->cls,
@@ -787,6 +1037,9 @@ run (void *cls)
 
   FAILIF (GNUNET_OK !=
          test_wire_fee ());
+  FAILIF (GNUNET_OK !=
+         test_tipping ());
+
 
   if (-1 == result)
     result = 0;
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index ab31701..0b739d6 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -24,6 +24,7 @@
 
 #include <taler/taler_util.h>
 #include <taler/taler_error_codes.h>
+#include <taler/taler_exchange_service.h>
 #include <gnunet/gnunet_curl_lib.h>
 #include <jansson.h>
 
@@ -309,10 +310,6 @@ struct TALER_MERCHANT_PayCoin
    */
   struct TALER_Amount amount_without_fee;
 
-  /**
-   * Next coin used to pay
-   */
-  struct TALER_MERCHANT_PayCoin *next;
 };
 
 
@@ -685,11 +682,11 @@ typedef void
  * @param ctx execution context
  * @param backend_uri base URL of the merchant backend
  * @param instance which merchant instance is performing this call
- * @param start return `delta` records starting from position `start`
- * @param delta return `delta` records starting from position `start`
+ * @param start return @a delta records starting from position @a start
+ * @param delta return @a delta records starting from position @a start
  * @param date only transactions younger than/equals to date will be returned
  * @param history_cb callback which will work the response gotten from the 
backend
- * @param history_cb_cls closure to pass to history_cb
+ * @param history_cb_cls closure to pass to @a history_cb
  * @return handle for this operation, NULL upon errors
  */
 struct TALER_MERCHANT_HistoryOperation *
@@ -707,9 +704,198 @@ TALER_MERCHANT_history (struct GNUNET_CURL_Context *ctx,
 /**
  * Cancel a pending /history request
  *
- * @param handle from the operation to cancel
+ * @param ho handle from the operation to cancel
  */
 void
 TALER_MERCHANT_history_cancel (struct TALER_MERCHANT_HistoryOperation *ho);
 
+
+/* ********************** /tip-enable ************************* */
+
+
+/**
+ * Handle for a /tip-enable operation.
+ */
+struct TALER_MERCHANT_TipEnableOperation;
+
+
+/**
+ * Callback for a /tip-enable request.  Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend
+ * @param ec taler-specific error code
+ */
+typedef void
+(*TALER_MERCHANT_TipEnableCallback) (void *cls,
+                                     unsigned int http_status,
+                                     enum TALER_ErrorCode ec);
+
+
+/**
+ * Issue a /tip-enable request to the backend.  Informs the backend
+ * that a reserve is now available for tipping.  Note that the
+ * respective @a reserve_priv must also be bound to one or more
+ * instances (together with the URI of the exchange) via the backend's
+ * configuration file before it can be used.  Usually, the process
+ * is that one first configures an exchange and a @a reserve_priv for
+ * an instance, and then enables (or re-enables) the reserve by
+ * performing wire transfers and informs the backend about it using
+ * this API.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param amount amount that was credited to the reserve
+ * @param expiration when will the reserve expire
+ * @param reserve_priv private key of the reserve
+ * @param credit_uuid unique ID of the wire transfer
+ * @param enable_cb callback which will work the response gotten from the 
backend
+ * @param enable_cb_cls closure to pass to @a enable_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipEnableOperation *
+TALER_MERCHANT_tip_enable (struct GNUNET_CURL_Context *ctx,
+                           const char *backend_uri,
+                           const struct TALER_Amount *amount,
+                           struct GNUNET_TIME_Absolute expiration,
+                           const struct TALER_ReservePrivateKeyP *reserve_priv,
+                           const struct GNUNET_HashCode *credit_uuid,
+                           TALER_MERCHANT_TipEnableCallback enable_cb,
+                           void *enable_cb_cls);
+
+
+
+/**
+ * Cancel a pending /tip-enable request
+ *
+ * @param teo handle from the operation to cancel
+ */
+void
+TALER_MERCHANT_tip_enable_cancel (struct TALER_MERCHANT_TipEnableOperation 
*teo);
+
+
+/* ********************** /tip-authorize ********************** */
+
+/**
+ * Handle for a /tip-authorize operation.
+ */
+struct TALER_MERCHANT_TipAuthorizeOperation;
+
+
+/**
+ * Callback for a /tip-authorize request.  Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend
+ * @param ec taler-specific error code
+ * @param tip_id which tip ID should be used to pickup the tip
+ * @param tip_expiration when does the tip expire (needs to be picked up 
before this time)
+ * @param exchange_uri at what exchange can the tip be picked up
+ */
+typedef void
+(*TALER_MERCHANT_TipAuthorizeCallback) (void *cls,
+                                        unsigned int http_status,
+                                        enum TALER_ErrorCode ec,
+                                        const struct GNUNET_HashCode *tip_id,
+                                        struct GNUNET_TIME_Absolute 
tip_expiration,
+                                        const char *exchange_uri);
+
+
+/**
+ * Issue a /tip-authorize request to the backend.  Informs the backend
+ * that a tip should be created.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param amount amount to be handed out as a tip
+ * @param instance which backend instance should create the tip (identifies 
the reserve and exchange)
+ * @param justification which justification should be stored (human-readable 
reason for the tip)
+ * @param authorize_cb callback which will work the response gotten from the 
backend
+ * @param authorize_cb_cls closure to pass to @a authorize_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipAuthorizeOperation *
+TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context *ctx,
+                              const char *backend_uri,
+                              const struct TALER_Amount *amount,
+                              const char *instance,
+                              const char *justification,
+                              TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
+                              void *authorize_cb_cls);
+
+
+
+/**
+ * Cancel a pending /tip-authorize request
+ *
+ * @param ta handle from the operation to cancel
+ */
+void
+TALER_MERCHANT_tip_authorize_cancel (struct 
TALER_MERCHANT_TipAuthorizeOperation *ta);
+
+/* ********************** /tip-pickup ************************* */
+
+
+/**
+ * Handle for a /tip-pickup operation.
+ */
+struct TALER_MERCHANT_TipPickupOperation;
+
+
+/**
+ * Callback for a /tip-pickup request.  Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend, "200 OK" 
on success
+ * @param ec taler-specific error code
+ * @param reserve_pub public key of the reserve that made the @a reserve_sigs, 
NULL on error
+ * @param num_reserve_sigs length of the @a reserve_sigs array, 0 on error
+ * @param reserve_sigs array of signatures authorizing withdrawals, NULL on 
error
+ * @param json original json response
+ */
+typedef void
+(*TALER_MERCHANT_TipPickupCallback) (void *cls,
+                                     unsigned int http_status,
+                                     enum TALER_ErrorCode ec,
+                                     const struct TALER_ReservePublicKeyP 
*reserve_pub,
+                                     unsigned int num_reserve_sigs,
+                                     const struct TALER_ReserveSignatureP 
*reserve_sigs,
+                                     const json_t *json);
+
+
+/**
+ * Issue a /tip-pickup request to the backend.  Informs the backend
+ * that a customer wants to pick up a tip.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param tip_id unique identifier for the tip
+ * @param num_planches number of planchets provided in @a planchets
+ * @param planchets array of planchets to be signed into existence for the tip
+ * @param pickup_cb callback which will work the response gotten from the 
backend
+ * @param pickup_cb_cls closure to pass to @a pickup_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipPickupOperation *
+TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
+                           const char *backend_uri,
+                           const struct GNUNET_HashCode *tip_id,
+                           unsigned int num_planchets,
+                           struct TALER_PlanchetDetail *planchets,
+                           TALER_MERCHANT_TipPickupCallback pickup_cb,
+                           void *pickup_cb_cls);
+
+
+/**
+ * Cancel a pending /tip-pickup request
+ *
+ * @param tp handle from the operation to cancel
+ */
+void
+TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupOperation 
*tp);
+
+
 #endif  /* _TALER_MERCHANT_SERVICE_H */
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index a5e91f5..b36a04e 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -203,11 +203,18 @@ struct TALER_MERCHANTDB_Plugin
 
   /**
    * Mark contract terms as payed.  Needed by /history as only payed
-   * contracts must be shown.  NOTE: we can't get the list of (payed)
-   * contracts from the transactions table because it lacks contract_terms
-   * plain JSON.  In facts, the protocol doesn't allow to store contract_terms
-   * in transactions table, as /pay handler doesn't receive this data
+   * contracts must be shown.
+   *
+   * NOTE: we can't get the list of (payed) contracts from the
+   * transactions table because it lacks contract_terms plain JSON.
+   * In facts, the protocol doesn't allow to store contract_terms in
+   * transactions table, as /pay handler doesn't receive this data
    * (only /proposal does).
+   *
+   * @param cls closure
+   * @param h_contract_terms hash of the contract that is now paid
+   * @param merchant_pub merchant's public key
+   * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
   (*mark_proposal_paid) (void *cls,
@@ -427,7 +434,7 @@ struct TALER_MERCHANTDB_Plugin
                                 struct GNUNET_TIME_Absolute start_date,
                                 struct GNUNET_TIME_Absolute end_date,
                                 const struct TALER_MasterSignatureP 
*exchange_sig);
-                                
+
 
   /**
    * Find information about a transaction.
@@ -443,7 +450,7 @@ struct TALER_MERCHANTDB_Plugin
                                 struct GNUNET_TIME_Absolute date,
                                 TALER_MERCHANTDB_TransactionCallback cb,
                                 void *cb_cls);
-  
+
 
   /**
    * Find information about a transaction.
@@ -480,7 +487,7 @@ struct TALER_MERCHANTDB_Plugin
                     const struct TALER_MerchantPublicKeyP *merchant_pub,
                     TALER_MERCHANTDB_CoinDepositCallback cb,
                     void *cb_cls);
-  
+
 
   /**
    * Lookup information about coin payments by h_contract_terms and coin.
@@ -555,7 +562,7 @@ struct TALER_MERCHANTDB_Plugin
                          const struct TALER_WireTransferIdentifierRawP *wtid,
                          TALER_MERCHANTDB_ProofCallback cb,
                          void *cb_cls);
-  
+
 
   /**
    * Obtain information about wire fees charged by an exchange,
@@ -584,7 +591,7 @@ struct TALER_MERCHANTDB_Plugin
                      struct GNUNET_TIME_Absolute *end_date,
                      struct TALER_MasterSignatureP *exchange_sig);
 
-  
+
   /**
    * Function called when some backoffice staff decides to award or
    * increase the refund on an existing contract.
@@ -624,6 +631,103 @@ struct TALER_MERCHANTDB_Plugin
                                           void *rc_cls);
 
   /**
+   * Add @a credit to a reserve to be used for tipping.  Note that
+   * this function does not actually perform any wire transfers to
+   * credit the reserve, it merely tells the merchant backend that
+   * a reserve was topped up.  This has to happen before tips can be
+   * authorized.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param reserve_priv which reserve is topped up or created
+   * @param credit_uuid unique identifier for the credit operation
+   * @param credit how much money was added to the reserve
+   * @param expiration when does the reserve expire?
+   * @return transaction status, usually
+   *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+   *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
+   */
+  enum GNUNET_DB_QueryStatus
+  (*enable_tip_reserve)(void *cls,
+                        const struct TALER_ReservePrivateKeyP *reserve_priv,
+                        const struct GNUNET_HashCode *credit_uuid,
+                        const struct TALER_Amount *credit,
+                        struct GNUNET_TIME_Absolute expiration);
+
+
+  /**
+   * Authorize a tip over @a amount from reserve @a reserve_priv.  Remember
+   * the authorization under @a tip_id for later, together with the
+   * @a justification.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param justification why was the tip approved
+   * @param amount how high is the tip (with fees)
+   * @param reserve_priv which reserve is debited
+   * @param exchange_uri which exchange manages the tip
+   * @param[out] expiration set to when the tip expires
+   * @param[out] tip_id set to the unique ID for the tip
+   * @return transaction status,
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but 
has expired
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+   *      #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has 
insufficient funds left
+   *      #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client 
should retry)
+   *      #TALER_EC_NONE upon success
+   */
+  enum TALER_ErrorCode
+  (*authorize_tip)(void *cls,
+                   const char *justification,
+                   const struct TALER_Amount *amount,
+                   const struct TALER_ReservePrivateKeyP *reserve_priv,
+                  const char *exchange_uri,
+                   struct GNUNET_TIME_Absolute *expiration,
+                   struct GNUNET_HashCode *tip_id);
+
+
+  /**
+   * Find out tip authorization details associated with @a tip_id
+   *
+   * @param cls closure, typically a connection to the d
+   * @param tip_id the unique ID for the tip
+   * @param[out] exchange_uri set to the URI of the exchange (unless NULL)
+   * @param[out] amount set to the authorized amount (unless NULL)
+   * @param[out] timestamp set to the timestamp of the tip authorization 
(unless NULL)
+   * @return transaction status, usually
+   *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+   *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_tip_by_id)(void *cls,
+                      const struct GNUNET_HashCode *tip_id,
+                      char **exchange_uri,
+                      struct TALER_Amount *amount,
+                      struct GNUNET_TIME_Absolute *timestamp);
+
+
+  /**
+   * Pickup a tip over @a amount using pickup id @a pickup_id.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param amount how high is the amount picked up (with fees)
+   * @param tip_id the unique ID from the tip authorization
+   * @param pickup_id the unique ID identifying the pick up operation
+   *        (to allow replays, hash over the coin envelope and denomination 
key)
+   * @param[out] reserve_priv which reserve key to use to sign
+   * @return taler error code
+   *      #TALER_EC_TIP_PICKUP_ID_UNKNOWN if @a tip_id is unknown
+   *      #TALER_EC_TIP_PICKUP_NO_FUNDS if @a tip_id has insufficient funds 
left
+   *      #TALER_EC_TIP_PICKUP_DB_ERROR on database errors
+   *      #TALER_EC_NONE upon success (@a reserve_priv was set)
+   */
+  enum TALER_ErrorCode
+  (*pickup_tip)(void *cls,
+                const struct TALER_Amount *amount,
+                const struct GNUNET_HashCode *tip_id,
+                const struct GNUNET_HashCode *pickup_id,
+                struct TALER_ReservePrivateKeyP *reserve_priv);
+
+
+  /**
    * Roll back the current transaction of a database connection.
    *
    * @param cls the `struct PostgresClosure` with the plugin-specific state
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index aebf74b..5f46122 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -10,13 +10,16 @@ lib_LTLIBRARIES = \
   libtalermerchant.la
 
 libtalermerchant_la_LDFLAGS = \
-  -version-info 1:0:0 \
+  -version-info 2:0:0 \
   -no-undefined
 
 libtalermerchant_la_SOURCES = \
   merchant_api_common.c merchant_api_common.h \
   merchant_api_proposal.c \
   merchant_api_pay.c \
+  merchant_api_tip_authorize.c \
+  merchant_api_tip_enable.c \
+  merchant_api_tip_pickup.c \
   merchant_api_track_transaction.c \
   merchant_api_track_transfer.c \
   merchant_api_history.c \
@@ -55,6 +58,7 @@ test_merchant_api_LDADD = \
   libtalermerchant.la \
   $(LIBGCRYPT_LIBS) \
   -ltalerfakebank \
+  -ltalerbank \
   -ltalerexchange \
   -ltalerjson \
   -ltalerutil \
diff --git a/src/lib/merchant_api_tip_authorize.c 
b/src/lib/merchant_api_tip_authorize.c
new file mode 100644
index 0000000..3c7899d
--- /dev/null
+++ b/src/lib/merchant_api_tip_authorize.c
@@ -0,0 +1,276 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Lesser General Public License as published by the Free 
Software
+  Foundation; either version 2.1, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+
+  You should have received a copy of the GNU Lesser General Public License 
along with
+  TALER; see the file COPYING.LGPL.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_tip_authorize.c
+ * @brief Implementation of the /tip-authorize request of the merchant's HTTP 
API
+ * @author Marcello Stanisci
+ * @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 <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+#include "merchant_api_common.h"
+
+
+/**
+ * @brief A handle for tracking transactions.
+ */
+struct TALER_MERCHANT_TipAuthorizeOperation
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * JSON encoding of the request to POST.
+   */
+  char *json_enc;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_TipAuthorizeCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * We got a 200 response back from the exchange (or the merchant).
+ * Now we need to parse the response and if it is well-formed,
+ * call the callback (and set it to NULL afterwards).
+ *
+ * @param tao handle of the original authorization operation
+ * @param json cryptographic proof returned by the exchange/merchant
+ * @return #GNUNET_OK if response is valid
+ */
+static int
+check_ok (struct TALER_MERCHANT_TipAuthorizeOperation *tao,
+          const json_t *json)
+{
+  struct GNUNET_HashCode tip_id;
+  struct GNUNET_TIME_Absolute tip_expiration;
+  const char *exchange_uri;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_absolute_time ("expiration", &tip_expiration),
+    GNUNET_JSON_spec_fixed_auto ("tip_id", &tip_id),
+    GNUNET_JSON_spec_string ("exchange_url", &exchange_uri),
+    GNUNET_JSON_spec_end()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  tao->cb (tao->cb_cls,
+           MHD_HTTP_OK,
+           TALER_JSON_get_error_code (json),
+           &tip_id,
+           tip_expiration,
+           exchange_uri);
+  tao->cb = NULL; /* do not call twice */
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TipAuthorizeOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_tip_authorize_finished (void *cls,
+                               long response_code,
+                               const json_t *json)
+{
+  struct TALER_MERCHANT_TipAuthorizeOperation *tao = cls;
+
+  tao->job = NULL;
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    if (GNUNET_OK != check_ok (tao,
+                              json))
+    {
+      GNUNET_break_op (0);
+      response_code = 0;
+    }
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Well-defined status code, pass on to application! */
+    break;
+  case MHD_HTTP_PRECONDITION_FAILED:
+    /* Well-defined status code, pass on to 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",
+                (unsigned int) response_code);
+    GNUNET_break (0);
+    response_code = 0;
+    break;
+  }
+  if (NULL != tao->cb)
+    tao->cb (tao->cb_cls,
+             response_code,
+             TALER_JSON_get_error_code (json),
+             NULL,
+             GNUNET_TIME_UNIT_ZERO_ABS,
+             NULL);
+  TALER_MERCHANT_tip_authorize_cancel (tao);
+}
+
+
+/**
+ * Issue a /tip-authorize request to the backend.  Informs the backend
+ * that a tip should be created.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param amount amount to be handed out as a tip
+ * @param instance which backend instance should create the tip (identifies 
the reserve and exchange)
+ * @param justification which justification should be stored (human-readable 
reason for the tip)
+ * @param authorize_cb callback which will work the response gotten from the 
backend
+ * @param authorize_cb_cls closure to pass to @a authorize_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipAuthorizeOperation *
+TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context *ctx,
+                              const char *backend_uri,
+                              const struct TALER_Amount *amount,
+                              const char *instance,
+                              const char *justification,
+                              TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
+                              void *authorize_cb_cls)
+{
+  struct TALER_MERCHANT_TipAuthorizeOperation *tao;
+  CURL *eh;
+  json_t *te_obj;
+
+  tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeOperation);
+  tao->ctx = ctx;
+  tao->cb = authorize_cb;
+  tao->cb_cls = authorize_cb_cls;
+  tao->url = MAH_path_to_url_ (backend_uri,
+                               "/tip-authorize");
+  te_obj = json_pack ("{"
+                      " s:o," /* amount */
+                      " s:s," /* instance */
+                      " s:s," /* justification */
+                      " s:s," /* pickup_url */
+                      "}",
+                      "amount", TALER_JSON_from_amount (amount),
+                      "instance", instance,
+                      "justification", justification,
+                      "pickup_url", "https://example.com";);
+  if (NULL == te_obj)
+  {
+    GNUNET_break (0);
+    GNUNET_free (tao->url);
+    GNUNET_free (tao);
+    return NULL;
+  }
+  if (NULL == (tao->json_enc =
+               json_dumps (te_obj,
+                           JSON_COMPACT)))
+  {
+    GNUNET_break (0);
+    json_decref (te_obj);
+    GNUNET_free (tao->url);
+    GNUNET_free (tao);
+    return NULL;
+  }
+  json_decref (te_obj);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URI '%s'\n",
+              tao->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   tao->url));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDS,
+                                   tao->json_enc));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDSIZE,
+                                   strlen (tao->json_enc)));
+  tao->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_tip_authorize_finished,
+                                  tao);
+  return tao;
+}
+
+
+/**
+ * Cancel a /track/transaction request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param tao handle to the tracking operation being cancelled
+ */
+void
+TALER_MERCHANT_tip_authorize_cancel (struct 
TALER_MERCHANT_TipAuthorizeOperation *tao)
+{
+  if (NULL != tao->job)
+  {
+    GNUNET_CURL_job_cancel (tao->job);
+    tao->job = NULL;
+  }
+  GNUNET_free_non_null (tao->json_enc);
+  GNUNET_free (tao->url);
+  GNUNET_free (tao);
+}
+
+/* end of merchant_api_tip_authorize.c */
diff --git a/src/lib/merchant_api_tip_enable.c 
b/src/lib/merchant_api_tip_enable.c
new file mode 100644
index 0000000..eb7dadf
--- /dev/null
+++ b/src/lib/merchant_api_tip_enable.c
@@ -0,0 +1,227 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Lesser General Public License as published by the Free 
Software
+  Foundation; either version 2.1, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+
+  You should have received a copy of the GNU Lesser General Public License 
along with
+  TALER; see the file COPYING.LGPL.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_tip_enable.c
+ * @brief Implementation of the /tip-enable request of the merchant's HTTP API
+ * @author Marcello Stanisci
+ * @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 <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+#include "merchant_api_common.h"
+
+
+/**
+ * @brief A handle for tracking transactions.
+ */
+struct TALER_MERCHANT_TipEnableOperation
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * JSON encoding of the request to POST.
+   */
+  char *json_enc;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_TipEnableCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TipEnableOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_tip_enable_finished (void *cls,
+                            long response_code,
+                            const json_t *json)
+{
+  struct TALER_MERCHANT_TipEnableOperation *teo = cls;
+
+  teo->job = NULL;
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    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",
+                (unsigned int) response_code);
+    GNUNET_break (0);
+    response_code = 0;
+    break;
+  }
+  teo->cb (teo->cb_cls,
+           response_code,
+          TALER_JSON_get_error_code (json));
+  TALER_MERCHANT_tip_enable_cancel (teo);
+}
+
+
+/**
+ * Issue a /tip-enable request to the backend.  Informs the backend
+ * that a reserve is now available for tipping.  Note that the
+ * respective @a reserve_priv must also be bound to one or more
+ * instances (together with the URI of the exchange) via the backend's
+ * configuration file before it can be used.  Usually, the process
+ * is that one first configures an exchange and a @a reserve_priv for
+ * an instance, and then enables (or re-enables) the reserve by
+ * performing wire transfers and informs the backend about it using
+ * this API.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param amount amount that was credited to the reserve
+ * @param expiration when will the reserve expire
+ * @param reserve_priv private key of the reserve
+ * @param credit_uuid unique ID of the wire transfer
+ * @param enable_cb callback which will work the response gotten from the 
backend
+ * @param enable_cb_cls closure to pass to @a enable_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipEnableOperation *
+TALER_MERCHANT_tip_enable (struct GNUNET_CURL_Context *ctx,
+                           const char *backend_uri,
+                           const struct TALER_Amount *amount,
+                           struct GNUNET_TIME_Absolute expiration,
+                           const struct TALER_ReservePrivateKeyP *reserve_priv,
+                           const struct GNUNET_HashCode *credit_uuid,
+                           TALER_MERCHANT_TipEnableCallback enable_cb,
+                           void *enable_cb_cls)
+{
+  struct TALER_MERCHANT_TipEnableOperation *teo;
+  CURL *eh;
+  json_t *te_obj;
+
+  (void) GNUNET_TIME_round_abs (&expiration);
+  teo = GNUNET_new (struct TALER_MERCHANT_TipEnableOperation);
+  teo->ctx = ctx;
+  teo->cb = enable_cb;
+  teo->cb_cls = enable_cb_cls;
+  teo->url = MAH_path_to_url_ (backend_uri,
+                               "/tip-enable");
+  te_obj = json_pack ("{"
+                      " s:o," /* amount */
+                      " s:o," /* expiration */
+                      " s:o," /* credit_uuid */
+                      " s:o," /* reserve_priv */
+                      "}",
+                      "credit", TALER_JSON_from_amount (amount),
+                      "expiration", GNUNET_JSON_from_time_abs (expiration),
+                      "credit_uuid", GNUNET_JSON_from_data_auto (credit_uuid),
+                      "reserve_priv", GNUNET_JSON_from_data_auto 
(reserve_priv));
+  if (NULL == te_obj)
+  {
+    GNUNET_break (0);
+    GNUNET_free (teo->url);
+    GNUNET_free (teo);
+    return NULL;
+  }
+  if (NULL == (teo->json_enc =
+               json_dumps (te_obj,
+                           JSON_COMPACT)))
+  {
+    GNUNET_break (0);
+    json_decref (te_obj);
+    GNUNET_free (teo->url);
+    GNUNET_free (teo);
+    return NULL;
+  }
+  json_decref (te_obj);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URI '%s'\n",
+              teo->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   teo->url));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDS,
+                                   teo->json_enc));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDSIZE,
+                                   strlen (teo->json_enc)));
+  teo->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_tip_enable_finished,
+                                  teo);
+  return teo;
+}
+
+
+/**
+ * Cancel a /track/transaction request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param teo handle to the tracking operation being cancelled
+ */
+void
+TALER_MERCHANT_tip_enable_cancel (struct TALER_MERCHANT_TipEnableOperation 
*teo)
+{
+  if (NULL != teo->job)
+  {
+    GNUNET_CURL_job_cancel (teo->job);
+    teo->job = NULL;
+  }
+  GNUNET_free_non_null (teo->json_enc);
+  GNUNET_free (teo->url);
+  GNUNET_free (teo);
+}
+
+/* end of merchant_api_tip_enable.c */
diff --git a/src/lib/merchant_api_tip_pickup.c 
b/src/lib/merchant_api_tip_pickup.c
new file mode 100644
index 0000000..e967588
--- /dev/null
+++ b/src/lib/merchant_api_tip_pickup.c
@@ -0,0 +1,336 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Lesser General Public License as published by the Free 
Software
+  Foundation; either version 2.1, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+
+  You should have received a copy of the GNU Lesser General Public License 
along with
+  TALER; see the file COPYING.LGPL.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_tip_pickup.c
+ * @brief Implementation of the /tip-pickup request of the merchant's HTTP API
+ * @author Marcello Stanisci
+ * @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 <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+#include "merchant_api_common.h"
+
+
+/**
+ * @brief A handle for tracking transactions.
+ */
+struct TALER_MERCHANT_TipPickupOperation
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * JSON encoding of the request to POST.
+   */
+  char *json_enc;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_TipPickupCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Expected number of planchets.
+   */
+  unsigned int num_planchets;
+};
+
+
+/**
+ * We got a 200 response back from the exchange (or the merchant).
+ * Now we need to parse the response and if it is well-formed,
+ * call the callback (and set it to NULL afterwards).
+ *
+ * @param tpo handle of the original authorization operation
+ * @param json cryptographic proof returned by the exchange/merchant
+ * @return #GNUNET_OK if response is valid
+ */
+static int
+check_ok (struct TALER_MERCHANT_TipPickupOperation *tpo,
+          const json_t *json)
+{
+  struct TALER_ReservePublicKeyP reserve_pub;
+  json_t *ja;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub),
+    GNUNET_JSON_spec_json ("reserve_sigs", &ja),
+    GNUNET_JSON_spec_end()
+  };
+  unsigned int ja_len;
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  ja_len = json_array_size (ja);
+  if (ja_len != tpo->num_planchets)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  {
+    struct TALER_ReserveSignatureP reserve_sigs[ja_len];
+
+    for (unsigned int i=0;i<ja_len;i++)
+    {
+      json_t *pj = json_array_get (ja, i);
+
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("reserve_sig", &reserve_sigs[i]),
+        GNUNET_JSON_spec_end()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (pj,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+    }
+    tpo->cb (tpo->cb_cls,
+             MHD_HTTP_OK,
+             TALER_JSON_get_error_code (json),
+             &reserve_pub,
+             ja_len,
+             reserve_sigs,
+             json);
+    tpo->cb = NULL; /* do not call twice */
+  }
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TipPickupOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_tip_pickup_finished (void *cls,
+                            long response_code,
+                            const json_t *json)
+{
+  struct TALER_MERCHANT_TipPickupOperation *tpo = cls;
+
+  tpo->job = NULL;
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    if (GNUNET_OK != check_ok (tpo,
+                               json))
+    {
+      GNUNET_break_op (0);
+      response_code = 0;
+    }
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    break;
+  case MHD_HTTP_SERVICE_UNAVAILABLE:
+    /* legal, can happen if we pickup a tip twice... */
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u\n",
+                (unsigned int) response_code);
+    GNUNET_break (0);
+    response_code = 0;
+    break;
+  }
+  if (NULL != tpo->cb)
+    tpo->cb (tpo->cb_cls,
+             response_code,
+             TALER_JSON_get_error_code (json),
+             NULL,
+             0,
+             NULL,
+             json);
+  TALER_MERCHANT_tip_pickup_cancel (tpo);
+}
+
+
+/**
+ * Issue a /tip-pickup request to the backend.  Informs the backend
+ * that a customer wants to pick up a tip.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param tip_id unique identifier for the tip
+ * @param num_planches number of planchets provided in @a planchets
+ * @param planchets array of planchets to be signed into existence for the tip
+ * @param pickup_cb callback which will work the response gotten from the 
backend
+ * @param pickup_cb_cls closure to pass to @a pickup_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipPickupOperation *
+TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
+                           const char *backend_uri,
+                           const struct GNUNET_HashCode *tip_id,
+                           unsigned int num_planchets,
+                           struct TALER_PlanchetDetail *planchets,
+                           TALER_MERCHANT_TipPickupCallback pickup_cb,
+                           void *pickup_cb_cls)
+{
+  struct TALER_MERCHANT_TipPickupOperation *tpo;
+  CURL *eh;
+  json_t *pa;
+  json_t *tp_obj;
+
+  if (0 == num_planchets)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  pa = json_array ();
+  for (unsigned int i=0;i<num_planchets;i++)
+  {
+    const struct TALER_PlanchetDetail *planchet = &planchets[i];
+    json_t *p;
+
+    p = json_pack ("{"
+                   " s:o," /* denom_pub_hash */
+                   " s:o," /* coin_ev */
+                   "}",
+                   "denom_pub_hash", GNUNET_JSON_from_data_auto 
(&planchet->denom_pub_hash),
+                   "coin_ev", GNUNET_JSON_from_data (planchet->coin_ev,
+                                                     planchet->coin_ev_size));
+    if (NULL == p)
+    {
+      GNUNET_break (0);
+      json_decref (pa);
+      return NULL;
+    }
+    if (0 !=
+        json_array_append_new (pa,
+                               p))
+    {
+      GNUNET_break (0);
+      json_decref (pa);
+      return NULL;
+    }
+  }
+  tp_obj = json_pack ("{"
+                      " s:o," /* tip_id */
+                      " s:o," /* planchets */
+                      "}",
+                      "tip_id", GNUNET_JSON_from_data_auto (tip_id),
+                      "planchets", pa);
+  if (NULL == tp_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  tpo = GNUNET_new (struct TALER_MERCHANT_TipPickupOperation);
+  tpo->num_planchets = num_planchets;
+  tpo->ctx = ctx;
+  tpo->cb = pickup_cb;
+  tpo->cb_cls = pickup_cb_cls;
+  tpo->url = MAH_path_to_url_ (backend_uri,
+                               "/tip-pickup");
+  if (NULL == (tpo->json_enc =
+               json_dumps (tp_obj,
+                           JSON_COMPACT)))
+  {
+    GNUNET_break (0);
+    json_decref (tp_obj);
+    GNUNET_free (tpo->url);
+    GNUNET_free (tpo);
+    return NULL;
+  }
+  json_decref (tp_obj);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URI '%s'\n",
+              tpo->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   tpo->url));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDS,
+                                   tpo->json_enc));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDSIZE,
+                                   strlen (tpo->json_enc)));
+  tpo->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_tip_pickup_finished,
+                                  tpo);
+  return tpo;
+}
+
+
+/**
+ * Cancel a /track/transaction request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param tpo handle to the tracking operation being cancelled
+ */
+void
+TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupOperation 
*tpo)
+{
+  if (NULL != tpo->job)
+  {
+    GNUNET_CURL_job_cancel (tpo->job);
+    tpo->job = NULL;
+  }
+  GNUNET_free_non_null (tpo->json_enc);
+  GNUNET_free (tpo->url);
+  GNUNET_free (tpo);
+}
+
+/* end of merchant_api_tip_pickup.c */
diff --git a/src/lib/merchant_api_track_transaction.c 
b/src/lib/merchant_api_track_transaction.c
index 25a88b7..5357d37 100644
--- a/src/lib/merchant_api_track_transaction.c
+++ b/src/lib/merchant_api_track_transaction.c
@@ -65,26 +65,6 @@ struct TALER_MERCHANT_TrackTransactionHandle
   struct GNUNET_CURL_Context *ctx;
 };
 
-/**
- * Handle #MHD_HTTP_OK response to /track/transaction.
- * Parse @a json and if successful call the callback in @a tdo.
- *
- * @param tdo handle of the operation
- * @param json json to parse
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-static int
-parse_track_transaction_ok (struct TALER_MERCHANT_TrackTransactionHandle *tdo,
-                            const json_t *json)
-{
-  tdo->cb (tdo->cb_cls,
-           MHD_HTTP_OK,
-          TALER_EC_NONE,
-           json);
-
-  return GNUNET_OK;
-}
-
 
 /**
  * Function called when we're done processing the
@@ -107,15 +87,12 @@ handle_track_transaction_finished (void *cls,
   case 0:
     break;
   case MHD_HTTP_OK:
-    if (GNUNET_OK ==
-        parse_track_transaction_ok (tdo,
-                                    json))
-    {
-      TALER_MERCHANT_track_transaction_cancel (tdo);
-      return;
-    }
-    response_code = 0;
-    break;
+    tdo->cb (tdo->cb_cls,
+             MHD_HTTP_OK,
+             TALER_EC_NONE,
+             json);
+    TALER_MERCHANT_track_transaction_cancel (tdo);
+    return;
   case MHD_HTTP_ACCEPTED:
     {
       /* Expect time stamp of when the transfer is supposed to happen */
diff --git a/src/lib/reserve_dtip.priv b/src/lib/reserve_dtip.priv
new file mode 100644
index 0000000..d7fae39
--- /dev/null
+++ b/src/lib/reserve_dtip.priv
@@ -0,0 +1 @@
+�A?���*K4K3�1�b�'u�y�D;��ȃ��C
\ No newline at end of file
diff --git a/src/lib/reserve_tip.priv b/src/lib/reserve_tip.priv
new file mode 100644
index 0000000..3cd7505
Binary files /dev/null and b/src/lib/reserve_tip.priv differ
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index dbc2d5c..9bbcb00 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2017 GNUnet e.V. and INRIA
+  Copyright (C) 2014-2017 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License as published by the Free 
Software
@@ -18,9 +18,13 @@
  * @brief testcase to test merchant's HTTP API interface
  * @author Christian Grothoff
  * @author Marcello Stanisci
+ *
+ * TODO:
+ * - add test logic for tips to main test interpreter
  */
 #include "platform.h"
 #include <taler/taler_exchange_service.h>
+#include <taler/taler_bank_service.h>
 #include <taler/taler_fakebank_lib.h>
 #include <taler/taler_json_lib.h>
 #include <taler/taler_util.h>
@@ -32,19 +36,24 @@
 #include <microhttpd.h>
 
 /**
- * URI under which the merchant is reachable during the testcase.
+ * URL under which the merchant is reachable during the testcase.
+ */
+#define MERCHANT_URL "http://localhost:8082";
+
+/**
+ * URL under which the exchange is reachable during the testcase.
  */
-#define MERCHANT_URI "http://localhost:8082";
+#define EXCHANGE_URL "http://localhost:8084/";
 
 /**
- * URI under which the exchange is reachable during the testcase.
+ * Account number of the exchange at the bank.
  */
-#define EXCHANGE_URI "http://localhost:8081/";
+#define EXCHANGE_ACCOUNT_NO 2
 
 /**
- * URI of the bank.
+ * URL of the bank.
  */
-#define BANK_URI "http://localhost:8083/";
+#define BANK_URL "http://localhost:8083/";
 
 /**
  * On which port do we run the (fake) bank?
@@ -173,6 +182,11 @@ enum OpCode
   OC_RUN_AGGREGATOR,
 
   /**
+   * Run the wirewatcher to check for incoming transactions.
+   */
+  OC_RUN_WIREWATCH,
+
+  /**
    * Check that the fakebank has received a certain transaction.
    */
   OC_CHECK_BANK_TRANSFER,
@@ -205,7 +219,22 @@ enum OpCode
   /**
    * Test refund lookup
    */
-  OC_REFUND_LOOKUP
+  OC_REFUND_LOOKUP,
+
+  /**
+   * Start a reserve for tipping.
+   */
+  OC_TIP_ENABLE,
+
+  /**
+   * Authorize a tip.
+   */
+  OC_TIP_AUTHORIZE,
+
+  /**
+   * Pickup a tip.
+   */
+  OC_TIP_PICKUP
 
 };
 
@@ -233,28 +262,31 @@ struct MeltDetails
 
 
 /**
- * Information about a fresh coin generated by the refresh operation.
+ * State of the interpreter loop.
+ */
+struct InterpreterState;
+
+
+/**
+ * Internal withdraw handle used when withdrawing tips.
  */
-struct FreshCoin
+struct WithdrawHandle
 {
 
   /**
-   * 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.
+   * Withdraw operation this handle represents.
    */
-  const struct TALER_EXCHANGE_DenomPublicKey *pk;
+  struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
 
   /**
-   * Set (by the interpreter) to the exchange's signature over the
-   * coin's public key.
+   * Interpreter state we are part of.
    */
-  struct TALER_DenominationSignature sig;
+  struct InterpreterState *is;
 
   /**
-   * Set (by the interpreter) to the coin's private key.
+   * Offset of this withdraw operation in the current @e is command.
    */
-  struct TALER_CoinSpendPrivateKeyP coin_priv;
+  unsigned int off;
 
 };
 
@@ -299,19 +331,43 @@ struct Command
       const char *reserve_reference;
 
       /**
+       * Instance to use if we are filling a tipping-reserve. In this
+       * case, @e reserve_priv is filled from the configuration instead
+       * of at random.  Usually NULL (for random @e reserve_priv).
+       */
+      const char *instance;
+
+      /**
        * String describing the amount to add to the reserve.
        */
       const char *amount;
 
       /**
-       * Sender's bank account details (JSON).
+       * Wire transfer subject. NULL to use public key corresponding
+       * to @e reserve_priv or @e reserve_reference.  Should only be
+       * set manually to test invalid wire transfer subjects.
+       */
+      const char *subject;
+
+      /**
+       * Sender (debit) account number.
+       */
+      uint64_t debit_account_no;
+
+      /**
+       * Receiver (credit) account number.
        */
-      const char *sender_details;
+      uint64_t credit_account_no;
 
       /**
-       * Transfer details (JSON)
+       * Username to use for authentication.
        */
-      const char *transfer_details;
+      const char *auth_username;
+
+      /**
+       * Password to use for authentication.
+       */
+      const char *auth_password;
 
       /**
        * Set (by the interpreter) to the reserve's private key
@@ -322,7 +378,12 @@ struct Command
       /**
        * Set to the API's handle during the operation.
        */
-      struct TALER_EXCHANGE_AdminAddIncomingHandle *aih;
+      struct TALER_BANK_AdminAddIncomingHandle *aih;
+
+      /**
+       * Set to the wire transfer's unique ID.
+       */
+      uint64_t serial_id;
 
     } admin_add_incoming;
 
@@ -397,14 +458,9 @@ struct Command
       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.
+       * Set (by the interpreter) to the planchet's secrets.
        */
-      struct TALER_DenominationBlindingKeyP blinding_key;
+      struct TALER_PlanchetSecretsP ps;
 
       /**
        * Withdraw handle (while operation is running).
@@ -464,7 +520,9 @@ struct Command
 
       /**
        * ";"-separated list of references to withdrawn coins to be used
-       * in the payment.
+       * in the payment.  Each reference has the syntax "LABEL[/NUMBER]"
+       * where NUMBER refers to a particular coin (in case multiple coins
+       * were created in a step).
        */
       char *coin_ref;
 
@@ -516,6 +574,20 @@ struct Command
     struct {
 
       /**
+       * Process for the wirewatcher.
+       */
+      struct GNUNET_OS_Process *wirewatch_proc;
+
+      /**
+       * ID of task called whenever we get a SIGCHILD.
+       */
+      struct GNUNET_SCHEDULER_Task *child_death_task;
+
+    } run_wirewatch;
+
+    struct {
+
+      /**
        * Which amount do we expect to see transferred?
        */
       const char *amount;
@@ -674,6 +746,140 @@ struct Command
 
     } refund_lookup;
 
+    struct {
+
+      /**
+       * Reference to the operation that provisioned the reserve.
+       * Used to determine the reserve private key and the instance.
+       */
+      const char *admin_add_incoming_ref;
+
+      /**
+       * Reference to another enable operation, usually NULL. Can
+       * be set to a non-NULL value to call enable again with the
+       * same @e credit_uuid that was previously used.
+       */
+      const char *uuid_ref;
+
+      /**
+       * How much should be put into the tipping reserve? If
+       * NULL, the amount is taken from the @e admin_add_incoming_ref.
+       */
+      const char *amount;
+
+      /**
+       * Handle to the ongoing operation.
+       */
+      struct TALER_MERCHANT_TipEnableOperation *teo;
+
+      /**
+       * UUID used for the enable operation, set by the interpreter to
+       * a random value UNLESS @e uuid_ref is non-NULL.
+       */
+      struct GNUNET_HashCode credit_uuid;
+
+      /**
+       * EC expected for the operation.
+       */
+      enum TALER_ErrorCode expected_ec;
+
+    } tip_enable;
+
+    struct {
+
+      /**
+       * Specify the instance (to succeed, this must match a prior
+       * enable action and the respective wire transfer's instance).
+       */
+      const char *instance;
+
+      /**
+       * Reason to use for enabling the tip (required by the API, but not
+       * yet really useful as we do not have a way to read back the
+       * justifications stored in the merchant's DB).
+       */
+      const char *justification;
+
+      /**
+       * How much should the tip be?
+       */
+      const char *amount;
+
+      /**
+       * Handle for the ongoing operation.
+       */
+      struct TALER_MERCHANT_TipAuthorizeOperation *tao;
+
+      /**
+       * Unique ID for the authorized tip, set by the interpreter.
+       */
+      struct GNUNET_HashCode tip_id;
+
+      /**
+       * When does the authorization expire?
+       */
+      struct GNUNET_TIME_Absolute tip_expiration;
+
+      /**
+       * EC expected for the operation.
+       */
+      enum TALER_ErrorCode expected_ec;
+
+    } tip_authorize;
+
+    struct {
+
+      /**
+       * Reference to operation that authorized the tip. Used
+       * to obtain the `tip_id`.
+       */
+      const char *authorize_ref;
+
+      /**
+       * Number of coins we pick up.
+       */
+      unsigned int num_coins;
+
+      /**
+       * Array of @e num_coins denominations of the coins we pick up.
+       */
+      const char **amounts;
+
+      /**
+       * Handle for the ongoing operation.
+       */
+      struct TALER_MERCHANT_TipPickupOperation *tpo;
+
+      /**
+       * Temporary data structure to store the blinding keys while the
+       * pickup operation runs.
+       */
+      struct TALER_PlanchetSecretsP *psa;
+
+      /**
+       * Array of denomination keys matching the @e amounts.
+       */
+      const struct TALER_EXCHANGE_DenomPublicKey **dks;
+
+      /**
+       * Temporary data structure of @e num_coins entries for the
+       * withdraw operations.
+       */
+      struct WithdrawHandle *withdraws;
+
+      /**
+       * Set (by the interpreter) to an array of @a num_coins signatures
+       * created from the (successful) tip operation.
+       */
+      struct TALER_DenominationSignature *sigs;
+
+      /**
+       * EC expected for the operation.
+       */
+      enum TALER_ErrorCode expected_ec;
+
+    } tip_pickup;
+
   } details;
 
 };
@@ -729,9 +935,8 @@ get_instance_priv (struct GNUNET_CONFIGURATION_Handle 
*config,
   struct GNUNET_CRYPTO_EddsaPrivateKey *ret;
 
   (void) GNUNET_asprintf (&config_section,
-                      "merchant-instance-%s",
-                      instance);
-
+                          "merchant-instance-%s",
+                          instance);
   if (GNUNET_OK !=
     GNUNET_CONFIGURATION_get_value_filename (config,
                                              config_section,
@@ -739,12 +944,14 @@ get_instance_priv (struct GNUNET_CONFIGURATION_Handle 
*config,
                                              &filename))
   {
     GNUNET_break (0);
+    GNUNET_free (config_section);
     return NULL;
   }
+  GNUNET_free (config_section);
   if (NULL ==
-   (ret = GNUNET_CRYPTO_eddsa_key_create_from_file (filename)))
+      (ret = GNUNET_CRYPTO_eddsa_key_create_from_file (filename)))
     GNUNET_break (0);
-
+  GNUNET_free (filename);
   return ret;
 }
 
@@ -828,18 +1035,21 @@ next_command (struct InterpreterState *is)
  * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful 
status request
  *                    0 if the exchange's reply is bogus (fails to follow the 
protocol)
  * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param serial_id unique ID of the wire transfer
  * @param full_response full response from the exchange (for logging, in case 
of errors)
  */
 static void
 add_incoming_cb (void *cls,
                  unsigned int http_status,
                 enum TALER_ErrorCode ec,
+                 uint64_t serial_id,
                  const json_t *full_response)
 {
   struct InterpreterState *is = cls;
   struct Command *cmd = &is->commands[is->ip];
 
   cmd->details.admin_add_incoming.aih = NULL;
+  cmd->details.admin_add_incoming.serial_id = serial_id;
   if (MHD_HTTP_OK != http_status)
   {
     GNUNET_break (0);
@@ -1232,6 +1442,7 @@ refund_increase_cb (void *cls,
  * @param cls closure, NULL
  * @param key current key
  * @param value a `struct TALER_Amount`
+ * @return always #GNUNET_YES (continue to iterate)
  */
 static int
 hashmap_free (void *cls,
@@ -1338,7 +1549,7 @@ refund_lookup_cb (void *cls,
     GNUNET_assert (NULL != (icoin =
                            find_command (is,
                                          icoin_ref)));
-    GNUNET_CRYPTO_eddsa_key_get_public 
(&icoin->details.reserve_withdraw.coin_priv.eddsa_priv,
+    GNUNET_CRYPTO_eddsa_key_get_public 
(&icoin->details.reserve_withdraw.ps.coin_priv.eddsa_priv,
                                         &icoin_pub.eddsa_pub);
     GNUNET_CRYPTO_hash (&icoin_pub,
                         sizeof (struct TALER_CoinSpendPublicKeyP),
@@ -1353,7 +1564,7 @@ refund_lookup_cb (void *cls,
                                      &acc,
                                      iamount));
   }
-
+  GNUNET_free (icoin_refs);
   /* Check if refund has been 100% covered */
   GNUNET_assert (increase =
                  find_command (is,
@@ -1458,15 +1669,31 @@ maint_child_death (void *cls)
   const struct GNUNET_DISK_FileHandle *pr;
   char c[16];
 
-  cmd->details.run_aggregator.child_death_task = NULL;
-  pr = GNUNET_DISK_pipe_handle (sigpipe,
-                               GNUNET_DISK_PIPE_END_READ);
-  GNUNET_break (0 < GNUNET_DISK_file_read (pr,
-                                          &c,
-                                          sizeof (c)));
-  GNUNET_OS_process_wait (cmd->details.run_aggregator.aggregator_proc);
-  GNUNET_OS_process_destroy (cmd->details.run_aggregator.aggregator_proc);
-  cmd->details.run_aggregator.aggregator_proc = NULL;
+  switch (cmd->oc) {
+  case OC_RUN_AGGREGATOR:
+    cmd->details.run_aggregator.child_death_task = NULL;
+    pr = GNUNET_DISK_pipe_handle (sigpipe,
+                                  GNUNET_DISK_PIPE_END_READ);
+    GNUNET_break (0 < GNUNET_DISK_file_read (pr,
+                                             &c,
+                                             sizeof (c)));
+    GNUNET_OS_process_wait (cmd->details.run_aggregator.aggregator_proc);
+    GNUNET_OS_process_destroy (cmd->details.run_aggregator.aggregator_proc);
+    cmd->details.run_aggregator.aggregator_proc = NULL;
+    break;
+  case OC_RUN_WIREWATCH:
+    cmd->details.run_wirewatch.child_death_task = NULL;
+    pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
+    GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c)));
+    GNUNET_OS_process_wait (cmd->details.run_wirewatch.wirewatch_proc);
+    GNUNET_OS_process_destroy (cmd->details.run_wirewatch.wirewatch_proc);
+    cmd->details.run_wirewatch.wirewatch_proc = NULL;
+    break;
+  default:
+    GNUNET_break (0);
+    fail (is);
+    return;
+  }
   next_command (is);
 }
 
@@ -1528,6 +1755,7 @@ track_transfer_cb (void *cls,
  *
  * @param cls closure
  * @param http_status HTTP status code we got
+ * @param json full response we got
  */
 static void
 proposal_lookup_cb (void *cls,
@@ -1551,8 +1779,6 @@ proposal_lookup_cb (void *cls,
  * @param http_status HTTP status code we got, 0 on exchange protocol violation
  * @param ec taler-specific error code
  * @param json original json reply
- * @param num_transfers number of wire transfers involved in setting the 
transaction
- * @param transfers detailed list of transfers involved and their coins
  */
 static void
 track_transaction_cb (void *cls,
@@ -1580,79 +1806,319 @@ track_transaction_cb (void *cls,
 
 
 /**
- * Find denomination key matching the given amount.
+ * Callback for a /tip-enable request.  Returns the result of
+ * the operation.
  *
- * @param keys array of keys to search
- * @param amount coin value to look for
- * @return NULL if no matching key was found
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend
+ * @param ec taler-specific error code
  */
-static const struct TALER_EXCHANGE_DenomPublicKey *
-find_pk (const struct TALER_EXCHANGE_Keys *keys,
-         const struct TALER_Amount *amount)
+static void
+tip_enable_cb (void *cls,
+               unsigned int http_status,
+               enum TALER_ErrorCode ec)
 {
-  struct GNUNET_TIME_Absolute now;
-  char *str;
+  struct InterpreterState *is = cls;
+  struct Command *cmd = &is->commands[is->ip];
 
-  now = GNUNET_TIME_absolute_get ();
-  for (unsigned int i=0;i<keys->num_denom_keys;i++)
+  cmd->details.tip_enable.teo = NULL;
+  if (cmd->expected_response_code != http_status)
   {
-    const struct TALER_EXCHANGE_DenomPublicKey *pk;
-
-    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;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u to command %s\n",
+                http_status,
+                cmd->label);
+    fail (is);
+    return;
   }
-  /* do 2nd pass to check if expiration times are to blame for failure */
-  str = TALER_amount_to_string (amount);
-  for (unsigned int i=0;i<keys->num_denom_keys;i++)
+  if (cmd->details.tip_enable.expected_ec != ec)
   {
-    const struct TALER_EXCHANGE_DenomPublicKey *pk;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected error code %u to command %s\n",
+                ec,
+                cmd->label);
+    fail (is);
+    return;
+  }
+  next_command (is);
+}
 
-    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) ) )
+
+/**
+ * Callback for a /tip-authorize request.  Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend
+ * @param ec taler-specific error code
+ * @param tip_id which tip ID should be used to pickup the tip
+ * @param tip_expiration when does the tip expire (needs to be picked up 
before this time)
+ * @param exchange_uri at what exchange can the tip be picked up
+ */
+static void
+tip_authorize_cb (void *cls,
+                  unsigned int http_status,
+                  enum TALER_ErrorCode ec,
+                  const struct GNUNET_HashCode *tip_id,
+                  struct GNUNET_TIME_Absolute tip_expiration,
+                  const char *exchange_uri)
+{
+  struct InterpreterState *is = cls;
+  struct Command *cmd = &is->commands[is->ip];
+
+  cmd->details.tip_authorize.tao = 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);
+    fail (is);
+    return;
+  }
+  if (cmd->details.tip_authorize.expected_ec != ec)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected error code %u to command %s\n",
+                ec,
+                cmd->label);
+    fail (is);
+    return;
+  }
+  if ( (MHD_HTTP_OK == http_status) &&
+       (TALER_EC_NONE == ec) )
+  {
+    if (0 != strcmp (exchange_uri,
+                     EXCHANGE_URL))
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Have denomination key for `%s', but with wrong expiration 
range %llu vs [%llu,%llu)\n",
-                  str,
-                  (unsigned long long) now.abs_value_us,
-                  (unsigned long long) pk->valid_from.abs_value_us,
-                  (unsigned long long) pk->withdraw_valid_until.abs_value_us);
-      GNUNET_free (str);
-      return NULL;
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected exchange URL %s to command %s\n",
+                  exchange_uri,
+                  cmd->label);
+      fail (is);
+      return;
     }
+    cmd->details.tip_authorize.tip_id = *tip_id;
+    cmd->details.tip_authorize.tip_expiration = tip_expiration;
   }
-  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-              "No denomination key for amount %s found\n",
-              str);
-  GNUNET_free (str);
-  return NULL;
+  next_command (is);
 }
 
 
 /**
- * Reset the interpreter's state.
+ * Callbacks of this type are used to serve the result of submitting a
+ * withdraw request to a exchange.
  *
- * @param is interpreter to reset
+ * @param cls closure, a `struct WithdrawHandle *`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful 
status request
+ *                    0 if the exchange's reply is bogus (fails to follow the 
protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param sig signature over the coin, NULL on error
+ * @param full_response full response from the exchange (for logging, in case 
of errors)
  */
 static void
-cleanup_state (struct InterpreterState *is)
+pickup_withdraw_cb (void *cls,
+                    unsigned int http_status,
+                    enum TALER_ErrorCode ec,
+                    const struct TALER_DenominationSignature *sig,
+                    const json_t *full_response)
 {
-  struct Command *cmd;
+  struct WithdrawHandle *wh = cls;
+  struct InterpreterState *is = wh->is;
+  struct Command *cmd = &is->commands[is->ip];
 
-  for (unsigned int i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
+  wh->wsh = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Withdraw operation completed with %u/%u\n",
+              http_status,
+              ec);
+  GNUNET_assert (wh->off < cmd->details.tip_pickup.num_coins);
+  if ( (MHD_HTTP_OK != http_status) ||
+       (TALER_EC_NONE != ec) )
   {
-    switch (cmd->oc)
-    {
-    case OC_END:
-      GNUNET_assert (0);
-      break;
-    case OC_PROPOSAL_LOOKUP:
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%u to command %s when 
withdrawing\n",
+                http_status,
+                ec,
+                cmd->label);
+    fail (is);
+    return;
+  }
+  if (NULL == cmd->details.tip_pickup.sigs)
+    cmd->details.tip_pickup.sigs = GNUNET_new_array 
(cmd->details.tip_pickup.num_coins,
+                                                     struct 
TALER_DenominationSignature);
+  cmd->details.tip_pickup.sigs[wh->off].rsa_signature
+    = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature);
+  for (unsigned int i=0;i<cmd->details.tip_pickup.num_coins;i++)
+    if (NULL != cmd->details.tip_pickup.withdraws[wh->off].wsh)
+      return; /* still some ops ongoing */
+  GNUNET_free (cmd->details.tip_pickup.withdraws);
+  cmd->details.tip_pickup.withdraws = NULL;
+  next_command (is);
+}
+
+
+/**
+ * Callback for a /tip-pickup request.  Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend, "200 OK" 
on success
+ * @param ec taler-specific error code
+ * @param reserve_pub public key of the reserve that made the @a reserve_sigs, 
NULL on error
+ * @param num_reserve_sigs length of the @a reserve_sigs array, 0 on error
+ * @param reserve_sigs array of signatures authorizing withdrawals, NULL on 
error
+ * @param json original json response
+ */
+static void
+pickup_cb (void *cls,
+           unsigned int http_status,
+           enum TALER_ErrorCode ec,
+           const struct TALER_ReservePublicKeyP *reserve_pub,
+           unsigned int num_reserve_sigs,
+           const struct TALER_ReserveSignatureP *reserve_sigs,
+           const json_t *json)
+{
+  struct InterpreterState *is = cls;
+  struct Command *cmd = &is->commands[is->ip];
+
+  cmd->details.tip_pickup.tpo = NULL;
+  if (http_status != cmd->expected_response_code)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%u to command %s\n",
+                http_status,
+                ec,
+                cmd->label);
+    fail (is);
+    return;
+  }
+  if (ec != cmd->details.tip_pickup.expected_ec)
+  {
+    GNUNET_break (0);
+    fail (is);
+    return;
+  }
+
+  if ( (MHD_HTTP_OK != http_status) ||
+       (TALER_EC_NONE != ec) )
+  {
+    next_command (is);
+    return;
+  }
+  if (num_reserve_sigs != cmd->details.tip_pickup.num_coins)
+  {
+    GNUNET_break (0);
+    fail (is);
+    return;
+  }
+
+  /* pickup successful, now withdraw! */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Obtained %u signatures for withdrawal from picking up a tip\n",
+              num_reserve_sigs);
+  cmd->details.tip_pickup.withdraws
+    = GNUNET_new_array (num_reserve_sigs,
+                        struct WithdrawHandle);
+  for (unsigned int i=0;i<num_reserve_sigs;i++)
+  {
+    struct WithdrawHandle *wh = &cmd->details.tip_pickup.withdraws[i];
+
+    wh->off = i;
+    wh->is = is;
+    wh->wsh = TALER_EXCHANGE_reserve_withdraw2 (exchange,
+                                                cmd->details.tip_pickup.dks[i],
+                                                &reserve_sigs[i],
+                                                reserve_pub,
+                                                
&cmd->details.tip_pickup.psa[i],
+                                                &pickup_withdraw_cb,
+                                                wh);
+    if (NULL == wh->wsh)
+    {
+      GNUNET_break (0);
+      fail (is);
+      return;
+    }
+  }
+  if (0 == num_reserve_sigs)
+    next_command (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_EXCHANGE_DenomPublicKey *
+find_pk (const struct TALER_EXCHANGE_Keys *keys,
+         const struct TALER_Amount *amount)
+{
+  struct GNUNET_TIME_Absolute now;
+  char *str;
+
+  now = GNUNET_TIME_absolute_get ();
+  for (unsigned int i=0;i<keys->num_denom_keys;i++)
+  {
+    const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+    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 (unsigned int i=0;i<keys->num_denom_keys;i++)
+  {
+    const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+    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,
+                  (unsigned long long) now.abs_value_us,
+                  (unsigned long long) pk->valid_from.abs_value_us,
+                  (unsigned long long) 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;
+}
+
+
+/**
+ * Reset the interpreter's state.
+ *
+ * @param is interpreter to reset
+ */
+static void
+cleanup_state (struct InterpreterState *is)
+{
+  struct Command *cmd;
+
+  for (unsigned int i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
+  {
+    switch (cmd->oc)
+    {
+    case OC_END:
+      GNUNET_assert (0);
+      break;
+    case OC_PROPOSAL_LOOKUP:
       if (NULL != cmd->details.proposal_lookup.plo)
       {
         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -1669,7 +2135,7 @@ cleanup_state (struct InterpreterState *is)
                     "Command %u (%s) did not complete\n",
                     i,
                     cmd->label);
-        TALER_EXCHANGE_admin_add_incoming_cancel 
(cmd->details.admin_add_incoming.aih);
+        TALER_BANK_admin_add_incoming_cancel 
(cmd->details.admin_add_incoming.aih);
         cmd->details.admin_add_incoming.aih = NULL;
       }
       break;
@@ -1743,6 +2209,22 @@ cleanup_state (struct InterpreterState *is)
         cmd->details.run_aggregator.child_death_task = NULL;
       }
       break;
+    case OC_RUN_WIREWATCH:
+      if (NULL != cmd->details.run_wirewatch.wirewatch_proc)
+      {
+        GNUNET_break (0 ==
+                      GNUNET_OS_process_kill 
(cmd->details.run_wirewatch.wirewatch_proc,
+                                              SIGKILL));
+        GNUNET_OS_process_wait (cmd->details.run_wirewatch.wirewatch_proc);
+        GNUNET_OS_process_destroy (cmd->details.run_wirewatch.wirewatch_proc);
+        cmd->details.run_wirewatch.wirewatch_proc = NULL;
+      }
+      if (NULL != cmd->details.run_wirewatch.child_death_task)
+      {
+        GNUNET_SCHEDULER_cancel (cmd->details.run_wirewatch.child_death_task);
+        cmd->details.run_wirewatch.child_death_task = NULL;
+      }
+      break;
     case OC_CHECK_BANK_TRANSFER:
       GNUNET_free_non_null (cmd->details.check_bank_transfer.subject);
       cmd->details.check_bank_transfer.subject = NULL;
@@ -1770,7 +2252,6 @@ cleanup_state (struct InterpreterState *is)
         cmd->details.history.ho = NULL;
       }
       break;
-
     case OC_REFUND_INCREASE:
       if (NULL != cmd->details.refund_increase.rio)
       {
@@ -1778,7 +2259,6 @@ cleanup_state (struct InterpreterState *is)
         cmd->details.refund_increase.rio = NULL;
       }
       break;
-
     case OC_REFUND_LOOKUP:
       if (NULL != cmd->details.refund_lookup.rlo)
       {
@@ -1786,6 +2266,63 @@ cleanup_state (struct InterpreterState *is)
         cmd->details.refund_lookup.rlo = NULL;
       }
       break;
+    case OC_TIP_ENABLE:
+      if (NULL != cmd->details.tip_enable.teo)
+      {
+        TALER_MERCHANT_tip_enable_cancel (cmd->details.tip_enable.teo);
+        cmd->details.tip_enable.teo = NULL;
+      }
+      break;
+    case OC_TIP_AUTHORIZE:
+      if (NULL != cmd->details.tip_authorize.tao)
+      {
+        TALER_MERCHANT_tip_authorize_cancel (cmd->details.tip_authorize.tao);
+        cmd->details.tip_authorize.tao = NULL;
+      }
+      break;
+    case OC_TIP_PICKUP:
+      if (NULL != cmd->details.tip_pickup.tpo)
+      {
+        TALER_MERCHANT_tip_pickup_cancel (cmd->details.tip_pickup.tpo);
+        cmd->details.tip_pickup.tpo = NULL;
+      }
+      if (NULL != cmd->details.tip_pickup.psa)
+      {
+        GNUNET_free (cmd->details.tip_pickup.psa);
+        cmd->details.tip_pickup.psa = NULL;
+      }
+      if (NULL != cmd->details.tip_pickup.dks)
+      {
+        GNUNET_free (cmd->details.tip_pickup.dks);
+        cmd->details.tip_pickup.dks = NULL;
+      }
+      if (NULL != cmd->details.tip_pickup.withdraws)
+      {
+        for (unsigned int j=0;j<cmd->details.tip_pickup.num_coins;j++)
+        {
+          struct WithdrawHandle *wh = &cmd->details.tip_pickup.withdraws[j];
+
+          if (NULL != wh->wsh)
+          {
+            TALER_EXCHANGE_reserve_withdraw_cancel (wh->wsh);
+            wh->wsh = NULL;
+          }
+        }
+        GNUNET_free (cmd->details.tip_pickup.withdraws);
+        cmd->details.tip_pickup.withdraws = NULL;
+      }
+      if (NULL != cmd->details.tip_pickup.sigs)
+      {
+        for (unsigned int j=0;j<cmd->details.tip_pickup.num_coins;j++)
+        {
+          if (NULL != cmd->details.reserve_withdraw.sig.rsa_signature)
+          {
+            GNUNET_CRYPTO_rsa_signature_free 
(cmd->details.tip_pickup.sigs[j].rsa_signature);
+            cmd->details.tip_pickup.sigs[j].rsa_signature = NULL;
+          }
+        }
+      }
+      break;
     default:
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                   "Shutdown: unknown instruction %d at %u (%s)\n",
@@ -1800,6 +2337,7 @@ cleanup_state (struct InterpreterState *is)
 
 /**
  * Run the main interpreter loop that performs exchange operations.
+ *
  * @param cls contains the `struct InterpreterState`
  */
 static void
@@ -1810,11 +2348,7 @@ interpreter_run (void *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 *sender_details;
-  json_t *transfer_details;
 
   is->task = NULL;
   tc = GNUNET_SCHEDULER_get_task_context ();
@@ -1843,6 +2377,7 @@ interpreter_run (void *cls)
     is->ip = 0;
     instance_idx++;
     instance = instances[instance_idx];
+    GNUNET_free_non_null (instance_priv);
     instance_priv = get_instance_priv (cfg, instance);
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Switching instance: `%s'\n",
@@ -1851,84 +2386,134 @@ interpreter_run (void *cls)
                                          is);
 
       break;
-    case OC_PROPOSAL_LOOKUP:
+  case OC_PROPOSAL_LOOKUP:
     {
       const char *order_id;
 
       GNUNET_assert (NULL != cmd->details.proposal_lookup.proposal_reference);
       GNUNET_assert (NULL != (ref =
-                             find_command (is,
-                                           
cmd->details.proposal_lookup.proposal_reference)));
+                              find_command (is,
+                                            
cmd->details.proposal_lookup.proposal_reference)));
 
       order_id = json_string_value (json_object_get 
(ref->details.proposal.contract_terms,
-                                    "order_id"));
+                                                     "order_id"));
       if (NULL == (cmd->details.proposal_lookup.plo
-          = TALER_MERCHANT_proposal_lookup (ctx,
-                                            MERCHANT_URI,
-                                            order_id,
-                                            instance,
-                                            proposal_lookup_cb,
-                                            is)))
+                   = TALER_MERCHANT_proposal_lookup (ctx,
+                                                     MERCHANT_URL,
+                                                     order_id,
+                                                     instance,
+                                                     proposal_lookup_cb,
+                                                     is)))
       {
         GNUNET_break (0);
         fail (is);
       }
     }
     break;
-
   case OC_ADMIN_ADD_INCOMING:
-    if (NULL !=
-        cmd->details.admin_add_incoming.reserve_reference)
-    {
-      GNUNET_assert (NULL != (ref
-        = find_command (is,
-                       cmd->details.admin_add_incoming.reserve_reference)));
-      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;
+      char *subject;
+      struct TALER_BANK_AuthenticationData auth;
 
-      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);
-    GNUNET_assert (GNUNET_OK ==
-      TALER_string_to_amount (cmd->details.admin_add_incoming.amount,
-                              &amount));
-    execution_date = GNUNET_TIME_absolute_get ();
-    GNUNET_TIME_round_abs (&execution_date);
-    GNUNET_assert (NULL != (sender_details
-      = json_loads (cmd->details.admin_add_incoming.sender_details,
-                    JSON_REJECT_DUPLICATES,
-                    NULL)));
-    GNUNET_assert (NULL != (transfer_details
-      = json_loads (cmd->details.admin_add_incoming.transfer_details,
-                    JSON_REJECT_DUPLICATES,
-                    NULL)));
-
-    cmd->details.admin_add_incoming.aih
-      = TALER_EXCHANGE_admin_add_incoming (exchange,
-                                           "http://localhost:18080/";,
-                                           &reserve_pub,
-                                           &amount,
-                                           execution_date,
-                                           sender_details,
-                                           transfer_details,
-                                           &add_incoming_cb,
-                                           is);
-    json_decref (sender_details);
-    json_decref (transfer_details);
-    if (NULL == cmd->details.admin_add_incoming.aih)
-    {
-      GNUNET_break (0);
-      fail (is);
+      if (NULL !=
+          cmd->details.admin_add_incoming.subject)
+      {
+        subject = GNUNET_strdup (cmd->details.admin_add_incoming.subject);
+      }
+      else
+      {
+        /* Use reserve public key as subject */
+        if (NULL !=
+            cmd->details.admin_add_incoming.reserve_reference)
+        {
+          GNUNET_assert (NULL != (ref
+                                  = find_command (is,
+                                                  
cmd->details.admin_add_incoming.reserve_reference)));
+          GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+          cmd->details.admin_add_incoming.reserve_priv
+            = ref->details.admin_add_incoming.reserve_priv;
+        }
+        else if (NULL !=
+                 cmd->details.admin_add_incoming.instance)
+        {
+          char *section;
+          char *keys;
+          struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
+
+          GNUNET_asprintf (&section,
+                           "merchant-instance-%s",
+                           cmd->details.admin_add_incoming.instance);
+          if (GNUNET_OK !=
+              GNUNET_CONFIGURATION_get_value_string (cfg,
+                                                     section,
+                                                     
"TIP_RESERVE_PRIV_FILENAME",
+                                                     &keys))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Configuration fails to specify reserve private key 
filename in section %s\n",
+                        section);
+            GNUNET_free (section);
+            fail (is);
+            return;
+          }
+          pk = GNUNET_CRYPTO_eddsa_key_create_from_file (keys);
+          if (NULL == pk)
+          {
+            GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                       section,
+                                       "TIP_RESERVE_PRIV_FILENAME",
+                                       "Failed to read private key");
+            GNUNET_free (keys);
+            GNUNET_free (section);
+            fail (is);
+            return;
+          }
+          cmd->details.admin_add_incoming.reserve_priv.eddsa_priv = *pk;
+          GNUNET_free (pk);
+          GNUNET_free (keys);
+          GNUNET_free (section);
+        }
+        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);
+        subject = GNUNET_STRINGS_data_to_string_alloc (&reserve_pub,
+                                                       sizeof (reserve_pub));
+      }
+
+      GNUNET_CRYPTO_eddsa_key_get_public 
(&cmd->details.admin_add_incoming.reserve_priv.eddsa_priv,
+                                          &reserve_pub.eddsa_pub);
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_string_to_amount 
(cmd->details.admin_add_incoming.amount,
+                                             &amount));
+      auth.method = TALER_BANK_AUTH_BASIC;
+      auth.details.basic.username = (char *) 
cmd->details.admin_add_incoming.auth_username;
+      auth.details.basic.password = (char *) 
cmd->details.admin_add_incoming.auth_password;
+      cmd->details.admin_add_incoming.aih
+        = TALER_BANK_admin_add_incoming (ctx,
+                                         BANK_URL,
+                                         &auth,
+                                         EXCHANGE_URL,
+                                         subject,
+                                         &amount,
+                                         
cmd->details.admin_add_incoming.debit_account_no,
+                                         
cmd->details.admin_add_incoming.credit_account_no,
+                                         &add_incoming_cb,
+                                         is);
+      GNUNET_free (subject);
+      if (NULL == cmd->details.admin_add_incoming.aih)
+      {
+        GNUNET_break (0);
+        fail (is);
+      }
+      break;
     }
-    break;
   case OC_WITHDRAW_STATUS:
     GNUNET_assert (NULL !=
                    cmd->details.reserve_status.reserve_reference);
@@ -1961,27 +2546,14 @@ interpreter_run (void *cls)
     GNUNET_assert (NULL != (cmd->details.reserve_withdraw.pk
       = find_pk (is->keys,
                  &amount)));
-    /* 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);
-    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
-                                &cmd->details.reserve_withdraw.blinding_key,
-                                sizeof 
(cmd->details.reserve_withdraw.blinding_key));
 
+    TALER_planchet_setup_random (&cmd->details.reserve_withdraw.ps);
     cmd->details.reserve_withdraw.wsh
       = TALER_EXCHANGE_reserve_withdraw
         (exchange,
          cmd->details.reserve_withdraw.pk,
          &ref->details.admin_add_incoming.reserve_priv,
-         &cmd->details.reserve_withdraw.coin_priv,
-         &cmd->details.reserve_withdraw.blinding_key,
+         &cmd->details.reserve_withdraw.ps,
          &reserve_withdraw_cb,
          is);
     if (NULL == cmd->details.reserve_withdraw.wsh)
@@ -1991,155 +2563,182 @@ interpreter_run (void *cls)
     }
     break;
   case OC_PROPOSAL:
-  {
-    json_t *order;
-    json_error_t error;
-
-    GNUNET_assert (NULL != (order = json_loads (cmd->details.proposal.order,
-                                                JSON_REJECT_DUPLICATES,
-                                                &error)));
-    if (NULL != instance)
-    {
-      json_t *merchant;
-
-      merchant = json_object ();
-      json_object_set_new (merchant,
-                           "instance",
-                           json_string (instance));
-      json_object_set_new (order,
-                           "merchant",
-                           merchant);
-    }
-    cmd->details.proposal.po = TALER_MERCHANT_order_put (ctx,
-                                                         MERCHANT_URI,
-                                                         order,
-                                                         &proposal_cb,
-                                                         is);
-    json_decref (order);
-    if (NULL == cmd->details.proposal.po)
-    {
-      GNUNET_break (0);
-      fail (is);
-    }
-    break;
-  }
-  case OC_PAY:
-  {
-    struct TALER_MERCHANT_PayCoin *pc;
-    struct TALER_MERCHANT_PayCoin *icoin;
-    char *coins;
-    unsigned int npc;
-    const char *order_id;
-    struct GNUNET_TIME_Absolute refund_deadline;
-    struct GNUNET_TIME_Absolute pay_deadline;
-    struct GNUNET_TIME_Absolute timestamp;
-    struct GNUNET_HashCode h_wire;
-    struct TALER_MerchantPublicKeyP merchant_pub;
-    struct TALER_MerchantSignatureP merchant_sig;
-    struct TALER_Amount total_amount;
-    struct TALER_Amount max_fee;
-    char *token;
-    const char *error_name;
-    unsigned int error_line;
-
-    /* get proposal */
-    GNUNET_assert (NULL != (ref = find_command
-      (is,
-       cmd->details.pay.contract_ref)));
-    merchant_sig = ref->details.proposal.merchant_sig;
-    GNUNET_assert (NULL != ref->details.proposal.contract_terms);
     {
-      /* Get information that needs to be replied in the deposit permission */
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_string ("order_id", &order_id),
-        GNUNET_JSON_spec_absolute_time ("refund_deadline", &refund_deadline),
-        GNUNET_JSON_spec_absolute_time ("pay_deadline", &pay_deadline),
-        GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
-        GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
-        GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire),
-        TALER_JSON_spec_amount ("amount", &total_amount),
-        TALER_JSON_spec_amount ("max_fee", &max_fee),
-        GNUNET_JSON_spec_end()
-      };
+      json_t *order;
+      json_error_t error;
 
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (ref->details.proposal.contract_terms,
-                           spec,
-                           &error_name,
-                           &error_line))
-    {
-      GNUNET_break_op (0);
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Parser failed on %s:%u\n",
-                  error_name,
-                  error_line);
-      /**
-       * Let's use fail() here, as the proposal might be broken
-       * because of backend's fault.
-       */
-      fail (is);
-      return;
-    }
-    cmd->details.pay.merchant_pub = merchant_pub;
+      GNUNET_assert (NULL != (order = json_loads (cmd->details.proposal.order,
+                                                  JSON_REJECT_DUPLICATES,
+                                                  &error)));
+      if (NULL != instance)
+      {
+        json_t *merchant;
+
+        merchant = json_object ();
+        json_object_set_new (merchant,
+                             "instance",
+                             json_string (instance));
+        json_object_set_new (order,
+                             "merchant",
+                             merchant);
+      }
+      cmd->details.proposal.po = TALER_MERCHANT_order_put (ctx,
+                                                           MERCHANT_URL,
+                                                           order,
+                                                           &proposal_cb,
+                                                           is);
+      json_decref (order);
+      if (NULL == cmd->details.proposal.po)
+        {
+          GNUNET_break (0);
+          fail (is);
+        }
+      break;
     }
-    /* strtok loop here */
-    coins = GNUNET_strdup (cmd->details.pay.coin_ref);
-    GNUNET_assert (NULL != (token = strtok (coins, ";")));
-    pc = GNUNET_new (struct TALER_MERCHANT_PayCoin);
-    icoin = pc;
-    npc = 1;
-    do
+  case OC_PAY:
     {
-      const struct Command *coin_ref;
-
-      GNUNET_assert (coin_ref = find_command (is,
-                                              token));
-      switch (coin_ref->oc)
+      struct TALER_MERCHANT_PayCoin *pc;
+      unsigned int npc;
+      char *coins;
+      const char *order_id;
+      struct GNUNET_TIME_Absolute refund_deadline;
+      struct GNUNET_TIME_Absolute pay_deadline;
+      struct GNUNET_TIME_Absolute timestamp;
+      struct GNUNET_HashCode h_wire;
+      struct TALER_MerchantPublicKeyP merchant_pub;
+      struct TALER_MerchantSignatureP merchant_sig;
+      struct TALER_Amount total_amount;
+      struct TALER_Amount max_fee;
+      char *token;
+      const char *error_name;
+      unsigned int error_line;
+
+      /* get proposal */
+      GNUNET_assert (NULL != (ref = find_command
+                              (is,
+                               cmd->details.pay.contract_ref)));
+      merchant_sig = ref->details.proposal.merchant_sig;
+      GNUNET_assert (NULL != ref->details.proposal.contract_terms);
       {
-      case OC_WITHDRAW_SIGN:
-        icoin->coin_priv = coin_ref->details.reserve_withdraw.coin_priv;
-        icoin->denom_pub = coin_ref->details.reserve_withdraw.pk->key;
-        icoin->denom_sig = coin_ref->details.reserve_withdraw.sig;
-        icoin->denom_value = coin_ref->details.reserve_withdraw.pk->value;
-       break;
-      default:
-        GNUNET_assert (0);
+        /* Get information that needs to be replied in the deposit permission 
*/
+        struct GNUNET_JSON_Specification spec[] = {
+          GNUNET_JSON_spec_string ("order_id", &order_id),
+          GNUNET_JSON_spec_absolute_time ("refund_deadline", &refund_deadline),
+          GNUNET_JSON_spec_absolute_time ("pay_deadline", &pay_deadline),
+          GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
+          GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
+          GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire),
+          TALER_JSON_spec_amount ("amount", &total_amount),
+          TALER_JSON_spec_amount ("max_fee", &max_fee),
+          GNUNET_JSON_spec_end()
+        };
+
+        if (GNUNET_OK !=
+            GNUNET_JSON_parse (ref->details.proposal.contract_terms,
+                               spec,
+                               &error_name,
+                               &error_line))
+        {
+          GNUNET_break_op (0);
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Parser failed on %s:%u\n",
+                      error_name,
+                      error_line);
+          /**
+           * Let's use fail() here, as the proposal might be broken
+           * because of backend's fault.
+           */
+          fail (is);
+          return;
+        }
+        cmd->details.pay.merchant_pub = merchant_pub;
       }
+      /* strtok loop here */
+      coins = GNUNET_strdup (cmd->details.pay.coin_ref);
+
+      pc = NULL;
+      npc = 0;
+      for (token = strtok (coins, ";");
+           NULL != token;
+           token = strtok (NULL, ";"))
+      {
+        const struct Command *coin_ref;
+        char *ctok;
+        unsigned int ci;
+        struct TALER_MERCHANT_PayCoin *icoin;
+
+        /* Token syntax is "LABEL[/NUMBER]" */
+        ctok = strchr (token, '/');
+       ci = 0;
+        if (NULL != ctok)
+        {
+          *ctok = '\0';
+          ctok++;
+         if (1 != sscanf (ctok,
+                          "%u",
+                          &ci))
+         {
+           GNUNET_break (0);
+           fail (is);
+           return;
+         }
+       }
+        GNUNET_assert (coin_ref = find_command (is,
+                                                token));
+        GNUNET_array_grow (pc,
+                           npc,
+                           npc + 1);
+        icoin = &pc[npc-1];
+        switch (coin_ref->oc)
+        {
+        case OC_WITHDRAW_SIGN:
+          icoin->coin_priv = coin_ref->details.reserve_withdraw.ps.coin_priv;
+          icoin->denom_pub = coin_ref->details.reserve_withdraw.pk->key;
+          icoin->denom_sig = coin_ref->details.reserve_withdraw.sig;
+          icoin->denom_value = coin_ref->details.reserve_withdraw.pk->value;
+          break;
+        case OC_TIP_PICKUP:
+          icoin->coin_priv = coin_ref->details.tip_pickup.psa[ci].coin_priv;
+          icoin->denom_pub = coin_ref->details.tip_pickup.dks[ci]->key;
+          icoin->denom_sig = coin_ref->details.tip_pickup.sigs[ci];
+          icoin->denom_value = coin_ref->details.tip_pickup.dks[ci]->value;
+          break;
+        default:
+          GNUNET_assert (0);
+        }
 
-      GNUNET_assert (GNUNET_OK ==
-        TALER_string_to_amount (cmd->details.pay.amount_without_fee,
-                                &icoin->amount_without_fee));
-      GNUNET_assert (GNUNET_OK ==
-          TALER_string_to_amount (cmd->details.pay.amount_with_fee,
-                                  &icoin->amount_with_fee));
-      token = strtok (NULL, ";");
-      if (NULL == token)
-        break;
-      icoin->next = GNUNET_new (struct TALER_MERCHANT_PayCoin);
-      icoin = icoin->next;
-    } while (1);
-
-    icoin->next = NULL;
-    cmd->details.pay.ph = TALER_MERCHANT_pay_wallet
-      (ctx,
-       MERCHANT_URI,
-       instance,
-       &ref->details.proposal.hash,
-       &total_amount,
-       &max_fee,
-       &merchant_pub,
-       &merchant_sig,
-       timestamp,
-       refund_deadline,
-       pay_deadline,
-       &h_wire,
-       EXCHANGE_URI,
-       order_id,
-       npc /* num_coins */,
-       pc /* coins */,
-       &pay_cb,
-       is);
-  }
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_string_to_amount 
(cmd->details.pay.amount_without_fee,
+                                               &icoin->amount_without_fee));
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_string_to_amount 
(cmd->details.pay.amount_with_fee,
+                                               &icoin->amount_with_fee));
+      }
+      GNUNET_free (coins);
+
+      cmd->details.pay.ph = TALER_MERCHANT_pay_wallet
+        (ctx,
+         MERCHANT_URL,
+         instance,
+         &ref->details.proposal.hash,
+         &total_amount,
+         &max_fee,
+         &merchant_pub,
+         &merchant_sig,
+         timestamp,
+         refund_deadline,
+         pay_deadline,
+         &h_wire,
+         EXCHANGE_URL,
+         order_id,
+         npc /* num_coins */,
+         pc /* coins */,
+         &pay_cb,
+         is);
+      GNUNET_array_grow (pc,
+                         npc,
+                         0);
+    }
     if (NULL == cmd->details.pay.ph)
     {
       GNUNET_break (0);
@@ -2166,6 +2765,37 @@ interpreter_run (void *cls)
                                           &maint_child_death, is);
     }
     break;
+  case OC_RUN_WIREWATCH:
+    {
+      const struct GNUNET_DISK_FileHandle *pr;
+      static int once;
+
+      cmd->details.run_wirewatch.wirewatch_proc
+        = GNUNET_OS_start_process (GNUNET_NO,
+                                   GNUNET_OS_INHERIT_STD_ALL,
+                                   NULL, NULL, NULL,
+                                   "taler-exchange-wirewatch",
+                                   "taler-exchange-wirewatch",
+                                   "-c", "test_merchant_api.conf",
+                                   "-t", "test", /* use Taler's bank/fakebank 
*/
+                                   "-T", /* exit when done */
+                                   (0 == once ? "-r" : NULL),
+                                   NULL);
+      if (NULL == cmd->details.run_wirewatch.wirewatch_proc)
+      {
+        GNUNET_break (0);
+        fail (is);
+        return;
+      }
+      once = 1;
+      pr = GNUNET_DISK_pipe_handle (sigpipe,
+                                    GNUNET_DISK_PIPE_END_READ);
+      cmd->details.run_wirewatch.child_death_task
+        = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
+                                          pr,
+                                          &maint_child_death, is);
+      return;
+    }
   case OC_CHECK_BANK_TRANSFER:
     {
       GNUNET_assert (GNUNET_OK == TALER_string_to_amount
@@ -2176,7 +2806,7 @@ interpreter_run (void *cls)
             &amount,
             cmd->details.check_bank_transfer.account_debit,
             cmd->details.check_bank_transfer.account_credit,
-            EXCHANGE_URI,
+            EXCHANGE_URL,
             &cmd->details.check_bank_transfer.subject))
       {
         GNUNET_break (0);
@@ -2213,11 +2843,11 @@ interpreter_run (void *cls)
          sizeof (wtid)));
       if (NULL == (cmd->details.track_transfer.tdo
           = TALER_MERCHANT_track_transfer (ctx,
-                                           MERCHANT_URI,
+                                           MERCHANT_URL,
                                            instance,
                                           "test",
                                            &wtid,
-                                           EXCHANGE_URI,
+                                           EXCHANGE_URL,
                                            &track_transfer_cb,
                                            is)))
       {
@@ -2243,7 +2873,7 @@ interpreter_run (void *cls)
 
     if (NULL == (cmd->details.track_transaction.tth
         = TALER_MERCHANT_track_transaction (ctx,
-                                            MERCHANT_URI,
+                                            MERCHANT_URL,
                                             instance,
                                             order_id,
                                             &track_transaction_cb,
@@ -2264,7 +2894,7 @@ interpreter_run (void *cls)
     }
     if (NULL == (cmd->details.history.ho
         = TALER_MERCHANT_history (ctx,
-                                 MERCHANT_URI,
+                                 MERCHANT_URL,
                                   instance,
                                   cmd->details.history.start,
                                   cmd->details.history.nrows,
@@ -2277,44 +2907,206 @@ interpreter_run (void *cls)
     }
     break;
   case OC_REFUND_INCREASE:
-  {
-    struct TALER_Amount refund_amount;
-
-    GNUNET_assert (GNUNET_OK == TALER_string_to_amount
-      (cmd->details.refund_increase.refund_amount,
-       &refund_amount));
-    if (NULL == (cmd->details.refund_increase.rio
-         = TALER_MERCHANT_refund_increase
-           (ctx,
-            MERCHANT_URI,
-            cmd->details.refund_increase.order_id,
-            &refund_amount,
-            cmd->details.refund_increase.reason,
-            instance,
-            refund_increase_cb,
-            is)))
     {
-      GNUNET_break (0);
-      fail (is);
+      struct TALER_Amount refund_amount;
+
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_string_to_amount
+                     (cmd->details.refund_increase.refund_amount,
+                      &refund_amount));
+      if (NULL == (cmd->details.refund_increase.rio
+                   = TALER_MERCHANT_refund_increase
+                   (ctx,
+                    MERCHANT_URL,
+                    cmd->details.refund_increase.order_id,
+                    &refund_amount,
+                    cmd->details.refund_increase.reason,
+                    instance,
+                    refund_increase_cb,
+                    is)))
+        {
+          GNUNET_break (0);
+          fail (is);
+        }
+      break;
     }
-    break;
-  }
   case OC_REFUND_LOOKUP:
-  {
-    if (NULL == (cmd->details.refund_lookup.rlo
-          = TALER_MERCHANT_refund_lookup
-            (ctx,
-             MERCHANT_URI,
-             cmd->details.refund_lookup.order_id,
-             instance,
-             refund_lookup_cb,
-             is)))
     {
-      GNUNET_break (0);
-      fail (is);
+      if (NULL == (cmd->details.refund_lookup.rlo
+                   = TALER_MERCHANT_refund_lookup
+                   (ctx,
+                    MERCHANT_URL,
+                    cmd->details.refund_lookup.order_id,
+                    instance,
+                    refund_lookup_cb,
+                    is)))
+      {
+        GNUNET_break (0);
+        fail (is);
+      }
+      break;
+    }
+  case OC_TIP_ENABLE:
+    {
+      const struct Command *uuid_ref;
+      struct TALER_ReservePrivateKeyP reserve_priv;
+      struct GNUNET_TIME_Absolute expiration;
+
+      if (NULL != cmd->details.tip_enable.admin_add_incoming_ref)
+      {
+        ref = find_command (is,
+                            cmd->details.tip_enable.admin_add_incoming_ref);
+        GNUNET_assert (NULL != ref);
+        GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+      }
+      else
+      {
+        ref = NULL;
+      }
+
+      /* Initialize 'credit_uuid' */
+      if (NULL != cmd->details.tip_enable.uuid_ref)
+      {
+        uuid_ref = find_command (is,
+                                 cmd->details.tip_enable.uuid_ref);
+        GNUNET_assert (NULL != uuid_ref);
+        GNUNET_assert (OC_TIP_ENABLE == uuid_ref->oc);
+        cmd->details.tip_enable.credit_uuid
+          = uuid_ref->details.tip_enable.credit_uuid;
+      }
+      else
+      {
+        uuid_ref = NULL;
+        GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                    &cmd->details.tip_enable.credit_uuid,
+                                    sizeof 
(cmd->details.tip_enable.credit_uuid));
+      }
+
+      /* Initialize 'amount' */
+      if ( (NULL != ref) &&
+           (NULL == cmd->details.tip_enable.amount) )
+      {
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_string_to_amount 
(ref->details.admin_add_incoming.amount,
+                                               &amount));
+      }
+      else
+      {
+        GNUNET_assert (NULL != cmd->details.tip_enable.amount);
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_string_to_amount (cmd->details.tip_enable.amount,
+                                               &amount));
+      }
+      if (NULL == ref)
+        GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                    &reserve_priv,
+                                    sizeof (reserve_priv));
+      /* Simply picked long enough for the test (we do not test expiration
+         behavior for now), should be short enough so that the reserve
+        expires before the test is run again, so that we avoid old
+        state messing up fresh runs. */
+      expiration = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
+
+      if (NULL == (cmd->details.tip_enable.teo
+                   = TALER_MERCHANT_tip_enable
+                   (ctx,
+                    MERCHANT_URL,
+                    &amount,
+                    expiration,
+                    (ref != NULL) ? 
&ref->details.admin_add_incoming.reserve_priv : &reserve_priv,
+                    &cmd->details.tip_enable.credit_uuid,
+                    &tip_enable_cb,
+                    is)))
+      {
+        GNUNET_break (0);
+        fail (is);
+      }
+      break;
+    }
+  case OC_TIP_AUTHORIZE:
+    {
+      GNUNET_assert (NULL != cmd->details.tip_authorize.amount);
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_string_to_amount (cmd->details.tip_authorize.amount,
+                                             &amount));
+      if (NULL == (cmd->details.tip_authorize.tao
+                   = TALER_MERCHANT_tip_authorize
+                   (ctx,
+                    MERCHANT_URL,
+                    &amount,
+                    cmd->details.tip_authorize.instance,
+                    cmd->details.tip_authorize.justification,
+                    &tip_authorize_cb,
+                    is)))
+      {
+        GNUNET_break (0);
+        fail (is);
+      }
+      break;
+    }
+  case OC_TIP_PICKUP:
+    {
+      unsigned int num_planchets;
+
+      for (num_planchets=0;
+           NULL != cmd->details.tip_pickup.amounts[num_planchets];
+           num_planchets++);
+      cmd->details.tip_pickup.num_coins = num_planchets;
+      {
+        struct TALER_PlanchetDetail planchets[num_planchets];
+
+        ref = find_command (is,
+                            cmd->details.tip_pickup.authorize_ref);
+        GNUNET_assert (NULL != ref);
+        GNUNET_assert (OC_TIP_AUTHORIZE == ref->oc);
+        cmd->details.tip_pickup.psa = GNUNET_new_array (num_planchets,
+                                                        struct 
TALER_PlanchetSecretsP);
+        cmd->details.tip_pickup.dks = GNUNET_new_array (num_planchets,
+                                                        const struct 
TALER_EXCHANGE_DenomPublicKey *);
+        for (unsigned int i=0;i<num_planchets;i++)
+        {
+          GNUNET_assert (GNUNET_OK ==
+                         TALER_string_to_amount 
(cmd->details.tip_pickup.amounts[i],
+                                                 &amount));
+
+          cmd->details.tip_pickup.dks[i]
+            = find_pk (is->keys,
+                       &amount);
+          if (NULL == cmd->details.tip_pickup.dks[i])
+          {
+            GNUNET_break (0);
+            fail (is);
+            return;
+          }
+          TALER_planchet_setup_random (&cmd->details.tip_pickup.psa[i]);
+          if (GNUNET_OK !=
+              TALER_planchet_prepare (&cmd->details.tip_pickup.dks[i]->key,
+                                      &cmd->details.tip_pickup.psa[i],
+                                      &planchets[i]))
+          {
+            GNUNET_break (0);
+            fail (is);
+            return;
+          }
+        }
+        if (NULL == (cmd->details.tip_pickup.tpo
+                     = TALER_MERCHANT_tip_pickup
+                     (ctx,
+                      MERCHANT_URL,
+                      &ref->details.tip_authorize.tip_id,
+                      num_planchets,
+                      planchets,
+                      &pickup_cb,
+                      is)))
+        {
+          GNUNET_break (0);
+          fail (is);
+        }
+        for (unsigned int i=0;i<num_planchets;i++)
+          GNUNET_free (planchets[i].coin_ev);
+      }
+      break;
     }
-    break;
-  }
   default:
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Unknown instruction %d at %u (%s)\n",
@@ -2359,7 +3151,11 @@ do_shutdown (void *cls)
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Shutdown executing\n");
   cleanup_state (is);
-
+  if (NULL != instance_priv)
+  {
+    GNUNET_free (instance_priv);
+    instance_priv = NULL;
+  }
   if (NULL != is->task)
   {
     GNUNET_SCHEDULER_cancel (is->task);
@@ -2457,6 +3253,10 @@ static void
 run (void *cls)
 {
   struct InterpreterState *is;
+  static const char *pickup_amounts_1[] = {
+    "EUR:5",
+    NULL
+  };
   static struct Command commands[] =
   {
     /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per
@@ -2464,12 +3264,20 @@ run (void *cls)
     { .oc = OC_ADMIN_ADD_INCOMING,
       .label = "create-reserve-1",
       .expected_response_code = MHD_HTTP_OK,
-      .details.admin_add_incoming.sender_details
-      = "{ \"type\":\"test\", \"bank_uri\":\"" BANK_URI "\", \
-        \"account_number\":62, \"uuid\":1 }",
-      .details.admin_add_incoming.transfer_details
-        = "{ \"uuid\": 1}",
+      .details.admin_add_incoming.debit_account_no = 62,
+      .details.admin_add_incoming.credit_account_no = EXCHANGE_ACCOUNT_NO,
+      .details.admin_add_incoming.auth_username = "user62",
+      .details.admin_add_incoming.auth_password = "pass62",
       .details.admin_add_incoming.amount = "EUR:10.02" },
+    /* Run wirewatch to observe /admin/add/incoming */
+    { .oc = OC_RUN_WIREWATCH,
+      .label = "wirewatch-1" },
+    { .oc = OC_CHECK_BANK_TRANSFER,
+      .label = "check_bank_transfer-1",
+      .details.check_bank_transfer.amount = "EUR:10.02",
+      .details.check_bank_transfer.account_debit = 62,
+      .details.check_bank_transfer.account_credit = EXCHANGE_ACCOUNT_NO
+    },
 
     /* Withdraw a 5 EUR coin, at fee of 1 ct */
     { .oc = OC_WITHDRAW_SIGN,
@@ -2522,7 +3330,7 @@ run (void *cls)
       .label = "deposit-simple",
       .expected_response_code = MHD_HTTP_OK,
       .details.pay.contract_ref = "create-proposal-1",
-      .details.pay.coin_ref = "withdraw-coin-1;withdraw-coin-2",
+      .details.pay.coin_ref = "withdraw-coin-1",
       .details.pay.amount_with_fee = "EUR:5",
       .details.pay.amount_without_fee = "EUR:4.99" },
 
@@ -2590,29 +3398,37 @@ run (void *cls)
     { .oc = OC_ADMIN_ADD_INCOMING,
       .label = "create-reserve-2",
       .expected_response_code = MHD_HTTP_OK,
-      .details.admin_add_incoming.sender_details
-        = "{ \"type\":\"test\",\
-          \"bank_uri\":\"" BANK_URI "\",\
-          \"account_number\":63,\
-          \"uuid\":2 }",
-      .details.admin_add_incoming.transfer_details
-        = "{ \"uuid\": 2}",
+      .details.admin_add_incoming.debit_account_no = 63,
+      .details.admin_add_incoming.credit_account_no = EXCHANGE_ACCOUNT_NO,
+      .details.admin_add_incoming.auth_username = "user63",
+      .details.admin_add_incoming.auth_password = "pass63",
       .details.admin_add_incoming.amount = "EUR:1" },
 
     /* Add another 4.01 EUR to reserve #2 */
     { .oc = OC_ADMIN_ADD_INCOMING,
       .label = "create-reserve-2b",
       .expected_response_code = MHD_HTTP_OK,
-      .details.admin_add_incoming.reserve_reference
-        = "create-reserve-2",
-      .details.admin_add_incoming.sender_details
-        = "{ \"type\":\"test\",\
-          \"bank_uri\":\"" BANK_URI "\",\
-          \"account_number\":63,\
-          \"uuid\":3  }",
-      .details.admin_add_incoming.transfer_details
-        = "{ \"uuid\": 3}",
+      .details.admin_add_incoming.reserve_reference = "create-reserve-2",
+      .details.admin_add_incoming.debit_account_no = 63,
+      .details.admin_add_incoming.credit_account_no = EXCHANGE_ACCOUNT_NO,
+      .details.admin_add_incoming.auth_username = "user63",
+      .details.admin_add_incoming.auth_password = "pass63",
       .details.admin_add_incoming.amount = "EUR:4.01" },
+    /* Run wirewatch to observe /admin/add/incoming */
+    { .oc = OC_RUN_WIREWATCH,
+      .label = "wirewatch-2" },
+    { .oc = OC_CHECK_BANK_TRANSFER,
+      .label = "check_bank_transfer-2",
+      .details.check_bank_transfer.amount = "EUR:1",
+      .details.check_bank_transfer.account_debit = 63,
+      .details.check_bank_transfer.account_credit = EXCHANGE_ACCOUNT_NO
+    },
+    { .oc = OC_CHECK_BANK_TRANSFER,
+      .label = "check_bank_transfer-2",
+      .details.check_bank_transfer.amount = "EUR:4.01",
+      .details.check_bank_transfer.account_debit = 63,
+      .details.check_bank_transfer.account_credit = EXCHANGE_ACCOUNT_NO
+    },
 
     /* Withdraw a 5 EUR coin, at fee of 1 ct */
     { .oc = OC_WITHDRAW_SIGN,
@@ -2768,6 +3584,144 @@ run (void *cls)
       .details.refund_lookup.increase_ref = "refund-increase-1",
       .details.refund_lookup.pay_ref = "deposit-simple"
     },
+
+    /* Test tipping */
+    { .oc = OC_ADMIN_ADD_INCOMING,
+      .label = "create-reserve-tip-1",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.admin_add_incoming.instance = "tip",
+      .details.admin_add_incoming.debit_account_no = 62,
+      .details.admin_add_incoming.credit_account_no = EXCHANGE_ACCOUNT_NO,
+      .details.admin_add_incoming.auth_username = "user62",
+      .details.admin_add_incoming.auth_password = "pass62",
+      /* we run *two* instances, but only this first call will
+         actually fill the reserve, as the second one will be seen as
+         a duplicate. Hence fill with twice the require amount per
+         round. */
+      .details.admin_add_incoming.amount = "EUR:20.04" },
+    /* Run wirewatch to observe /admin/add/incoming */
+    { .oc = OC_RUN_WIREWATCH,
+      .label = "wirewatch-3" },
+    { .oc = OC_CHECK_BANK_TRANSFER,
+      .label = "check_bank_transfer-tip-1",
+      .details.check_bank_transfer.amount = "EUR:20.04",
+      .details.check_bank_transfer.account_debit = 62,
+      .details.check_bank_transfer.account_credit = EXCHANGE_ACCOUNT_NO
+    },
+    { .oc = OC_TIP_ENABLE,
+      .label = "enable-tip-1",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_enable.admin_add_incoming_ref = "create-reserve-tip-1",
+      .details.tip_enable.amount = "EUR:5.01" },
+    /* Test incrementing active reserve balance */
+    { .oc = OC_TIP_ENABLE,
+      .label = "enable-tip-2",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_enable.admin_add_incoming_ref = "create-reserve-tip-1",
+      .details.tip_enable.amount = "EUR:5.01" },
+    /* Authorize two tips */
+    { .oc = OC_TIP_AUTHORIZE,
+      .label = "authorize-tip-1",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_authorize.instance = "tip",
+      .details.tip_authorize.justification = "tip 1",
+      .details.tip_authorize.amount = "EUR:5.01" },
+    { .oc = OC_TIP_AUTHORIZE,
+      .label = "authorize-tip-2",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_authorize.instance = "tip",
+      .details.tip_authorize.justification = "tip 2",
+      .details.tip_authorize.amount = "EUR:5.01" },
+    /* Test authorization failure modes */
+    { .oc = OC_TIP_AUTHORIZE,
+      .label = "authorize-tip-3-insufficient-funds",
+      .expected_response_code = MHD_HTTP_PRECONDITION_FAILED,
+      .details.tip_authorize.instance = "tip",
+      .details.tip_authorize.justification = "tip 3",
+      .details.tip_authorize.amount = "EUR:5.01",
+      .details.tip_authorize.expected_ec = 
TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS },
+    { .oc = OC_TIP_AUTHORIZE,
+      .label = "authorize-tip-4-unknown-instance",
+      .expected_response_code = MHD_HTTP_NOT_FOUND,
+      .details.tip_authorize.instance = "unknown",
+      .details.tip_authorize.justification = "tip 4",
+      .details.tip_authorize.amount = "EUR:5.01",
+      .details.tip_authorize.expected_ec = 
TALER_EC_TIP_AUTHORIZE_INSTANCE_UNKNOWN },
+    { .oc = OC_TIP_AUTHORIZE,
+      .label = "authorize-tip-5-notip-instance",
+      .expected_response_code = MHD_HTTP_NOT_FOUND,
+      .details.tip_authorize.instance = "default",
+      .details.tip_authorize.justification = "tip 5",
+      .details.tip_authorize.amount = "EUR:5.01",
+      .details.tip_authorize.expected_ec = 
TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP },
+    { .oc = OC_TIP_AUTHORIZE,
+      .label = "authorize-tip-6-not-enabled-instance",
+      .expected_response_code = MHD_HTTP_NOT_FOUND,
+      .details.tip_authorize.instance = "dtip",
+      .details.tip_authorize.justification = "tip 6",
+      .details.tip_authorize.amount = "EUR:5.01",
+      .details.tip_authorize.expected_ec = 
TALER_EC_TIP_AUTHORIZE_RESERVE_NOT_ENABLED },
+    /* Withdraw tip */
+    { .oc = OC_TIP_PICKUP,
+      .label = "pickup-tip-1",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_pickup.authorize_ref = "authorize-tip-1",
+      .details.tip_pickup.amounts = pickup_amounts_1 },
+    { .oc = OC_TIP_PICKUP,
+      .label = "pickup-tip-2",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_pickup.authorize_ref = "authorize-tip-2",
+      .details.tip_pickup.amounts = pickup_amounts_1 },
+    { .oc = OC_TIP_PICKUP,
+      .label = "pickup-tip-3-too-much",
+      .expected_response_code = MHD_HTTP_SERVICE_UNAVAILABLE,
+      .details.tip_pickup.expected_ec = TALER_EC_TIP_PICKUP_NO_FUNDS,
+      .details.tip_pickup.authorize_ref = "authorize-tip-1",
+      .details.tip_pickup.amounts = pickup_amounts_1 },
+    /* Spend tip (just to be sure...) */
+    { .oc = OC_PROPOSAL,
+      .label = "create-proposal-tip-1",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.proposal.order = "{\
+        \"max_fee\":\
+          {\"currency\":\"EUR\",\
+           \"value\":0,\
+           \"fraction\":50000000},\
+        \"order_id\":\"1-tip\",\
+        \"refund_deadline\":\"\\/Date(0)\\/\",\
+        \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+        \"amount\":\
+          {\"currency\":\"EUR\",\
+           \"value\":5,\
+           \"fraction\":0},\
+       \"summary\": \"merchant-lib testcase\",\
+        \"products\":\
+          [ {\"description\":\"ice cream tip\",\
+             \"value\":\"{EUR:5}\"} ] }"},
+    { .oc = OC_PAY,
+      .label = "deposit-tip-simple",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.pay.contract_ref = "create-proposal-tip-1",
+      .details.pay.coin_ref = "pickup-tip-1",
+      .details.pay.amount_with_fee = "EUR:5",
+      .details.pay.amount_without_fee = "EUR:4.99" },
+    /* Run transfers. */
+    { .oc = OC_RUN_AGGREGATOR,
+      .label = "run-aggregator-tip-1" },
+    { .oc = OC_CHECK_BANK_TRANSFER,
+      .label = "check_bank_transfer-tip-498c",
+      .details.check_bank_transfer.amount = "EUR:4.98",
+      /* exchange-outgoing */
+      .details.check_bank_transfer.account_debit = 2,
+      /* merchant */
+      .details.check_bank_transfer.account_credit = 62
+    },
+
+    /* Check that there are no other unusual transfers */
+    { .oc = OC_CHECK_BANK_TRANSFERS_EMPTY,
+      .label = "check_bank_empty" },
+
+
     /* end of testcase */
     { .oc = OC_END }
   };
@@ -2791,7 +3745,7 @@ run (void *cls)
   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   GNUNET_assert (NULL != (exchange
     = TALER_EXCHANGE_connect (ctx,
-                              EXCHANGE_URI,
+                              EXCHANGE_URL,
                               &cert_cb, is,
                               TALER_EXCHANGE_OPTION_END)));
   timeout_task
@@ -2834,6 +3788,7 @@ main (int argc,
                                            "merchant",
                                            "INSTANCES",
                                            &_instances));
+
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Found instances `%s'\n",
               _instances);
@@ -2925,7 +3880,7 @@ main (int argc,
       return 77;
     }
   }
-  while (0 != system ("wget -q -t 1 -T 1 " EXCHANGE_URI "keys -o /dev/null -O 
/dev/null"));
+  while (0 != system ("wget -q -t 1 -T 1 " EXCHANGE_URL "keys -o /dev/null -O 
/dev/null"));
   fprintf (stderr, "\n");
   if (NULL == (merchantd = GNUNET_OS_start_process
        (GNUNET_NO,
@@ -2969,7 +3924,7 @@ main (int argc,
       return 77;
     }
   }
-  while (0 != system ("wget -q -t 1 -T 1 " MERCHANT_URI " -o /dev/null -O 
/dev/null"));
+  while (0 != system ("wget -q -t 1 -T 1 " MERCHANT_URL " -o /dev/null -O 
/dev/null"));
   fprintf (stderr, "\n");
 
   result = GNUNET_SYSERR;
diff --git a/src/lib/test_merchant_api.conf b/src/lib/test_merchant_api.conf
index a01c4e0..c4a4eb7 100644
--- a/src/lib/test_merchant_api.conf
+++ b/src/lib/test_merchant_api.conf
@@ -35,11 +35,18 @@ WIREFORMAT = test
 # section like X-wireformat and merchant-instance-X
 INSTANCES = tor default
 
+# Default choice for maximum wire fee.
+DEFAULT_MAX_WIRE_FEE = EUR:0.10
+
+# Default choice for maximum wire fee.
+DEFAULT_MAX_DEPOSIT_FEE = EUR:0.10
 
 [exchange-wire-test]
 # Enable 'test' for testing of the actual coin operations.
 ENABLE = YES
 
+BANK_URI = http://localhost:8083/
+
 # Fees for the forseeable future...
 # If you see this after 2017, update to match the next 10 years...
 WIRE-FEE-2017 = EUR:0.01
@@ -65,7 +72,7 @@ CLOSING-FEE-2026 = EUR:0.01
 
 
 [merchant-exchange-test]
-URI = http://localhost:8081/
+URI = http://localhost:8084/
 MASTER_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
 
 [merchant-instance-default]
@@ -77,9 +84,25 @@ TEST_RESPONSE_FILE = 
${TALER_CONFIG_HOME}/merchant/wire/test.json
 [merchant-instance-tor]
 KEYFILE = tor_merchant.priv
 
+[merchant-instance-tip]
+KEYFILE = reserve_tip.priv
+TIP_EXCHANGE = http://localhost:8084/
+TIP_RESERVE_PRIV_FILENAME = reserve_key.priv
+
+[merchant-instance-dtip]
+KEYFILE = reserve_dtip.priv
+TIP_EXCHANGE = http://localhost:8084/
+TIP_RESERVE_PRIV_FILENAME = reserve_dkey.priv
+
 [merchant-instance-wireformat-tor]
 TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/merchant/wire/test.json
 
+[merchant-instance-wireformat-tip]
+TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/merchant/wire/test.json
+
+[merchant-instance-wireformat-dtip]
+TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/merchant/wire/test.json
+
 # Auditors must be in sections "auditor-", the rest of the section
 # name could be anything.
 [merchant-auditor-ezb]
@@ -116,11 +139,14 @@ ADDRESS = "Garching"
 DB = postgres
 
 # HTTP port the exchange listens to
-PORT = 8081
+PORT = 8084
 
 # Our public key
 MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
 
+# Base URL of the exchange.
+BASE_URL = http://localhost:8084/
+
 [exchangedb-postgres]
 DB_CONN_STR = "postgres:///talercheck"
 
diff --git a/src/merchant-tools/Makefile.am b/src/merchant-tools/Makefile.am
index fb950e1..df271a2 100644
--- a/src/merchant-tools/Makefile.am
+++ b/src/merchant-tools/Makefile.am
@@ -3,7 +3,8 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include
 
 bin_PROGRAMS = \
   taler-merchant-dbinit \
-  taler-merchant-generate-payments
+  taler-merchant-generate-payments \
+  taler-merchant-tip-enable
 
 taler_merchant_dbinit_SOURCES = \
   taler-merchant-dbinit.c
@@ -15,6 +16,16 @@ taler_merchant_dbinit_LDADD = \
   -ltalerutil \
   -ltalerpq
 
+taler_merchant_tip_enable_SOURCES = \
+  taler-merchant-tip-enable.c
+
+taler_merchant_tip_enable_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/lib/libtalermerchant.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ltalerutil
+
 taler_merchant_generate_payments_SOURCES = \
   taler-merchant-generate-payments.c
 
@@ -30,4 +41,3 @@ taler_merchant_generate_payments_LDADD = \
   -lgnunetcurl \
   -lgnunetutil \
   -ljansson
-
diff --git a/src/merchant-tools/taler-merchant-generate-payments.c 
b/src/merchant-tools/taler-merchant-generate-payments.c
index bf5ecad..3e19943 100644
--- a/src/merchant-tools/taler-merchant-generate-payments.c
+++ b/src/merchant-tools/taler-merchant-generate-payments.c
@@ -251,14 +251,9 @@ struct Command
       struct TALER_DenominationSignature sig;
 
       /**
-       * Set (by the interpreter) to the coin's private key.
+       * Secrets of the planchet. Set by the interpreter.
        */
-      struct TALER_CoinSpendPrivateKeyP coin_priv;
-
-      /**
-       * Blinding key used for the operation.
-       */
-      struct TALER_DenominationBlindingKeyP blinding_key;
+      struct TALER_PlanchetSecretsP ps;
 
       /**
        * Withdraw handle (while operation is running).
@@ -767,14 +762,15 @@ find_pk (const struct TALER_EXCHANGE_Keys *keys,
  * Allocates and return a string representing a order.
  * In this process, this function gives the order those
  * prices specified by the user. Please NOTE that any amount
- * must be given in the form "XX.YY".
+ * must be given in the form "CUR:XX.YY".
  *
  * @param max_fee merchant's allowed max_fee
  * @param amount total amount for this order
+ * @return JSON string for the order, NULL on errors
  */
 static json_t *
-make_order (char *maxfee,
-            char *total)
+make_order (const char *maxfee,
+            const char *total)
 {
   struct TALER_Amount tmp_amount;
   json_t *total_j;
@@ -784,12 +780,26 @@ make_order (char *maxfee,
   struct GNUNET_TIME_Absolute now;
   char *timestamp;
 
-  TALER_string_to_amount (maxfee, &tmp_amount);
+  if (GNUNET_OK !=
+      TALER_string_to_amount (maxfee,
+                              &tmp_amount))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
   maxfee_j = TALER_JSON_from_amount (&tmp_amount);
-  TALER_string_to_amount (total, &tmp_amount);
+  GNUNET_assert (NULL != maxfee_j);
+  if (GNUNET_OK !=
+      TALER_string_to_amount (total,
+                              &tmp_amount))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
   total_j = TALER_JSON_from_amount (&tmp_amount);
+  GNUNET_assert (NULL != total_j);
   now = GNUNET_TIME_absolute_get ();
-
+  GNUNET_TIME_round_abs (&now);
   GNUNET_asprintf (&timestamp,
                    "/Date(%u)/",
                    now.abs_value_us / 1000LL / 1000LL);
@@ -951,7 +961,6 @@ interpreter_run (void *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 *sender_details;
@@ -1049,14 +1058,15 @@ interpreter_run (void *cls)
 
         {
           const struct Command *coin_ref;
+
           memset (&pc, 0, sizeof (pc));
           coin_ref = find_command (is,
                                    cmd->details.pay.coin_ref);
-          GNUNET_assert (NULL != ref);
+          GNUNET_assert (NULL != coin_ref);
           switch (coin_ref->oc)
           {
           case OC_WITHDRAW_SIGN:
-            pc.coin_priv = coin_ref->details.reserve_withdraw.coin_priv;
+            pc.coin_priv = coin_ref->details.reserve_withdraw.ps.coin_priv;
             pc.denom_pub = coin_ref->details.reserve_withdraw.pk->key;
             pc.denom_sig = coin_ref->details.reserve_withdraw.sig;
             pc.denom_value = coin_ref->details.reserve_withdraw.pk->value;
@@ -1275,26 +1285,12 @@ interpreter_run (void *cls)
         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);
-      GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
-                                  &cmd->details.reserve_withdraw.blinding_key,
-                                  sizeof 
(cmd->details.reserve_withdraw.blinding_key));
-
+      TALER_planchet_setup_random (&cmd->details.reserve_withdraw.ps);
       cmd->details.reserve_withdraw.wsh
         = TALER_EXCHANGE_reserve_withdraw (exchange,
                                            cmd->details.reserve_withdraw.pk,
                                            
&ref->details.admin_add_incoming.reserve_priv,
-                                           
&cmd->details.reserve_withdraw.coin_priv,
-                                           
&cmd->details.reserve_withdraw.blinding_key,
+                                           &cmd->details.reserve_withdraw.ps,
                                            &reserve_withdraw_cb,
                                            is);
       if (NULL == cmd->details.reserve_withdraw.wsh)
diff --git a/src/merchant-tools/taler-merchant-tip-enable.c 
b/src/merchant-tools/taler-merchant-tip-enable.c
new file mode 100644
index 0000000..278cd22
--- /dev/null
+++ b/src/merchant-tools/taler-merchant-tip-enable.c
@@ -0,0 +1,277 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2017 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant-tools/taler-merchant-tip-enable.c
+ * @brief enable tips by telling the backend that a reserve was charged
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <microhttpd.h> /* just for HTTP status code, no need to link against 
*/
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * -a option: how much money was deposited into the reserve
+ */
+static struct TALER_Amount amount;
+
+/**
+ * For which instance did we charge the reserve?
+ */
+static char *instance;
+
+/**
+ * Under which URI does the backend run?
+ */
+static char *backend_uri;
+
+/**
+ * UUID of the wire transfer.
+ */
+static char *credit_uuid;
+
+/**
+ * Expiration time for the reserve.
+ */
+static struct GNUNET_TIME_Absolute expiration;
+
+/**
+ * Main execution context for the main loop of the exchange.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Context for running the #ctx's event loop.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Handle for the /tip-enable operation.
+ */
+static struct TALER_MERCHANT_TipEnableOperation *teo;
+
+
+/**
+ * Function run when the test terminates (good or bad).
+ * Cleans up our state.
+ *
+ * @param cls the interpreter state.
+ */
+static void
+do_shutdown (void *cls)
+{
+  if (NULL != teo)
+  {
+    TALER_MERCHANT_tip_enable_cancel (teo);
+    teo = NULL;
+  }
+  if (NULL != ctx)
+  {
+    GNUNET_CURL_fini (ctx);
+    ctx = NULL;
+  }
+  if (NULL != rc)
+  {
+    GNUNET_CURL_gnunet_rc_destroy (rc);
+    rc = NULL;
+  }
+}
+
+
+/**
+ * Callback for a /tip-enable request.  Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend
+ * @param ec taler-specific error code
+ */
+static void
+enable_cb (void *cls,
+           unsigned int http_status,
+           enum TALER_ErrorCode ec)
+{
+  teo = NULL;
+  GNUNET_SCHEDULER_shutdown ();
+  if ( (MHD_HTTP_OK == http_status) &&
+       (TALER_EC_NONE == ec) )
+  {
+    global_ret = 0;
+    return;
+  }
+  fprintf (stderr,
+           "Failed with HTTP status %u and error code %u\n",
+           http_status,
+           ec);
+  global_ret = 3;
+}
+
+
+/**
+ * Main function that will be run.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be 
NULL!)
+ * @param cfg configuration
+ */
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  struct TALER_ReservePrivateKeyP reserve_priv;
+  struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
+  char *section;
+  char *tip_reserves;
+  struct GNUNET_HashCode hcredit_uuid;
+  struct GNUNET_CURL_Context *ctx;
+
+  GNUNET_asprintf (&section,
+                   "merchant-instance-%s",
+                   instance);
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             section,
+                                             "TIP_RESERVE_PRIV_FILENAME",
+                                             &tip_reserves))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "TIP_RESERVE_PRIV_FILENAME");
+    GNUNET_free (section);
+    global_ret = 1;
+    return;
+  }
+  pk = GNUNET_CRYPTO_eddsa_key_create_from_file (tip_reserves);
+  if (NULL == pk)
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "TIP_RESERVE_PRIV_FILENAME",
+                               "Failed to read private key");
+    GNUNET_free (section);
+    GNUNET_free (tip_reserves);
+    global_ret = 1;
+    return;
+  }
+  GNUNET_free (tip_reserves);
+  GNUNET_free (section);
+
+  GNUNET_CRYPTO_hash (credit_uuid,
+                      strlen (credit_uuid),
+                      &hcredit_uuid);
+
+  ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+                          &rc);
+  if (NULL == ctx)
+  {
+    GNUNET_break (0);
+    global_ret = 1;
+    return;
+  }
+  rc = GNUNET_CURL_gnunet_rc_create (ctx);
+  reserve_priv.eddsa_priv = *pk;
+  GNUNET_free (pk);
+  teo = TALER_MERCHANT_tip_enable (ctx,
+                                   backend_uri,
+                                   &amount,
+                                   expiration,
+                                   &reserve_priv,
+                                   &hcredit_uuid,
+                                   &enable_cb,
+                                   NULL);
+  GNUNET_assert (NULL != teo);
+  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+                                 NULL);
+}
+
+
+/**
+ * The main function of the database initialization tool.
+ * Used to initialize the Taler Exchange's database.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+      char *const *argv)
+{
+  struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_option_mandatory
+    (TALER_getopt_get_amount ('a',
+                              "amount",
+                              "VALUE",
+                              "value that was added to the reserve",
+                              &amount)),
+    GNUNET_GETOPT_option_mandatory
+    (GNUNET_GETOPT_option_string ('b',
+                                  "backend",
+                                  "URI",
+                                  "URI of the backend to use",
+                                  &backend_uri)),
+    GNUNET_GETOPT_option_mandatory
+    (GNUNET_GETOPT_option_string ('C',
+                                  "credit-uuid",
+                                  "UUID",
+                                  "unique identifier of the wire transfer (to 
detect duplicate invocations)",
+                                  &credit_uuid)),
+    GNUNET_GETOPT_option_mandatory
+    (GNUNET_GETOPT_option_absolute_time ('e',
+                                         "expiration",
+                                         "TIMESTAMP",
+                                         "when does the reserve expire",
+                                         &expiration)),
+    GNUNET_GETOPT_option_mandatory
+    (GNUNET_GETOPT_option_string ('i',
+                                  "instance",
+                                  "NAME",
+                                  "name of the instance of which the reserve 
was charged",
+                                  &instance)),
+    GNUNET_GETOPT_OPTION_END
+  };
+
+  /* force linker to link against libtalerutil; if we do
+     not do this, the linker may "optimize" libtalerutil
+     away and skip #TALER_OS_init(), which we do need */
+  (void) TALER_project_data_default ();
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_log_setup ("taler-merchant-tip-enable",
+                                   "INFO",
+                                   NULL));
+  global_ret = 2;
+  if (GNUNET_OK !=
+      GNUNET_PROGRAM_run (argc, argv,
+                          "taler-merchant-tip-enable",
+                         "Enable tipping by telling the backend that a reserve 
was charged",
+                         options,
+                         &run,
+                          NULL))
+    return 1;
+  return global_ret;
+}
+
+
+/* end of taler-exchange-tip-enable.c */

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]